Learning Platform
Глоссарий Troubleshooting
Урок 06.02 · 28 мин
Продвинутый
DeploymentRolling updateRollbackStrategyRevision historykubectl rollout

Deployment: основной workload для stateless apps

Deployment — то, чем вы будете запускать ~80% workload-ов в production. И ~80% задач на CKAD-экзамене. Это высокоуровневый объект, который сидит над ReplicaSet и Pod-ами и решает за вас три вещи, которые иначе пришлось бы делать руками: постепенное обновление (rolling update), хранение истории (revisions), откат на любую предыдущую версию (rollback).

Понять Deployment — значит понять, как одна декларативная декларация превращается в сложную хореографию из множества RS и Pod-ов, координируемых через reconcile loops.


Зачем нужен Docker Compose: от одиночных контейнеров к сервисам

Базовый Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 100m
              memory: 64Mi

Ключевые поля:

  • spec.replicas — желаемое число Pod-ов
  • spec.selector — label selector (immutable после создания)
  • spec.template — Pod template (можно менять — это триггерит rollout)
  • spec.strategy — стратегия обновления: RollingUpdate (default) или Recreate
  • spec.revisionHistoryLimit — сколько старых ReplicaSet хранить для rollback (default 10)
  • spec.progressDeadlineSeconds — за сколько секунд rollout должен завершиться, иначе помечается failed (default 600)

Иерархия: Deployment → ReplicaSet → Pod

Ключевая модель: Deployment не создаёт Pod-ы напрямую. Он создаёт ReplicaSet, а тот уже создаёт Pod-ы. Каждая ревизия Deployment-а — это новый ReplicaSet.

Иерархия владения
DeploymentВысокоуровневый объект. Хранит template и strategy. Watch-ится DeploymentController. Имя стабильное — не меняется при обновлениях.
owns (controller)
ReplicaSet rev1Старая ревизия. Имя = <deploy-name>-<pod-template-hash>. Pod template hash — это deterministic hash от spec.template, гарантирующий уникальность имени RS для каждой ревизии.
ReplicaSet rev2Новая ревизия. Создаётся когда меняется любое поле в spec.template (image, env, command, resources). Имя — новый hash от нового template.
owns Pods
Pods rev1Pod-ы старой ревизии. Имеют label pod-template-hash=7d4b9f. Постепенно скейлятся вниз во время rolling update.
Pods rev2Pod-ы новой ревизии. Имеют label pod-template-hash=9c2e8a. Постепенно скейлятся вверх до spec.replicas.

Подсмотрим в кластер после обновления:

kubectl get deploy,rs,pods -l app=web

# NAME                       READY   UP-TO-DATE   AVAILABLE
# deployment.apps/web        3/3     3            3
#
# NAME                             DESIRED   CURRENT   READY
# replicaset.apps/web-7d4b9f4f7    0         0         0       # ← старый, scaled to 0
# replicaset.apps/web-9c2e8a55b    3         3         3       # ← новый, active
#
# NAME                       READY   STATUS
# pod/web-9c2e8a55b-abc12    1/1     Running
# pod/web-9c2e8a55b-def34    1/1     Running
# pod/web-9c2e8a55b-xyz56    1/1     Running

Старый RS не удаляется — он остаётся “пустым” (replicas=0) и хранится в etcd для возможного rollback. Сколько таких пустых RS хранить — определяется revisionHistoryLimit.

NOTE

DeploymentController автоматически добавляет в Pod template уникальный label pod-template-hash — это deterministic хеш от template. Selector каждого RS включает этот hash, поэтому каждый RS управляет только Pods своей ревизии и они не путаются.


RollingUpdate: maxSurge и maxUnavailable

Стратегия по умолчанию — RollingUpdate. Контроллер постепенно скейлит новый RS вверх и старый RS вниз, поддерживая инвариант доступности.

Управляется двумя параметрами:

  • maxSurge — максимальное число Pod-ов СВЕРХ spec.replicas, которые могут существовать одновременно. Default 25%. Можно абсолютное число.
  • maxUnavailable — максимальное число Pod-ов, которые могут быть НЕДОСТУПНЫ в любой момент. Default 25%. Можно абсолютное число.

Например, replicas=10, maxSurge=2, maxUnavailable=2:

  • Минимум 10 - 2 = 8 Pod-ов доступно всегда
  • Максимум 10 + 2 = 12 Pod-ов существует одновременно
  • Controller балансирует: создаёт новые до лимита 12, ждёт ready, удаляет старые до лимита 8 доступных
