Taking a node out of service without dropping traffic is one of the most practical skills the CKA exam tests. You will be asked to patch a kernel, resize a VM, or rotate hardware — and the cluster is supposed to keep running while you do it. Do it wrong and you evict pods that had nowhere to go, break a stateful workload, or leave a node quarantined after you finish. Do it right and the whole thing is three commands and a short wait.
This guide covers the node-maintenance workflow the CKA exam expects: kubectl cordon, kubectl drain, and kubectl uncordon, how the eviction path actually works, why PodDisruptionBudget exists, and the flags you will reach for when a drain refuses to complete. It pairs naturally with the CKA cluster upgrade guide, since draining a node is the first step of every rolling upgrade, and with the CKA workloads and scheduling guide, which explains where evicted pods get rescheduled. If you are still mapping out your prep, start with the CKA exam guide for 2026.
Why Node Maintenance Is Its Own Skill
Kubernetes is designed to tolerate node failure — but unplanned failure and planned maintenance are different problems. When a node dies unexpectedly, the control plane notices after a timeout and reschedules its pods elsewhere, accepting a window of disruption. Planned maintenance is your chance to avoid that window entirely: you tell the scheduler to stop placing new work on the node, gracefully move the existing work off, and only then touch the machine.
The exam frames this as a scenario: “Node worker-2 needs to be taken down for maintenance. Safely evict its workloads and mark it unschedulable.” That single sentence maps to a precise command sequence, and the grader is checking that pods actually moved and that you did not corrupt anything on the way.
The Two States a Node Can Be In
Every worker node has a scheduling status that is separate from its health. Understanding this split is the key to the whole topic.
- Schedulable — the default. The scheduler may place new pods here.
- Unschedulable (cordoned) — the node still runs its existing pods, but the scheduler will not add new ones. This is set by a taint,
node.kubernetes.io/unschedulable:NoSchedule, thatcordonapplies.
Cordoning is non-disruptive. Nothing moves. You are simply closing the door to new arrivals. Draining, by contrast, actively removes the pods that are already there.
# Inspect scheduling status — watch the STATUS column
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# worker-2 Ready,SchedulingDisabled <none> 40d v1.32.0
That SchedulingDisabled suffix is your confirmation that a node is cordoned. Memorise it — the exam expects you to verify state, not just run commands blindly.
Step 1: cordon — Stop New Pods
kubectl cordon marks a node unschedulable and does nothing else. Use it when you want to quarantine a node before draining, or when you are investigating a flaky node and want to stop it accumulating new work while you look.
kubectl cordon worker-2
# node/worker-2 cordoned
You rarely cordon as a standalone step on the exam, because kubectl drain cordons automatically as its first action. But knowing that drain implies cordon explains why a node stays SchedulingDisabled after a drain finishes — and why you must explicitly uncordon it later.
Step 2: drain — Gracefully Evict Workloads
kubectl drain is the workhorse. It cordons the node, then evicts every eligible pod so the scheduler can recreate them on other nodes. A plain drain almost never succeeds on a real node, though, because of two guard rails Kubernetes puts in your way on purpose.
kubectl drain worker-2
# node/worker-2 cordoned
# error: unable to drain node "worker-2" due to pods:
# kube-system/kube-proxy-abc12 (DaemonSet-managed),
# default/logger-xyz89 (uses emptyDir), cannot delete ...
That error is not a failure — it is Kubernetes protecting you. Two flags clear it:
--ignore-daemonsets— DaemonSet pods (likekube-proxyor a CNI agent) are managed per-node and will just be recreated on the same node, so draining them is pointless. This flag tells drain to leave them running and stop complaining. You will use it on essentially every real drain.--delete-emptydir-data— pods usingemptyDirvolumes hold data that lives and dies with the node. Draining destroys that data, so Kubernetes refuses unless you explicitly acknowledge the loss with this flag.
The command the exam actually wants is almost always:
kubectl drain worker-2 --ignore-daemonsets --delete-emptydir-data
# node/worker-2 already cordoned
# evicting pod default/web-7d9c...
# evicting pod default/api-5f8b...
# pod/web-7d9c... evicted
# node/worker-2 drained
When it returns drained, every non-DaemonSet, non-mirror pod has been evicted and (for controller-managed pods) recreated elsewhere. Verify before you touch the machine:
kubectl get pods -o wide --field-selector spec.nodeName=worker-2
# Only DaemonSet pods should remain
What drain skips automatically
You do not need flags for these — drain handles them:
| Pod type | Behaviour during drain |
|---|---|
| DaemonSet-managed | Skipped (with --ignore-daemonsets); recreated per node anyway |
| Static / mirror pods | Never evicted — they are owned by the kubelet, not the API server |
| Already-terminating pods | Left alone |
| Standalone (no controller) | Evicted and not recreated — there is nothing to recreate it |
That last row is the exam trap: a bare pod with no Deployment, ReplicaSet, or StatefulSet behind it will be deleted and gone forever. If the scenario has a lone pod you must preserve, that is a signal — either it is intentional (accept the loss) or you are meant to notice it.
Step 3: uncordon — Return the Node to Service
After the maintenance is done and the node is Ready again, put it back in rotation:
kubectl uncordon worker-2
# node/worker-2 uncordoned
kubectl get nodes worker-2
# NAME STATUS ROLES AGE VERSION
# worker-2 Ready <none> 40d v1.32.0
uncordon removes the unschedulable taint. Forgetting this step is the single most common mistake on this task — the node comes back healthy, but the scheduler still ignores it, and your cluster is quietly running at reduced capacity. Always finish the loop.
The Full Maintenance Loop
Commit this sequence to muscle memory. It is the same every time, whether you are rebooting for a kernel patch or draining before a kubeadm upgrade.
# 1. Evict workloads and cordon in one step
kubectl drain worker-2 --ignore-daemonsets --delete-emptydir-data
# 2. Confirm the node is empty of movable pods
kubectl get pods -o wide --field-selector spec.nodeName=worker-2
# 3. Do the maintenance (reboot, patch, resize) — outside kubectl
ssh worker-2 'sudo reboot'
# 4. Wait for the node to report Ready again
kubectl get nodes -w
# 5. Return it to the scheduler
kubectl uncordon worker-2
How Eviction Actually Works
kubectl drain does not delete pods directly. It calls the Eviction API (POST .../pods/<name>/eviction), which is the graceful, budget-aware path. The distinction matters for the exam and for real clusters:
- A delete removes a pod immediately, ignoring disruption budgets.
- An eviction respects
PodDisruptionBudget, honours the pod’sterminationGracePeriodSeconds, and sendsSIGTERMbeforeSIGKILL.
Because drain uses eviction, a PodDisruptionBudget can legitimately block your drain — which is exactly what it is designed to do, and the source of the most interesting question on this topic.
PodDisruptionBudgets: Protecting Availability During Drains
A PodDisruptionBudget (PDB) tells Kubernetes the minimum availability a workload must keep during voluntary disruptions like draining. It does not stop crashes or node failures — only voluntary evictions.
You express it one of two ways:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
minAvailable: 2 # keep at least 2 pods running
selector:
matchLabels:
app: web
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
spec:
maxUnavailable: 1 # allow at most 1 pod down at a time
selector:
matchLabels:
app: web
Use minAvailable when you care about a floor of running replicas; use maxUnavailable when you care about the size of the disruption. For a Deployment with 3 replicas, minAvailable: 2 and maxUnavailable: 1 mean the same thing — but on a larger fleet they diverge, so read the intent in the question.
When a PDB stalls your drain
If evicting the next pod would violate the budget, the Eviction API rejects it and kubectl drain waits, retrying:
kubectl drain worker-2 --ignore-daemonsets
# evicting pod default/web-abc
# error when evicting pod "web-abc" (will retry after 5s):
# Cannot evict pod as it would violate the pod's disruption budget.
This is correct behaviour, not a bug. The usual cause is that there is nowhere for the evicted pod to reschedule — every other node is full or cordoned — so the replica count can never recover and the budget can never be satisfied. Diagnose it:
kubectl get pdb
# NAME MIN AVAILABLE ALLOWED DISRUPTIONS AGE
# web-pdb 2 0 3d
ALLOWED DISRUPTIONS: 0 is the smoking gun — the budget has no slack. To unblock the drain you either add capacity so replacement pods can schedule, temporarily relax or delete the PDB, or (as a last resort) force deletion with --force, accepting the availability hit. On the exam, if a drain hangs on a disruption budget, first check whether the pods can actually reschedule anywhere.
Edge Cases the Exam Loves
| Situation | What to do |
|---|---|
| Drain complains about DaemonSet pods | Add --ignore-daemonsets (safe — they are per-node) |
Drain complains about emptyDir | Add --delete-emptydir-data (accepts that scratch data is lost) |
| A standalone pod (no controller) blocks drain | Add --force — it will be deleted and not recreated |
| Drain hangs retrying on a PDB | Check kubectl get pdb; add capacity or relax the budget |
| Node still ignored after maintenance | You forgot kubectl uncordon |
| Static pod won’t evict | Expected — edit or move the manifest in /etc/kubernetes/manifests on the node |
The --force flag is worth a closer look: it is required whenever a pod is not backed by a controller (ReplicationController, ReplicaSet, Job, DaemonSet, or StatefulSet). Without it, drain refuses to evict “unmanaged” pods because deleting them is irreversible. With it, you are telling Kubernetes you understand those pods will not come back.
A Worked Exam Scenario
“Worker node
node01must be taken offline for a memory upgrade. Ensure no workloads are running on it and that it will not receive new pods. A DaemonSet-based log collector runs on all nodes.”
The answer is a single command plus verification:
kubectl drain node01 --ignore-daemonsets --delete-emptydir-data
kubectl get nodes node01 # expect Ready,SchedulingDisabled
kubectl get pods -o wide --field-selector spec.nodeName=node01
The mention of a DaemonSet is the hint that --ignore-daemonsets is mandatory. If the scenario later asks you to bring the node back, that is kubectl uncordon node01. Nothing more.
Practising the Command Reflexes
Node maintenance rewards muscle memory more than understanding — under the exam clock, you want drain --ignore-daemonsets --delete-emptydir-data to come out of your fingers without thinking, and you want to instinctively verify with kubectl get nodes and a field-selector pod query. Reading the flags is not the same as typing them under a two-hour, performance-based clock while five other tasks wait.
That is the gap realistic practice closes. The Certified Kubernetes Administrator (CKA) Mock Exam Bundle runs you through performance-lab tasks in a live cluster — draining nodes, unblocking disruption budgets, and returning nodes to service — so the sequence becomes automatic before exam day. Pair it with the 30-day CKA study plan to schedule your hands-on reps, keep the kubectl cheat sheet handy for the flags, and review the CKA troubleshooting guide for when a drain refuses to finish.
Frequently Asked Questions
What is the difference between kubectl cordon and kubectl drain?
cordon marks a node unschedulable so the scheduler stops placing new pods on it, but leaves existing pods running — it is non-disruptive. drain first cordons the node and then evicts the existing pods so they reschedule elsewhere. Use cordon to quarantine a node; use drain when you need it actually empty for maintenance. Every drain includes a cordon, which is why a drained node stays SchedulingDisabled until you uncordon it.
Why does kubectl drain fail with a DaemonSet error?
DaemonSet pods are managed to run one-per-node, so evicting them is pointless — the DaemonSet controller would immediately recreate them on the same node. Kubernetes refuses to drain them unless you pass --ignore-daemonsets, which tells drain to leave DaemonSet pods running and proceed with everything else. On a real node you will almost always need this flag because system components like kube-proxy and CNI agents run as DaemonSets.
What does —delete-emptydir-data do during a drain?
emptyDir volumes store data on the node itself, so that data is permanently lost when a pod is evicted from the node. To prevent accidental data loss, kubectl drain refuses to evict pods using emptyDir unless you explicitly pass --delete-emptydir-data. Adding the flag acknowledges that the scratch data in those volumes will be discarded — which is usually fine for caches and temporary files, but confirm the workload does not rely on it.
How does a PodDisruptionBudget affect draining a node?
A PodDisruptionBudget sets the minimum availability (via minAvailable or maxUnavailable) a workload must keep during voluntary disruptions. Because kubectl drain uses the Eviction API, it honours PDBs — if evicting the next pod would breach the budget, the eviction is rejected and drain retries until it can proceed. If pods cannot reschedule anywhere (no spare capacity), the budget can never recover and the drain hangs; check kubectl get pdb for ALLOWED DISRUPTIONS: 0.
Do I need to uncordon a node after maintenance?
Yes. drain leaves the node cordoned (unschedulable) even after it becomes Ready again, so the scheduler will keep ignoring it until you run kubectl uncordon <node>. Forgetting this is the most common node-maintenance mistake — the cluster silently runs at reduced capacity because a healthy node never rejoins the scheduling pool.
How do I drain a pod that has no controller managing it?
A standalone pod (not owned by a Deployment, ReplicaSet, StatefulSet, Job, or DaemonSet) will block a drain because deleting it is irreversible — nothing will recreate it. Add the --force flag to evict such unmanaged pods anyway. Be deliberate: once forced out, that pod is gone, so make sure the scenario intends for it to be lost before you use --force.