Kubernetes PodDisruptionBudget with Drain Examples

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; Kubernetes 1.31+ for stable unhealthyPodEvictionPolicy support
Cert prep CKA
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm. This walkthrough uses two schedulable nodes (k8s-cp and worker01); the control-plane NoSchedule taint is removed for the lab so replicas can land on both nodes during drain tests, then restored.
Privilege Normal user for kubectl when kubeconfig is available
Kubernetes permissions Create, update, get, watch, and delete Namespaces, Deployments, Pods, and PodDisruptionBudgets; exec into Pods; patch Node taints; and create Pod evictions through kubectl drain.
Scope Voluntary vs involuntary disruptions, policy/v1 selectors, minAvailable and maxUnavailable (including percentages), PDB status fields, kubectl drain interaction, readiness and healthy Pods, unhealthyPodEvictionPolicy, and how PDB relates to Deployment rollouts. Does not cover Deployment strategy design, drain flag encyclopedia, priority and preemption, cluster autoscaler, or application-level failover.
Related guides Upgrade Kubernetes cluster version
Kubernetes Pods and Pod Lifecycle

When you cordon a node for maintenance, a PodDisruptionBudget (PDB) tells the cluster how many replicated Pods must stay available during that voluntary work. In this guide you will build a three-replica Deployment, attach a PDB, and run real kubectl drain commands with both minAvailable and maxUnavailable so you can see when eviction is allowed and when drain has to wait.

IMPORTANT

This guide covers voluntary disruptions only. Those are the evictions kubectl drain sends through the eviction API. Keep these boundaries in mind:


Voluntary vs involuntary disruptions

Kubernetes treats two kinds of disruption differently:

Voluntary Involuntary
Node drain Hardware failure
Cluster upgrade draining nodes Kernel crash
Administrative eviction Network partition
Planned node maintenance Sudden node loss

A PDB limits voluntary evictions. That matters in practice because:

  • Voluntary — drain and the eviction API must respect your budget; Kubernetes can delay or retry the eviction
  • Involuntary — node failure, kernel panic, or network loss can still remove Pods; the API cannot pause a dead node the way it can delay a drain

Involuntary events still affect PDB status. They lower the observed healthy count even though the eviction API never blocked them.


Prepare a three-replica Deployment

Start with a small nginx Deployment and a readiness probe. Three replicas matter here: with minAvailable: 2 you can evict one Pod voluntarily while two stay healthy.

On my two-node lab I remove the control-plane NoSchedule taint first so Pods can schedule on both nodes during drain tests. Restore that taint in Clean up when you finish:

bash
kubectl taint nodes k8s-cp node-role.kubernetes.io/control-plane:NoSchedule-
output
node/k8s-cp untainted

Create the namespace:

bash
kubectl create ns pdb-demo
output
namespace/pdb-demo created

Apply the Deployment and a PDB that keeps at least two Pods available. The Pod template uses a lab-specific label and a topology spread constraint so three replicas land as two Pods on one node and one Pod on the other:

bash
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: pdb-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      pdb-demo: "true"
  template:
    metadata:
      labels:
        app: web
        pdb-demo: "true"
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web
            pdb-demo: "true"
      containers:
      - name: nginx
        image: nginx:1.27-alpine
        ports:
        - containerPort: 80
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 2
          periodSeconds: 3
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: pdb-demo
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web
      pdb-demo: "true"
EOF
output
deployment.apps/web created
poddisruptionbudget.policy/web-pdb created

Wait for the rollout, then confirm Pods landed on more than one node:

bash
kubectl -n pdb-demo rollout status deploy/web --timeout=90s
output
deployment "web" successfully rolled out
bash
kubectl -n pdb-demo get pods -o wide -l 'app=web,pdb-demo=true'
output
NAME                   READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
web-6569b64977-5vpcg   1/1     Running   0          8s    192.168.5.56     worker01   <none>           <none>
web-6569b64977-f4bmc   1/1     Running   0          8s    192.168.5.55     worker01   <none>           <none>
web-6569b64977-x2jnz   1/1     Running   0          8s    192.168.62.162   k8s-cp     <none>           <none>

