Two symptoms send people to the PodDisruptionBudget docs, and they point in opposite directions. The first: kubectl drain node-7 hangs on “evicting pod …” and never returns, the cluster autoscaler logs that it cannot scale down, a node-pool upgrade stalls on a single node for an hour. The second: a team adds a PDB expecting it to keep their app alive when a node dies, then loses half their replicas to a hardware failure anyway and wonders why the budget did nothing. Both come from the same misreading of what a PDB is actually for.

Voluntary versus involuntary disruptions

Kubernetes splits pod disruptions into two families, and a PDB touches exactly one of them.

Voluntary disruptions go through the Eviction API. That means kubectl drain, the cluster autoscaler removing an underused node, a managed node-pool rolling upgrade, or anything issuing a POST .../pods/<name>/eviction. These are deliberate actions initiated by an operator or a controller.

Involuntary disruptions are everything else: a kernel panic, hardware failure, the node running out of memory and the OOM killer taking your pod, a network partition, a preemption for a higher-priority pod. Note one entry that surprises people: kubectl delete pod is a direct delete, not an eviction, so the PDB never sees it either.

A PodDisruptionBudget is a gate in front of the Eviction API and nothing more. It cannot resurrect a pod that went down with its node, and it never schedules a replacement. If your concern is node crashes, the levers are replica count, topologySpreadConstraints and anti-affinity, not a PDB. What a budget buys you is protection against your own tooling removing too many pods at once during planned operations. It caps concurrent voluntary evictions so a drain or a scale-down cannot drop you below a floor you set.

minAvailable versus maxUnavailable

A PDB carries exactly one of two fields; they are mutually exclusive.

  • minAvailable: the number (or percentage) of pods that must stay healthy. An eviction is refused if it would push the count below this floor.
  • maxUnavailable: the number (or percentage) of pods allowed to be down at once. An eviction is refused once that many are already unavailable.

The disruption controller turns either one into a single computed value. It reads the expected pod count from the owning controller (a Deployment’s spec.replicas, for example), counts how many pods are currently healthy (healthy meaning Ready, which is why your readiness probe directly feeds this math), and derives disruptionsAllowed = currentHealthy − desiredHealthy. With minAvailable: 4, desiredHealthy is 4. With maxUnavailable: 1 on 6 replicas, desiredHealthy is 5. When disruptionsAllowed reaches 0, the Eviction API returns HTTP 429 and kubectl drain backs off and retries until the budget reopens.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: api
spec:
  minAvailable: 4          # never drop below 4 Ready pods
  selector:
    matchLabels:
      app: api
  # unhealthyPodEvictionPolicy: AlwaysAllow   # see gotchas
$ kubectl get pdb api
NAME   MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
api    4               N/A               2                     3d

ALLOWED DISRUPTIONS: 2 means two pods can be evicted right now; drain a node holding three api pods and the third eviction waits.

Prefer maxUnavailable when you scale horizontally: it stays correct as replicas changes, whereas a fixed minAvailable silently becomes stricter as you scale down and looser as you scale up.

Interaction with drains and the cluster autoscaler

kubectl drain cordons the node, then evicts its pods one by one through the Eviction API, honoring every matching PDB. If a budget is exhausted, drain does not fail; it loops, waiting for pods to reschedule elsewhere and become Ready so the budget reopens. A drain that “hangs” is almost always a PDB doing its job while replacements are not coming up (no room to schedule, or a stuck rollout).

The cluster autoscaler leans on the same API for scale-down. Before removing an underused node it checks whether every pod on it can be evicted without violating a PDB. If not, the node is marked unremovable and stays. A PDB that can never allow a disruption therefore pins nodes in place and defeats consolidation, which shows up as a cloud bill that never shrinks.

Common misconfigurations

maxUnavailable: 0 or minAvailable: 100%. This forbids all voluntary evictions. Every drain blocks forever and the autoscaler can never reclaim the node. It reads like maximum safety; it is a permanent operational deadlock.

A single replica with minAvailable: 1. The one pod can never be evicted, so any drain of its node hangs indefinitely. A PDB cannot make a single-replica workload highly available; it can only make it un-drainable. Raise the replica count first, or accept that this pod takes brief downtime during node operations.

Percentages on small replica counts. Percentages round up to the nearest whole pod. minAvailable: 50% on 3 replicas resolves to 2, leaving room for only one eviction, not the “half” you pictured. Do the arithmetic for your actual replica count before trusting the intent.

A selector that matches the wrong pods. A PDB whose selector matches nothing silently guards nothing. A selector matching pods owned by several controllers leaves the disruption controller unable to compute an expected count from a percentage or maxUnavailable, and it will refuse evictions conservatively. One PDB per workload, selector matching exactly that workload.

Broken pods you cannot evict. By default (unhealthyPodEvictionPolicy: IfHealthyBudget), a not-Ready pod can be evicted only while the budget is currently met. If you are already below the floor because pods are crashing, you cannot even evict the broken ones to drain the node, so recovery deadlocks. Setting unhealthyPodEvictionPolicy: AlwaysAllow lets not-Ready pods be evicted regardless, which is usually what you want for a smooth drain.

What to remember

A PodDisruptionBudget is a rate limiter on voluntary evictions, not an availability guarantee. It protects planned operations (drains, autoscaler scale-down, node upgrades) from removing more pods than you allow; it does nothing about node crashes, OOM kills, or kubectl delete pod. Set it with maxUnavailable on scalable workloads, never at a value that allows zero disruptions, never on a single replica, and consider AlwaysAllow so broken pods do not wedge your drains. Getting these primitives right is what makes cluster maintenance boring, which is the whole point of reliability engineering. More Kubernetes material in the kubernetes category.