Kubernetes Pod Disruption Budgets: Protecting Availability During Planned Disruptions

Content authorBy Irina BaghdyanPublished onReading time16 min read
Modern smart factory with automated industrial machinery, production equipment, and digital manufacturing systems

Routine Kubernetes maintenance should not put business-critical services at risk. Yet poorly configured Pod Disruption Budgets (PDBs) can delay upgrades, block infrastructure maintenance, and increase the risk of service interruptions. For organizations running production workloads, managing PDBs is not simply a Kubernetes configuration task. It is part of maintaining service availability, operational resilience, and predictable infrastructure operations. This guide explains how to configure, validate, and monitor PDBs to support reliable Kubernetes maintenance.

When routine maintenance stops being routine

A Kubernetes pod disruption budget decides whether your next node drain finishes in minutes or hangs until someone force-deletes pods at midnight. That's the whole stake. Most teams write one budget per service during an availability review, then never look at it again, and the gap between what the budget says and what the workload actually needs grows quietly until an upgrade exposes it.

Ketankumar Jani put the problem plainly in a Cloud Native Now field guide: PodDisruptionBudgets are "simple to write, easy to misuse, and cause more 'why won't this node drain?' confusions than any other Kubernetes primitive." He's right, and the cost lands on the maintenance window. What follows is how to set a Kubernetes pod disruption budget that protects serving capacity and still lets your platform team upgrade on schedule.

Kubernetes pod disruption budget basics

A Kubernetes pod disruption budget (PDB) is a policy object that tells the API server to refuse evictions that would push a workload below the availability threshold you declared. It has a label selector, plus either a floor (minAvailable) or a ceiling (maxUnavailable) to express minavailable vs maxunavailable. Nothing else.

The enforcement point is the Eviction API. When a tool creates an Eviction object, the API server answers with 200 OK if the eviction is allowed or 429 Too Many Requests when the budget won't permit it right now. A 500 response indicates misconfiguration, such as two budgets selecting the same pod. kubectl drain is a client of that API, which is why drains retry.

Three things decide whether an eviction goes through. The budget counts pods that are currently healthy, which means ready according to your readiness probe, against the threshold you set. It derives the total expected pod count from the owning controller, so controllers with a scale subresource work with percentages, while bare pods or operator-managed resources without a scale subresource only accept an integer minAvailable. And the selector has to match the right pods, which is where stale labels do real damage. Because availability is measured against the expected pod count, replicas that are already unavailable use up the allowance, so the number of evictions a budget permits changes with the workload’s current health.

This matters for every operation that drains a node:

  • Cluster and node pool upgrades, where the provider cordons and drains nodes in sequence

  • Cluster Autoscaler scale-down, which retries evictions for up to 2 minutes per node before giving up and keeping the node

  • Manual drains for hardware replacement or kernel patching

What PDBs cannot protect

Budgets apply to voluntary disruptions only. A node that loses power or a kernel panic never asks permission. Node-pressure eviction performed by the kubelet bypasses the Eviction API entirely, so a budget that reads minAvailable: 9 offers exactly zero protection when the node runs out of memory.

Direct deletion bypasses it too. kubectl delete pod is a DELETE on the pod resource, not an Eviction, so any automation that deletes pods instead of evicting them walks straight through the budget. Controller-managed rollouts are a separate system as well, because a Deployment's own maxUnavailable and maxSurge settings govern rolling updates, and the budget doesn't pace them.

A PDB is an eviction-control policy, not a pod scheduling constraint. It can’t create replicas you never ran and can't fix a readiness probe that reports healthy before the application warms its cache. Replication and probe accuracy still carry that weight. The budget's job is narrow: pace planned work so the application keeps serving while it happens.

That makes a PDB one component of Kubernetes availability engineering. It has to be used alongside replica management, topology spreading, and maintenance validation, because replica placement, application readiness, infrastructure capacity, and failure-domain redundancy all determine whether maintenance is actually safe.

Choose the right budget

Start from the number that matters to the business, which is minimum serving capacity under load. If your API needs six pods to hold latency targets at peak and offers two for maintenance when all eight pods are ready. Working backward from replica count instead of capacity produces budgets that look reasonable and block drains.

