🐳 Розділ 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 гарантує, що дві версії застосунку ніколи не працюватимуть одночасно» — вони гарантовано працюють паралельно на етапі перемикання.

Пов’язані питання