Learning Platform
Глоссарий Troubleshooting
Урок 17.01 · 25 мин
Продвинутый
RollingUpdatemaxSurgemaxUnavailableReplicaSetreadinessProbeminReadySeconds

RollingUpdate: механика maxSurge и maxUnavailable

RollingUpdate — стратегия по умолчанию для Deployment. Она звучит просто: “постепенно заменим старые Pods на новые”. Но под капотом это сложный танец двух ReplicaSet-ов: один скейлится вверх, второй — вниз, причём так, чтобы в любой момент времени сумма Available-Pods не упала ниже инварианта.

Понять механику — значит понять, почему maxSurge=0 и maxUnavailable=0 нельзя поставить вместе, почему без readinessProbe rolling update превращается в roulette, и почему CKAD-задание “обновить без downtime” — это всегда про конкретные значения этих двух параметров.


Compose vs Kubernetes: когда переходить к оркестратору

Что такое RollingUpdate

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 5

Поля стратегии:

  • spec.strategy.type: RollingUpdate — постепенная замена (default)
  • spec.strategy.rollingUpdate.maxSurge — сколько Pod-ов СВЕРХ spec.replicas можно создать (default 25%)
  • spec.strategy.rollingUpdate.maxUnavailable — сколько Pod-ов могут быть UNAVAILABLE одновременно (default 25%)

Оба параметра — либо процент (округляется вверх для surge, вниз для unavailable), либо абсолютное число. Для replicas=4 и default 25%:

  • maxSurge = ceil(4 * 0.25) = 1
  • maxUnavailable = floor(4 * 0.25) = 1

Это значит: в пике может быть до 5 Pods, минимум 3 Available.


Mechanics: что делает DeploymentController

DeploymentController — это reconcile loop внутри kube-controller-manager. Он наблюдает за Deployment-ом и при изменении spec.template запускает rollout. Алгоритм по шагам.

Цикл rolling update (replicas=4, maxSurge=1, maxUnavailable=1)
step 1DeploymentController замечает изменение spec.template (hash изменился). Создаёт новый ReplicaSet с template новой версии и selector с pod-template-hash. Старый RS пока не трогается — он держит свои 4 Pod-а.
scale up by maxSurge
step 2Скейлим новый RS вверх на maxSurge (=1). Теперь rs-new.replicas=1, всего Pod-ов 4+1=5. Это и есть surge — временный overshoot над desired replicas. Если new Pod в Ready не входит сразу — controller ждёт.
wait for new Pod Ready
step 3Readiness probe нового Pod прошла → controller теперь может уменьшить старый RS. Сколько уменьшить? Так, чтобы Available не упало ниже replicas - maxUnavailable = 4 - 1 = 3. У нас 4 старых + 1 новый = 5 Available. Можно убить один старый — останется 4 Available.
loop: surge up → ready → scale down
step 4..NЦиклически: surge up new RS (на maxSurge от текущего), ждём ready, scale down old RS (до maxUnavailable). Каждый шаг ограничен этими двумя параметрами. Controller не делает большие batched moves — он работает инкрементально, реагируя на изменения статуса Pod-ов.
rollout complete
step doners-new.replicas=4 (=desired), rs-old.replicas=0. Старый RS НЕ удаляется — он остаётся в etcd с replicas=0 для возможного rollback. Сколько таких 'мёртвых' RS хранится — управляется revisionHistoryLimit (default 10).

Важно: controller не “знает план” из 4 шагов заранее. Он просто реагирует на каждое изменение статуса:

  1. Видит desired=4, текущий new RS replicas < desired → пытается скейлить вверх, ограничен maxSurge
  2. Видит Available > replicas - maxUnavailable → может убить старый Pod
  3. Повторяет, пока new RS не достигнет desired, а old RS — 0

Это reconciliation pattern, не imperative script.


maxSurge и maxUnavailable: инварианты

Два математических инварианта, которые controller всегда сохраняет:

  • Pod countreplicas + maxSurge
  • Available Pod countreplicas - maxUnavailable

Из этих двух инвариантов следует поведение rollout-а при разных конфигурациях.

maxSurge=1, maxUnavailable=0 — safe, no downtime

spec:
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  • Pod count ≤ replicas + 1
  • Available count ≥ replicas (никогда не теряем Pod до того как новый Ready)

Сначала создаётся 1 новый Pod, ждёт Ready, потом убивается 1 старый. Цикл повторяется. Самый безопасный вариант, никогда нет downtime. Расход: +1 Pod ресурса на время rollout.

maxSurge=0, maxUnavailable=1 — fast, with planned downtime

