🐳 Раздел 14 · Вопрос #22

Как организовать rolling update в Kubernetes

Rolling Update — это замена колёс на движущемся многоосном грузовике или поезде:


🟢 Junior Level

30-секундный ответ

Rolling Update (Плавное волновое обновление) — это стандартная стратегия обновления приложений в объекте Deployment, при которой старые поды заменяются новыми постепенно, небольшими группами, без остановки сервиса и без простоя (Zero Downtime).

  • Вместо одновременного уничтожения всех работающих инстансов (стратегия Recreate), Kubernetes запускает один или несколько новых подов.
  • Дожидается, пока новые поды успешно пройдут пробу готовности (readinessProbe).
  • Только после подтверждения готовности новой версии Kubernetes начинает плавно гасить старые поды.
  • В любой момент времени пользователи получают непрерывный доступ к приложению.

Наглядная аналогия из жизни

Rolling Update — это замена колёс на движущемся многоосном грузовике или поезде:

  • У грузовика 8 колёс.
  • Механики приподнимают одну ось, меняют старое колесо на новое, проверяют затяжку болтов (readinessProbe) и опускают его на дорогу.
  • Затем переходят к следующему колесу.
  • Грузовик ни на секунду не останавливается и продолжает везти груз. Альтернатива Recreate — заглушить двигатель, снять все 8 колёс сразу, оставив грузовик на кирпичах (полный простой бизнеса).

Базовый манифест Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # Максимум +1 под сверх нормы (не более 5 подов всего)
      maxUnavailable: 0  # Ни один под не должен быть недоступен (всегда минимум 4)
  selector:
    matchLabels:
      app: payment
  template:
    metadata:
      labels:
        app: payment
    spec:
      containers:
      - name: payment-app
        image: payment-service:2.1.0 # Конкретный тег версии (НЕ :latest!)
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          periodSeconds: 5

🟡 Middle Level

Математика maxSurge и maxUnavailable

Поведение волнового обновления управляется двумя ключевыми параметрами в процентах или абсолютных числах:

Базовое число реплик: replicas = 4
maxSurge: 1       -> В пике обновления в кластере может быть создано: 4 + 1 = 5 подов
maxUnavailable: 0 -> В любой момент времени клиентам должны отвечать минимум: 4 - 0 = 4 пода

Фазы обновления по шагам:

  1. Шаг 1: Создаётся 1 новый под версии v2. Всего в кластере 5 подов (4 v1 + 1 v2).
  2. Шаг 2: Новый под v2 проходит readinessProbe. Трафик начинает поступать на него.
  3. Шаг 3: Удаляется 1 старый под v1. В кластере остаётся 4 пода (3 v1 + 1 v2).
  4. Шаг 4: Процесс циклически повторяется до полной замены всех 4 подов на версию v2.

Управление процессом через kubectl rollout

Kubernetes предоставляет мощный набор команд для контроля деплоя:

# 1. Запустить обновление новой версии образа
kubectl set image deployment/payment-service payment-app=payment-service:2.2.0

# 2. Отслеживать прогресс обновления в реальном времени
kubectl rollout status deployment/payment-service

# 3. Приостановить деплой (например, для ручной проверки на канареечном поде)
kubectl rollout pause deployment/payment-service

# 4. Возобновить деплой дальше
kubectl rollout resume deployment/payment-service

# 5. Посмотреть историю ревизий
kubectl rollout history deployment/payment-service

# 6. МГНОВЕННЫЙ ОТКАТ к предыдущей стабильной ревизии при аварии
kubectl rollout undo deployment/payment-service

Внутренний механизм: Роль ReplicaSet

Объект Deployment сам по себе не управляет подами напрямую. Он управляет объектами ReplicaSet:

  • При изменении секции spec.template контроллер Deployment создаёт новый ReplicaSet (например, payment-78d4f8).
  • Постепенно увеличивает счетчик replicas в новом ReplicaSet и уменьшает его в старом ReplicaSet (payment-59c6b8).
  • Старый ReplicaSet остаётся в кластере со значением replicas: 0. Это позволяет выполнить команду rollout undo за полсекунды без повторной генерации конфигураций!