Topology spread places two Pods on one node and one on the other. Either node can receive the pair, so calculate the drain target from the live placement before you run the first drain test:

bash
DRAIN_NODE=$(
  kubectl -n pdb-demo get pods \
    -l 'app=web,pdb-demo=true' \
    -o custom-columns='NODE:.spec.nodeName' \
    --no-headers |
  sort |
  uniq -c |
  sort -nr |
  awk 'NR == 1 {print $2}'
)

echo "$DRAIN_NODE"
output
worker01

On my lab that node was worker01. When you drain $DRAIN_NODE, two protected Pods sit on the same node and the PDB has to pace evictions.


How PDB selects Pods

Your PDB only protects Pods its selector matches. Use the same labels on the Deployment Pod template (app: web and pdb-demo: "true" here). Confirm the match count with the same label query the controller uses:

bash
kubectl -n pdb-demo get pods -l 'app=web,pdb-demo=true' --no-headers | wc -l
output
3

Three Pods match, so the budget is watching the Deployment you just created.

In policy/v1, a missing selector and an empty selector are not the same:

Selector shape What it matches
Selector omitted (Selector: <unset>) No Pods
Empty selector (selector: {}) Every Pod in the namespace

That distinction trips people up in production:

  • Selector omitted — the budget matches nothing; ALLOWED DISRUPTIONS stays at zero and Events may report no matching Pods
  • Empty selector: {} — the budget counts every Pod in the namespace, including unrelated workloads

Always set explicit matchLabels (or matchExpressions) for the workload you intend to protect.


Protect with minAvailable

The PDB already uses:

yaml
minAvailable: 2

With three healthy replicas and minAvailable: 2, Kubernetes allows one voluntary eviction at a time. Two Pods stay healthy while maintenance moves a third.

That pattern works well for a small replicated frontend:

  • You keep a floor of capacity during drain
  • Maintenance can still move forward one Pod at a time

Inspect the budget:

bash
kubectl -n pdb-demo get pdb
output
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   2               N/A               1                     25s

ALLOWED DISRUPTIONS is 1 here because:

  • Current healthy — 3 Ready Pods selected by the PDB
  • Desired healthy — 2 (from minAvailable: 2)
  • Room for eviction — 3 − 2 = 1 voluntary disruption allowed right now

Protect with maxUnavailable

Pick only one field per PDB: minAvailable or maxUnavailable, not both. Delete the current budget and recreate it with maxUnavailable: 1:

bash
kubectl -n pdb-demo delete pdb web-pdb
output
poddisruptionbudget.policy "web-pdb" deleted from pdb-demo namespace
bash
kubectl apply -f - <<'EOF'
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: pdb-demo
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: web
      pdb-demo: "true"
EOF
output
poddisruptionbudget.policy/web-pdb created
bash
kubectl -n pdb-demo get pdb
output
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   N/A             1                 1                     2s

With three healthy Pods, maxUnavailable: 1 and minAvailable: 2 give you the same practical limit: only one Pod may be unavailable from a voluntary eviction at a time. The difference shows up when you scale:

  • Integer maxUnavailable: 1 — always permits exactly one unavailable Pod, no matter how many replicas the Deployment runs
  • Percentage maxUnavailable — the permitted unavailable count grows with the desired replica count

Percentage budgets and rounding

After the integer examples, try a percentage budget. Kubernetes rounds up when it converts a percentage to a Pod count. On my lab, three Pods with maxUnavailable: 34% allowed two disruptions:

bash
kubectl apply -f - <<'EOF'
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: pdb-demo
spec:
  maxUnavailable: 34%
  selector:
    matchLabels:
      app: web
      pdb-demo: "true"
