Full lesson
Explore the full explanation, examples, and visuals at your own pace.
Terminal: draining node-1
Your drain is stuck, and it won't give up. The node got cordoned fine, but one checkout Pod refuses to be evicted. Kubectl says it would violate the disruption budget, waits five seconds, and tries again. Same error, same Pod, with no default timeout to end it.
$ kubectl drain node-1 --ignore-daemonsets
node/node-1 cordoned
evicting pod shop/checkout-6f9c8-x2k4p
error when evicting pods/"checkout-6f9c8-x2k4p" -n "shop"
(will retry after 5s): Cannot evict pod as it would
violate the pod's disruption budget.
evicting pod shop/checkout-6f9c8-x2k4p
error when evicting pods/"checkout-6f9c8-x2k4p" ...Step 1: cordon, then evict
Here's the healthy path. kubectl first asks the API Server to cordon node-1, so nothing new gets scheduled there. Then it posts an Eviction for each Pod. The API Server checks every PDB that selects that Pod. If the allowance is above zero, it deletes the Pod gracefully and returns 201 Created.
Step 2: the blocked checkout Pod
Same lanes, but now the path is blocked. kubectl asks the API Server to evict the checkout Pod, and the API Server checks the PDB. The PDB answers with zero allowed, so the API Server returns 429. The Pod is never touched. kubectl waits five seconds and tries again, and that loop is the hang.
Cordon fences the node; eviction moves Pods
Cordon and eviction do different jobs. Cordon marks node-1 unschedulable, so no new Pods land there, but running Pods stay put. Only eviction removes one, and only if the PDB allows it. Then the ReplicaSet notices the missing Pod and recreates it on another node.
The number to check
Look at ALLOWED DISRUPTIONS. It says zero, so the eviction has no budget to spend. The pod list explains why. Three replicas exist, but the one on node-3 is in CrashLoopBackOff, not Ready. Only two count as healthy, and the PDB requires two. This output is illustrative.
$ kubectl get pdb -n shop
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS
checkout-pdb 2 N/A 0
$ kubectl get pods -n shop -l app=checkout -o wide
NAME READY STATUS NODE
checkout-6f9c8-x2k4p 1/1 Running node-1
checkout-6f9c8-m8q7z 1/1 Running node-2
checkout-6f9c8-p3n5d 0/1 CrashLoopBackOff node-3Allowed = healthy minus required
Allowed disruptions is healthy Pods minus required healthy Pods. Here, two Ready minus a minAvailable of two leaves zero, so the eviction gets a 429. With maxUnavailable, required is expected replicas minus that value. Drain doesn't force anything. It asks permission for each Pod, and the budget answers.
Three traps, same zero
Run the formula on each trap. One healthy replica minus one required is zero. Three minus three is zero, and maxUnavailable zero lands the same place. Our checkout case has two healthy minus two required. Different specs, same zero, same stuck drain.
- 1 replica, minAvailable 1
- 3 replicas, minAvailable 3 or maxUnavailable 0
- Reasonable PDB, unready replicas
Trap 3: looks fine, isn't
On paper, checkout has three replicas and minAvailable 2, so one eviction should be fine. But allowance counts only Ready Pods. With one replica crashing, two are healthy, and the allowance drops to zero. Nothing warns you. The trap stays hidden until drain starts.
What a PDB does not protect
A PodDisruptionBudget only guards one path: voluntary evictions through the Eviction API, where the PDB check decides if the Pod can go. A direct delete skips that check. So does a node crash. Those losses still happen, and they shrink your healthy count, which can then block later drains.
Diagnostic path
When a drain keeps retrying, start with the error. It names the Pod being evicted. Then run kubectl get pdb and find the budget that selects it. If allowed disruptions is zero, compare Ready replicas against minAvailable. For checkout, that's two Ready against two required, which tells you which trap you're in.
Fixes for checkout
Three ways to get checkout's allowance back to one. Describe the crashing replica and fix the real cause. Or scale to four replicas, so a new Ready Pod lands elsewhere. Or patch minAvailable down to one. Then watch the PDB, and drain proceeds once allowance rises.
# Root cause: inspect the crashing replica
$ kubectl describe pod checkout-6f9c8-p3n5d -n shop
# Or add headroom (needs capacity elsewhere)
$ kubectl scale deployment checkout -n shop --replicas=4
# Or loosen the budget temporarily
$ kubectl patch pdb checkout-pdb -n shop \
-p '{"spec":{"minAvailable":1}}'
# Watch allowance rise to 1, drain then proceeds
$ kubectl get pdb checkout-pdb -n shop -wTrade-offs
Fixing the unready replica solves the real problem, though it takes time. Scaling up works only if the cluster has room to schedule the new Pod. Loosening the PDB is quick, but you accept less redundancy. If you change it, revert it after the upgrade.
- Fix unready: best, but takes time
- Scale up: needs spare capacity
- Loosen PDB: accepts less redundancy briefly
Bypassing eviction removes the protection the PDB exists to give
As a last resort, --disable-eviction or deleting Pods skips the PDB check entirely. That removes the protection, and managed platforms may enforce their own PDB timeouts.
Check ALLOWED DISRUPTIONS first
Drain asks permission for each Pod, and a PDB with zero allowance never says yes. Two Ready Pods minus minAvailable of two leaves zero allowed, so the eviction returns 429 and drain keeps retrying. Before every node upgrade, check ALLOWED DISRUPTIONS first. If it's zero, fix that before you drain.