Then check four things before you write the manifest. How many replicas does the workload actually run at its autoscaler minimum? How many nodes do you expect to drain at once during an upgrade? How long does a replacement pod take to become ready, which includes image pull and warm-up? And what happens to the application when one instance leaves, which is a different question for a stateless API than for a broker holding partition leadership.

A Kubernetes pod disruption budget that allows zero disruptions is a real choice, as long as you've decided it deliberately and know who gets paged when the upgrade stalls.

minAvailable vs maxUnavailable

The two fields express the same constraint from opposite directions, and you can only set one. The choice in minAvailable vs maxUnavailable comes down to whether your replica count moves.

Percentages round up in both directions, which produces results that surprise people. With 7 pods and minAvailable: "50%"4 pods must stay available. The engineering team at Statsig hit the sharper edge of this: their minAvailable: 80% policy meant services running fewer than five pods were locked against any disruption at all, because the rounded floor equaled the total pod count. Switching to maxUnavailable: 20% meant at least one pod could be evicted whenever the rest of the workload was healthy.

# Fixed replica count, explicit floorapiVersion: policy/v1kind: PodDisruptionBudgetmetadata:  name: checkout-api-pdb  namespace: productionspec:  minAvailable: 6  selector:    matchLabels:      app: checkout-api
# Replica count changes with loadapiVersion: policy/v1kind: PodDisruptionBudgetmetadata:  name: checkout-api-pdb  namespace: productionspec:  maxUnavailable: 1  selector:    matchLabels:      app: checkout-api

In minAvailable vs maxUnavailable terms: an integer minAvailable pins the floor to a capacity number you reasoned about, while maxUnavailable scales with the controller and permits a fixed number of evictions as long as the rest of the workload is healthy. For autoscaled workloads, choose based on the minimum serving capacity you have validated, the HPA’s minimum and maximum, and how much maintenance disruption you can accept. maxUnavailable suits workloads whose replica count moves widely, while an integer minAvailable fits a workload with a validated minimum capacity, provided the HPA floor leaves room above it.

Need IT Support?

Book a free consultation with ABS Technologies experts we'll help you find the right managed IT, cloud, or security solution for your business.

Book a Free Consultation →

Kubernetes PDB and replicas

Set minAvailable equal to the replica count and you've forbidden voluntary eviction. The Kubernetes documentation is explicit that draining a node running such a pod never completes, and that this is permitted behavior. The same applies to maxUnavailable: 0 and minAvailable: "100%" as minAvailable vs maxUnavailable configurations.

The harder failure is the budget that's fine most of the time. A Kubernetes PDB with minAvailable: 3 against a Deployment that runs 5 replicas looks generous until the HPA scales to its minimum of 3 overnight, which is when the upgrade automation runs. Now allowed disruptions is zero, and the drain waits.

Temporary scale changes do the same thing. Someone scales a service down to free capacity during an incident, or a canary rollout holds replicas at a reduced count. Each of these makes a reasonable Kubernetes PDB unsatisfiable without anyone editing the budget, which is why replica-to-budget drift belongs in monitoring.

Stateless workload example

Take a Deployment with 8 replicas behind an HPA with a minimum of 7, where load testing showed 6 pods hold your p99 latency target at peak. The HPA floor sits one pod above the capacity requirement on purpose, so a single eviction never takes serving capacity below 6. Pods carry the label app: catalog-api, and the Kubernetes PDB selector matches that label exactly.

apiVersion: policy/v1kind: PodDisruptionBudgetmetadata:  name: catalog-api-pdb  namespace: productionspec:  maxUnavailable: 1  selector:    matchLabels:      app: catalog-api  unhealthyPodEvictionPolicy: AlwaysAllow

With all 8 pods ready, kubectl get pdb catalog-api-pdb -n production reports ALLOWED DISRUPTIONS of 1. At the autoscaler floor of 7, it still reports 1, which is the point of using the ceiling instead of the floor here. Evicting that one pod leaves 6 ready pods, which still meets the capacity requirement, and a drain that touches several nodes proceeds one pod at a time. The allowance holds only while the rest of the workload is healthy: if one replica is already unavailable, allowed disruptions drop to 0, and the drain waits, which is the budget protecting the 6-pod floor. If you would rather keep the HPA minimum at 6, use minAvailable: 6 instead and accept that evictions will be blocked whenever no spare capacity exists.

The unhealthyPodEvictionPolicy field matters more than its obscurity suggests. Introduced as alpha in Kubernetes 1.26 and enabled by default from 1.27, AlwaysAllow lets the API server evict running-but-not-ready pods instead of waiting for them to recover. Kubernetes documentation now recommends AlwaysAllow precisely so misbehaving applications don't deadlock a node drain.

Stateful workload example

For quorum systems, replica count is the wrong starting number. A 5-member etcd cluster tolerates two member failures because quorum is three, but sizing a budget at exactly quorum leaves no room for leader election or the involuntary failure that arrives mid-drain. Set minAvailable: 4 on a 5-member set and you're pacing maintenance one member at a time with a margin.

Three other behaviors shape the number. Storage attachment takes time, because a replacement pod can't start until its volume detaches from the old node and attaches to the new one. Termination behavior matters too, since a broker that must transfer partition leadership needs a terminationGracePeriodSeconds long enough to finish, and the Eviction API honors that grace period. Failover itself consumes availability, so the practical rule is that a system which resyncs on every eviction needs a floor above raw quorum.

Single-replica stateful workloads deserve an explicit decision. maxUnavailable: 0 blocks every drain, which is honest if the data genuinely can't move, and dangerous if nobody told the platform team. Operator-managed systems ship their own budgets, and the ScyllaDB Operator, for example, creates one PDB per datacenter with maxUnavailable: 1 as its Kubernetes PDB. Adding your own on top produces overlapping selectors, and the API server refuses evictions outright when two budgets reference the same pod.

Need IT Support?

Book a free consultation with ABS Technologies experts we'll help you find the right managed IT, cloud, or security solution for your business.

Book a Free Consultation →

Account for pod placement

High-tech neon infographic illustrating the Kubernetes pod scheduling process with glowing diagrams, icons, and vibrant color accents.

Budgets count pods without placing them. Eight replicas on one node satisfy minAvailable: 6 perfectly right up until that node goes away, which is why placement is a separate control that has to be configured alongside the Kubernetes PDB.

Topology spread constraints are the mechanism. You define a topology key, a max skew, and a policy for what the scheduler does when the constraint can't be met, and AWS prescriptive guidance recommends combining zone and node spreading for exactly this reason. Pod anti-affinity does similar work with coarser control, and it predates spread constraints, which the Kubernetes project introduced as a more expressive Pod-level API in 1.18.

Strict placement rules and tight budgets interact badly, and the failure mode is a replacement pod stuck in Pending while the drain waits for it. The usual causes:

  • A hard spread rule with whenUnsatisfiable: DoNotSchedule when the target zone has no spare node capacity

  • Node taints that the replacement pod doesn't tolerate, so the scheduler has fewer candidate nodes than you assumed

  • Zone-locked storage, because an EBS volume is a single-zone resource and a pod gets stuck Pending when no node exists in the volume's zone

For multi-zone clusters, Datadog's debugging guidance points at WaitForFirstConsumer as the storage class binding mode that avoids provisioning a volume in a zone the scheduler can't use. Set it before you discover the problem during an upgrade.

Test maintenance safely

Validate the Kubernetes PDB in pre-production against the same replica counts and node sizes you run in production, because a budget tested at 10 replicas tells you nothing about its behavior at the autoscaler floor of 3.

Run the sequence in order:

  1. Read the status. kubectl get pdb -n shows ALLOWED DISRUPTIONS and CURRENT. If allowed disruptions is zero at steady state, stop and fix the budget before testing anything else.

  2. Evict one pod through the API. Confirm the eviction returns 200 and the pod passes readiness. Time it.

  3. Scale the workload to its HPA minimum and read the status again. This is the test that catches the minAvailable vs maxUnavailable mistake, because the floor-based budget drops to zero allowed disruptions while the ceiling-based one doesn't.

  4. Drain a test node with kubectl drain --ignore-daemonsets and watch whether replacements land or stall.

  5. Record recovery time from eviction to ready, and compare it against your maintenance window divided by node count.