EOF
output
poddisruptionbudget.policy/web-pdb configured
bash
kubectl -n pdb-demo get pdb web-pdb
output
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   N/A             34%               2                     1s

34% of three Pods rounds up to two, so this budget is looser than the integer maxUnavailable: 1. Put minAvailable: 2 back on web-pdb for the rest of this guide. You can also keep maxUnavailable: 1; both allow one disruption while all three Pods are Ready:

bash
kubectl apply -f - <<'EOF'
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
  namespace: pdb-demo
spec:
  minAvailable: 2
  selector:
    matchLabels:
      app: web
      pdb-demo: "true"
EOF
output
poddisruptionbudget.policy/web-pdb configured

Inspect PDB status

kubectl get pdb gives you the quick columns. kubectl describe pdb shows the full controller status. Run describe when you need to see why disruptions allowed changed:

bash
kubectl -n pdb-demo describe pdb web-pdb
output
Name:           web-pdb
Namespace:      pdb-demo
Min available:  2
Selector:       app=web,pdb-demo=true
Status:
    Allowed disruptions:  1
    Current:              3
    Desired:              2
    Total:                3
Events:                   <none>

Read the status fields together:

  • Current healthy — Ready Pods selected by the PDB
  • Desired healthy — how many must stay available (minAvailable, or total minus maxUnavailable)
  • Expected Pods (Total) — Pods the budget is counting
  • Disruptions allowed — how many voluntary evictions may proceed right now

When disruptions allowed is 0, drain and other eviction API callers wait or retry until the healthy count recovers.


Test PDB with kubectl drain

Drain calls the eviction API, so it respects your PDB. Confirm all three replicas are 1/1 Ready before you drain. If one Pod is NotReady, disruptions allowed can stay at zero and drain will retry indefinitely.

Drain $DRAIN_NODE, which holds two protected Pods. For real maintenance you still need the DaemonSet and emptyDir flags shown below. Flag details are in kubectl drain, cordon, and uncordon.

bash
kubectl drain "$DRAIN_NODE" --ignore-daemonsets --delete-emptydir-data
output
node/worker01 cordoned
Warning: ignoring DaemonSet-managed Pods: calico-system/calico-node-j9m4s, calico-system/csi-node-driver-hczmm, kube-system/kube-proxy-g4df2
evicting pod pdb-demo/web-6569b64977-5vpcg
evicting pod pdb-demo/web-6569b64977-f4bmc
error when evicting pods/"web-6569b64977-f4bmc" -n "pdb-demo" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
evicting pod kube-system/metrics-server-5b58578978-wmrjc
evicting pod local-path-storage/local-path-provisioner-6d484fd799-c79sh
pod/web-6569b64977-5vpcg evicted
evicting pod pdb-demo/web-6569b64977-f4bmc
pod/web-6569b64977-f4bmc evicted
pod/local-path-provisioner-6d484fd799-c79sh evicted
pod/metrics-server-5b58578978-wmrjc evicted
node/worker01 drained

Watch what drain does under minAvailable: 2:

  • It evicts one protected Pod first
  • The second eviction is rejected until a replacement becomes Ready
  • After the replacement is Ready, drain evicts the second Pod and finishes

A PDB limits simultaneous unavailability, not how many Pods drain can eventually remove. Drain keeps retrying rejected evictions until every targeted Pod is gone.

While drain runs, ALLOWED DISRUPTIONS can move:

  • 1 before the first eviction
  • 0 while a replacement is still starting
  • 1 again after that replacement becomes Ready

The value you see immediately after drain depends on whether the final replacement is Ready yet:

bash
kubectl -n pdb-demo get pdb web-pdb
output
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   2               N/A               1                     41s

Uncordon when the node should accept work again:

bash
kubectl uncordon "$DRAIN_NODE"
output
node/worker01 uncordoned

If you try another eviction while ALLOWED DISRUPTIONS is still 0, drain prints Cannot evict pod as it would violate the pod's disruption budget and retries. That wait is the PDB doing its job. The next protected Pod leaves only after enough peers are healthy again.


Understand readiness and healthy Pods

PDB math cares about the Ready condition, not just Running or the replica count on the Deployment. If a Pod is Running but NotReady, currentHealthy drops and disruptions allowed can hit zero even when three Pod objects still exist.

To show that, recreate the Deployment so each container creates /tmp/ready and the readiness probe requires that file:

bash
kubectl apply -f - <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: pdb-demo
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
      pdb-demo: "true"
  template:
    metadata:
      labels:
        app: web
        pdb-demo: "true"
    spec:
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web
            pdb-demo: "true"
      containers:
      - name: nginx
        image: nginx:1.27-alpine
        ports:
        - containerPort: 80
        command: ["/bin/sh","-c"]
        args: ["touch /tmp/ready && nginx -g 'daemon off;'"]
        readinessProbe:
          exec:
            command: ["cat","/tmp/ready"]
          initialDelaySeconds: 1
          periodSeconds: 2
EOF

Wait for the new rollout, then confirm placement and remove the ready file on two Pods so only one stays Ready. Reuse $DRAIN_NODE from the earlier placement step, or recalculate it with the same command if you opened a new shell:

bash
kubectl -n pdb-demo rollout status deploy/web --timeout=90s
output
deployment "web" successfully rolled out
bash
mapfile -t DRAIN_NODE_PODS < <(
  kubectl -n pdb-demo get pods \
    -l 'app=web,pdb-demo=true' \
    --field-selector "spec.nodeName=${DRAIN_NODE}" \
    -o custom-columns='NAME:.metadata.name' \
    --no-headers
)

OTHER_POD=$(
  kubectl -n pdb-demo get pods \
    -l 'app=web,pdb-demo=true' \
    -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName' \
    --no-headers |
  awk -v node="$DRAIN_NODE" '$2 != node {print $1; exit}'
)