🔴 Senior Level

Проблема сосуществования версий и паттерн «Expand / Contract»

Во время Rolling Update в кластере одновременно работают поды старой версии (v1) и новой версии (v2):

  • Запрос одного пользователя может попасть на v1, а следующий — на v2.
  • Если разработчик в версии v2 удалит столбец в базе данных или переименует поле в JSON, работающие поды v1 немедленно начнут падать с ошибками.

Паттерн эволюции БД «Expand and Contract» (Параллельный запуск):

Фаза 1 (Expand):  Выполняем миграцию Liquibase/Flyway: ДОБАВЛЯЕМ новую колонку как NULLABLE.
                  Старая версия v1 продолжает работать, игнорируя эту колонку.
Фаза 2 (Rollout): Раскатываем Rolling Update версии v2. v2 пишет данные и в старую, и в новую колонку.
Фаза 3 (Contract):После 100% успеха раскатки отдельным скриптом удаляем старую ненужную колонку.

Конфликт с PodDisruptionBudget (PDB) и ResourceQuota

Две наиболее частые причины «зависания» Rolling Update в продакшене:

1. Зависание из-за ResourceQuota (Нехватка квот)

Если в Namespace установлена жесткая квота, например ровно на 10 подов, а у вас работает replicas: 10:

  • При деплое с maxSurge: 2 Kubernetes попытается создать 11-й и 12-й под.
  • API-сервер заблокирует их создание с ошибкой exceeded quota.
  • Новые поды зависнут в статусе Pending, и обновление заблокируется на 10 минут (progressDeadlineSeconds).
  • Решение: при дефиците квот использовать конфигурацию maxSurge: 0, maxUnavailable: 1 (сначала освободить место, затем создавать).

2. Зависание из-за конфликта с PodDisruptionBudget

Если объявлен PDB с требованием minAvailable: 100%, а в Deployment задано maxUnavailable: 25%:

  • Контроллер деплоя не сможет удалить ни один старый под, так как это нарушит контракт PDB!

Идеальная формула 100% Zero-Downtime для Java/Spring Boot

Для гарантированного отсутствия ошибок 502 Bad Gateway во время деплоя необходимо одновременное выполнение 4 условий:

  1. maxUnavailable: 0 — не выключать старые поды до поднятия новых.
  2. Надежная readinessProbe — новый Spring Boot под начинает получать трафик только после полной загрузки контекста.
  3. preStop: sleep 10 — задержка перед SIGTERM для компенсации сетевого лага обновления iptables агентами kube-proxy.
  4. Spring Boot Graceful Shutdown — параметр server.shutdown: graceful в application.yaml, чтобы Tomcat корректно завершил активные транзакции перед гибелью.

4 Tricky Questions

1. Почему использование тега образа :latest (например, image: my-app:latest) полностью ломает механизм автоматического запуска Rolling Update в Kubernetes?

Ответ: Контроллер Deployment отслеживает изменения в манифесте по хэшу спецификации spec.template (поле pod-template-hash):

  • Если вы собрали новый Docker-образ, запушили его в реестр под тем же именем my-app:latest и выполнили kubectl apply -f deployment.yaml, то с точки зрения API-сервера манифест не изменился ни на один символ.
  • Контроллер видит прежнюю строку image: my-app:latest, не регистрирует факта обновления и вообще не запускает процедуру Rolling Update.
  • Все поды продолжат работать на старом коде. Для корректной работы всегда необходимо использовать семантические теги версий (:1.4.2) или SHA256-хэш дайджеста образа (@sha256:...).

2. Что произойдёт с объектами ConfigMap и Secret, если выполнить команду отката ревизии kubectl rollout undo?

Ответ: Они НЕ откатятся! Это критическая ловушка для DevOps-инженеров:

  • Механизм ревизий Deployment и его дочерних ReplicaSet сохраняет историю исключительно спецификации самого пода (образы, переменные, порты, селекторы).
  • Внешние объекты ConfigMap и Secret версионируются независимо.
  • Если вы обновили конфиг в ConfigMap и раскатали новую версию, а затем выполнили rollout undo, под откатится на старый Docker-образ, но будет монтировать НОВЫЙ, уже изменённый ConfigMap! Это может привести к несовместимости и падению старого бинарника.
  • Решение: паттерн Immutable ConfigMaps с добавлением хэша в имя конфига (app-config-v123) или использование инструментов GitOps (Helm / ArgoCD).

