kubectl drain: Evict Pods Before Node Work
kubectl drain marks a node unschedulable and evicts its pods so the node can be upgraded or removed safely.
Before patching or terminating a node, drain moves the workloads off it, honoring PodDisruptionBudgets so availability is preserved.
What it does
kubectl drain first cordons the node (no new pods schedule there), then evicts the running pods via the Eviction API, which respects PodDisruptionBudgets. DaemonSet pods and bare (unmanaged) pods need explicit flags because drain refuses to evict them by default.
Common usage
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data
# include unmanaged pods and set a timeout
kubectl drain node-1 --ignore-daemonsets --force --timeout=120s
# put it back in service afterward
kubectl uncordon node-1Options
| Flag | What it does |
|---|---|
| --ignore-daemonsets | Proceed even though DaemonSet pods stay |
| --delete-emptydir-data | Allow evicting pods that use emptyDir volumes |
| --force | Evict pods not managed by a controller |
| --grace-period=<s> | Override pod termination grace period |
| --timeout=<dur> | Give up if eviction is not done in time |
| --disable-eviction | Delete pods directly, bypassing the Eviction API/PDBs |
In CI
Use --timeout so an undrainable node fails the automation instead of hanging forever. Avoid --disable-eviction in shared clusters; it ignores PodDisruptionBudgets and can take a service below its minimum. Always pair drain with kubectl uncordon on success or rollback.
Common errors in CI
"error: cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore)" is the most common; add the flag. "Cannot evict pod as it would violate the pod\'s disruption budget" means a PDB has no allowed disruptions right now; wait, scale up first, or rethink the budget. "cannot delete Pods with local storage (use --delete-emptydir-data ...)" means emptyDir pods need that flag.
Using this in CI
A runner has no kubeconfig, no cached context, and no interactive auth. Every kubectl invocation in CI needs the context supplied explicitly, and most confusing CI failures here are the command running against the wrong cluster or no cluster at all.
# never rely on the ambient context on a runner
kubectl --context "$KUBE_CONTEXT" -n "$NAMESPACE" get pods
# confirm what you are actually connected to before mutating anything
kubectl config current-context
kubectl cluster-info
# fail fast instead of hanging on an unreachable API server
kubectl --request-timeout=30s get nodes