Add a PodDisruptionBudget
A PodDisruptionBudget caps how many pods of one application can be down at once during a voluntary disruption, such as the node drains that happen on every Kubernetes upgrade and repair. Without one, a drain can evict your last pod and take the application offline. With one, the drain waits.
Get one wrong and it fails in two directions, both only visible mid-maintenance: a budget with no slack, or one too tight to satisfy.
What a PDB does and does not protect
A PDB protects against voluntary disruptions: node drains, and anything else that asks Kubernetes to move a pod politely. It does not protect against a node crash or hardware failure, because nothing asks permission there. So a PDB is no substitute for running enough replicas: replicas are the floor, and the PDB just meters how fast a drain works through them.
minAvailable or maxUnavailable
Pick exactly one:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web
spec:
maxUnavailable: 1 # or: minAvailable: 2
selector:
matchLabels:
app: web
maxUnavailable: 1 lets a drain take at most one pod at a time, the right default for a horizontally scaled service. minAvailable: 2 keeps a fixed number serving no matter what. Reach for maxUnavailable on a stateless service scaled by replica count, since it reads as "how much churn I tolerate," which is exactly what a drain does. Reach for minAvailable when a fixed floor matters regardless of replica count, such as a quorum.
Either way, match the number to your replica count:
| Replicas | maxUnavailable | Leaves serving |
|---|---|---|
| 2 | 1 | 1 |
| 3 | 1 | 2 |
| 10 | 2 (or 20%) | 8 |
Never set maxUnavailable to the whole replica count or minAvailable to 0: that tells the drain it may take everything at once, and the budget stops protecting anything. And put the pods on different nodes, or the budget has nothing to spread. See topology spread .
Why ALLOWED DISRUPTIONS reads 0
$ kubectl get pdb -n team-web web
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
web N/A 1 1 2m
ALLOWED DISRUPTIONS is how many pods may be evicted right now. If it reads 0 when you expect 1, a pod is currently not ready, so the budget has no slack to give. A drain then waits for that pod to recover. A 0 that never climbs means a pod is stuck. Fix its readiness before the next maintenance, not during it.
When a drain hangs and will not finish
The other symptom looks like a hang. The drain grinds through the full 180 seconds, then evicts the pod anyway, because the budget was set too tight to ever satisfy. A drain waits only as long as nodeDrainTimeoutSeconds allows, a per-pool default of 180 seconds that is raisable per pool in the Cluster topology. After that it proceeds, and the pods on that node go with it. A PDB provides an orderly, one-at-a-time move, never an indefinite block.
Warning
A single-replica application with maxUnavailable: 0 (or minAvailable: 1) tells the drain it may never evict the pod. The drain waits out the whole timeout and takes the pod regardless. A PDB cannot make one replica highly available. Run two.
Verify
Rehearse the drain before an upgrade does it for you. With the app running, drain a node that holds one of its pods and watch the budget pace the eviction:
$ kubectl get pdb -n team-web web -w # one terminal
$ kubectl drain <node> --ignore-daemonsets --delete-emptydir-data # another
ALLOWED DISRUPTIONS drops to 0 while the evicted pod reschedules and its replacement passes readiness, then climbs back to 1 once the budget has slack again. The drain releases one pod at a time and never dips below your floor. If instead it hangs for the full 180 seconds and then evicts anyway, the budget is too tight, so walk back through the two symptoms above. Uncordon the node when you are done.
Stateful and quorum applications
For a quorum-based application such as etcd or a database with a raft group, size the PDB so a drain never breaks quorum: with three members, maxUnavailable: 1. Pair it with one-per-node placement so no single drain can reach two members at once. See Keep stateful pods safe across node replacement .
Set resource requests and limits
Why the right default on this platform is a memory limit equal to the request, no CPU limit, and Burstable QoS.
Handle graceful shutdown
Finish in-flight requests when a node is replaced instead of dropping connections, by closing the endpoint-deregistration race with preStop and SIGTERM handling.