Drain Blocked by a PodDisruptionBudget and a NotReady Replica
A 5-node Kubernetes 1.29 cluster runs a Deployment api with 4 replicas and a PodDisruptionBudget api-pdb with minAvailable: 3. The pods have a readiness probe on /healthz and a 30-second terminationGracePeriodSeconds. During a drain of worker-2 for a kernel update, kubectl drain worker-2 --ignore-daemonsets hangs on an api pod and returns Cannot evict pod as it would violate the pod's disruption budget. Investigation shows the api pods are spread 2/1/1 across workers, one pod on worker-1 is Running but 0/1 Ready because its readiness probe fails after a ConfigMap update. A bare pod indexer-0 on worker-2 mounts a local-path PVC. worker-2 hosts 42 pods, the kubelet has maxPods: 110, and there is no cluster autoscaler. The change window is 90 minutes, and the on-call engineer suggests kubectl drain --force --disable-eviction.
1. Why does kubectl drain refuse to evict the api pod, and what does the PDB status show?
Read the worked answer
The PDB does not count desired replicas; it counts healthy pods. api has 4 pods, but one on worker-1 is Running with 0/1 Ready, so currentHealthy is 3. desiredHealthy is minAvailable: 3. The controller computes disruptionsAllowed = currentHealthy - desiredHealthy = 3 - 3 = 0. kubectl get pdb api-pdb therefore shows ALLOWED DISRUPTIONS 0, and kubectl describe pdb api-pdb records eviction-denied events. Drain cordons the node and then uses the Eviction API for each pod; every eviction of an api pod is rejected because it would leave only 2 healthy replicas. Drain retries and appears to hang.
The NotReady pod is the trigger, not the PDB value. Had all 4 pods been Ready, allowed disruptions would be 1, and drain could evict one api pod at a time, waiting for its replacement to become Ready before the next. The second blocker is indexer-0: it is a bare pod with a local-path PVC, so it has no controller to recreate it and drain will refuse to evict it without --force. --ignore-daemonsets does not address either issue.
2. Give an ordered plan to complete the drain within 90 minutes. Explain why --disable-eviction is not the first choice.
Read the worked answer
- Abort the hung drain with Ctrl-C. The node remains cordoned. If capacity is tight, temporarily uncordon it until readiness is fixed, then cordon again; otherwise evicted pods may have nowhere to schedule.
- Diagnose the NotReady pod:
kubectl describe pod <pod>,kubectl logs <pod> --previous, andkubectl get cm api-config -o yaml. If a bad ConfigMap update broke the probe, restore the ConfigMap andkubectl rollout restart deploy/api, orkubectl rollout undo deploy/api. Wait forkubectl rollout status deploy/apiand confirm 4/4 Ready. - Confirm
kubectl get pdb api-pdbnow shows 1 allowed disruption andkubectl get pods -o wideshows allapipods Ready. - Check capacity on the remaining nodes with
kubectl describe nodesand verify that worker-2's non-DaemonSet pods fit elsewhere. With no autoscaler, scale down non-critical workloads or add a node if they do not. - Handle
indexer-0: inspectkubectl get pod indexer-0 -o yaml. If it has no ownerReference and its PVC is node-local, decide explicitly whether to back up and recreate it on another node or accept downtime. Thenkubectl drain worker-2 --ignore-daemonsets --delete-emptydir-data --timeout=20m. - If eviction is still blocked, scale
apito 5 replicas so allowed disruptions becomes 2, or temporarily lower the PDB tominAvailable: 2with change approval.--disable-evictionmakes drain delete pods directly, bypassing the PDB entirely. That can remove the last healthy replica and turn a maintenance task into an outage; it is a last resort with explicit risk acceptance, not a first move.
3. What changes would you make so the next kernel update does not need a war room?
Read the worked answer
Fix the readiness design first. A readiness probe that depends on a mutable ConfigMap turns a configuration change into a cluster-wide availability event. Use immutable ConfigMaps with a content hash in the name, so a bad update produces a new ReplicaSet and a controlled rollout instead of breaking running pods. Keep the PDB but make it match the replica count: with 4 replicas, maxUnavailable: 1 is equivalent to minAvailable: 3 and is less brittle if the replica count changes.
Give indexer-0 a controller and portable storage. A bare pod with a local-path PVC is inherently undrainable without force. Convert it to a StatefulSet backed by replicated storage, or document a tested migration procedure with backups. Add topology spread constraints or anti-affinity so all api replicas cannot concentrate on one node.
Add operational safeguards: alert when kube_poddisruptionbudget_status_disruptions_allowed is 0 and when any critical pod is NotReady; keep at least one node of spare capacity or install a cluster autoscaler; run kubectl drain --dry-run=server as a pre-check in the runbook; and rehearse the drain in staging. Finally, separate DaemonSet and local-storage pods from the eviction path explicitly in the runbook so the on-call engineer does not discover them at 02:00.