Deployments intermediate ⏱ 15 minutes K8s 1.28+

Kubernetes Pod Disruption Budget (PDB) Guide

Configure PodDisruptionBudgets with minAvailable or maxUnavailable to keep apps up during node drains, upgrades and autoscaler scale-down.

By Luca Berton • • 📖 7 min read

💡 Quick Answer: A PodDisruptionBudget (PDB) limits how many pods matching a selector can be voluntarily evicted at once. Set minAvailable (pods that must stay up) or maxUnavailable (pods that may be down) — one or the other, not both. kubectl drain, cluster upgrades, Cluster Autoscaler and Karpenter all go through the Eviction API and respect it.

Key config: maxUnavailable: 1 for a 3-replica Deployment — drains proceed one pod at a time.

Gotcha: PDBs don’t protect against involuntary disruptions (node crash, OOMKill) or Deployment rolling updates. A PDB whose ALLOWED DISRUPTIONS is 0 blocks node drains forever — see PDB allowed disruptions 0.

Voluntary vs Involuntary Disruptions

PDB applies (Eviction API):          PDB does NOT apply:
- kubectl drain                      - Node/hardware failure, kernel panic
- Cluster Autoscaler / Karpenter     - OOMKilled, liveness probe restarts
  scale-down and consolidation       - kubelet node-pressure eviction
- Managed node pool upgrades         - kubectl delete pod / deployment
- Direct calls to pods/eviction      - Deployment rolling updates
                                       (governed by strategy.rollingUpdate)

minAvailable vs maxUnavailable

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  minAvailable: 2            # At least 2 matching pods must stay healthy
  selector:
    matchLabels:
      app: web
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api-pdb
spec:
  maxUnavailable: 1          # At most 1 matching pod down at a time
  selector:
    matchLabels:
      app: api
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: worker-pdb
spec:
  maxUnavailable: "25%"      # Percentages round UP: 25% of 10 = 3
  selector:
    matchLabels:
      app: worker

Imperative: kubectl create pdb web-pdb --selector=app=web --min-available=2.

Setting3 replicas5 replicasBest for
minAvailable: 12 can be down4 can be downMinimum viable capacity
minAvailable: 21 can be down3 can be downFixed capacity floor / quorum
maxUnavailable: 11 can be down1 can be downDefault for most workloads
maxUnavailable: "25%"1 can be down2 can be downLarge or autoscaled fleets

maxUnavailable adapts when an HPA changes the replica count; a fixed minAvailable equal to the HPA’s minReplicas silently blocks drains whenever the workload is scaled in.

Decision Matrix

Workload               Replicas   PDB
Stateless API          3          maxUnavailable: 1
Stateless API          10+        maxUnavailable: 25%
Quorum DB / etcd       3          minAvailable: 2   (never lose quorum)
Quorum DB / etcd       5          minAvailable: 3
Message queue          3          maxUnavailable: 1
Singleton              1          maxUnavailable: 1 allows drains (brief outage)
                                  minAvailable: 1 blocks drains until handled manually

StatefulSets and Primaries

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: postgres-replicas-pdb
spec:
  minAvailable: 2               # Quorum of a 3-member cluster
  selector:
    matchLabels:
      app: postgresql
      role: replica
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: postgres-primary-pdb
spec:
  minAvailable: 1               # Primary is never evicted without a manual switchover
  selector:
    matchLabels:
      app: postgresql
      role: primary

Operators such as CloudNativePG create and manage these PDBs themselves — don’t add competing ones. Avoid multiple PDBs selecting the same pod: the Eviction API refuses to evict pods covered by more than one PDB.

Unhealthy Pod Eviction Policy

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: app-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: myapp
  unhealthyPodEvictionPolicy: AlwaysAllow
  # IfHealthyBudget (default): Running-but-not-Ready pods can only be evicted if the budget allows
  # AlwaysAllow: not-Ready pods can always be evicted, so CrashLooping pods don't block drains

Beta and on by default since Kubernetes 1.27, GA in 1.31. Recommended for most workloads.

PDB and Node Drain

kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data --timeout=300s
# Evicts pods one by one; for a PDB-blocked pod kubectl retries until the budget allows:
# error when evicting pods/"api-xyz" (will retry after 5s): Cannot evict pod as it
# would violate the pod's disruption budget.