RollingUpdate в действии (replicas=4, maxSurge=1, maxUnavailable=1)
T0Старт rollout. 4 Pod-а старой ревизии, 0 новой. Controller начинает: можно добавить 1 (maxSurge=1, всего станет 5).
создать 1 новый, удалить 1 старый
T1Новый Pod создан. Ждём ready. Когда ready — controller убивает один старый Pod (теперь доступно 3 = replicas - maxUnavailable).
дальше по циклу
T2..T4Циклически: новый ready → старый kill → новый create. Внутри: maxSurge=1 ограничивает создание (нельзя создать 2 новых, если ещё не убили 1 старого), maxUnavailable=1 ограничивает удаление.
rollout complete
T5Старый RS scaled to 0 (но остаётся в etcd для rollback). Новый RS = replicas. Deployment status.availableReplicas = 4.

Когда maxSurge=0, maxUnavailable=0

Нельзя оба сразу — controller заблокирован: новый Pod создать нельзя (surge=0), старый удалить нельзя (unavailable=0). API server отклонит такую конфигурацию.

maxSurge=100%, maxUnavailable=0 — blue/green-стиль

Сначала создаются ВСЕ новые Pods (replicas стало 2×), ждут ready, потом все старые удаляются. Без downtime, но удваивается потребление ресурсов на время rollout.

maxSurge=0, maxUnavailable=100% — fast roll, с downtime

Все старые сразу killed, потом создаются новые. Похоже на Recreate, но в рамках RollingUpdate стратегии.

TIP

В CKAD-задачах часто требуется “обновить deployment без downtime”. Это значит — оставить дефолтные RollingUpdate с maxSurge > 0 и maxUnavailable < replicas, плюс readinessProbe на контейнерах (без неё Pod считается ready как только запустился).


Recreate: для apps которые не выдерживают двух версий

spec:
  strategy:
    type: Recreate

При Recreate controller сначала убивает ВСЕ старые Pod-ы (новый RS scaled to 0), и только потом создаёт ВСЕ новые. Это даёт downtime, но гарантирует, что в любой момент времени работает только одна версия.

Use cases:

  • Apps с миграциями БД, которые ломают совместимость старых клиентов
  • Apps, использующие эксклюзивный ресурс (например, leader election с фиксированным именем)
  • Apps, где две версии одновременно ломают консистентность (некоторые stateful workloads)

Rollout: триггеры обновления

Rolling update триггерится автоматически когда меняется ЛЮБОЕ поле в spec.template:

# image change (типичный)
kubectl set image deploy/web nginx=nginx:1.28

# environment variable
kubectl set env deploy/web LOG_LEVEL=debug

# resource limits
kubectl set resources deploy/web --limits=cpu=500m,memory=512Mi

# patch произвольного поля template
kubectl patch deploy/web -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.29"}]}}}}'

# rollout restart (форсирует пересоздание Pod-ов без изменения template)
kubectl rollout restart deploy/web
WARNING

Изменение spec.replicas НЕ триггерит rollout — это просто scaling в рамках текущего RS. Rollout — только изменения в template.

kubectl rollout restart хитрый — он добавляет аннотацию kubectl.kubernetes.io/restartedAt: <timestamp> в template, что технически меняет template и триггерит создание нового RS. Используется когда нужно перезапустить Pods (например, чтобы перечитать ConfigMap-ы), но image остался тот же.


kubectl rollout: status, history, undo

# Следить за прогрессом rollout
kubectl rollout status deploy/web
# Waiting for deployment "web" rollout to finish: 2 out of 4 new replicas have been updated...
# deployment "web" successfully rolled out

# История ревизий
kubectl rollout history deploy/web
# REVISION  CHANGE-CAUSE
# 1         <none>
# 2         kubectl set image deploy/web nginx=nginx:1.28
# 3         kubectl set image deploy/web nginx=nginx:1.29

# Детали конкретной ревизии
kubectl rollout history deploy/web --revision=2

# Откат к предыдущей
kubectl rollout undo deploy/web

# Откат к конкретной ревизии
kubectl rollout undo deploy/web --to-revision=2

# Пауза (новые изменения не триггерят rollout)
kubectl rollout pause deploy/web

# Возобновить
kubectl rollout resume deploy/web

change-cause annotation

Раньше был флаг --record, который сохранял команду kubectl в аннотацию. С v1.22 он deprecated. Теперь правильный способ — добавлять аннотацию явно:

metadata:
  annotations:
    kubernetes.io/change-cause: "Bump to nginx 1.28 — CVE-2024-1234 fix"

Или через kubectl annotate:

kubectl annotate deploy/web kubernetes.io/change-cause="Bump to nginx 1.28" --overwrite

Эта аннотация показывается в kubectl rollout history как CHANGE-CAUSE и помогает понять, зачем была сделана конкретная ревизия.


revisionHistoryLimit

Каждая ревизия = старый RS остаётся в кластере с replicas=0. Это нужно для rollback. Но если ревизий накопится много, etcd распухнет.

