Как работает контроллер Deployment при обновлении приложения?

Общая идея

Deployment — это контроллер в Kubernetes, который управляет жизненным циклом набора реплик через ReplicaSet. Когда вы меняете spec.template (например, образ контейнера, переменные окружения, resource limits и т.д.), Deployment трактует это как новую версию приложения и начинает процесс обновления, чтобы привести фактическое состояние к желаемому без ручного удаление/создания Pod'ов.

Что происходит при изменении шаблона Pod (PodTemplate)

  1. kubectl/API-сервер записывает обновлённый Deployment в etcd.

  2. Контроллер deployment (в controller-manager) видит изменение шаблона и создаёт новый ReplicaSet с этим шаблоном. У нового RS появляется своя аннотация/ревизия (deployment.kubernetes.io/revision) и метка pod-template-hash.

  3. Начинается стратегия обновления — по умолчанию RollingUpdate, но может быть Recreate. Контроллер будет постепенно увеличивать количество Pod-ов в новом RS и уменьшать количество Pod-ов в старом RS согласно правилам стратегии.

RollingUpdate — как именно контроллер меняет Pod'ы

RollingUpdate контролируется двумя параметрами в spec.strategy.rollingUpdate:

  • maxSurge — сколько дополнительных Pod'ов (в абсолютном числе или процентах) можно создать выше желаемого replicas во время обновления.

  • maxUnavailable — сколько Pod'ов может быть одновременно недоступно (неготовых) во время обновления.

Пример расчёта (replicas=10, maxSurge=2, maxUnavailable=1):

  • В любой момент число Pod-ов = старые + новые ≤ replicas + maxSurge = 12.

  • Обеспечивается, что доступных Pod-ов (готовых по readiness) ≥ replicas - maxUnavailable = 9.
    Алгоритм итеративный: контроллер поднимает новые Pod'ы (в пределах maxSurge), ждёт их готовности (readiness), затем уменьшает старые Pod'ы, и повторяет, пока все старые не заменятся новыми.

Контроллер использует статус ReplicaSet'ов и Pod'ов: считает сколько новых Pod'ов создано, сколько уже Ready, и сколько старых ещё осталось — и действует так, чтобы не нарушить условия maxSurge/maxUnavailable.

Recreate vs RollingUpdate

  • Recreate: сначала контроллер остановит (удалит) все старые Pod'ы, а затем создаст новые. Приводит к простою, но полезен, если нельзя держать одновременно старые и новые экземпляры (например, при миграции состояния).

  • RollingUpdate (по умолчанию): обеспечивает непрерывность, заменяя Pod'ы постепенно.

Что учитывается при принятии решения о продвижении обновления

  • Readiness probe: Pod считается готовым только после прохождения readiness; контроллер ждёт этого перед удалением старых экземпляров. Неправильно настроенная probe — частая причина «застревания» rollout.

  • Liveness и startup probes: влияют на рестарты контейнера, но для rollout важна именно готовность.

  • Resource requests / scheduling: если нет места на нодах, новые Pod'ы будут в Pending и rollout не двинется вперёд.

  • ImagePullBackOff / CrashLoopBackOff: если новые контейнеры не запускаются, прогресс останавливается и rollout может закончиться с ошибкой.

Контроль прогресса и ошибки

  • progressDeadlineSeconds (типично 600s по умолчанию) — если за этот период Deployment не достигает прогресса (новые Pods не становятся Ready), контроллер пометит Deployment как Progressing=False с причиной ProgressDeadlineExceeded.

  • revisionHistoryLimit — сколько старых ReplicaSet хранить для отката (rollback). По умолчанию обычно 10. Старые RS удаляются по мере старения, чтобы ограничить хранение истории.

Пауза, возобновление и откат

  • Обновление можно поставить на паузу (pause) — контроллер остановит продвижение обновления, это даёт время вручную проверить состояние или внести дополнительные изменения.

  • После resume продвижение продолжается.

  • Откат (rollback) реализуется через восстановление предыдущего шаблона — контроллер создаёт новый ReplicaSet, копирующий шаблон старой ревизии, и запускает аналогичный RollingUpdate в обратную сторону. Для отката нужна сохранённая ревизия (не стертая revisionHistoryLimit).

Технические детали и нюансы

  • Каждый новый уникальный PodTemplate приводит к созданию отдельного ReplicaSet; имена ReplicaSet'ов включают hash шаблона (pod-template-hash) для различения.

  • Selector Deployment (label selector) обычно неизменяем; это гарантирует, что контроллер управляет постоянным набором Pod'ов.

  • Контроллер использует «adoption»/«orphan» логику для захвата существующих ReplicaSet/Pod'ов при необходимости, но это требует совпадения селекторов/меток.

  • Для специальных стратегий развёртывания (canary, blue/green, A/B) нативный Deployment даёт базовую функциональность (rolling), но часто используют дополнительные инструменты или манипуляции с сервисами/Ingress, чтобы направлять процент трафика на новую версию.

Поведение при проблемах

  • Если новые Pod'ы не становятся Ready — rollout останавливается, Deployment остаётся в состоянии ожидающего прогресса. Нельзя «пройти» проверку просто потому, что контейнер запущен: важна readiness.

  • Для аварийного принудительного перезапуска можно изменить аннотацию в spec.template.metadata (например, добавить timestamp) — это создаст новую ревизию и запустит заново обновление.