Сравнение стратегий и выбор для production
Четыре стратегии (RollingUpdate, Recreate, Blue-green, Canary) — это не “лучше / хуже”. Каждая решает свой класс задач, и production-ready команда умеет выбрать правильную для конкретного сервиса. В этом уроке — decision tree, сравнительная таблица и production best practices, которые делают любой rollout безопасным.
L4 vs L7 load balancing: transport против application layer
Сравнительная таблица
| Strategy | Downtime | Resource overhead | Rollback speed | Complexity | K8s native |
|---|---|---|---|---|---|
| RollingUpdate | None | +maxSurge replicas | Slow (новый rolling update) | Low | Yes |
| Recreate | Yes | None | Slow (новый Recreate) | Lowest | Yes |
| Blue-green | None | 2× (двойной) | Instant (switch selector) | Medium | Manual |
| Canary | None | +canary replicas (~10%) | Adjust replicas | Medium | Manual |
| Progressive (Argo Rollouts) | None | Variable | Automatic + analysis | High | CRD |
Расшифровка ключевых колонок:
- Downtime: есть ли период когда 0 Pods Ready. Recreate — единственная с downtime.
- Resource overhead: сколько extra Pods нужно во время rollout. Blue-green — самый дорогой (2×).
- Rollback speed: насколько быстро можно откатиться. Blue-green с idle blue Pods — instant. RollingUpdate — нужно прогнать rolling update обратно.
- Complexity: сколько ручной работы / extra инфраструктуры. Native K8s strategies — простые, blue-green/canary — требуют ручного управления Services, progressive — нужны CRD контроллеры.
Decision tree: какую выбрать
Schema migrations: специальный случай
Database schema migrations — самый частый источник deployment problems. Главный вопрос: backward-compatible ли изменение?
Backward-compatible изменения (типовое 80%)
- Добавление nullable column
- Добавление нового table
- Добавление nullable index
- Расширение type (
VARCHAR(50)→VARCHAR(100))
Эти изменения не ломают старую версию. Можно делать RollingUpdate:
# Helm chart с pre-upgrade migration
apiVersion: batch/v1
kind: Job
metadata:
name: db-migration-v1.5
annotations:
helm.sh/hook: pre-upgrade
helm.sh/hook-weight: "1"
helm.sh/hook-delete-policy: hook-succeeded
spec:
template:
spec:
containers:
- name: migrate
image: my-app:v1.5
command: ["./migrate", "up"]
restartPolicy: OnFailure
Workflow:
helm upgradeзапускает pre-upgrade Job (migration). Schema меняется.- После успеха Job — Helm обновляет Deployment (RollingUpdate).
- Старая версия работает с новой schema (backward-compatible).
- Новые Pods используют новые поля.
Non-backward-compatible изменения
- Rename column (
name→full_name) - Drop column
- Change type incompatibly (
VARCHAR→INT) - Drop table
Нельзя RollingUpdate — в момент рассогласования старая и новая версии обе ломаются. Варианты:
Вариант A: двухфазный deploy (правильный)
- Phase 1: deploy intermediate version, которая работает с обеими schemas (read both, write both)
- Run data migration (backfill new column from old)
- Deploy version that uses only new schema
- Drop old column в third phase
Вариант B: Recreate + migration job
- Pre-upgrade Job выполняет breaking migration
- Deployment.strategy=Recreate — все старые Pods убиваются ДО создания новых
- Новые Pods стартуют с новым кодом, новой schema
- Downtime 30s — несколько минут, но atomicity
Если у тебя RollingUpdate Deployment + non-backward-compatible migration — это bug. В момент когда новые Pods Ready (и Service балансирует на них), старые Pods всё ещё работают на старой schema, но колонка уже переименована. Старые Pods 500-ят, пока их не убьют. Решение: либо A (двухфазный), либо B (Recreate).
PodDisruptionBudget: контроль над evictions
PodDisruptionBudget (PDB) — отдельный объект, который ограничивает сколько Pod-ов могут быть одновременно недоступными во время voluntary disruptions: node drain, autoscaler, kubectl evict, но НЕ во время rolling update (rolling update controls availability через own maxUnavailable).
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
minAvailable: 2 # или maxUnavailable: 1
selector:
matchLabels:
app: web
Это значит: при node drain или admin eviction, kube-scheduler не позволит уйти больше чем (replicas - minAvailable) Pod-ам одновременно. Гарантирует SLA на доступность при инфраструктурных операциях.
PDB и Deployment.strategy.maxUnavailable — это разные механизмы. PDB защищает от voluntary disruptions (node drain, autoscaler). maxUnavailable управляет rolling update. PDB не блокирует Deployment rolling update — Deployment controller считает свою собственную availability через ReplicaSets. Production-grade: ставить ОБА: PDB для инфраструктурных операций + maxUnavailable для controlled deployments.
Best practices: чек-лист для production deploy
1. Readiness probe — обязательно
Без readiness probe Phase=Running считается готовностью, и rolling update убивает старые Pods до того как новые реально готовы. Всегда:
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
2. Resource requests — обязательно
Без requests scheduler может разместить Pod на overloaded ноду, где старт будет CPU-throttled. Это удлиняет initialDelaySeconds и может ломать readiness window.
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
3. PodDisruptionBudget для multi-replica services
spec:
minAvailable: 50% # для 4 replicas — минимум 2 всегда доступны
Защищает от node drain не-вовремя.
4. progressDeadlineSeconds
spec:
progressDeadlineSeconds: 300 # 5 минут на rollout
После этого срока Deployment.status.conditions[].type=Progressing помечается False. CI/CD должен абортировать deploy и rollback.
5. change-cause annotation для traceability
metadata:
annotations:
kubernetes.io/change-cause: "Bump nginx 1.27 -> 1.28 — CVE-2024-1234"
В production без этого не получится понять задним числом, что было в каждом rollout.
6. CI testing rolling updates на staging
Перед production:
- Deploy на staging cluster
- Wait для rolling update
- Smoke test через curl / synthetic load
- Только потом promote в production
7. terminationGracePeriodSeconds + preStop
Для приложений с активными connections:
spec:
terminationGracePeriodSeconds: 60
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5 && /app/drain"]
PreStop runs до SIGTERM, sleep 5 даёт endpoint-controller время удалить Pod из Service (избегает trafic на shutting-down Pod), потом /app/drain делает graceful drain in-flight requests.
CKAD задание: choose + implement
Типовое CKAD задание:
“У вас есть PostgreSQL Deployment с 1 replica, RWO PVC. Изменить image на новую версию без deadlock.”
Анализ:
- RWO PVC + 1 replica → RollingUpdate с maxSurge=1 даст Multi-Attach error если новый Pod schedulится на другую ноду
- Решение:
strategy.type=Recreate
kubectl patch deploy/postgres -p '{"spec":{"strategy":{"$retainKeys":["type"],"type":"Recreate"}}}'
kubectl set image deploy/postgres postgres=postgres:16
kubectl rollout status deploy/postgres
Другое задание:
“У вас 10 Pods nginx Deployment, нужно протестировать новую версию на 10% трафика.”
Анализ:
- 10% трафика, разделить через replicas ratio
- Canary через 2 Deployments + один Service
Реализация — стандартный canary YAML (см. урок 4).
Killer-моменты
- Default стратегия — RollingUpdate. Recreate, blue-green, canary — это исключения для специфических кейсов.
- Schema migration + RollingUpdate non-backward-compatible = bug. Либо двухфазный deploy, либо Recreate с pre-upgrade Job.
- PDB и Deployment.maxUnavailable — независимые. PDB защищает от voluntary disruptions, Deployment.maxUnavailable управляет rolling update.
- Best practice чек-лист: readinessProbe + resource requests + PDB + progressDeadlineSeconds + change-cause + preStop drain.
- CI testing на staging — единственный способ отловить проблемы rolling update до production (медленный startup, неправильные probes, resource starvation).