spec.revisionHistoryLimit (default 10) ограничивает сколько старых RS хранить:

spec:
  revisionHistoryLimit: 5

Когда ревизий больше лимита, controller удаляет самые старые. Нельзя откатиться на ревизию, RS которой уже удалён — поэтому нужно балансировать: больше history = больше места в etcd, но больше глубина rollback.

В production обычно ставят 3-5. Дефолт 10 — для удобства разработки.

WARNING

revisionHistoryLimit=0 означает что НИ ОДИН старый RS не хранится — rollback невозможен. Использовать только для эфемерных Deployments, например в Helm-релизах с собственным rollback-механизмом.


Полный rollout: трассируем шаг за шагом

CKAD-сценарий: обновить image, проверить статус, откатиться при проблемах.

# Текущая ревизия
kubectl rollout history deploy/web
# REVISION  CHANGE-CAUSE
# 1         <none>

# Обновить image
kubectl set image deploy/web nginx=nginx:1.28
kubectl annotate deploy/web kubernetes.io/change-cause="Bump nginx 1.27 → 1.28"

# Следить за прогрессом
kubectl rollout status deploy/web
# ...
# deployment "web" successfully rolled out

# История теперь
kubectl rollout history deploy/web
# REVISION  CHANGE-CAUSE
# 1         <none>
# 2         Bump nginx 1.27 → 1.28

# Что-то пошло не так — откатываем
kubectl rollout undo deploy/web
# deployment.apps/web rolled back

# Проверяем
kubectl rollout history deploy/web
# REVISION  CHANGE-CAUSE
# 2         Bump nginx 1.27 → 1.28
# 3         <none>   ← rollback создаёт новую ревизию с template из rev 1
NOTE

Rollback — это не “удалить новую ревизию”, а “создать новую ревизию с template из старой”. Поэтому после undo номер ревизии увеличивается. Это сохраняет линейность истории и позволяет потом откатиться обратно.


progressDeadlineSeconds: что значит “failed rollout”

spec:
  progressDeadlineSeconds: 600

Если за это время rollout не сделал прогресс (новые Pods не стали ready), Deployment помечается со статусом Progressing=False, reason=ProgressDeadlineExceeded.

Это не останавливает rollout автоматически — controller продолжает пытаться. Но kubectl rollout status выйдет с non-zero exit code, что важно для CI/CD пайплайнов.

kubectl rollout status deploy/web --timeout=300s
# error: deployment "web" exceeded its progress deadline
echo $?   # 1

kubectl: основные команды

# Создать декларативно
kubectl apply -f deploy.yaml

# Создать imperative
kubectl create deployment web --image=nginx:1.27 --replicas=3

# Сгенерировать YAML без создания
kubectl create deployment web --image=nginx:1.27 --replicas=3 --dry-run=client -o yaml

# Изменить image
kubectl set image deploy/web nginx=nginx:1.28

# Scale
kubectl scale deploy/web --replicas=5

# Описать
kubectl describe deploy/web

# Удалить (cascade — RS и Pods тоже удалятся)
kubectl delete deploy/web

kubectl describe deploy/web показывает:

StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
OldReplicaSets:         web-7d4b9f4f7 (0/0 replicas created)
NewReplicaSet:          web-9c2e8a55b (3/3 replicas created)
Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  ScalingReplicaSet 5m    deployment-controller  Scaled up replica set web-9c2e8a55b to 1
  Normal  ScalingReplicaSet 5m    deployment-controller  Scaled down replica set web-7d4b9f4f7 to 2
  ...

Проверка знанийKnowledge check
Команда `kubectl rollout undo deploy/web` была выполнена — Deployment был на ревизии 5, откат к ревизии 4. Какой номер ревизии будет в history после успешного отката, и почему?
ОтветAnswer
После отката появится новая ревизия 6 с template, скопированным из ревизии 4. Rollback в Kubernetes — это НЕ удаление новой ревизии. Это создание новой ревизии, в которой template взят из целевой. То есть history будет: 4, 5, 6 (где 6 — копия 4). Это сохраняет линейность ревизий и даёт возможность 'откатить откат' — снова сделать kubectl rollout undo и вернуться на 5. Также важно: ревизия 4 ВЫПАДЕТ из history если бы revisionHistoryLimit=2, потому что её RS будет удалён (старее лимита). Поэтому в продакшене revisionHistoryLimit должен быть не меньше глубины возможного отката.

Проверьте понимание

Результат: 0 из 0
Прикладной
Вопрос 1 из 6. При выполнении kubectl set image deploy/web nginx=nginx:1.28 в кластере с Deployment (replicas=4, RollingUpdate, maxSurge=25%, maxUnavailable=25%). Что произойдёт?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5