kubectl drain node-1 --ignore-daemonsets --dry-run=server   # Preview
kubectl get pdb -w                                          # Watch budgets during drain

--force does not bypass PDBs — it only allows deleting bare pods without a controller. --disable-eviction deletes pods directly and skips PDBs; use it only when you accept the outage.

DaemonSet pods are skipped by --ignore-daemonsets, so a PDB on a DaemonSet doesn’t pace drains. PDBs on workloads without a scale subresource (DaemonSets, bare pods, most custom controllers) only support an integer minAvailable.

Check PDB Status

kubectl get pdb -A
# NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
# web-pdb   2               N/A               1                     5d

kubectl describe pdb web-pdb
#   Current Healthy:      3
#   Desired Healthy:      2
#   Disruptions Allowed:  1
#   Expected Pods:        3

kubectl get pdb web-pdb -o jsonpath='{.status.disruptionsAllowed}'

# Pods the PDB actually selects
kubectl get pods -l app=web --show-labels
flowchart TB
    D[kubectl drain node-1] --> E{PDB allows disruption?}
    E -->|disruptionsAllowed > 0| F[Evict pod]
    F --> G[Controller recreates pod elsewhere]
    G --> H[Pod Ready: budget restored]
    H --> E
    E -->|0| W[Retry until budget allows or timeout]

PDB with Cluster Autoscaler and Karpenter

Both evict through the Eviction API, so PDBs are respected automatically — no annotation on the PDB is needed. A node whose pods can’t be evicted within budget is simply skipped for scale-down. To pin a specific pod, annotate the pod with cluster-autoscaler.kubernetes.io/safe-to-evict: "false" (Cluster Autoscaler) or karpenter.sh/do-not-disrupt: "true" (Karpenter).

Common Issues

IssueCauseFix
Drain stuck, ALLOWED DISRUPTIONS 0minAvailable ≥ healthy replicas, single replica, or unready podsScale up, fix unhealthy pods, switch to maxUnavailable: 1, add AlwaysAllow
PDB not protecting podsSelector doesn’t match pod labels, or wrong namespaceCompare kubectl get pdb -o yaml with kubectl get pods --show-labels
Autoscaler never removes a nodePDB blocks eviction of a pod on itEnsure disruptions allowed > 0 and replicas spread across nodes
Eviction error: more than one PDBOverlapping selectorsOne PDB per pod set
Outage during rollout despite PDBRolling updates ignore PDBsTune maxUnavailable/maxSurge in the Deployment strategy

Best Practices

  1. Every production workload with 2+ replicas gets a PDB
  2. Default to maxUnavailable: 1; use minAvailable for quorum
  3. Never set minAvailable equal to replicas — blocks every drain and upgrade
  4. Spread replicas with topology spread constraints or anti-affinity so one drain touches one replica
  5. Set unhealthyPodEvictionPolicy: AlwaysAllow unless the app needs unready pods protected
  6. Alert on kube_poddisruptionbudget_status_pod_disruptions_allowed == 0 before maintenance windows

Frequently Asked Questions

What is a PodDisruptionBudget in Kubernetes?

A policy/v1 object that tells the Eviction API how many pods of a set must stay available (or may be unavailable) during voluntary disruptions such as node drains, upgrades and autoscaler scale-down.

Should I use minAvailable or maxUnavailable?

maxUnavailable in most cases: it keeps working as replicas change and always allows progress when there are enough healthy pods. Use minAvailable when you need an absolute floor, such as quorum for a 3- or 5-member cluster.

Does kubectl drain —force ignore PDBs?

No. --force only lets drain delete pods not managed by a controller. Drain still uses the Eviction API and waits for the PDB. --disable-eviction is the flag that bypasses PDBs by deleting pods directly.

Do PDBs apply to rolling updates?

No. Deployment and StatefulSet rollouts are controlled by their update strategy (maxUnavailable, maxSurge, partition), not by PDBs. PDBs only gate evictions.

Can I use a PDB with a single replica?

minAvailable: 1 on a single-replica workload blocks all voluntary evictions, so drains hang until someone intervenes. maxUnavailable: 1 allows the drain with a brief outage. Running 2+ replicas is the real fix.

#pdb #poddisruptionbudget #availability #disruption #maintenance #upgrades #node-drain #eviction
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens