Not every pod restart in Kubernetes happens because something broke. Node upgrades, cluster autoscaler scale-downs, kubectl drain during maintenance — these are all voluntary disruptions, and left unmanaged, they can take down more of your application at once than you’d ever tolerate. PodDisruptionBudgets (PDBs) exist to put guardrails around exactly this scenario. Let’s go through them properly, including how they interact with cluster autoscaling and node upgrades on AWS EKS.
Voluntary vs Involuntary Disruptions
Kubernetes draws a meaningful distinction here:
- Involuntary disruptions — hardware failure, kernel panic, node running out of resources, network partition. Nothing prevents these; they’re just handled by rescheduling.
- Voluntary disruptions — a human or controller deliberately evicts pods:
kubectl drain, cluster autoscaler scaling down a node, a rolling node upgrade, a Karpenter consolidation action.
PodDisruptionBudgets only apply to voluntary disruptions. They tell the eviction API: “don’t evict this pod if doing so would violate my availability constraint.” An involuntary node crash will still take pods down regardless of PDB — PDBs aren’t a magic force field, they’re a constraint on deliberate actions.
How PDBs Work Mechanically
When something wants to evict a pod voluntarily (via the Eviction API, which kubectl drain uses), the request is checked against any matching PDB. If evicting the pod would drop the number of available/healthy pods below the PDB’s threshold, the eviction is rejected with a 429 Too Many Requests — the caller (drain, autoscaler, etc.) is expected to retry later, once conditions change.
Basic PDB Manifest
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb
namespace: production
spec:
minAvailable: 2
selector:
matchLabels:
app: web-app
This says: no matter what voluntary disruption is attempted, at least 2 pods matching app: web-app must remain available at all times.
Alternatively, express it as a percentage or as a max unavailable:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb-percent
namespace: production
spec:
minAvailable: 80%
selector:
matchLabels:
app: web-app
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb-maxunavail
namespace: production
spec:
maxUnavailable: 1
selector:
matchLabels:
app: web-app
You can only set one of minAvailable or maxUnavailable on a given PDB, not both — they’re two ways of expressing the same constraint from different directions, and mixing them is disallowed by the API.
kubectl apply -f web-app-pdb.yaml
kubectl get pdb web-app-pdb -n production
NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
web-app-pdb 2 N/A 1 5m
ALLOWED DISRUPTIONS is the genuinely useful column here — it tells you, right now, how many pods could be voluntarily evicted before the budget is violated. If you have 3 replicas and minAvailable: 2, you’ll see 1 — exactly one pod can be drained at a time.
Choosing minAvailable vs maxUnavailable
minAvailableis more intuitive for services where you know the hard floor of capacity you need regardless of replica count (e.g., “I always need at least 2 pods serving traffic”).maxUnavailablescales more naturally with replica count changes — if you have an HPA scaling between 5 and 50 replicas,maxUnavailable: 10%adapts automatically, whereas a fixedminAvailablemight be too conservative at low scale or too loose at high scale.
For workloads under active HPA scaling, I generally lean toward percentage-based maxUnavailable.
PDBs for Single-Replica Workloads
A common trap: setting minAvailable: 1 on a Deployment that only has 1 replica. This effectively means zero disruptions are ever allowed — kubectl drain will hang forever waiting for a slot that can never open, since evicting the only pod would drop availability below the minimum.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: single-replica-pdb
namespace: production
spec:
maxUnavailable: 0
selector:
matchLabels:
app: single-instance-app
If you explicitly want to allow disruption on a single-replica app (accepting brief downtime during maintenance), don’t create a PDB for it at all, or set maxUnavailable: 1 so it’s evictable.
PDBs and Node Drains
kubectl drain ip-10-0-1-15.ec2.internal --ignore-daemonsets --delete-emptydir-data
If pods on that node are protected by a PDB that has zero allowed disruptions right now, you’ll see:
error when evicting pods/"web-app-7d8f9c6b5d-abc12" -n production (will retry after 5s):
Cannot evict pod as it would violate the pod's disruption budget.
kubectl drain retries automatically — but if the PDB can never be satisfied (e.g., minAvailable equals total replica count), the drain will hang indefinitely and require manual intervention. This is a real operational trap during planned node maintenance if PDBs aren’t sized sensibly relative to replica counts.
PDBs and Cluster Autoscaler / Karpenter on EKS
Both Cluster Autoscaler and Karpenter respect PDBs when scaling down nodes or consolidating workloads. This is genuinely important on EKS: without PDBs, a scale-down event or Karpenter consolidation could evict every replica of a service simultaneously if they happen to land on the same node being reclaimed.
Karpenter, for example, will not disrupt a node if doing so would violate a PDB on any pod running there — it waits, tries other nodes, or leaves the node alone if no safe disruption path exists. This makes PDBs a first-class safety mechanism in EKS’s most common autoscaling setups, not just a kubectl drain nicety.
PDBs for StatefulSets
The same principle applies but matters even more for stateful workloads like Kafka or Elasticsearch, where losing quorum during a rolling maintenance event can cause real outages, not just reduced capacity:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: kafka-pdb
namespace: kafka
spec:
maxUnavailable: 1
selector:
matchLabels:
app: kafka
For a 3-broker Kafka cluster with replication factor 3, maxUnavailable: 1 ensures node maintenance never takes down more than one broker at a time, preserving quorum throughout.
PDBs for Critical System Components
It’s worth applying the same discipline to platform-level components, not just application workloads. For example, protecting your Ingress controller or CoreDNS replicas from being evicted en masse:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: coredns-pdb
namespace: kube-system
spec:
minAvailable: 1
selector:
matchLabels:
k8s-app: kube-dns
Unhealthy Pod Eviction Policy (Kubernetes 1.26+)
By default, PDBs also protect unhealthy pods from eviction in older versions, which can be counterproductive — you generally want unhealthy pods evicted and replaced, not preserved. The unhealthyPodEvictionPolicy field addresses this:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-app-pdb
namespace: production
spec:
minAvailable: 2
unhealthyPodEvictionPolicy: AlwaysAllow
selector:
matchLabels:
app: web-app
AlwaysAllow permits eviction of unhealthy pods regardless of the disruption budget, which is usually the behavior you actually want — there’s little value in “protecting” a pod that’s already failing its readiness checks from being evicted and replaced.
Combining PDBs with Deployment Strategy
PDBs work alongside, not instead of, your Deployment’s RollingUpdate strategy. The rolling update strategy governs your own deployment rollouts; the PDB governs external voluntary disruptions like drains and autoscaler actions. Both should be sized consistently:
# Deployment
spec:
replicas: 6
strategy:
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
# PDB
spec:
maxUnavailable: 1
Keeping these numbers aligned avoids a confusing situation where your own rollout strategy is more conservative than what the PDB would otherwise allow an external actor to do.
Monitoring PDB Health
kubectl get pdb -A
NAMESPACE NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE
production web-app-pdb 2 N/A 1 10d
kafka kafka-pdb N/A 1 1 30d
kube-system coredns-pdb 1 N/A 2 90d
With Prometheus and kube-state-metrics:
kube_poddisruptionbudget_status_expected_pods - kube_poddisruptionbudget_status_current_healthy
Alert if ALLOWED DISRUPTIONS sits at 0 for an extended period — it means the workload is currently un-drainable, which could block node upgrades or autoscaler actions unexpectedly.
Common Mistakes
- Setting
minAvailableequal to total replica count, making the workload permanently un-drainable and silently blocking node maintenance. - Forgetting PDBs entirely on critical stateful workloads, letting a routine node scale-down take out quorum-sensitive systems like ZooKeeper or Kafka.
- Not aligning PDB thresholds with actual replica counts as they change — a PDB written for a 3-replica service doesn’t automatically adjust if you later scale to 20 replicas via HPA (percentage-based
maxUnavailablehandles this better than a fixedminAvailable). - Assuming PDBs protect against involuntary disruptions like node crashes — they don’t, and can’t.
Best Practices
- Always create a PDB for anything running fewer than 3 replicas of a critical service — these are exactly the workloads most vulnerable to a single drain event taking down all capacity.
- Prefer percentage-based
maxUnavailablefor HPA-scaled workloads. - Set
unhealthyPodEvictionPolicy: AlwaysAllowunless you have a specific reason to preserve unhealthy pods during disruptions. - Apply PDBs to platform-critical system components (CoreDNS, ingress controllers, admission webhooks), not just application workloads.
- Test PDB behavior deliberately with a
kubectl drain --dry-runor a controlled node replacement before relying on it during a real incident.
Summary
PodDisruptionBudgets are the mechanism that keeps voluntary disruptions — node drains, cluster autoscaler scale-downs, Karpenter consolidation — from taking down more of a service than you can tolerate. They don’t protect against crashes or hardware failure, only deliberate eviction actions, and on EKS they integrate directly with both Cluster Autoscaler and Karpenter’s scale-down logic. Size them thoughtfully relative to actual replica counts, watch out for the “permanently undrainable” trap with single-replica or overly conservative budgets, and extend the same discipline to critical system components, not just your own application workloads.