Kubernetes PodDisruptionBudget: Stop Losing Pods During Rolling Updates
Kubernetes PodDisruptionBudget prevents voluntary disruptions from killing your app.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Kubernetes cluster with multiple nodes
- ✓Understanding of Deployments and ReplicaSets
- ✓kubectl configured
Use kubectl create pdb to protect your app. For a 3-replica deployment, set minAvailable: 2 or maxUnavailable: 1. This ensures at least 2 pods stay running during voluntary disruptions.
Imagine you're running a food truck with three cooks. A PDB is like a rule that says 'never let more than one cook leave at a time.' If you need to send one for a break (voluntary disruption), the others keep serving. But if a cook collapses (involuntary disruption), the rule doesn't apply — you just deal with it.
You've seen it: a rolling update or node drain takes down all your pods at once, and your service goes dark. Kubernetes gives you the tools to prevent this, but most teams ignore them until it's too late. PodDisruptionBudgets are the difference between a graceful maintenance and a PagerDuty alert at 3 AM.
The problem is simple: voluntary disruptions — node drains, cluster upgrades, autoscaling down — can evict pods. Without a PDB, the eviction controller doesn't care about your app's availability. It'll drain a node hosting all your replicas, and boom, downtime.
By the end of this, you'll know exactly how to configure PDBs for zero-downtime maintenance, how to debug them when they block an operation, and the one gotcha that makes people rip their hair out.
What Exactly Is a Voluntary Disruption?
Kubernetes splits disruptions into two types: voluntary and involuntary. Involuntary disruptions are hardware failures, kernel panics, OOM kills — things you can't control. Voluntary disruptions are actions you or the cluster take: node drains, cluster autoscaler scale-down, rolling updates, pod evictions via API. A PDB only protects against voluntary disruptions. If a node dies, your pods are gone — PDB won't save you. But for everything else, it's your safety net.
Without a PDB, the eviction controller treats each pod independently. It doesn't know your app needs at least 2 replicas. So when you drain a node, it evicts all pods on that node, even if that means your app goes to zero. That's the bug.
PDBs work by intercepting eviction requests. When you drain a node or delete a pod, the eviction API checks the PDB. If evicting the pod would drop the available count below minAvailable (or exceed maxUnavailable), the eviction is rejected. The drain pauses, and the controller waits until a new pod is ready elsewhere.
maxUnavailable instead of minAvailable when you want to express 'I can tolerate losing this many pods.' It's more intuitive for rolling updates: maxUnavailable: 1 means 'never take down more than 1 pod at a time.'minAvailable vs maxUnavailable: When to Use Which
You have two knobs: minAvailable and maxUnavailable. They're mutually exclusive — pick one. minAvailable specifies the absolute number or percentage of pods that must remain available. maxUnavailable specifies how many can be unavailable. For a 3-replica deployment, minAvailable: 2 is equivalent to maxUnavailable: 1.
Use minAvailable when you have a hard floor on capacity. For example, a database read replica pool must always have at least 3 replicas to handle query load. Use maxUnavailable when you know your burst tolerance. For a web app that can handle a 50% traffic drop briefly, maxUnavailable: 50% is fine.
The percentage is calculated from the current number of replicas, not the desired. If you scale down from 10 to 5, maxUnavailable: 50% allows 5 pods down at the start, but as pods are removed, the percentage shrinks. This can cause unexpected blocking. I've seen a scale-down get stuck because maxUnavailable: 50% of 5 is 2.5 (rounded up to 3), but after evicting 3, only 2 remain, and the next eviction would violate the budget. The fix: use absolute numbers for critical apps.
maxUnavailable: 50%, that's 1.5 → 2 pods allowed down. After evicting 2, only 1 remains, and the next eviction is blocked. The scale-down hangs. Use absolute numbers for critical paths.How PDBs Interact with Rolling Updates and Node Drains
A common misconception: the Deployment's maxSurge and maxUnavailable in the update strategy control all disruptions. They don't. Those only apply during a rolling update triggered by a Deployment change. Node drains, cluster autoscaler scale-down, and manual evictions bypass the Deployment's strategy entirely. That's where PDBs come in.
When you kubectl drain a node, the eviction API checks the PDB for each pod on that node. If evicting a pod would violate the PDB, the drain pauses. The node gets marked as SchedulingDisabled, and the drain command waits. It won't time out — it'll sit there until the PDB allows the eviction. This is often what people see when a drain hangs.
Rolling updates also respect PDBs. The Deployment controller creates new pods, waits for them to be ready, then evicts old pods. But the eviction of old pods goes through the eviction API, which checks the PDB. If the PDB says maxUnavailable: 1, the rolling update will only evict one old pod at a time, even if the Deployment strategy allows more. This is usually fine, but it can slow down updates. If you need speed, increase maxUnavailable temporarily.
--disable-eviction bypasses PDBs entirely. It force-deletes pods. Only use this when a node is already failing and you need to evacuate immediately. Otherwise, you're asking for downtime.PDB and Cluster Autoscaler: The Silent Scale-Down Blocker
Cluster Autoscaler (CA) scales down nodes when they're underutilized. It evicts pods from those nodes before removing them. If a PDB blocks the eviction, CA can't scale down that node. This leads to nodes sitting idle, wasting money.
I've seen this in production: a team had a PDB with minAvailable: 3 on a 3-replica deployment. CA tried to scale down a node that hosted one of those replicas. The eviction was blocked because removing that pod would leave only 2 available, violating the PDB. CA gave up, and the node stayed. The fix: either set minAvailable: 2 or use maxUnavailable: 1 to allow one pod to be evicted.
Another gotcha: CA uses a different eviction mechanism than kubectl drain. It respects PDBs the same way, but it has a timeout. If the eviction is blocked for too long (default 10 minutes), CA moves on. This can leave pods stranded on a node that should have been scaled down. Monitor CA logs for skip node ... - pod ... has a disruption budget that prevents eviction.
Unhealthy Pod Eviction: The PDB Bypass You Need to Know
There's a critical exception: PDBs do not protect unhealthy pods. If a pod has a status condition like Ready=False or has been in Terminating for more than a few minutes, the eviction controller may evict it even if it would violate the PDB. This is by design — you don't want a broken pod blocking a drain.
But here's the trap: the definition of 'unhealthy' is strict. The pod must have a status.conditions entry with type: Ready and status: False. If the pod is simply not ready (e.g., liveness probe failing but condition not updated), it's still considered healthy by the eviction controller. I've seen a pod with a failing liveness probe block a drain for 5 minutes because the condition wasn't set to False.
To force eviction of a stuck pod, you can delete it directly with kubectl delete pod --force --grace-period=0. This bypasses the eviction API entirely. Use this when a pod is truly dead and blocking operations.
Debugging PDBs: Why Is My Drain Stuck?
When a drain hangs, the first thing to check is the PDB status. Run kubectl get pdb -n <namespace>. Look at the DISRUPTIONS ALLOWED column. If it's 0, the PDB is blocking evictions. Then check EXPECTED PODS and CURRENT HEALTHY — if they're equal, no pod can be evicted without violating the budget.
Next, check which pods are on the draining node: kubectl get pods --field-selector spec.nodeName=<node>. See if any of those pods match the PDB selector. If they do, you need to either increase maxUnavailable, scale up the deployment, or force the drain.
Another common issue: the PDB selector doesn't match any pods. This happens when labels change. The PDB will show EXPECTED PODS: 0, meaning it's a no-op. You think you're protected, but you're not. Always verify with kubectl describe pdb <name>.
kubectl get events --field-selector involvedObject.kind=PodDisruptionBudget. It shows eviction attempts and why they were rejected.When Not to Use a PDB
PDBs aren't free. They add latency to drains and rolling updates. For stateless batch jobs that can be interrupted, skip the PDB. For development clusters, skip it — you want fast iteration, not safety nets. For single-replica deployments, a PDB with minAvailable: 1 is pointless because evicting that one pod would always violate it, blocking all drains. Either run multiple replicas or accept downtime during maintenance.
Also, don't use PDBs for DaemonSets. DaemonSet pods are tied to nodes — draining a node will evict the DaemonSet pod regardless. PDBs don't apply to DaemonSets in practice because the eviction controller treats them specially (they're allowed to be evicted even with a PDB). Check the docs, but I've seen this confuse people.
minAvailable: 1 on a single-replica deployment. It blocks all drains and updates. Either run at least 2 replicas or accept the risk.The Node Drain That Killed Our API
maxSurge: 1 and maxUnavailable: 0 would protect us.minAvailable: 2 and re-drained. The drain paused until a new pod was ready elsewhere.- Deployment update strategy only controls rolling updates, not node drains or cluster upgrades.
- Always pair with a PDB.
kubectl drain hangs with 'Cannot evict pod as it would violate the pod's disruption budget'kubectl get pdb -n <ns> to see ALLOWED DISRUPTIONS. If 0, the budget is full. 2. Check kubectl describe pdb <name> for CURRENT HEALTHY vs EXPECTED PODS. 3. Either scale up the deployment (increase replicas) to create slack, or increase maxUnavailable temporarily. 4. If urgent, use kubectl drain --disable-eviction but be aware of downtime risk.kubectl logs -n kube-system cluster-autoscaler-xxx. Look for 'disruption budget prevents eviction'. 2. Identify the PDB blocking: kubectl get pdb --all-namespaces. 3. Adjust PDB to allow at least one pod to be evicted (e.g., set maxUnavailable: 1). 4. Consider using --max-graceful-termination-sec to speed up evictions.kubectl get pdb -w. 2. If ALLOWED DISRUPTIONS is 0, the update is waiting for a new pod to become ready. 3. Increase maxUnavailable temporarily to allow faster rollouts. 4. Alternatively, increase maxSurge in the Deployment to create more new pods before evicting old ones.kubectl get pdb --all-namespaceskubectl describe pdb <name> -n <ns>| File | Command / Code | Purpose |
|---|---|---|
| pdb-basic.yaml | apiVersion: policy/v1 | What Exactly Is a Voluntary Disruption? |
| pdb-maxunavailable.yaml | apiVersion: policy/v1 | minAvailable vs maxUnavailable |
| drain-example.sh | kubectl drain node-1 --ignore-daemonsets | How PDBs Interact with Rolling Updates and Node Drains |
| pdb-ca-friendly.yaml | apiVersion: policy/v1 | PDB and Cluster Autoscaler |
| force-delete.sh | kubectl delete pod payments-api-xxx --force --grace-period=0 | Unhealthy Pod Eviction |
| debug-pdb.sh | kubectl get pdb -n default | Debugging PDBs |
Key takeaways
maxUnavailable for burst tolerance, minAvailable for hard capacity floors. Prefer absolute numbers over percentages for small deployments.Ready=False condition) bypass PDBs. Force-delete stuck pods with --force only as a last resort.Interview Questions on This Topic
How does a PodDisruptionBudget interact with a rolling update when the Deployment's `maxUnavailable` is set to 0?
maxSurge), then evict old pods. The eviction of old pods goes through the eviction API, which checks the PDB. If the PDB has maxUnavailable: 1, only one old pod is evicted at a time, even though the Deployment strategy allows zero unavailable. The update proceeds but slower.Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Kubernetes. Mark it forged?
5 min read · try the examples if you haven't