PodDisruptionBudgets That Actually Protect You
A PodDisruptionBudget keeps your service up during node drains and upgrades, but a wrong value blocks drains or protects nothing. How to set PDBs correctly.
Part of Kubernetes Operations for Production Platforms
A PodDisruptionBudget is what keeps your service running when a node is drained or the cluster is upgraded, but only if you set it correctly. Set it right and Kubernetes will never voluntarily take down more pods than your service can spare. Set minAvailable equal to your replica count and you block node drains forever; set it too low and it protects nothing. The PDB is a small object with a sharp edge: it is either load-bearing or a footgun, depending on one number.
PDBs are also one of the most-skipped items in a deployment checklist, which is why “the cluster upgrade took the service down” remains a common incident. The fix is cheap; the omission is expensive.
Why PodDisruptionBudgets matter
Kubernetes clusters are constantly in motion: nodes get drained for maintenance, the cluster gets upgraded, the autoscaler removes underused nodes. Each of those is a voluntary disruption that evicts the pods on the affected node. Without a PDB, nothing stops Kubernetes from evicting all of your service’s pods at once if they happen to share a node or a drain wave.
A PodDisruptionBudget is how you tell the cluster “you may take some of my pods during maintenance, but never so many that I go down.” It is the contract that makes routine operations safe for your service. This post is part of the Kubernetes operations series.
What does a PodDisruptionBudget actually protect against?
A PDB protects against voluntary disruptions: node drains, cluster upgrades, and other deliberate evictions. It explicitly does not protect against involuntary disruptions, a node crashing, hardware failing, a pod being OOM-killed. This distinction is the single most misunderstood thing about PDBs, and getting it wrong leads people to expect protection a PDB cannot give.
The practical takeaway: use PDBs to make drains and upgrades safe, and use replica count plus anti-affinity (spreading pods across nodes and zones) to survive crashes. They solve different halves of availability, and you need both.
How do you set minAvailable on a PodDisruptionBudget?
Set minAvailable to the number of pods your service genuinely needs to keep serving, while leaving headroom for at least one pod to be evicted at a time so drains can actually make progress. The classic safe value for a three-replica deployment is minAvailable: 2: the service stays healthy on two pods, and one pod can be drained at a time. You can also express it as maxUnavailable instead, which is often clearer for larger deployments.
The two boundaries to avoid:
| Setting | Effect | Verdict |
|---|---|---|
minAvailable == replica count | No pod can ever be evicted; drains block forever | Broken |
minAvailable very low (e.g. 0) | Everything can be evicted at once | Protects nothing |
minAvailable = replicas − 1 (e.g. 2 of 3) | One pod drains at a time; service stays up | Usually right |
maxUnavailable as a percentage | Scales with replica count | Good for large deployments |
The reasoning is that a drain needs to evict pods to proceed, so the budget must permit at least one eviction at a time, while still keeping enough pods up to serve. That tension, allow progress, preserve availability, is the whole design of the value.
Why is my node drain stuck or blocked?
A stuck node drain is very often an unsatisfiable PodDisruptionBudget. If minAvailable equals the replica count, Kubernetes can never evict a pod without violating the budget, so the drain blocks indefinitely. The same happens with a single-replica deployment that has a PDB requiring that one pod stay available: there is no way to drain it without breaking the budget.
The fixes are direct: lower minAvailable so at least one eviction is allowed, or add replicas so the service can tolerate losing one. A single-replica service fundamentally cannot satisfy both “stay available” and “be drained,” so if it matters, give it more than one replica. This is also a good prompt to check that your replicas are spread across nodes, because a PDB does you little good if all your pods sit on the node being drained.
minAvailable or maxUnavailable: which should you use?
Use maxUnavailable for larger or variable-replica deployments and minAvailable for small fixed ones. They express the same budget from opposite ends, but maxUnavailable (often as a percentage) stays correct when your replica count changes, while a hardcoded minAvailable can silently become wrong after a scale event. For a service whose replica count moves with autoscaling, a percentage-based maxUnavailable is the more robust choice.
The trap with minAvailable is that it is an absolute number that does not track replica changes. Set minAvailable: 2 on a three-replica service and it is correct; let that service autoscale up to twenty replicas and the same PDB now allows eighteen pods to be drained at once, far more disruption than you intended. The budget did not change, but its meaning did, because the replica count moved underneath it.
maxUnavailable: 25% avoids that drift: it always means “at most a quarter of whatever is currently running,” so it scales with the deployment. For a small, fixed-size critical service, minAvailable set to replicas minus one is clear and fine. For anything that autoscales, prefer the percentage-based maxUnavailable so the protection stays proportional as the service grows and shrinks. Either way, generate it from policy rather than hand-setting numbers that drift.
How do PDBs and node upgrades actually interact?
The reason PDBs exist becomes concrete during a cluster upgrade, and understanding the sequence explains most of the behaviour people find surprising.
A managed node-pool upgrade proceeds node by node. For each node it cordons it (no new pods scheduled), then evicts the existing pods, then replaces the node. The eviction step is where your PDB is consulted, via the eviction API rather than a direct delete — which is exactly why kubectl delete pod bypasses the budget entirely while kubectl drain respects it.
When an eviction would breach a budget, the API rejects it and the drain retries. It does not give up and it does not force. So a PDB that can never be satisfied does not fail loudly; it produces a drain that retries indefinitely, and an upgrade that appears to hang with no error explaining why. That is the signature to recognise: an upgrade stuck on one node for a long time is almost always a PDB, not a broken cluster.
The corollary that catches teams during urgent work: this blocking is also what stops you from applying a security patch quickly. A budget that is too strict trades away your ability to respond fast, and that trade is usually made accidentally by someone being cautious. The balance to aim for is a budget that permits one pod at a time to move, which keeps the service up while letting maintenance proceed steadily.
Three practical consequences worth internalising:
- Drains are serialised by your budget. With
minAvailable: 2of 3, one pod moves at a time, so a large deployment across many nodes takes proportionally longer to drain. This is correct behaviour and it means upgrade windows must be sized against replica count and startup time, not just node count. - Slow pod startup multiplies the cost. The replacement pod must become ready before the next eviction is allowed. A 60-second startup turns each drain step into a minute of waiting, which is why pod startup time shows up as an upgrade-duration problem as well as a scaling one.
- Readiness must be truthful or the budget is fiction. The budget counts ready pods. If your readiness probe reports ready before the pod can serve, Kubernetes will happily evict the next pod while the previous replacement is not actually working — and your PDB will have permitted an outage while reporting compliance.
That last point is the one worth remembering, because it ties the two objects together: a PodDisruptionBudget is only as trustworthy as the readiness probe it counts. Both have to be right, and a careful PDB sitting on top of a lying probe provides confidence rather than protection.
Which workloads need a PDB, and which do not?
Not every Deployment wants one, and applying PDBs indiscriminately is how clusters end up impossible to maintain. The decision follows from what the workload does when a pod disappears.
| Workload | PDB? | Why |
|---|---|---|
| Stateless HTTP service, 3+ replicas | Yes | Losing several at once drops capacity below what traffic needs |
| Quorum system (etcd, ZooKeeper, Kafka controllers) | Yes, critically | Losing quorum is unrecoverable-by-drain; this is the strongest case |
| Stateful database with replicas | Yes | Evicting the primary and a replica together can lose write availability |
| Queue consumer | Usually not | Work is durable in the queue; brief capacity loss means lag, not errors |
| Batch job / CronJob | No | Interruption is expected and the job reruns |
| Single-replica anything | No | A PDB here blocks every drain forever without adding protection |
| DaemonSet | Rarely | Draining the node is the point; a PDB fights the operation |
The quorum row deserves the emphasis. A 3-node etcd cluster tolerates losing one member; losing two costs you the quorum and the cluster stops accepting writes. A node drain that evicts two members simultaneously is an entirely plausible way to do that to yourself during routine maintenance, and minAvailable: 2 is what prevents it. If you run anything quorum-based, this is the workload where a PDB is genuinely load-bearing rather than a nice-to-have.
The single-replica row is the inverse and the most common misconfiguration. A PDB on one replica has no valid state in which eviction is allowed, so it converts every future node drain into a stuck operation. If the workload matters enough to protect, give it more replicas; if it does not, leave it without a PDB so maintenance can proceed.
For queue consumers the reasoning is worth making explicit, because it looks like an omission. Their work is already durable — messages stay in the queue — so losing consumers briefly increases lag rather than dropping requests. Unless lag has a hard SLO, the right trade is to let maintenance proceed freely and let the backlog drain afterwards, which is exactly the property that makes queue-backed architectures operationally pleasant.
What does a PDB not protect you from?
The most dangerous misunderstanding about PodDisruptionBudgets is believing they are a general availability guarantee. They are not, and the boundary is precise: a PDB constrains only voluntary disruptions.
| Disruption | Respects your PDB? |
|---|---|
kubectl drain for maintenance | Yes |
| Cluster autoscaler removing a node | Yes |
| Node pool upgrade | Yes |
| Rolling deployment update | No — governed by the Deployment’s own surge/unavailable settings |
| Node hardware failure or kernel panic | No |
| Node running out of memory, kubelet evicting pods | No |
Someone running kubectl delete pod | No |
| Cloud provider terminating a spot instance | No |
Read that table as one sentence: a PDB protects you from planned operations, not from things going wrong. A PDB of minAvailable: 2 does not prevent a datacentre losing power and taking all three of your replicas with it. Real availability under involuntary disruption comes from replica count, spreading across failure domains with topology constraints and anti-affinity, and the ability to reschedule quickly.
The rolling-update row surprises people most. Your Deployment’s maxUnavailable governs updates, and a PDB does not tighten it. If you set a careful PDB and a careless deployment strategy, your own deploys remain the largest source of unavailability — and they are the disruption you cause most often.
What goes wrong with PDBs in practice?
Three failure modes account for nearly all PDB incidents, and all three are configuration rather than concept.
A PDB that can never be satisfied blocks drains forever. minAvailable: 1 on a single-replica Deployment means evicting the only pod would breach the budget, so it is never allowed. Every node drain touching that pod hangs, and cluster upgrades stall on it. This is the single most common PDB problem, and the fix is either more replicas or accepting that a single-replica workload does not want a PDB at all.
Percentage-based budgets round in surprising ways. minAvailable: 50% on 3 replicas rounds up to 2, permitting one eviction at a time; the same percentage on 2 replicas permits one; on 1, it blocks everything. Percentages are attractive because they scale with the Deployment, and their behaviour at small replica counts deserves checking rather than assuming.
Selectors that overlap or miss. A PDB selects pods by label. A selector matching nothing silently protects nothing — it will not error, it will simply have no effect, and you will believe you are protected. Two PDBs matching the same pods produce behaviour that is hard to reason about. After creating one, verify with kubectl get pdb that ALLOWED DISRUPTIONS and the expected pod count look right; a PDB reporting zero expected pods is a misconfiguration wearing a green checkmark.
The operational habit worth adopting: drain a node in a non-production cluster after adding or changing a PDB. It takes minutes and it is the only way to find out that a budget blocks maintenance before it blocks maintenance during an urgent security patch.
A PodDisruptionBudget checklist
For every important deployment:
- A PDB exists (it is part of being production-ready, not optional for real services).
minAvailable(ormaxUnavailable) keeps enough pods up to serve, while allowing at least one eviction.- It is never set equal to the replica count (that blocks drains forever).
- Single-replica services that need availability have been given more replicas, not just a PDB.
- Replicas are spread across nodes/zones with anti-affinity, so the PDB and the spread reinforce each other.
- You understand it covers voluntary disruptions only; crash survival comes from replicas + spread.
- You tested a node drain in staging and confirmed it proceeds without taking the service down.
How do PDBs interact with the cluster autoscaler?
PodDisruptionBudgets directly shape how the cluster autoscaler removes nodes. When the autoscaler wants to scale down by removing an underused node, it must evict that node’s pods, and it will respect your PDBs while doing so. A PDB that cannot be satisfied can therefore block scale-down, leaving you paying for a node the autoscaler wanted to reclaim.
This is the same blocked-drain dynamic as a manual node drain, just triggered automatically. If a deployment’s PDB does not allow any eviction, the autoscaler cannot drain the node hosting its pods, so that node stays up indefinitely and your cluster does not shrink when it should. The symptom is a cluster that never scales down even when utilization is low, and the cause is often a too-strict PDB somewhere.
The resolution is the same discipline as everywhere else with PDBs: set budgets that permit at least one eviction at a time, spread replicas across nodes so evicting one node never threatens the whole service, and avoid single-replica deployments with availability-requiring PDBs. Get this right and node scale-down and PDBs cooperate: the autoscaler reclaims nodes by draining them a pod at a time, your service stays up throughout, and you stop paying for capacity you are not using.
A closing operational note that saves real time: audit your PDBs before every planned upgrade, not during one. Running kubectl get pdb --all-namespaces and checking that ALLOWED DISRUPTIONS is at least one for every budget takes seconds and catches exactly the misconfiguration that would otherwise wedge the upgrade halfway through, at the point where rolling back is most awkward. A budget showing zero allowed disruptions is a stuck drain that has not happened yet, and finding it on a quiet afternoon costs a minute where finding it mid-upgrade costs an evening. Add the check to whatever pre-upgrade runbook you already run, so it happens by default rather than by memory.
What I’d do differently
The mistake I have seen most is treating PDBs as a box to tick, copying minAvailable: 1 onto everything, which on a two-replica service means both pods can be evicted down to one but a single-replica service quietly becomes undrainable. The value is not boilerplate; it has to reflect the actual replica count and availability needs of each service.
If I were standardizing this, I would generate the PDB from the deployment’s replica count by policy (for example, always replicas − 1 or a sensible maxUnavailable percentage) rather than hand-setting a number that drifts out of sync when replicas change. And I would test a node drain as part of validating any new service, because a blocked drain discovered during a real cluster upgrade is a far worse time to learn that the PDB was wrong. PDBs are small, and like most small Kubernetes objects, they punish carelessness precisely when you are mid-maintenance.
Sources
- Kubernetes, Specifying a Disruption Budget: kubernetes.io/docs/tasks/run-application/configure-pdb
- Kubernetes, Disruptions (voluntary vs involuntary): kubernetes.io/docs/concepts/workloads/pods/disruptions
- Kubernetes, Safely drain a node: kubernetes.io/docs/tasks/administer-cluster/safely-drain-node
Frequently asked questions
What is a PodDisruptionBudget?
A PodDisruptionBudget (PDB) is a Kubernetes object that limits how many pods of an application can be voluntarily disrupted at once, such as during a node drain or cluster upgrade. It tells Kubernetes the minimum number (or maximum unavailable) of pods that must stay running, so a routine maintenance operation cannot take your whole service down.
What does a PodDisruptionBudget actually protect against?
Voluntary disruptions: node drains, cluster upgrades, and other operations that evict pods on purpose. It does not protect against involuntary disruptions like a node crashing or running out of memory. PDBs make planned maintenance safe; they are not a defense against hardware failure.
How do you set minAvailable on a PodDisruptionBudget?
Set it to the number of pods your service genuinely needs to stay healthy, leaving room for at least one pod to be evicted at a time so drains can proceed. With three replicas, minAvailable 2 is common. Setting it equal to the replica count blocks drains entirely; setting it too low protects nothing.
Why is my node drain stuck or blocked?
Often a PodDisruptionBudget that cannot be satisfied. If minAvailable equals the replica count, or a single-replica deployment has a PDB requiring that one pod stay up, the drain can never evict the pod without violating the budget, so it blocks forever. Fix the PDB or add replicas.
What does a PodDisruptionBudget not protect against?
Involuntary disruptions. A PDB constrains drains, autoscaler node removal, and node pool upgrades, but not node hardware failure, kernel panics, memory-pressure evictions, manual pod deletion, spot-instance termination, or your own rolling deployments, which are governed by the Deployment's maxUnavailable instead.
Which workloads actually need a PodDisruptionBudget?
Quorum systems such as etcd or ZooKeeper need one most, since losing quorum during a drain is severe. Stateless services with three or more replicas and stateful databases with replicas also benefit. Queue consumers, batch jobs, DaemonSets, and single-replica workloads usually should not have one.