spec:
  strategy:
    rollingUpdate:
      maxSurge: 0
      maxUnavailable: 1
  • Pod count ≤ replicas (никаких лишних)
  • Available count ≥ replicas - 1

Сначала убивается 1 старый, потом на его место создаётся 1 новый. На время — пока новый не Ready — Available count меньше replicas. Это planned mini-downtime. Использовать, когда нет ресурсов для surge.

maxSurge=25%, maxUnavailable=25% — default

Default. Для replicas=4: surge=1, unavailable=1. В пике 5 Pods, минимум 3 Available. Компромисс между скоростью и расходом ресурсов.

maxSurge=100%, maxUnavailable=0 — blue-green-like

spec:
  strategy:
    rollingUpdate:
      maxSurge: 100%
      maxUnavailable: 0

Сразу создаётся вторая копия (2× ресурсов), ждёт пока все новые Ready, потом убиваются все старые. Без downtime, но временно нужен двукратный объём ресурсов.

WARNING

maxSurge=0 и maxUnavailable=0 ОБА вместе — невалидная конфигурация. API-сервер отклонит её: новый Pod создать нельзя (surge=0), старый удалить нельзя (unavailable=0) → controller заблокирован, rollout deadlock. Хотя бы один из двух должен быть > 0.


Readiness probe — критична для rolling update

Без readinessProbe Pod считается Available как только его main-process запустился (Phase=Running). Это до того как приложение реально готово принимать трафик. Rolling update в таком случае работает в blind-mode: controller думает “новый Pod ready”, убивает старый, а приложение ещё инициализируется.

containers:
  - name: app
    image: my-app:v2
    readinessProbe:
      httpGet:
        path: /healthz
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 5
      failureThreshold: 3

С readinessProbe:

  • Pod становится Ready только когда /healthz отвечает 200 OK
  • Service не отправляет трафик на Pod, у которого Ready=False
  • Deployment controller не считает такой Pod в availableReplicas, не идёт дальше rollout
Readiness probe как gate rolling update
Pod createdPod создан, контейнер стартовал, main-process запустился. Phase=Running. Но это не значит что приложение готово принимать трафик — внутри может идти migration, warmup кешей, JIT compile.
readiness probekubelet начинает выполнять readinessProbe после initialDelaySeconds. Каждый periodSeconds — повторяет. Pod становится Ready=True только после первого успешного probe (success threshold по умолчанию 1).
Ready=TrueПосле success threshold readiness probes. Endpoint Slice добавляет IP Pod-а — теперь Service балансирует на него трафик. Deployment controller считает Pod Available — можно идти дальше rollout.
DANGER

Rolling update БЕЗ readiness probe — частая ошибка. Phase=Running != “готов к трафику”. В первые секунды приложения часто отвечают ошибкой или висят. Если controller думает что Pod готов и убивает старый — клиенты получают 5xx. Всегда ставить readinessProbe для любого Pod, который принимает трафик.


minReadySeconds: ждать после Ready

spec:
  minReadySeconds: 10

Pod считается Available только когда он Ready дольше minReadySeconds подряд. Это страховка против Pod-ов, которые становятся Ready, а потом сразу падают.

  • minReadySeconds=0 (default) — Pod Available мгновенно после Ready=True
  • minReadySeconds=10 — controller ждёт 10 секунд после Ready, прежде чем считать Pod Available и двигаться дальше

Use case: приложение запускается за 2 секунды, но первые 5 секунд работает на холодных кешах и иногда падает. minReadySeconds=10 гарантирует, что мы не убьём старый Pod в этот неуверенный период.


Полный rollout: трассировка

CKAD-сценарий — обновить image и наблюдать поведение.

kubectl set image deploy/web nginx=nginx:1.28
kubectl rollout status deploy/web
# Waiting for deployment "web" rollout to finish: 1 out of 4 new replicas have been updated...
# Waiting for deployment "web" rollout to finish: 2 of 4 updated replicas are available...
# Waiting for deployment "web" rollout to finish: 3 out of 4 new replicas have been updated...
# deployment "web" successfully rolled out

# Параллельно watch
kubectl get pods -l app=web -w
# web-old-abc12   1/1   Running        0      10m
# web-old-def34   1/1   Running        0      10m
# web-old-ghi56   1/1   Running        0      10m
# web-old-jkl78   1/1   Running        0      10m
# web-new-mno90   0/1   ContainerCreating
# web-new-mno90   0/1   Running
# web-new-mno90   1/1   Running
# web-old-abc12   1/1   Terminating
# web-new-pqr12   0/1   ContainerCreating
# ...

