How to Implement PodDisruptionBudgets in Kubernetes

How to Implement PodDisruptionBudgets in Kubernetes

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:

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

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

Best Practices

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.

References

Exit mobile version