That last number is the one to bring to the upgrade review. As a simplified sequential estimate, if a replacement takes 90 seconds and your budget permits one eviction at a time across 40 pods, the drain portion alone runs about an hour (40 × 90 seconds). Treat that as a planning baseline, not an expected maintenance duration. Total drain time also depends on pod placement, concurrent evictions across workloads, scheduling, termination grace periods, and how quickly replacements become ready. Passing this sequence shows the budget behaves as intended, but it doesn’t guarantee safe maintenance on its own. Replica placement, infrastructure capacity, and failure-domain redundancy have to be validated alongside it.

Troubleshoot stuck disruptions

When a drain hangs, work the list in order.

Find the blocking Kubernetes pod disruption budget first. Google's documentation for GKE includes a JSONPath query that lists every PDB with disruptionsAllowed at zero across all namespaces, and it works on any conformant cluster. Then run kubectl describe pdb on the result and compare status.currentHealthy against status.desiredHealthy.

If currentHealthy is lower than expected, the problem is upstream of the budget. Check whether the selector still matches the pods you think it does, because a label change during a refactor leaves a budget pointing at nothing or at the wrong set. Check for pods in Pending and read their events, since scheduling failures name the constraint that blocked them: insufficient CPU or a taint without a toleration.

For autoscaler-driven drains, read the autoscaler's own reasoning. GKE emits no.scale.down.node.pod.not.enough.pdb and names the blocking pod in the event, which saves you the correlation work. Finally, check whether the stuck pods are running but unready, because that's the case unhealthyPodEvictionPolicy: AlwaysAllow was designed to unblock, and leaving the default in place means a crashlooping application can hold a node hostage.

Know your platform's escape hatch before you need it. GKE respects budgets and grace periods during node upgrades for a maximum of one hour and then force-evicts. Azure Kubernetes Service takes a different route by quarantining the undrainable node with a Cordon behavior and leaves it to you to resolve. Neither outcome is one you want to meet unprepared.

Monitor Kubernetes PDB health

Budgets drift because workloads change and budgets don't. Monitoring closes that gap, and kube-state-metrics already exposes what you need: current healthy, desired healthy, expected pods, and pod disruptions allowed as stable gauges per budget.

Alert on four conditions. Disruptions allowed sitting at zero for 30 minutes means a drain touching that workload would block, which matters most ahead of planned maintenance. A budget that hasn’t permitted a single disruption in 24 hours is a condition to investigate, not proof of misconfiguration. Zero allowed disruptions can be deliberate, particularly for sensitive stateful workloads, or it can reflect degraded application health. Alert when it persists unexpectedly, and judge it against the workload’s criticality and your approved availability policy.

Current healthy below desired healthy for 10 minutes points to an application problem the budget is now amplifying. And repeated autoscaler scale-down failures attributed to a specific Kubernetes PDB mean you're paying for nodes you can't release.

Two reviews belong in the operating rhythm. Run a coverage check that lists critical workloads with no Kubernetes PDB at all and budgets whose selectors match zero pods, both of which are silent failures. Then make a PDB status check a gate in the maintenance runbook, so nobody starts a node pool upgrade without knowing which budgets will block it.

Ownership has to be explicit. The service team sets the availability threshold because they know the capacity math, and the platform team owns the policy that validates it. A ValidatingAdmissionPolicy in Common Expression Language (CEL) can reject mistakes that are visible in the PDB itself or in parameters you supply, such as a minAvailable above a replica count passed in as a parameter. It can’t look up a Deployment’s live replica count on its own. To compare budgets against live replicas, use a custom admission webhook or a GitOps/CI validation step, and keep the drift check from the monitoring section running between deployments. Either way, catching these mistakes at write time beats discovering them halfway through a drain.

Build an implementation roadmap

Start with an inventory. List every Kubernetes pod disruption budget in the cluster alongside its selector and its current allowed disruptions, then list the critical workloads that have no budget at all. That single table surfaces more risk than a week of discussion, because it shows both the budgets that block maintenance and the services running without protection.

Rank what you find by workload criticality. A payment service with a budget that permits zero evictions outranks a batch worker with a stale selector, even though the selector takes two minutes to correct. Then test the prioritized corrections against the validation sequence above, in pre-production, at the autoscaler floor.

Keep Kubernetes maintenance predictable. ABS Technologies helps organizations assess Kubernetes availability risks, optimize disruption policies, and establish proactive monitoring and maintenance practices through managed cloud and DevOps services. If your upgrades keep stalling or you don’t know which budgets would block the next one, talk to our team about assessing current coverage and building a roadmap you can execute.

Need IT Support?

Book a free consultation with ABS Technologies experts we'll help you find the right managed IT, cloud, or security solution for your business.

Book a Free Consultation →

The API server rejects the eviction with a 500 response because overlapping budgets create an ambiguous availability constraint. Check each budget’s label selector with `kubectl describe pdb` and remove the overlap or narrow one selector. Operator-managed workloads can already have a budget, so inspect existing policies before adding another.

Kubernetes rounds percentage thresholds up, which can leave fewer disruptions than expected. For example, `minAvailable: "50%"` on seven pods requires four healthy pods. Test percentage settings at the workload’s lowest replica count, because a rounded floor can equal the total and block every voluntary eviction.

Use `unhealthyPodEvictionPolicy: AlwaysAllow` when running but unready pods could otherwise hold a drain indefinitely. The setting lets the Eviction API remove those pods while they fail readiness checks. It doesn’t protect against node failure or repair an application that reports readiness before it can serve traffic.

A replacement pod can remain Pending when scheduling rules leave no eligible node. Check hard topology spread constraints, taints without matching tolerations, available CPU, and zone-bound volumes. For multi-zone storage, `WaitForFirstConsumer` helps provision volumes in a zone the scheduler can use, which reduces placement failures during maintenance.

Inventory each Kubernetes pod disruption budget, its selector, allowed disruptions, and owning workload before the upgrade. Test evictions at the autoscaler minimum, then measure replacement readiness time against the maintenance window. ABS Technologies can assess coverage and help plan the review. Contact ABS at https://abs.am/contacts.

Schedule a Meeting

Book a time that works best for you and let's discuss your project needs.

You Might Also Like

Discover more insights and articles

Automated manufacturing production line with industrial machinery, conveyor systems, and digital monitoring equipment

GitHub Actions Reusable Workflows: Standardize Delivery Without Blocking Teams

Copy-pasted pipelines drift. Across a large GitHub organization, every duplicated workflow becomes a separate place to patch, audit, and get wrong. Replace them with a centrally maintained set of GitHub Actions reusable workflows, structured so teams can adopt them without friction. Pair that structure with a governance model that keeps shadow pipelines from creeping back in.

Industrial automation control system with connected electrical components and data infrastructure

Software Supply Chain Security: Controls That Protect Code from Commit to Production

Every step between a developer's commit and a running production workload is a place an attacker can intervene: a poisoned dependency, a tampered build, a stolen pipeline credential, an unsigned image. Mapping these attack paths end to end shows which control interrupts each one and where a single control covers several paths at once. Because funding every control at once is rarely possible, a ranking method then orders the work by the risk each control removes, so limited budgets go first to the gaps attackers are most likely to use.

Business team viewing a digital technology network and interconnected data systems in a modern corporate environment

GitHub Actions Self-Hosted Runners: Secure Architecture and Autoscaling Patterns

Self-hosted GitHub Actions runners give teams control over cost and environment, but they also put build infrastructure inside the trust boundary, where a single compromised workflow can reach internal systems. A defensible architecture starts by naming the threats runners introduce, then applies controls that contain them: ephemeral runners, default-deny network isolation, and short-lived workload identity in place of stored cloud credentials. Scaling models sized to real demand keep capacity honest rather than padded. A cost model and a migration path off persistent runners complete the case, laid out so engineering and finance can review and approve it in a single meeting.

Futuristic digital system with connected components and data flows representing document workflow automation

HashiCorp Vault Secrets Management: Architecture, Adoption, and Operational Reality

The real shift in secrets management comes from long-lived, standing credentials to workload identity that issues short-lived ones on demand. HashiCorp Vault secrets management is one route to that shift, but it fits only some environments. This guide lays out the difference: where Vault earns its operational cost, when a simpler managed store is the better call, and how to sequence adoption without a disruptive cutover.