Что видно:

  • В каждый момент времени count Pods ≤ 5 (= 4 + maxSurge=1)
  • Available count ≥ 3 (= 4 - maxUnavailable=1)
  • Pod становится Ready после успешной readiness probe
  • Старый Pod не убивается, пока новый не Ready
# Смотрим, какие RS существуют
kubectl get rs -l app=web
# NAME             DESIRED   CURRENT   READY
# web-7d4b9f4f7    0         0         0    # старый (сохранён для rollback)
# web-9c2e8a55b    4         4         4    # новый

Killer-моменты

  • maxSurge=0 + maxUnavailable=0 = deadlock, API server отклонит. Хотя бы один должен быть > 0.
  • Без readinessProbe rolling update не safe: Phase=Running != ready, controller думает что Pod готов, а он ещё инициализируется.
  • minReadySeconds — страховка против Pod-ов, которые Ready, но сразу падают. Production-grade.
  • Pod-template-hash в RS selectorDeploymentController добавляет deterministic hash в label каждого Pod-а, чтобы RS управлял только Pod-ами своей ревизии и не путал их со старыми.
  • progressDeadlineSeconds (default 600) — за сколько секунд rollout должен завершиться. Если нет — Deployment.status.conditions.Progressing = False, Reason=ProgressDeadlineExceeded. Это сигнал для CI/CD что rollout сломан.

Проверка знанийKnowledge check
Deployment с replicas=10, maxSurge=2, maxUnavailable=1, readinessProbe есть. Сколько Pod-ов МАКСИМУМ существует одновременно во время rolling update и сколько МИНИМУМ Available?
ОтветAnswer
Pod count <= replicas + maxSurge = 10 + 2 = 12 (максимум одновременно существующих Pod-ов). Available count >= replicas - maxUnavailable = 10 - 1 = 9 (минимум Available в любой момент). Controller балансирует: создаёт новые Pods до пика 12 одновременных, ждёт ready (благодаря readinessProbe это означает реальная готовность), убивает старые Pods до сохранения 9 Available.
Проверка знанийKnowledge check
Зачем нужен readinessProbe именно для rolling update, если Pod Phase=Running уже означает что контейнер запустился?
ОтветAnswer
Phase=Running значит main process запустился, но приложение ещё может не быть готово принимать трафик: идёт инициализация (warmup, JIT compile, открытие connections к DB), миграции, чтение конфигов. Без readinessProbe controller считает Pod ready как только Phase=Running — и сразу убивает старый Pod. Клиенты получают 5xx, пока новый Pod не доинициализируется. Readiness probe (HTTP /healthz, TCP socket check, exec script) гарантирует что Pod добавляется в EndpointSlice Service только когда реально готов отвечать. Deployment controller тоже использует Ready=True как сигнал что можно scale down старого RS.
Проверка знанийKnowledge check
maxSurge=0 и maxUnavailable=0 одновременно — что произойдёт?
ОтветAnswer
API-сервер отклонит конфигурацию валидацией. Если бы прошла — deployment deadlock: создать новый Pod нельзя (Pod count <= replicas + 0 = replicas, текущее = replicas), убить старый нельзя (Available count >= replicas - 0 = replicas, любое убийство нарушает). Хотя бы один параметр должен быть > 0. На CKAD это типичная ловушка — спросят 'safest rolling update' и ответом будет НЕ оба нуля, а maxSurge=1, maxUnavailable=0 (или соразмерные значения).
Проверка знанийKnowledge check
Что делает minReadySeconds=15 и в каком сценарии это спасает rolling update от disaster?
ОтветAnswer
Pod считается Available только если он Ready=True непрерывно дольше 15 секунд. Если за этот период Ready перейдёт обратно в False — таймер сбрасывается. Сценарий: приложение запускается за 2 секунды (Ready=True), но первые 10-12 секунд работает на холодных кешах / leader election ещё не завершён / connection pool ещё пустой — и иногда падает (Ready=False). Без minReadySeconds controller сразу после первого Ready=True считает Pod Available и идёт убивать следующий старый Pod. Через несколько секунд новый Pod падает — теперь Available count < replicas - maxUnavailable, controller замирает, но трафик уже сломан. С minReadySeconds=15 controller ждёт стабильности — если за 15 секунд Pod ни разу не упал, он действительно готов.

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

Результат: 0 из 0
Прикладной
Вопрос 1 из 5. Deployment с replicas=10, maxSurge=2, maxUnavailable=1. Сколько Pod-ов МАКСИМУМ существует одновременно во время rolling update и сколько МИНИМУМ Available?

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

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

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

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