3. Для чего нужен параметр spec.progressDeadlineSeconds, и что происходит при его превышении во время сбоя деплоя?

Ответ:

  • progressDeadlineSeconds (по умолчанию 600 секунд / 10 минут) задаёт максимальное время, в течение которого процесс обновления должен демонстрировать прогресс (создание новых подов, прохождение readiness probe).
  • Если новые поды зависают в ошибке (например, CrashLoopBackOff или ImagePullBackOff) и прогресса нет в течение этого периода:
    • Контроллер выставляет условию Progressing статус False с причиной ProgressDeadlineExceeded.
    • Автоматического отката (Rollback) Kubernetes НЕ выполняет! Процесс обновления просто замирает в текущей промежуточной точке (часть трафика на старых подах, часть зависших подов), сигнализируя мониторингу или CI/CD пайплайну о необходимости аварийного вмешательства.

4. В чём состоит разница между стратегиями обновления RollingUpdate в Deployment и в StatefulSet?

Ответ:

  • В Deployment: поды эфемерны, не имеют стабильного сетевого имени и постоянного диска. Они могут создаваться и удаляться пачками в произвольном порядке с превышением лимита (maxSurge).
  • В StatefulSet:
    • Не поддерживается концепция maxSurge (нельзя превышать желаемое число реплик, так как каждый под привязан к собственному выделенному PersistentVolume).
    • Обновление происходит строго по одному в строго обратном порядковом номере: сначала под app-2, затем app-1, затем app-0.
    • Новый под app-N создаётся с сохранением прежнего диска и сетевого DNS-имени только ПОСЛЕ того, как старый под с этим номером был полностью остановлен и отмонтирован.

🎯 Шпаргалка для интервью

Главное о Rolling Update

  • Суть: плавное волновое замещение старых подов новыми без остановки сервиса.
  • maxSurge: сколько подов можно создать сверх желаемого числа (запас мощности).
  • maxUnavailable: сколько подов допустимо вывести из строя во время процесса.
  • Формула Zero Downtime: maxUnavailable: 0 + readinessProbe + preStop: sleep 10 + Graceful Shutdown в Spring Boot.
  • Роль ReplicaSet: Deployment создаёт новый RS, наращивает его и сохраняет старый с 0 реплик для мгновенного отката (rollout undo).
  • Эволюция БД: паттерн Expand/Contract обязателен, так как v1 и v2 сосуществуют во время деплоя.
  • Антипаттерн :latest: при использовании :latest контроллер не видит изменений манифеста и не запускает деплой.

Сравнение стратегий деплоя в Kubernetes

| Стратегия | Downtime | Дополнительные ресурсы | Скорость отката | Особенности | | :— | :—: | :—: | :—: | :— | | RollingUpdate | 🟢 Zero | В пределах maxSurge | Быстрый (rollout undo) | Две версии работают одновременно | | Recreate | 🔴 Есть | 0 (сначала удаляет всё) | Долгий (пересоздание) | Для баз данных и несовместимых схем | | Blue-Green | 🟢 Zero | +100% (удвоение кластера) | Мгновенный (свитч Ingress) | Изоляция версий, дорого по ресурсам | | Canary | 🟢 Zero | + процент на канарейку | Мгновенный | Проверка на 5–10% реальных пользователей |

Красные флаги на собеседовании (Чего говорить нельзя)

  • ❌ «Чтобы обновить приложение, мы используем образ с тегом :latest» — Kubernetes не обнаружит изменений и обновление не запустится.
  • ❌ «При выполнении rollout undo база данных и секреты автоматически возвращаются к старой версии» — ConfigMaps, Secrets и БД живут отдельно и не откатываются.
  • ❌ «Rolling Update гарантирует, что две версии приложения никогда не будут работать одновременно» — они гарантированно работают параллельно на этапе переключения.

Связанные вопросы