Kubernetes Production — Helm, RBAC, Ingress
Kubernetes Production — набор подходов и ресурсов Kubernetes, необходимых для эксплуатации приложений в рабочем окружении: упаковка и выпуск через Helm, внешний доступ через Ingress, разграничение прав с RBAC, хранение состояния, автоматическое масштабирование и контроль готовности контейнеров.
Типичная производственная схема:
Пользователь
|
v
Load Balancer / внешний адрес
|
v
Ingress Controller
|
v
Ingress -> Service -> Pods
|
+-> ConfigMap / Secret
+-> PersistentVolumeClaim -> PersistentVolume
Helm управляет манифестами приложения.
RBAC ограничивает доступ пользователей и ServiceAccount.
HPA изменяет количество Pod в зависимости от нагрузки.
Readiness и liveness probes контролируют доступность контейнеров.Helm не заменяет Kubernetes API, а формирует обычные Kubernetes-манифесты и применяет их как единый релиз. Ingress сам по себе также не обрабатывает трафик: для этого в кластере должен работать Ingress Controller.
Содержание
- Helm
- Структура Helm chart
- Values и переопределение настроек
- Шаблоны Helm
- Управление Helm-релизами
- Ingress и Ingress Controller
- TLS и маршрутизация Ingress
- RBAC в Kubernetes
- ServiceAccount
- StatefulSet
- PersistentVolume и PersistentVolumeClaim
- StorageClass и динамическое выделение томов
- Автомасштабирование HPA
- Readiness, liveness и startup probes
- Рекомендации для production
- Общий пример
Helm
Helm — пакетный менеджер для Kubernetes. Он позволяет описать набор связанных Kubernetes-ресурсов в виде пакета — chart, настроить их через параметры и установить в кластер как единый release.
Основные понятия:
| Понятие | Назначение |
|---|---|
| Chart | Пакет с шаблонами Kubernetes-ресурсов |
| Release | Установленный в кластер экземпляр chart |
| Repository | Хранилище опубликованных chart |
| Values | Значения, подставляемые в шаблоны |
| Template | Kubernetes-манифест с выражениями Go templates |
| Dependency | Другой chart, используемый текущим chart |
Один chart можно установить несколько раз с разными именами и параметрами:
helm install shop-dev ./shop-chart \
--namespace shop-dev \
--create-namespace
helm install shop-prod ./shop-chart \
--namespace shop-prod \
--create-namespace \
-f values-prod.yamlshop-dev и shop-prod — два независимых Helm-релиза одного chart.
Основные команды Helm
# Проверить клиент Helm
helm version
# Создать каркас chart
helm create my-app
# Проверить chart на типичные ошибки
helm lint ./my-app
# Отрендерить манифесты локально без установки
helm template my-release ./my-app
# Установить chart
helm install my-release ./my-app
# Установить или обновить релиз одной командой
helm upgrade --install my-release ./my-app
# Показать релизы
helm list --all-namespaces
# Показать состояние релиза
helm status my-release
# Показать историю ревизий
helm history my-release
# Откатить релиз к указанной ревизии
helm rollback my-release 2
# Удалить релиз
helm uninstall my-releaseДля production-развёртывания часто применяют:
helm upgrade --install my-app ./chart \
--namespace production \
--create-namespace \
-f values-production.yaml \
--atomic \
--wait \
--timeout 10m--atomic— при неудачном обновлении автоматически откатывает релиз;--wait— ожидает готовности основных ресурсов;--timeout— ограничивает время ожидания;-f— подключает файл значений.
Структура Helm chart
Типичная структура:
my-app/
├── Chart.yaml
├── Chart.lock
├── values.yaml
├── values-production.yaml
├── charts/
└── templates/
├── _helpers.tpl
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── serviceaccount.yaml
├── hpa.yaml
├── configmap.yaml
├── NOTES.txt
└── tests/
└── test-connection.yamlChart.yaml
Содержит метаданные chart:
apiVersion: v2
name: my-app
description: Helm chart для веб-приложения
type: application
version: 1.2.0
appVersion: "2.5.1"version— версия самого chart;appVersion— версия упакованного приложения;type: application— устанавливаемый chart;type: library— библиотечный chart с переиспользуемыми шаблонами.
Версия chart и версия приложения не обязаны совпадать.
Зависимости
Зависимости объявляются в Chart.yaml:
dependencies:
- name: redis
version: "20.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabledЗагрузка и обновление зависимостей:
helm dependency update ./my-app
helm dependency build ./my-appChart.lock фиксирует разрешённые версии зависимостей. Его обычно сохраняют в системе контроля версий.
Values и переопределение настроек
values.yaml содержит значения по умолчанию:
replicaCount: 2
image:
repository: registry.example.com/my-app
tag: "2.5.1"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
targetPort: 8080
ingress:
enabled: false
className: nginx
host: app.example.com
tls:
enabled: false
secretName: app-tls
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512MiЗначения для production можно вынести в отдельный файл:
# values-production.yaml
replicaCount: 3
image:
tag: "2.5.1"
ingress:
enabled: true
host: app.example.com
tls:
enabled: true
secretName: app-tls
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1GiФайлы применяются слева направо, более поздние значения имеют приоритет:
helm upgrade --install my-app ./chart \
-f values.yaml \
-f values-production.yamlОтдельное значение можно передать через командную строку:
helm upgrade --install my-app ./chart \
--set replicaCount=4 \
--set image.tag=2.5.2Для строк, которые могут быть ошибочно преобразованы в число или логическое значение, используют --set-string:
helm upgrade --install my-app ./chart \
--set-string image.tag=2026.09Секреты не следует хранить в открытом виде в values.yaml. --set также нежелателен для секретов: значение может попасть в историю команд, журналы CI/CD и метаданные релиза. Обычно применяют внешний менеджер секретов, зашифрованные файлы или заранее созданные Kubernetes Secret.
Приоритет значений
В упрощённом виде приоритет возрастает так:
values.yamlвнутри chart;- значения родительского chart для зависимостей;
- файлы, переданные через
-fили--values; - параметры
--set,--set-stringи аналогичные.
Просмотр итоговых значений установленного релиза:
helm get values my-app
helm get values my-app --allШаблоны Helm
Шаблоны Helm основаны на Go templates и библиотеке функций Sprig. Выражения записываются внутри {{ ... }}.
Пример templates/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-app.fullname" . }}
labels:
{{- include "my-app.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
{{- include "my-app.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "my-app.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
resources:
{{- toYaml .Values.resources | nindent 12 }}Основные встроенные объекты:
| Объект | Содержимое |
|---|---|
.Values |
Значения из values.yaml, файлов -f и --set |
.Chart |
Данные из Chart.yaml |
.Release |
Имя, namespace и данные текущего релиза |
.Capabilities |
Возможности и версии API целевого кластера |
.Template |
Информация о текущем шаблоне |
.Files |
Доступ к дополнительным файлам chart |
Условия
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
# ...
{{- end }}Проверка нескольких условий:
{{- if and .Values.ingress.enabled .Values.ingress.tls.enabled }}
# TLS-настройки
{{- end }}Циклы
values.yaml:
env:
- name: LOG_LEVEL
value: info
- name: APP_MODE
value: productionШаблон:
env:
{{- range .Values.env }}
- name: {{ .name }}
value: {{ .value | quote }}
{{- end }}Конвейеры и функции
metadata:
name: {{ .Values.nameOverride | default .Chart.Name | trunc 63 | trimSuffix "-" }}Результат передаётся от одной функции к другой через |.
Полезные функции:
# Заключить значение в кавычки
value: {{ .Values.mode | quote }}
# Обязательное значение
host: {{ required "ingress.host обязателен" .Values.ingress.host }}
# Преобразовать структуру в YAML и выровнять отступ
{{- toYaml .Values.resources | nindent 10 }}
# Вычислить SHA-256 — полезно для перезапуска при изменении ConfigMap
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}Именованные шаблоны
Вспомогательные шаблоны обычно размещаются в _helpers.tpl:
{{- define "my-app.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}Подключение:
metadata:
labels:
{{- include "my-app.labels" . | nindent 4 }}Проверка результата
Перед установкой полезно выполнить:
helm lint ./chart
helm template my-app ./chart -f values-production.yaml
helm install my-app ./chart --dry-run --debughelm lint проверяет структуру chart, но не гарантирует, что ресурсы будут приняты конкретным кластером. Для production-процесса дополнительно применяют серверную проверку, схемы значений, policy-as-code и тестовое развёртывание.
Управление Helm-релизами
Установка:
helm install my-app ./chart \
--namespace production \
--create-namespaceОбновление:
helm upgrade my-app ./chart \
--namespace production \
-f values-production.yamlИдемпотентный вариант для CI/CD:
helm upgrade --install my-app ./chart \
--namespace production \
--create-namespace \
-f values-production.yamlПросмотр сгенерированных манифестов релиза:
helm get manifest my-app -n productionПросмотр заметок chart:
helm get notes my-app -n productionИстория и откат:
helm history my-app -n production
helm rollback my-app 3 -n production --waitСледует учитывать, что откат Kubernetes-манифестов не всегда откатывает внешние изменения: миграции базы данных, содержимое постоянного тома и операции внешних сервисов требуют отдельной стратегии.
Ingress и Ingress Controller
Ingress — Kubernetes-ресурс с правилами маршрутизации входящего HTTP/HTTPS-трафика к Service внутри кластера.
Ingress Controller — компонент, который наблюдает за объектами Ingress и фактически настраивает прокси или балансировщик нагрузки.
Популярные варианты контроллеров:
- ingress-nginx;
- Traefik;
- HAProxy Ingress;
- облачные контроллеры, создающие управляемые балансировщики;
- ingress-контроллеры сервис-мешей и API-шлюзов.
Создание только объекта Ingress без установленного и подходящего контроллера не обеспечит внешний доступ.
Базовая цепочка:
Клиент -> Load Balancer -> Ingress Controller -> Service -> PodПример Service:
apiVersion: v1
kind: Service
metadata:
name: web
namespace: production
spec:
type: ClusterIP
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080Пример Ingress:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: production
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80ingressClassName указывает, какой контроллер должен обработать ресурс.
Типы пути
pathType |
Поведение |
|---|---|
Exact |
Точное совпадение пути |
Prefix |
Совпадение по сегментам префикса URL |
ImplementationSpecific |
Поведение определяет Ingress Controller |
Маршрутизация нескольких путей:
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80Маршрутизация по разным доменам:
spec:
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api
port:
number: 80
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin
port:
number: 80Аннотации Ingress зависят от выбранного контроллера. Настройка, работающая с одним контроллером, может не поддерживаться другим. Поэтому аннотации следует проверять по документации конкретного Ingress Controller.
TLS и маршрутизация Ingress
TLS-секрет содержит сертификат и закрытый ключ:
kubectl create secret tls app-tls \
--cert=tls.crt \
--key=tls.key \
-n productionIngress с TLS:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
namespace: production
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80Обычно TLS завершается на Ingress Controller, после чего трафик к Service передаётся внутри кластера. При повышенных требованиях может использоваться TLS и между прокси и приложением.
Для автоматического выпуска и обновления сертификатов часто применяют отдельный контроллер сертификатов. Сертификаты, закрытые ключи и DNS-записи должны управляться как часть общей инфраструктуры, а не вручную на каждом релизе.
Диагностика Ingress:
kubectl get ingress -A
kubectl describe ingress web -n production
kubectl get ingressclass
kubectl get pods -n ingress-nginx
kubectl logs -n ingress-nginx deployment/ingress-nginx-controller
kubectl get endpointslices -n production -l kubernetes.io/service-name=webЕсли Ingress отвечает ошибкой, последовательно проверяют:
- DNS домена и внешний адрес контроллера;
- выбранный
ingressClassName; - правила host и path;
- существование Service и правильность его порта;
- наличие EndpointSlice с готовыми Pod;
- readiness probe приложения;
- журналы Ingress Controller;
- TLS Secret и соответствие сертификата домену.
RBAC в Kubernetes
RBAC — Role-Based Access Control, модель управления доступом на основе ролей.
RBAC отвечает на вопрос: какой субъект может выполнить какое действие над каким ресурсом.
Основные объекты:
| Объект | Область действия | Назначение |
|---|---|---|
Role |
Namespace | Набор разрешений внутри одного namespace |
ClusterRole |
Весь кластер | Кластерные или переиспользуемые разрешения |
RoleBinding |
Namespace | Назначает Role или ClusterRole субъекту в namespace |
ClusterRoleBinding |
Весь кластер | Назначает ClusterRole на уровне кластера |
Субъектом может быть:
- пользователь (
User); - группа (
Group); - сервисная учётная запись (
ServiceAccount).
Kubernetes RBAC не создаёт обычных пользователей. Аутентификация пользователей выполняется внешним механизмом, а RBAC определяет их полномочия после успешной аутентификации.
Role
Разрешение читать Pod и их журналы в namespace production:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]Пустая строка в apiGroups обозначает основную API-группу, к которой относятся Pod, Service, ConfigMap и Secret.
Частые значения verbs:
get, list, watch, create, update, patch, delete, deletecollectionRoleBinding
Назначение роли пользователю:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods
namespace: production
subjects:
- kind: User
name: developer@example.com
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.ioroleRef после создания binding нельзя произвольно заменить на другую роль — обычно объект удаляют и создают заново.
ClusterRole
Чтение узлов кластера:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: node-reader
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]Назначение на уровне всего кластера:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: read-nodes
subjects:
- kind: Group
name: platform-engineers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: node-reader
apiGroup: rbac.authorization.k8s.ioClusterRole можно назначить через RoleBinding. Тогда её разрешения действуют только на ресурсы указанного namespace:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: namespace-view
namespace: production
subjects:
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: view
apiGroup: rbac.authorization.k8s.ioПроверка доступа
# Может ли текущий пользователь читать Pod?
kubectl auth can-i get pods -n production
# Может ли пользователь удалить Deployment?
kubectl auth can-i delete deployments \
-n production \
--as=developer@example.com
# Может ли ServiceAccount читать Secret?
kubectl auth can-i get secrets \
-n production \
--as=system:serviceaccount:production:my-app
# Показать доступные текущему пользователю действия
kubectl auth can-i --list -n productionПринцип наименьших привилегий
Следует выдавать только необходимые действия и ресурсы. Опасные широкие правила:
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]Особого внимания требуют разрешения на:
- чтение
secrets; - создание и изменение Pod и workload-ресурсов;
pods/execиpods/portforward;- создание RoleBinding и ClusterRoleBinding;
- изменение RBAC-объектов;
- создание ресурсов от имени привилегированного ServiceAccount;
- доступ к узлам и служебным namespace.
Возможность создать Pod с произвольным ServiceAccount или примонтировать Secret часто фактически означает возможность получить полномочия этих объектов.
ServiceAccount
ServiceAccount — учётная запись для процессов, работающих внутри Pod.
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app
namespace: productionИспользование в Deployment:
spec:
template:
spec:
serviceAccountName: my-app
automountServiceAccountToken: false
containers:
- name: app
image: registry.example.com/my-app:2.5.1Если приложение не обращается к Kubernetes API, автоматическое подключение токена лучше отключить:
automountServiceAccountToken: falseЕсли приложению нужен доступ, создаётся минимальная Role и RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: config-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["my-app-config"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: my-app-config-reader
namespace: production
subjects:
- kind: ServiceAccount
name: my-app
namespace: production
roleRef:
kind: Role
name: config-reader
apiGroup: rbac.authorization.k8s.ioresourceNames ограничивает правило конкретными именованными объектами, но поддержка и поведение зависят от типа запроса. Например, обычный list нельзя полноценно ограничить списком имён так же, как get.
StatefulSet
StatefulSet управляет Pod, которым нужны стабильные идентификаторы, упорядоченное создание или постоянные тома.
В отличие от Deployment, StatefulSet предоставляет:
- стабильные имена Pod:
postgres-0,postgres-1; - стабильные DNS-имена;
- упорядоченное создание, обновление и удаление;
- отдельный PersistentVolumeClaim для каждой реплики;
- сохранение привязки Pod к его постоянному хранилищу.
StatefulSet применяют для баз данных, брокеров сообщений, распределённых хранилищ и других stateful-систем. Наличие StatefulSet само по себе не делает приложение отказоустойчивым: репликация, выбор лидера, резервное копирование и восстановление определяются самим приложением или оператором.
Пример headless Service:
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: production
spec:
clusterIP: None
selector:
app: postgres
ports:
- name: postgres
port: 5432Пример StatefulSet:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: production
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:17
ports:
- name: postgres
containerPort: 5432
env:
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: postgres-credentials
key: password
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
readinessProbe:
exec:
command:
- sh
- -c
- pg_isready -U postgres
initialDelaySeconds: 5
periodSeconds: 10
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20GiДля Pod postgres-0 будет создан PVC с предсказуемым именем, например data-postgres-0.
Масштабирование StatefulSet:
kubectl scale statefulset postgres --replicas=3 -n productionУвеличивать число реплик безопасно только тогда, когда приложение умеет формировать кластер и реплицировать данные. Простое создание трёх экземпляров базы данных не превращает их в единый кластер.
Удаление StatefulSet обычно не удаляет его PVC автоматически. Это защищает данные, но требует контролируемой очистки неиспользуемых томов.
PersistentVolume и PersistentVolumeClaim
PersistentVolume (PV) — ресурс кластера, представляющий постоянное хранилище.
PersistentVolumeClaim (PVC) — запрос приложения на хранилище с определённым размером, режимом доступа и классом.
Связь:
Pod -> PVC -> PV -> реальный диск или сетевое хранилищеПример PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
namespace: production
spec:
accessModes:
- ReadWriteOnce
storageClassName: fast
resources:
requests:
storage: 10GiПодключение PVC к Pod:
spec:
containers:
- name: app
image: registry.example.com/my-app:2.5.1
volumeMounts:
- name: data
mountPath: /var/lib/my-app
volumes:
- name: data
persistentVolumeClaim:
claimName: app-dataРежимы доступа
| Режим | Назначение |
|---|---|
ReadWriteOnce (RWO) |
Чтение и запись с одного узла |
ReadOnlyMany (ROX) |
Чтение с нескольких узлов |
ReadWriteMany (RWX) |
Чтение и запись с нескольких узлов |
ReadWriteOncePod (RWOP) |
Чтение и запись только одним Pod |
Фактическая поддержка режимов зависит от CSI-драйвера и типа хранилища. ReadWriteOnce относится прежде всего к подключению на уровне узла и не всегда означает «ровно один Pod».
Фазы PVC и PV
Частые состояния:
Pending— подходящий том ещё не выделен;Bound— PVC связан с PV;Released— claim удалён, но данные или PV ещё не переработаны;Available— PV доступен для привязки.
Диагностика:
kubectl get pvc -A
kubectl get pv
kubectl describe pvc app-data -n production
kubectl get storageclassStorageClass и динамическое выделение томов
StorageClass описывает класс хранилища и CSI-провайдер, который динамически создаёт тома для PVC.
Пример:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: csi.example.com
reclaimPolicy: Retain
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
type: ssdКлючевые поля:
provisioner— CSI-драйвер;reclaimPolicy— что делать с томом после удаления PVC;volumeBindingMode— когда выделять и привязывать том;allowVolumeExpansion— разрешено ли увеличение размера;parameters— настройки онкретного провайдера.
reclaimPolicy
Delete— после удаления PVC инфраструктурный том обычно удаляется;Retain— PV и данные сохраняются и требуют ручного решения.
Для критичных данных Retain уменьшает риск случайного удаления, но не заменяет резервное копирование.
volumeBindingMode
Immediate выделяет том сразу после создания PVC.
WaitForFirstConsumer откладывает выделение до планирования первого Pod. Это помогает выбрать хранилище в правильной зоне доступности и избежать ситуации, когда Pod нельзя разместить рядом с томом.
Расширение PVC
Если StorageClass и CSI-драйвер поддерживают расширение:
kubectl patch pvc app-data -n production \
-p '{"spec":{"resources":{"requests":{"storage":"20Gi"}}}}'Уменьшение существующего PVC обычно не поддерживается. Перед изменением критичного тома необходимо проверить возможности драйвера и наличие актуальной резервной копии.
Снимки и резервные копии
PV обеспечивает постоянство данных при перезапуске Pod, но не является резервной копией. Для production необходимы:
- регулярные backups;
- проверка восстановления;
- контроль срока хранения;
- копии в независимой зоне или учётной записи, если этого требуют риски;
- согласованные снимки для приложений, которым недостаточно копирования диска.
Автомасштабирование HPA
HorizontalPodAutoscaler (HPA) автоматически изменяет количество реплик Deployment, StatefulSet или другого масштабируемого workload на основе метрик.
Типичная схема:
Metrics Server / monitoring adapter
|
v
HPA
|
v
Deployment replicas: 2 -> 5 -> 3HPA не масштабирует отдельный Pod по вертикали. Он изменяет количество реплик целевого ресурса.
Пример HPA по CPU:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70Для метрики CPU типа Utilization контейнеры должны иметь resources.requests.cpu:
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: "1"
memory: 512MiУпрощённая логика расчёта:
желаемые реплики ≈ текущие реплики × текущая метрика / целевая метрикаНапример, если работают 4 реплики, средняя загрузка CPU равна 140%, а целевая — 70%, HPA стремится примерно к 8 репликам. Реальное решение учитывает допуски, отсутствующие метрики, готовность Pod и политики стабилизации.
HPA по нескольким метрикам
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: AverageValue
averageValue: 400MiПри нескольких метриках контроллер рассчитывает рекомендуемое число реплик для каждой и выбирает наибольшее допустимое значение.
Поведение масштабирования
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 60
- type: Pods
value: 4
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60Окно стабилизации при уменьшении числа реплик помогает избежать частых колебаний.
Условия работы HPA
Для HPA необходим источник метрик:
- resource metrics — обычно Metrics Server;
- custom metrics — адаптер системы мониторинга;
- external metrics — внешняя очередь, облачная метрика или другой источник через адаптер.
Проверка:
kubectl get hpa -A
kubectl describe hpa web -n production
kubectl top pods -n production
kubectl top nodesЧастые причины, по которым HPA не работает:
- Metrics Server или адаптер метрик не установлен;
- у контейнеров отсутствуют
requests; - метрика недоступна или имеет неверное имя;
minReplicasиmaxReplicasнастроены неправильно;- приложение долго запускается или не проходит readiness probe;
- кластеру не хватает узлов для размещения новых Pod;
- Deployment вручную постоянно возвращают к фиксированному
replicasчерез другой инструмент.
Если HPA управляет Deployment, значение spec.replicas в GitOps или Helm следует согласовать с автомасштабированием, чтобы разные контроллеры не перезаписывали число реплик друг у друга.
Readiness, liveness и startup probes
Проверки состояния помогают Kubernetes принимать решения о маршрутизации трафика и перезапуске контейнера.
| Probe | Что проверяет | Результат ошибки |
|---|---|---|
readinessProbe |
Готов ли контейнер принимать трафик | Pod исключается из endpoints Service |
livenessProbe |
Работоспособен ли контейнер | Контейнер перезапускается |
startupProbe |
Завершился ли запуск медленного приложения | До успеха отключает влияние liveness/readiness по правилам запуска |
Readiness probe
readinessProbe:
httpGet:
path: /ready
port: http
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
successThreshold: 1Если readiness probe не проходит, контейнер продолжает работать, но Pod перестаёт получать трафик через Service. Это полезно при прогреве приложения, временной перегрузке или потере критичной зависимости.
Liveness probe
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3Liveness должна проверять внутреннюю жизнеспособность процесса. Не следует без необходимости связывать её с доступностью внешней базы данных или стороннего API: сбой общей зависимости может вызвать одновременный перезапуск всех Pod и усугубить проблему.
Startup probe
startupProbe:
httpGet:
path: /health
port: http
periodSeconds: 5
failureThreshold: 30В этом примере приложению даётся до 150 секунд на запуск. Пока startup probe не завершилась успешно, liveness и readiness не начинают влиять на контейнер согласно обычному циклу проверок.
Виды проверок
HTTP GET
readinessProbe:
httpGet:
path: /ready
port: 8080
scheme: HTTPHTTP-код от 200 до 399 считается успешным.
TCP socket
livenessProbe:
tcpSocket:
port: 5432
periodSeconds: 10Проверяет возможность установить TCP-соединение, но не гарантирует корректную работу прикладного протокола.
Exec
readinessProbe:
exec:
command:
- sh
- -c
- test -f /tmp/ready
periodSeconds: 5Команда с кодом выхода 0 означает успех. Частые и тяжёлые exec-проверки могут создавать лишнюю нагрузку.
gRPC
livenessProbe:
grpc:
port: 50051
periodSeconds: 10Приложение должно поддерживать стандартный протокол gRPC health checking.
Параметры probes
| Поле | Значение |
|---|---|
initialDelaySeconds |
Задержка перед первой проверкой |
periodSeconds |
Интервал между проверками |
timeoutSeconds |
Максимальное время одной проверки |
failureThreshold |
Число ошибок до признания проверки неуспешной |
successThreshold |
Число успехов для восстановления состояния |
terminationGracePeriodSeconds |
Время на корректное завершение после ошибки liveness, если поддерживается конфигурацией |
Разделение endpoint
Рекомендуется разделять:
/health или /live — процесс жив и не находится в неисправимом состоянии
/ready — экземпляр готов принимать новый трафик
/startup — приложение завершило начальную загрузкуОдин и тот же endpoint допустим для простого приложения, но отдельные проверки позволяют точнее управлять поведением.
Graceful shutdown
При остановке Pod приложение должно прекратить принимать новый трафик и корректно завершить текущие запросы:
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]preStop не заменяет корректную обработку сигнала завершения приложением. Процесс должен обрабатывать SIGTERM, закрывать соединения и завершаться до окончания terminationGracePeriodSeconds.
Рекомендации для production
Helm
- фиксировать версии chart, приложений и зависимостей;
- хранить chart и несекретные values в системе контроля версий;
- проверять
helm lintи результатhelm templateв CI; - использовать
helm upgrade --install --atomic --waitтам, где автоматический откат соответствует процессу выпуска; - не хранить секреты в открытых values;
- добавлять
values.schema.jsonдля проверки структуры значений; - не выполнять необратимые миграции без отдельной стратегии совместимости и отката.
Ingress
- явно задавать
ingressClassName; - использовать TLS;
- ограничивать размер запросов, тайм-ауты и частоту запросов с учётом возможностей контроллера;
- следить за сроком действия сертификатов;
- не считать Ingress заменой NetworkPolicy, аутентификации и авторизации приложения;
- проверять совместимость аннотаций с конкретным контроллером.
RBAC
- применять принцип наименьших привилегий;
- предпочитать namespace-роли кластерным разрешениям;
- не выдавать
cluster-adminприложениям и обычным пользователям; - отключать автоматическое монтирование токена ServiceAccount, если доступ к API не нужен;
- регулярно проверять RoleBinding и ClusterRoleBinding;
- отдельно контролировать доступ к Secret,
pods/execи RBAC-ресурсам.
Stateful workloads
- использовать подходящий CSI-драйвер и StorageClass;
- выбирать
WaitForFirstConsumerдля зонального хранилища, когда это уместно; - планировать backups и регулярно проверять восстановление;
- учитывать
reclaimPolicyперед удалением PVC; - использовать anti-affinity или topology spread constraints для распределения реплик;
- применять оператор приложения, если он надёжно автоматизирует репликацию, обновление и восстановление.
HPA
- задавать реалистичные
requests; - выбирать метрики, связанные с фактической нагрузкой;
- оставлять запас мощности и контролировать максимальное число реплик;
- настраивать стабилизацию, чтобы избежать постоянных колебаний;
- учитывать скорость запуска Pod и доступность узлов;
- сочетать HPA с механизмом масштабирования узлов, если кластер должен автоматически увеличивать вычислительную ёмкость.
Probes
- отделять готовность от жизнеспособности;
- использовать startup probe для медленного запуска;
- не делать liveness зависимой от нестабильного внешнего сервиса;
- задавать явные тайм-ауты и пороги;
- тестировать проверки при нагрузке и деградации;
- обеспечивать корректное завершение Pod.
Общий пример
Ниже приведён упрощённый Helm chart для stateless-приложения с ServiceAccount, Deployment, Service, Ingress и HPA.
values.yaml
replicaCount: 2
image:
repository: registry.example.com/web
tag: "1.4.0"
pullPolicy: IfNotPresent
serviceAccount:
create: true
name: ""
service:
port: 80
targetPort: 8080
ingress:
enabled: true
className: nginx
host: web.example.com
tls:
enabled: true
secretName: web-tls
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: "1"
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70templates/_helpers.tpl
{{- define "web.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "web.fullname" -}}
{{- printf "%s-%s" .Release.Name (include "web.name" .) | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- define "web.selectorLabels" -}}
app.kubernetes.io/name: {{ include "web.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "web.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "web.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}templates/serviceaccount.yaml
{{- if .Values.serviceAccount.create }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "web.serviceAccountName" . }}
automountServiceAccountToken: false
{{- end }}templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "web.fullname" . }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
{{- include "web.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "web.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "web.serviceAccountName" . }}
terminationGracePeriodSeconds: 30
containers:
- name: web
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
startupProbe:
httpGet:
path: /health
port: http
periodSeconds: 5
failureThreshold: 30
readinessProbe:
httpGet:
path: /ready
port: http
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /health
port: http
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]Если HPA включён, поле replicas не выводится в шаблон Deployment, чтобы Helm не возвращал количество реплик к значению из values.yaml при каждом обновлении.
templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "web.fullname" . }}
spec:
type: ClusterIP
selector:
{{- include "web.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: httptemplates/ingress.yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "web.fullname" . }}
spec:
ingressClassName: {{ .Values.ingress.className }}
{{- if .Values.ingress.tls.enabled }}
tls:
- hosts:
- {{ .Values.ingress.host | quote }}
secretName: {{ .Values.ingress.tls.secretName }}
{{- end }}
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "web.fullname" . }}
port:
name: http
{{- end }}templates/hpa.yaml
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: {{ include "web.fullname" . }}
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: {{ include "web.fullname" . }}
minReplicas: {{ .Values.autoscaling.minReplicas }}
maxReplicas: {{ .Values.autoscaling.maxReplicas }}
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
behavior:
scaleDown:
stabilizationWindowSeconds: 300
{{- end }}Проверка и установка
helm lint ./web-chart
helm template web ./web-chart \
--namespace production \
-f values-production.yaml
helm upgrade --install web ./web-chart \
--namespace production \
--create-namespace \
-f values-production.yaml \
--atomic \
--wait \
--timeout 10mПроверка ресурсов:
kubectl get deploy,pods,svc,ingress,hpa -n production
kubectl rollout status deployment/web-web -n production
kubectl describe hpa web-web -n production
kubectl get endpointslices -n production
kubectl auth can-i --list \
--as=system:serviceaccount:production:web-web \
-n productionПолный production-процесс обычно также включает NetworkPolicy, Pod Security Standards, PodDisruptionBudget, распределение по узлам и зонам, мониторинг, централизованные журналы, управление секретами, резервное копирование и проверяемую стратегию восстановления.