if (( ${#DRAIN_NODE_PODS[@]} < 2 )) || [[ -z "$OTHER_POD" ]]; then
  echo "This demonstration requires two web Pods on ${DRAIN_NODE} and one on the other node."
  kubectl -n pdb-demo get pods -l 'app=web,pdb-demo=true' -o wide
  exit 1
fi

UNHEALTHY_DRAIN_NODE_POD=${DRAIN_NODE_PODS[0]}
HEALTHY_DRAIN_NODE_POD=${DRAIN_NODE_PODS[1]}

kubectl -n pdb-demo exec "$UNHEALTHY_DRAIN_NODE_POD" -- rm -f /tmp/ready
kubectl -n pdb-demo exec "$OTHER_POD" -- rm -f /tmp/ready

The exec commands exit with no output when the file is removed. Here is what each variable represents:

  • $UNHEALTHY_DRAIN_NODE_POD — first Pod on $DRAIN_NODE; you remove its ready file so it becomes NotReady
  • $OTHER_POD — the Pod on the other node; you remove its ready file too
  • $HEALTHY_DRAIN_NODE_POD — second Pod on $DRAIN_NODE; you leave this one Ready

After a few probe periods:

bash
kubectl -n pdb-demo get pods -o wide -l 'app=web,pdb-demo=true'
output
NAME                   READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
web-75bf8b9856-24fj2   1/1     Running   0          15s   192.168.5.53     worker01   <none>           <none>
web-75bf8b9856-8zxml   0/1     Running   0          15s   192.168.5.54     worker01   <none>           <none>
web-75bf8b9856-kfx84   0/1     Running   0          15s   192.168.62.160   k8s-cp     <none>           <none>
bash
kubectl -n pdb-demo get pdb
output
NAME      MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
web-pdb   2               N/A               0                     104s

You still have three Pod objects, but only one is Ready. Current healthy is one and desired healthy is two, so disruptions allowed is zero. That is why replica count alone is not enough when you troubleshoot drain.


Handle unhealthy Pods during drain

By default, unhealthyPodEvictionPolicy behaves like IfHealthyBudget. When the application is already below desired healthy, Running-but-NotReady Pods cannot be evicted either. That protects a struggling app, but it can also stall node drain.

You now have one Ready and two NotReady Pods under minAvailable: 2. Drain retries both the healthy and unhealthy Pods on $DRAIN_NODE. For this experiment, limit drain to the lab Pods only:

  • --pod-selector='app=web,pdb-demo=true' — the lab-specific selector limits this drain demonstration to Pods carrying both app=web and pdb-demo=true on the selected node; the flag is not namespace-scoped, which is why the additional label is important
  • This is not a complete node-maintenance drain; in production you would drain the whole node
bash
kubectl drain "$DRAIN_NODE" --ignore-daemonsets --delete-emptydir-data --pod-selector='app=web,pdb-demo=true'
output
node/worker01 cordoned
evicting pod pdb-demo/web-75bf8b9856-8zxml
evicting pod pdb-demo/web-75bf8b9856-24fj2
error when evicting pods/"web-75bf8b9856-24fj2" -n "pdb-demo" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"web-75bf8b9856-8zxml" -n "pdb-demo" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.

Both Pods are blocked. Press Ctrl+C after you confirm that both evictions are being retried. Drain leaves the node cordoned, so uncordon before you continue:

bash
kubectl uncordon "$DRAIN_NODE"

Patch the PDB so unhealthy Pods can be evicted even when the healthy budget is already exhausted:

bash
kubectl -n pdb-demo patch pdb web-pdb --type=merge -p '{"spec":{"unhealthyPodEvictionPolicy":"AlwaysAllow"}}'
output
poddisruptionbudget.policy/web-pdb patched

Drain again with the same selector:

bash
kubectl drain "$DRAIN_NODE" --ignore-daemonsets --delete-emptydir-data --pod-selector='app=web,pdb-demo=true'
output
node/worker01 cordoned
evicting pod pdb-demo/web-75bf8b9856-8zxml
evicting pod pdb-demo/web-75bf8b9856-24fj2
error when evicting pods/"web-75bf8b9856-24fj2" -n "pdb-demo" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/web-75bf8b9856-8zxml evicted

Under AlwaysAllow, the NotReady Pod ($UNHEALTHY_DRAIN_NODE_POD) is evicted. The healthy Pod ($HEALTHY_DRAIN_NODE_POD) is still rejected because disruptions allowed is zero.

That is the trade-off you are choosing:

  • Maintenance can clear stuck NotReady Pods
  • Ready replicas still stay protected while the app is below desired healthy

After the NotReady Pod is evicted and the Ready Pod continues to be rejected, press Ctrl+C to stop the demonstration, then uncordon the node.

Uncordon when finished:

bash
kubectl uncordon "$DRAIN_NODE"

PDB and Deployment rolling updates

Pods that become unavailable during a rollout change the observed PDB status the same way a drain does. That does not mean the Deployment controller asks the PDB before deleting Pods for its own rolling update.

Keep these roles separate:

  • Deployment strategy (maxUnavailable, maxSurge) — controls how the controller replaces Pods during a rollout; see Deployments and rolling updates
  • PodDisruptionBudget — controls voluntary eviction paths such as drain and direct eviction API calls

Workload controllers such as Deployment and StatefulSet are not blocked by a PDB during their built-in rolling-update deletes. Use the PDB for maintenance and eviction API callers, not as a substitute for rollout settings.


Common PDB mistakes

If a budget seems to do nothing, or drain never finishes, check whether you hit one of these patterns:

  • Single-replica workload with minAvailable: 1 — zero voluntary disruptions allowed; drain waits forever on that Pod
  • Selector matches no Pods — Allowed disruptions stays zero and Events show no matching Pods
  • Selector matches unrelated Pods — empty selector: {} in policy/v1 matches the whole namespace
  • Treating PDB as protection from node failure — involuntary loss still happens
  • Replica count too small for the maintenance you plan — three replicas with minAvailable: 3 blocks every voluntary eviction
  • NotReady Pods leave zero disruptions allowed — fix readiness before blaming drain
  • Multiple PDBs select the same Pod — the Eviction API does not support eviction when a Pod is covered by more than one PDB. Avoid overlapping selectors except during a short transition between budgets.
  • Expecting PDB to control a Deployment rollout — set the Deployment strategy instead

Troubleshooting

Symptom Likely cause Fix
Cannot evict pod as it would violate the pod's disruption budget Disruptions allowed is zero Wait for Ready replacements, raise replicas, or temporarily loosen the budget
Drain never completes Budget requires zero voluntary disruptions, or unhealthy Pods blocked under IfHealthyBudget Check kubectl describe pdb; consider AlwaysAllow for stuck NotReady Pods
PDB shows ALLOWED DISRUPTIONS 0 with replicas present NotReady or mismatched selector Compare kubectl get pods -l … with PDB selector; fix probes
Empty selector matches unexpected Pods policy/v1 empty selector matches all Pods in the namespace Set explicit matchLabels for one workload
Both minAvailable and maxUnavailable rejected Fields are mutually exclusive Keep only one field on the PDB

Clean up

When you finish the lab, delete the demo namespace:

bash
kubectl delete ns pdb-demo
output
namespace "pdb-demo" deleted

Restore the control-plane NoSchedule taint if you removed it for the lab:

bash
kubectl taint nodes k8s-cp node-role.kubernetes.io/control-plane:NoSchedule
output
node/k8s-cp tainted

What's Next


References


Summary

A PodDisruptionBudget is the availability contract you set for voluntary evictions. With a three-replica Deployment, minAvailable: 2 or maxUnavailable: 1 leaves one disruption allowed while every selected Pod is Ready. Drain uses that headroom to evict protected Pods one at a time and wait for each replacement to become Ready before evicting the next.

The status columns matter more than the YAML alone. Read them together:

  • Current healthy and desired healthy — tell you whether the app is above the floor right now
  • Disruptions allowed — tells you whether drain or another eviction API caller can proceed
  • NotReady replicas — lower current healthy even when the Pod object count looks fine

The default IfHealthyBudget policy can leave drain stuck on unhealthy Pods until you adopt AlwaysAllow or restore readiness.

Use PDBs alongside Deployment rollout settings, not instead of them. Restore any lab taints when you are done. Next, practice full drain and cordon workflows, or walk a controlled cluster upgrade where PDBs routinely decide how fast nodes can be emptied.


Frequently Asked Questions

1. Does a PodDisruptionBudget protect against node failure?

No. A PDB only limits voluntary evictions such as drain and eviction API calls. Hardware failure, kernel crashes, and other involuntary losses can still take Pods down and count against the budget without being blocked.

2. Can I set both minAvailable and maxUnavailable on one PDB?

No. A single PodDisruptionBudget accepts only one of those fields. Pick minAvailable when you care about a floor of healthy Pods, or maxUnavailable when you care about how many may be down at once.

3. Why does ALLOWED DISRUPTIONS show zero when I still have three replicas?

PDB math uses Ready Pods, not replica count alone. If a replica is Running but NotReady, currentHealthy drops. With minAvailable set to two and only two healthy Pods, disruptions allowed becomes zero until another Pod becomes Ready.

4. Why does kubectl drain hang on a node with my app Pods?

Drain uses the eviction API, which respects PodDisruptionBudgets. When disruptions allowed is zero, eviction retries and the drain waits. Fix readiness, raise replica count, temporarily adjust the budget, or use unhealthyPodEvictionPolicy AlwaysAllow when stuck unhealthy Pods are blocking maintenance.

5. Does a PDB stop a Deployment rolling update?

No. Workload controllers are not blocked by PDB during their own rolling-update deletes. Configure maxUnavailable and maxSurge on the Deployment strategy separately. Pods that become unavailable during a rollout still change the observed PDB status.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)