Под в Kubernetes живёт до того момента, пока кластеру выгодно его держать. Сбой ноды, вытеснение из-за нехватки памяти, обновление образа или ручное удаление стирают его вместе с IP-адресом и локальными данными. Поэтому в продакшне поды почти никогда не создают напрямую, их создают контроллеры: Deployment, ReplicaSet, StatefulSet, DaemonSet, Job и CronJob.
Каждый контроллер закрывает свой сценарий. Deployment держит нужное число реплик stateless-сервиса и обновляет их без простоя, StatefulSet выдаёт постоянные имена и тома, DaemonSet ставит по поду на каждую ноду, Job доводит разовую задачу до успешного завершения, а CronJob запускает такие задачи по расписанию. ReplicaSet напрямую не применяют: его создаёт Deployment и он же управляет ревизиями.
Дальше идут манифесты, команды проверки и таблица выбора. Ответ на главный вопрос короткий: смотрите на природу нагрузки (есть ли состояние), на требование размещения (нужен ли под на каждой ноде) и на жизненный цикл задачи (одноразовая или регулярная).
Почему поды эфемерны и как контроллеры управляют жизненным циклом
Что такое под и почему он недолговечен
Под (Pod) - минимальная единица развёртывания Kubernetes: один или несколько контейнеров с общей сетевой средой, общим IP и общими томами. Под нельзя перенести на другую ноду вместе с адресом: планировщик выбирает ноду заново, а IP выдаётся заново из пула CNI.
Причины исчезновения пода:
- нода ушла в статус NotReady, и kubelet перестал подтверждать работу подов;
- на ноде закончились ресурсы, и kubelet вытесняет поды с низким приоритетом (eviction);
- вы обновили образ в контроллере, и старые поды заменяются новыми;
- под удалили вручную или скриптом;
- контейнер упал и не перезапустился из-за restartPolicy или жёстких лимитов.
Создайте одиночный под с nginx, посмотрите его IP, удалите под и создайте заново:
kubectl run nginx-standalone --image=nginx:1.25 kubectl get pod nginx-standalone -o wide kubectl delete pod nginx-standalone kubectl run nginx-standalone --image=nginx:1.25
IP во втором выводе отличается от первого. Если удалить под и не запускать его снова, никто его не восстановит: у одиночного пода нет владельца. Такой под годится для отладки, но не для продакшна. Наблюдать за событиями удобно в режиме watch:
kubectl get pods -w
Роль контроллеров в управлении жизненным циклом подов
Контроллер - процесс в kube-controller-manager (или в вашем операторе), который работает в цикле согласования (reconcile loop). Он читает из API-сервера объект с описанием желаемого состояния, сравнивает его с текущим и делает шаги, чтобы они совпали.
Цикл выглядит так:
- Вы применяете манифест командой kubectl apply -f deployment.yaml. Объект сохраняется в etcd через API-сервер.
- Контроллер Deployment замечает изменение и создаёт ReplicaSet с нужным числом реплик.
- Контроллер ReplicaSet считает реальные поды и добавляет недостающие.
- Планировщик назначает поды на ноды, kubelet запускает контейнеры.
- Под удалён или нода упала: контроллер создаёт замену, и количество подов возвращается к заданному.
API-сервер и etcd держат единственный источник правды. Контроллеры не общаются друг с другом напрямую, они читают и пишут объекты. Механику watch и reconcile мы разбирали в статье про архитектуру взаимодействия контроллеров Kubernetes, а общие принципы работы контроллеров описаны в отдельном гиде по control loop.
Практический вывод: одиночный под не восстанавливается, а набор подов под управлением контроллера превращается в самовосстанавливающийся сервис с предсказуемым числом реплик.
Deployment и ReplicaSet: управление stateless-приложениями
Deployment подходит для stateless-нагрузок: HTTP API, веб-фронтенд, воркеры без локального состояния. Подами он не управляет напрямую, между ними стоит ReplicaSet, который и держит нужное количество реплик.
Как Deployment управляет ReplicaSet и версиями
При создании Deployment появляется ReplicaSet с хешем шаблона пода в имени, например nginx-7d9f8c5b64. Хеш считается от pod template, поэтому смена образа, переменных окружения или лимитов создаёт новый ReplicaSet. Старый остаётся в кластере с нулём реплик, и именно к нему Deployment вернётся при откате.
kubectl get deployments kubectl get rs kubectl get rs -o wide kubectl rollout history deployment/nginx kubectl rollout undo deployment/nginx
Посмотреть конкретную ревизию: kubectl rollout history deployment/nginx --revision=2. Связь ReplicaSet с подами видна через kubectl describe rs nginx-7d9f8c5b64, в поле Controlled By будет указан Deployment. Число реплик меняется командой kubectl scale deployment nginx --replicas=5.
Стратегии обновления: RollingUpdate и Recreate
У Deployment две стратегии. RollingUpdate заменяет поды порциями, Recreate сначала удаляет все старые поды и только потом создаёт новые. По умолчанию Deployment использует стратегию RollingUpdate с maxSurge: 25% и maxUnavailable: 25%; Kubernetes округляет surge вверх, а unavailability вниз (источник).
| Параметр | RollingUpdate | Recreate |
|---|---|---|
| Простой | нет при корректных настройках | есть, пока идёт смена всех подов |
| maxSurge | сколько подов можно создать сверх desired, по умолчанию 25% | не применяется |
| maxUnavailable | сколько подов может быть недоступно, по умолчанию 25% | не применяется |
| Когда выбирать | обычные веб-сервисы и API | когда старая и новая версии несовместимы |
Для нулевого простоя ставьте maxSurge: 1 и maxUnavailable: 0. Кластер сначала поднимает новый под, дожидается readinessProbe и только потом гасит старый. С maxUnavailable: 1 экономится время обновления, но один под в момент смены недоступен. Пример параметров стратегии с maxSurge: 25% и maxUnavailable: 25% приведён в разборе стратегий деплоя.
Готовый манифест Deployment и проверка
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
kubectl apply -f deployment.yaml kubectl get deployments kubectl get pods -l app=nginx kubectl rollout status deployment/nginx kubectl describe deployment nginx
В выводе kubectl get rs будет один ReplicaSet с DESIRED 3. После смены образа появится второй ReplicaSet с DESIRED 3, а прежний уйдёт в 0; старые поды исчезнут по одному. Расширенные примеры со стратегиями, probes и откатом мы собрали в полном руководстве по Deployment.
StatefulSet для stateful-приложений: постоянные имена и хранилище
StatefulSet нужен там, где поду важно, кто он: PostgreSQL, MySQL, Kafka, Elasticsearch, ZooKeeper, etcd. Каждый под получает порядковый индекс, собственную DNS-запись и собственный PersistentVolumeClaim.
Ключевые отличия StatefulSet от Deployment
| Свойство | Deployment | StatefulSet |
|---|---|---|
| Имена подов | случайный суффикс, например nginx-7d9f8c5b64-xk2lp | порядковый индекс: web-0, web-1, web-2 |
| Сетевой идентификатор | стабильного DNS нет | web-0.nginx через Headless Service |
| Хранилище | общий том или emptyDir | volumeClaimTemplates, PVC на каждый под |
| Порядок запуска и остановки | параллельно | последовательно, по возрастанию и убыванию индекса |
| Политика обновления | RollingUpdate или Recreate | RollingUpdate с порядком OrderedReady или Parallel |
| Типичная нагрузка | stateless API, фронтенд | базы данных, очереди, кластеры |
Постоянное хранилище и Headless Service
volumeClaimTemplates не создаёт один общий том, а разворачивает шаблон PVC на каждый под: data-web-0, data-web-1, data-web-2. По умолчанию PVC, созданные из volumeClaimTemplates, не удаляются при удалении StatefulSet или уменьшении числа реплик — это стандартная политика сохранения (retention) (Kubernetes 1.27: StatefulSet PVC Auto-Deletion). При масштабировании вниз тома сохраняются, а при масштабировании вверх старые PVC повторно подключаются к тем же ordinal-подам, поэтому данные переживают пересоздание (Kubernetes StatefulSet Guide).
Headless Service с clusterIP: None не балансирует трафик, а выдаёт DNS-имена конкретным подам: web-0.nginx, web-1.nginx. Через него приложения находят лидера и реплики. Поле serviceName в StatefulSet должно ссылаться на этот сервис, иначе стабильного DNS у подов не будет.
Манифест StatefulSet и проверка
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
clusterIP: None
selector:
app: nginx
ports:
- port: 80
name: web
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
name: web
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
kubectl apply -f statefulset.yaml kubectl get statefulsets kubectl get pods -l app=nginx kubectl get pvc kubectl scale statefulset web --replicas=5
Поды создаются по очереди: web-0, потом web-1, потом web-2. При масштабировании вниз порядок обратный. Проверить привязку тома к поду можно командой kubectl describe pvc data-web-0, в поле Used By будет указан конкретный под.
DaemonSet: поды на каждой ноде
DaemonSet создаёт по одному поду на каждой ноде либо на тех, что подходят под nodeSelector и affinity. Новая нода появляется в кластере, и контроллер сразу ставит на неё свой под без ручных действий.
Сценарии использования DaemonSet
- сбор логов: Fluentd, Fluent Bit, Filebeat читают каталоги ноды;
- метрики и мониторинг: node-exporter и агенты сбора статистики;
- сеть: Calico, Cilium и другие CNI-компоненты;
- хранилище: CSI-драйверы и демоны монтирования томов;
- безопасность: агенты аудита узлов и runtime-сканеры.
Под DaemonSet часто требует tolerations, иначе он не попадёт на ноды с taints, например на control-plane с меткой node-role.kubernetes.io/control-plane:NoSchedule. Для доступа к файлам ноды задают hostPath, а через resources.limits ограничивают потребление, чтобы агент не вытеснил приложение.
Манифест DaemonSet и проверка
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
labels:
app: fluentd
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: fluentd
image: fluent/fluentd:v1.16-debian-1
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 300m
memory: 400Mi
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
hostPath:
path: /var/log
kubectl apply -f daemonset.yaml kubectl get daemonsets -A kubectl get pods -l app=fluentd -o wide
В колонке NODE у подов будут разные ноды, и число подов совпадёт с числом нод, подходящих под селектор. Флаг -o wide помогает поймать ситуацию, когда под не встал на control-plane из-за отсутствия toleration.
Job и CronJob: разовые и регулярные задачи
Deployment, StatefulSet и DaemonSet запускают поды, которые живут постоянно. Job описывает задачу, которую нужно доделать до конца: миграция схемы, разовый бэкап, batch-обработка, разогрев кэша.
Job: запуск до завершения
Ключевые поля Job: completions (сколько успешных завершений нужно), parallelism (сколько подов работает одновременно), backoffLimit (сколько попыток повтора при сбое) и restartPolicy. Для Job допускается только restartPolicy со значением Never или OnFailure; значение Always недопустимо, под должен завершаться (Jobs | Kubernetes).
kubectl create job pi --image=perl:5.34 -- perl -Mbignum=bpi -wle 'print bpi(300)' kubectl get jobs kubectl describe job pi kubectl logs job/pi kubectl delete job pi
В выводе kubectl get jobs колонка COMPLETIONS покажет 1/1 после успеха. Поле ttlSecondsAfterFinished позволяет автоматически очищать завершённые Job (в статусе Completed или Failed): таймер запускается, когда status condition Job меняется на Complete или Failed, а по истечении TTL Job становится доступным для очистки и удаляется каскадно вместе с зависимыми объектами (Automatic Cleanup for Finished Jobs | Kubernetes). Это избавляет от накопления отработавших объектов. Параллельные batch-задачи удобно запускать через parallelism и completions, например completions: 10, parallelism: 3.
Обратите внимание: в старых версиях Kubernetes TTL-контроллер для завершённых Job находился в статусе alpha и требовал включения соответствующего feature gate (Продвинутые абстракции Kubernetes: Job, CronJob). Актуальный статус стабильности поля ttlSecondsAfterFinished и минимальную требуемую версию сверяйте с документацией вашей версии кластера.
CronJob: расписание и управление
CronJob создаёт Job по расписанию в формате cron из пяти полей. Если поле timeZone не указано, kube-controller-manager интерпретирует расписание относительно своей локальной временной зоны. Чтобы задать часовой пояс явно, используйте поле .spec.timeZone — оно позволяет интерпретировать расписание, например, относительно UTC (CronJob | Kubernetes). Поддержка часовых поясов в CronJob имеет статус Stable начиная с Kubernetes v1.21, а само поле .spec.timeZone появилось в CronJob API в Kubernetes 1.24 (Kubernetes 1.24: обзор нововведений).
concurrencyPolicy: Forbid запрещает параллельные запуски, пока предыдущий Job не завершился. Параметры successfulJobsHistoryLimit и failedJobsHistoryLimit ограничивают историю, а startingDeadlineSeconds задаёт окно, в котором пропущенный запуск ещё можно выполнить.
Манифесты Job и CronJob
apiVersion: batch/v1
kind: Job
metadata:
name: pg-backup
spec:
completions: 1
parallelism: 1
backoffLimit: 4
ttlSecondsAfterFinished: 86400
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: postgres:16
command: ["sh", "-c", "pg_dump $DATABASE_URL | gzip -9 /backup/dump.sql.gz"]
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: pg-credentials
key: url
volumeMounts:
- name: backup
mountPath: /backup
volumes:
- name: backup
persistentVolumeClaim:
claimName: backup-pvc
apiVersion: batch/v1
kind: CronJob
metadata:
name: pg-backup-nightly
spec:
schedule: "15 2 * * *"
timeZone: "Europe/Moscow"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 2
startingDeadlineSeconds: 600
jobTemplate:
spec:
backoffLimit: 2
template:
spec:
restartPolicy: OnFailure
containers:
- name: backup
image: postgres:16
command: ["sh", "-c", "pg_dump $DATABASE_URL | gzip -9 /backup/dump.sql.gz"]
kubectl apply -f cronjob.yaml kubectl get cronjobs kubectl get jobs --watch kubectl create job --from=cronjob/pg-backup-nightly pg-backup-manual kubectl logs job/pg-backup-manual
Ручной запуск через --from=cronjob удобен для проверки расписания без ожидания нужного часа. Больше примеров с retries, parallelism и отладкой собрали в руководстве по Job и CronJob.
Как выбрать контроллер: матрица и алгоритм
Сравнительная таблица контроллеров
| Контроллер | Назначение | Имена подов | Хранилище | Порядок запуска | Пример |
|---|---|---|---|---|---|
| Deployment | stateless-сервисы, реплики и обновления | случайный суффикс | общий том или emptyDir | параллельный | REST API, веб-фронтенд |
| ReplicaSet | поддержание числа реплик, создаётся Deployment | случайный суффикс | общий том или emptyDir | параллельный | вручную почти не применяют |
| StatefulSet | stateful-нагрузки с постоянным именем | порядковый индекс | PVC на каждый под | последовательный | PostgreSQL, Kafka, etcd |
| DaemonSet | по поду на каждой ноде | случайный суффикс с привязкой к ноде | hostPath или общий том | по мере появления нод | Fluentd, node-exporter, CNI |
| Job | разовая задача до успешного завершения | случайный суффикс | том по необходимости | по parallelism | миграция, разовый бэкап |
| CronJob | регулярный запуск Job по расписанию | случайный суффикс | том по необходимости | по расписанию | ночной бэкап, очистка кэша |
Алгоритм выбора за 3 шага
- Есть ли у приложения состояние? Нет: Deployment. Да, и нужны постоянные имена, порядок и свои тома: StatefulSet.
- Нужен ли под на каждой ноде? Да, для агента логирования, метрик, сети или хранилища: DaemonSet.
- Задача разовая или регулярная? Разовая до завершения: Job. По расписанию: CronJob.
ReplicaSet напрямую почти не используют: его создаёт Deployment, а ручное управление ReplicaSet лишает вас истории ревизий и отката. Развёрнутое сравнение сценариев с разбором ошибок собрано в статье про выбор контроллера Kubernetes.
Типичные ошибки и чек-лист перед продакшн
Ошибки при работе с StatefulSet и хранилищем
- База данных в Deployment: поды получают случайные имена, реплики не находят друг друга, а rolling update поднимает вторую копию поверх той же схемы данных.
- StatefulSet без volumeClaimTemplates: данные лежат в emptyDir и теряются вместе с подом.
- Удаление PVC вместе с StatefulSet. Команда kubectl delete statefulset тома не трогает, а kubectl delete pvc стирает данные. Сначала бэкап, потом очистка.
- Отсутствие Headless Service или неверное поле serviceName: поды не получают стабильных DNS-имён.
- PVC остаётся в статусе Pending из-за несуществующего StorageClass или отсутствия provisioner.
kubectl get pvc kubectl describe pvc data-web-0 kubectl get storageclass kubectl describe pod web-0
В событиях пода ошибки монтирования видны в разделе Events: FailedMount и FailedAttachVolume указывают на проблему с томом или зоной. Проверяйте события сразу после применения манифеста, а не после первой аварии.
Чек-лист перед деплоем в продакшн
- Контроллер подобран по природе нагрузки: stateless, stateful, агент на каждой ноде, разовая или регулярная задача.
- Заданы readinessProbe, livenessProbe и при медленном старте startupProbe, иначе трафик пойдёт на неготовый под.
- Указаны resources.requests и resources.limits, чтобы планировщик корректно распределял поды и kubelet не вытеснял их.
- Настроен PodDisruptionBudget, чтобы обновления нод и drain не сняли все реплики сразу.
- Для нескольких реплик заданы anti-affinity или topologySpreadConstraints, чтобы поды не собрались на одной ноде.
- Стратегия обновления проверена: maxSurge и maxUnavailable выставлены под требования SLA.
- Тома и StorageClass проверены на тестовом namespace, PVC переходят в статус Bound.
- Используются отдельные namespaces и RBAC-роли вместо запуска всего в default.
- Настроены сбор метрик и логов, а также алерты на рост числа рестартов и на поды в статусе Pending.
kubectl get all -n prod kubectl describe deployment nginx -n prod kubectl logs deployment/nginx -n prod kubectl rollout status deployment/nginx -n prod
Начните с тестового namespace: примените манифесты, дождитесь статуса Running у всех подов и только потом переносите конфигурацию в продакшн. Так ошибки в контроллерах и томах всплывут до того, как их увидят пользователи.