Kubernetes различает два типа нарушений работы Pod'ов: voluntary (плановые) и involuntary (непредвиденные). Node crash, kernel panic, OOM kill — involuntary disruptions. PDB с ними не работает. Но kubectl drain, rolling update, Cluster Autoscaler scale-down — voluntary disruptions, которые полностью контролируются. PDB — механизм, который не даёт плановым операциям вызвать незапланированный downtime. kubernetes.io/docs/concepts/workloads/pods/disruptions/
Pod Disruption Budget
PDB — объект policy/v1, который сообщает Kubernetes: «при voluntary disruption не удаляй больше N Pod'ов одновременно». Без PDB kubectl drain или rolling update может убить все реплики разом.
Параметры
Используется один из двух параметров (не оба одновременно):
Оба принимают число или процент ("50%").
Пример для stateless приложения
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app: web-app
При 4 репликах этот PDB гарантирует: не более 1 Pod'а недоступно одновременно. kubectl drain будет ждать, пока вытесненный Pod не пересоздастся на другой ноде, прежде чем вытеснить следующий.
Пример для stateful (quorum-based)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: etcd-pdb
namespace: kube-system
spec:
minAvailable: 2
selector:
matchLabels:
app: etcd
Для etcd-кластера из 3 нод: минимум 2 должны работать (quorum = N/2+1). При drain одной ноды 2 члена остаются — quorum сохраняется.
Проверка состояния PDB
kubectl get pdb -A
NAMESPACE NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
production web-app-pdb N/A 1 1 2d
kube-system etcd-pdb 2 N/A 1 5d
ALLOWED DISRUPTIONS — ключевое поле. Оно показывает, сколько Pod'ов можно безопасно удалить прямо сейчас. Если значение 0 — drain будет заблокирован.
Что происходит без PDB
Без PDB С PDB (maxUnavailable: 1)
───────────────────────── ─────────────────────────
kubectl drain node-1 kubectl drain node-1
├─ evict pod-1 ✓ ├─ evict pod-1 ✓
├─ evict pod-2 ✓ ├─ evict pod-2 ⏳ (ждёт reschedule pod-1)
└─ evict pod-3 ✓ │ pod-1 запустился на node-2
Все 3 реплики убиты ├─ evict pod-2 ✓
Сервис недоступен ├─ evict pod-3 ⏳ (ждёт reschedule pod-2)
└─ evict pod-3 ✓
Минимум 2 реплики работали всё время
Без PDB любая voluntary disruption — потенциальный полный downtime. С PDB — гарантированное соблюдение минимальной доступности.
PDB и Rolling Update
Deployment maxUnavailable и PDB maxUnavailable — независимые ограничения. Kubernetes соблюдает оба. Если PDB строже, чем strategy — PDB побеждает, обновление замедляется.
apiVersion: apps/v1
kind: Deployment
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2 # Deployment разрешает 2 одновременно
maxSurge: 1
Если при этом есть PDB с maxUnavailable: 1 — обновление будет идти по одному Pod'у, несмотря на maxUnavailable: 2 в Deployment spec.
Процедура drain ноды
Корректная последовательность для планового обслуживания:
1. cordon 2. drain 3. maintenance 4. uncordon
─────────── ─────────── ─────────── ───────────
Нода помечена Pod'ы эвакуируются Обновление OS, Нода снова
Unschedulable с учётом PDB kubelet, patching Schedulable
Новые Pod'ы Ждёт reschedule Pod'ы снова
не назначаются каждого Pod'а размещаются
# 1. Запретить размещение новых Pod'ов
kubectl cordon node-1
# Проверить, что нода помечена SchedulingDisabled
kubectl get node node-1
# 2. Эвакуировать Pod'ы (с учётом PDB)
kubectl drain node-1 \
--ignore-daemonsets \
--delete-emptydir-data \
--timeout=300s
# 3. Выполнить обслуживание (на ноде)
# apt update && apt upgrade, обновление kubelet, etc.
# 4. Вернуть ноду в строй
kubectl uncordon node-1
# Проверить Ready
kubectl get node node-1
Флаги kubectl drain:
kubernetes.io/docs/tasks/administer-cluster/safely-drain-node/
Стратегия обновления кластера
Порядок обновления
1. Control plane nodes 2. Worker nodes (по одной)
(по одной)
┌─────────────┐ ┌─────────────┐
│ master-1 │ ←─ first │ worker-1 │ ←─ cordon, drain, upgrade, uncordon
│ master-2 │ │ worker-2 │ ←─ cordon, drain, upgrade, uncordon
│ master-3 │ ←─ last │ worker-3 │ ←─ cordon, drain, upgrade, uncordon
└─────────────┘ └─────────────┘
Правила version skew
-
Control plane обновляется первым, workers — после. API server должен поддерживать версию kubelet на worker'ах. Обратное не гарантировано. kubernetes.io/releases/version-skew-policy/
-
Только один minor version за раз. 1.29 → 1.30 — корректно. 1.29 → 1.31 — не поддерживается (нужно 1.29 → 1.30 → 1.31).
-
Version skew policy (k8s 1.25+):
kube-controller-manager,kube-scheduler— не старше N-1 отkube-apiserverkubelet— не старше N-3 отkube-apiserver(до 1.25 было N-2)kube-proxy— не старше N-3 отkube-apiserver(до 1.25 было N-2)
При drain Pod'ов с failing readiness probe (k8s 1.27+) полезен PDB-параметр unhealthyPodEvictionPolicy: AlwaysAllow — он разрешает выселение нездоровых Pod'ов, даже если PDB их формально защищает, что разблокирует drain.
Пример обновления worker-ноды (kubeadm, Ubuntu, 1.29 → 1.30)
# --- На control plane ноде ---
kubectl cordon worker-1
kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data
# --- На worker-1 ---
# Обновить kubeadm
apt-mark unhold kubeadm
apt-get update && apt-get install -y kubeadm=1.30.x-*
apt-mark hold kubeadm
# Применить конфигурацию обновления
kubeadm upgrade node
# Обновить kubelet и kubectl
apt-mark unhold kubelet kubectl
apt-get install -y kubelet=1.30.x-* kubectl=1.30.x-*
apt-mark hold kubelet kubectl
systemctl daemon-reload
systemctl restart kubelet
# --- На control plane ноде ---
kubectl uncordon worker-1
kubectl get nodes # Проверить VERSION и STATUS=Ready
Команда kubeadm upgrade node (в отличие от kubeadm upgrade apply, которая используется на первом control plane ноде) применяет конфигурацию уже обновлённого control plane к данной ноде.
Ограничения PDB
1. Не защищает от involuntary disruptions Node crash убивает все Pod'ы мгновенно — PDB не участвует. Для защиты от этого нужны реплики на разных нодах/зонах (Pod Anti-Affinity).
2. Может заблокировать drain навсегда
replicas: 3, minAvailable: 3 → drain заблокирован бесконечно
replicas: 3, minAvailable: 2 → drain работает (по 1 Pod'у)
replicas: 1, maxUnavailable: 0 → drain заблокирован бесконечно
Если minAvailable равен количеству реплик или maxUnavailable: 0 при 1 реплике — эвакуация невозможна.
3. PDB считает healthy Pod'ы по readiness
Для расчёта ALLOWED DISRUPTIONS важен Ready=True, а не просто факт запуска контейнера. Pod в состоянии Running, но с failing readiness probe, уменьшает allowedDisruptions — и может неожиданно заблокировать drain.
4. Preemption может проигнорировать PDB Scheduler при preemption (вытеснение Pod'ов для размещения более приоритетного Pod'а) может обойти PDB как крайнюю меру. kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/
5. PDB не работает без достаточной capacity Если остальные ноды не могут принять вытесненные Pod'ы (нехватка CPU/Memory или node affinity ограничения) — drain будет ждать indefinitely, даже если PDB позволяет disruption. Нужен capacity запас или Cluster Autoscaler.
Когда использовать
PDB нужен всегда для production workloads:
- Любой Deployment с > 1 реплики в production — PDB с
maxUnavailable: 1как минимум. - Stateful workloads с quorum (etcd, ZooKeeper, Kafka) — PDB с
minAvailable: quorum_size. - Кластеры с Cluster Autoscaler — без PDB scale-down может вытеснить несколько Pod'ов одновременно.
- Перед обновлением кластера — убедиться, что PDB настроены корректно, иначе drain может быть заблокирован или, наоборот, вызовет downtime.
Проверить готовность кластера к обновлению:
# Посмотреть все PDB и их текущее состояние
kubectl get pdb -A
# Проверить ноды на наличие заблокированных drain
kubectl describe node worker-1 | grep -A5 Taints
# Убедиться в наличии запаса ресурсов
kubectl top nodes
kubectl describe nodes | grep -A3 "Allocated resources"
Типичные ошибки
1. PDB не создан для production Deployment
При первом kubectl drain (обновление ноды, cloud maintenance) все реплики вытесняются одновременно. Downtime фиксируется в error budget. Правило: PDB — обязательная часть production Deployment чеклиста.
2. minAvailable равен числу реплик
spec:
replicas: 2
# ...
---
spec:
minAvailable: 2 # При 2 репликах — drain невозможен никогда
kubectl drain будет заблокирован бесконечно. Этот конфиг выглядит «безопасным», но фактически делает плановое обслуживание невозможным.
3. Обновление пропуском minor version 1.28 → 1.30 напрямую. kubeadm откажет, но некоторые bootstrap-инструменты или managed кластеры могут позволить. Результат: непредсказуемое поведение, несовместимость API, сломанные admission webhooks.
4. Worker обновляется раньше control plane Kubelet нового minor version на worker'е, API server старого на control plane. Нарушение version skew policy. Некоторые функции не будут работать, возможны крашлупы.
5. Нет capacity запаса перед drain 3 worker-ноды, каждая загружена на 80%. Drain одной ноды — 2 оставшихся должны принять на 50% больше Pod'ов. Pod'ы уходят в Pending. SLO нарушается. Правило: перед drain убедиться, что N-1 нод вмещают текущую нагрузку.
6. Игнорирование stuck Pod'ов при drain
kubectl drain завис. Вместо диагностики (PDB? ресурсы? affinity?) инженер добавляет --force --disable-eviction. Это обходит PDB полностью и может вызвать именно тот downtime, от которого PDB защищал.
7. PDB с maxUnavailable: 0 для single-replica Deployment
Попытка «защитить» единственный Pod приводит к невозможности любого drain. Правильное решение: увеличить replicas до 2, затем установить maxUnavailable: 1.
Альтернативы
topologySpreadConstraints Распределяет Pod'ы равномерно по зонам/нодам. Работает в паре с PDB: PDB ограничивает одновременные disruptions, topologySpreadConstraints гарантирует, что Pod'ы не концентрируются на одной ноде/зоне. kubernetes.io/docs/concepts/scheduling-eviction/topology-spread-constraints/
Pod Anti-Affinity Запрещает размещение Pod'ов одного Deployment на одной ноде/зоне. Дополняет PDB: гарантирует, что при involuntary disruption одной ноды теряется не более одной реплики. Менее гибко, чем topologySpreadConstraints (бинарное решение).
Cluster Autoscaler с PDB Cluster Autoscaler уважает PDB при scale-down. Если удаление ноды нарушит PDB — Autoscaler не удалит ноду. Это делает PDB важным инструментом контроля Autoscaler поведения.
Karpenter (AWS) Альтернатива Cluster Autoscaler, нативно уважает PDB при disruption. Умеет консолидировать ноды (убирать недогруженные) с учётом PDB ограничений. Быстрее CA при provisioning новых нод. karpenter.sh
Blue/Green обновления (через ArgoCD Rollouts / Flux) Вместо rolling update с drain — создание новой группы нод, перенос workload, удаление старой группы. Сложнее в реализации, но даёт zero-downtime обновление без зависимости от PDB. Используется в managed Kubernetes (EKS managed node groups, GKE node pool upgrade).
Immutable infrastructure Ноды никогда не обновляются — они заменяются. Новые ноды с новым AMI/image создаются, старые дренируются и удаляются. PDB всё равно нужен для контроля скорости drain, но сам процесс обновления детерминирован.