Поды и контроллеры в Kubernetes: Deployment, ReplicaSet, StatefulSet, DaemonSet, Job и CronJob | AdminWiki

Поды и контроллеры в Kubernetes: Deployment, ReplicaSet, StatefulSet, DaemonSet, Job и CronJob

03 октября 2026 13 мин. чтения
Содержание статьи

Под в 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-сервера объект с описанием желаемого состояния, сравнивает его с текущим и делает шаги, чтобы они совпали.

Цикл выглядит так:

  1. Вы применяете манифест командой kubectl apply -f deployment.yaml. Объект сохраняется в etcd через API-сервер.
  2. Контроллер Deployment замечает изменение и создаёт ReplicaSet с нужным числом реплик.
  3. Контроллер ReplicaSet считает реальные поды и добавляет недостающие.
  4. Планировщик назначает поды на ноды, kubelet запускает контейнеры.
  5. Под удалён или нода упала: контроллер создаёт замену, и количество подов возвращается к заданному.

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 вниз (источник).

ПараметрRollingUpdateRecreate
Простойнет при корректных настройкахесть, пока идёт смена всех подов
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

СвойствоDeploymentStatefulSet
Имена подовслучайный суффикс, например nginx-7d9f8c5b64-xk2lpпорядковый индекс: web-0, web-1, web-2
Сетевой идентификаторстабильного DNS нетweb-0.nginx через Headless Service
Хранилищеобщий том или emptyDirvolumeClaimTemplates, PVC на каждый под
Порядок запуска и остановкипараллельнопоследовательно, по возрастанию и убыванию индекса
Политика обновленияRollingUpdate или RecreateRollingUpdate с порядком 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.

Как выбрать контроллер: матрица и алгоритм

Сравнительная таблица контроллеров

КонтроллерНазначениеИмена подовХранилищеПорядок запускаПример
Deploymentstateless-сервисы, реплики и обновленияслучайный суффиксобщий том или emptyDirпараллельныйREST API, веб-фронтенд
ReplicaSetподдержание числа реплик, создаётся Deploymentслучайный суффиксобщий том или emptyDirпараллельныйвручную почти не применяют
StatefulSetstateful-нагрузки с постоянным именемпорядковый индексPVC на каждый подпоследовательныйPostgreSQL, Kafka, etcd
DaemonSetпо поду на каждой нодеслучайный суффикс с привязкой к нодеhostPath или общий томпо мере появления нодFluentd, node-exporter, CNI
Jobразовая задача до успешного завершенияслучайный суффикстом по необходимостипо parallelismмиграция, разовый бэкап
CronJobрегулярный запуск Job по расписаниюслучайный суффикстом по необходимостипо расписаниюночной бэкап, очистка кэша

Алгоритм выбора за 3 шага

  1. Есть ли у приложения состояние? Нет: Deployment. Да, и нужны постоянные имена, порядок и свои тома: StatefulSet.
  2. Нужен ли под на каждой ноде? Да, для агента логирования, метрик, сети или хранилища: DaemonSet.
  3. Задача разовая или регулярная? Разовая до завершения: 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 указывают на проблему с томом или зоной. Проверяйте события сразу после применения манифеста, а не после первой аварии.

Чек-лист перед деплоем в продакшн

  1. Контроллер подобран по природе нагрузки: stateless, stateful, агент на каждой ноде, разовая или регулярная задача.
  2. Заданы readinessProbe, livenessProbe и при медленном старте startupProbe, иначе трафик пойдёт на неготовый под.
  3. Указаны resources.requests и resources.limits, чтобы планировщик корректно распределял поды и kubelet не вытеснял их.
  4. Настроен PodDisruptionBudget, чтобы обновления нод и drain не сняли все реплики сразу.
  5. Для нескольких реплик заданы anti-affinity или topologySpreadConstraints, чтобы поды не собрались на одной ноде.
  6. Стратегия обновления проверена: maxSurge и maxUnavailable выставлены под требования SLA.
  7. Тома и StorageClass проверены на тестовом namespace, PVC переходят в статус Bound.
  8. Используются отдельные namespaces и RBAC-роли вместо запуска всего в default.
  9. Настроены сбор метрик и логов, а также алерты на рост числа рестартов и на поды в статусе 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 у всех подов и только потом переносите конфигурацию в продакшн. Так ошибки в контроллерах и томах всплывут до того, как их увидят пользователи.

Поделиться: