Cordon, Drain and Uncordon Kubernetes Nodes

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA
Lab environment kubeadm cluster with worker01 (192.168.56.110) and cp-3 (192.168.56.113, control-plane NoSchedule taint removed for lab scheduling) — install Kubernetes with kubeadm
Privilege Cluster administrator for the complete lab. Equivalent custom RBAC must permit patching Nodes, managing the drain-lab resources and PDB, creating Pod evictions, and deleting Pods.
Scope kubectl cordon, drain, and uncordon for node maintenance: pre-flight inspection, DaemonSet and emptyDir flags, unmanaged Pods, PodDisruptionBudget interaction, and post-maintenance verification. Does not cover permanent node removal, kubeadm reset, Node NotReady recovery, or PDB authoring in depth.
Related guides Kubernetes Pods

When you patch a worker, replace a disk, or test kernel settings, you need the node to stop accepting new Pods and shed existing workloads without surprising outages. kubectl cordon, kubectl drain, and kubectl uncordon are the standard maintenance trio. This guide walks through each command on a kubeadm lab cluster with a Deployment, DaemonSet agents, emptyDir storage, a bare Pod, and a PodDisruptionBudget.

Permanent removal—kubectl delete node, kubeadm reset, and rejoin—is covered in add, remove and rejoin cluster nodes.


Cordon vs Drain vs Uncordon

Command Effect
kubectl cordon <node> Sets spec.unschedulable: true — prevents ordinary scheduler placements on the node
kubectl drain <node> Cordons the node and evicts eligible Pods through the eviction API
kubectl uncordon <node> Clears unschedulable — the node can receive new Pods again

Scheduler and placement exceptions:

  • Pods that tolerate node.kubernetes.io/unschedulable, including DaemonSet Pods, can still schedule on a cordoned node
  • Directly setting spec.nodeName bypasses the scheduler

How each command affects running workloads:

  • Cordon does not stop or move running Pods
  • Drain evicts controller-managed workloads so Deployments and StatefulSets recreate replicas on other nodes
  • Uncordon does not rebalance Pods that already moved; it only reopens scheduling

Prepare the Drain Lab

This lab needs at least two schedulable nodes:

  • worker01 is the worker being drained
  • cp-3 is the alternate destination. Its control-plane NoSchedule taint is removed only for this lab

Do not drain cp-3. In a normal environment, use a second worker instead of scheduling application Pods on a control-plane node.

The control-plane role label and control-plane scheduling taint are separate properties. Removing the taint makes cp-3 schedulable while it keeps the control-plane role label.

Make cp-3 schedulable:

bash
kubectl taint node cp-3 node-role.kubernetes.io/control-plane:NoSchedule-

Sample output:

output
node/cp-3 untainted

Save the manifest below as drain-lab.yaml. It creates the drain-lab namespace, a three-replica Deployment, and a PodDisruptionBudget that starts with minAvailable: 3 so the PDB demonstration blocks drain until you relax it.

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: drain-lab
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: drain-demo
  namespace: drain-lab
spec:
  replicas: 3
  selector:
    matchLabels:
      app: drain-demo
  template:
    metadata:
      labels:
        app: drain-demo
    spec:
      containers:
      - name: app
        image: registry.k8s.io/pause:3.10
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: drain-demo-pdb
  namespace: drain-lab
spec:
  minAvailable: 3
  selector:
    matchLabels:
      app: drain-demo

Cordon cp-3 before applying the lab. This provides deterministic initial placement without directly deleting the Deployment Pods:

bash
FALLBACK_NODE=cp-3

kubectl cordon "$FALLBACK_NODE"

Then apply the manifest and wait:

bash
kubectl apply -f drain-lab.yaml
bash
kubectl rollout status deployment/drain-demo -n drain-lab --timeout=120s

Sample output:

output
Waiting for deployment "drain-demo" rollout to finish: 0 of 3 updated replicas are available...
Waiting for deployment "drain-demo" rollout to finish: 1 of 3 updated replicas are available...
Waiting for deployment "drain-demo" rollout to finish: 2 of 3 updated replicas are available...
deployment "drain-demo" successfully rolled out

Derive and verify the target:

bash
TARGET_NODE=$(kubectl get pod -n drain-lab \
  -l app=drain-demo \
  -o jsonpath='{.items[0].spec.nodeName}')
bash
kubectl get pods -n drain-lab \
  -l app=drain-demo \
  -o wide

Sample output:

output
NAME                         READY   STATUS    RESTARTS   AGE   IP             NODE       NOMINATED NODE   READINESS GATES
drain-demo-8857dd8cc-4k6vd   1/1     Running   0          <age> 192.168.5.11   worker01   <none>           <none>
drain-demo-8857dd8cc-jpkbv   1/1     Running   0          <age> 192.168.5.9    worker01   <none>           <none>
drain-demo-8857dd8cc-xnsxs   1/1     Running   0          <age> 192.168.5.10   worker01   <none>           <none>

All three replicas should be on worker01. Now reopen the alternate destination:

bash
kubectl uncordon "$FALLBACK_NODE"
bash
echo "$TARGET_NODE"

Sample output:

output
worker01

With this placement:

  • Initial replicas run on worker01
  • cp-3 is available when worker01 is drained
  • After minAvailable changes to 2, replacements can become Ready on cp-3
  • Drain can retry and eventually evict all three replicas while respecting the PDB. Kubernetes documents that rejected eviction requests are retried until the Pods terminate or the configured timeout is reached

Direct Pod deletion does not use the Eviction API and therefore does not provide the PDB protection this article teaches. Kubernetes recommends eviction-aware tools such as kubectl drain when the PDB must be respected.

Pin the emptyDir and unmanaged Pods on that node with spec.nodeName:

bash
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
  namespace: drain-lab
spec:
  nodeName: $TARGET_NODE
  containers:
  - name: app
    image: registry.k8s.io/pause:3.10
    volumeMounts:
    - name: scratch
      mountPath: /scratch
  volumes:
  - name: scratch
    emptyDir: {}
---
apiVersion: v1
kind: Pod
metadata:
  name: unmanaged-pod
  namespace: drain-lab
spec:
  nodeName: $TARGET_NODE
  containers:
  - name: app
    image: registry.k8s.io/pause:3.10
  restartPolicy: Never
EOF

Wait until both directly assigned Pods report Ready:

bash
kubectl wait -n drain-lab \
  --for=condition=Ready \
  pod/emptydir-pod pod/unmanaged-pod \
  --timeout=60s

Sample output:

output
pod/emptydir-pod condition met
pod/unmanaged-pod condition met

kubectl wait supports multiple resources and waits until the condition is satisfied for each one.

Confirm the PDB reports zero allowed disruptions while minAvailable: 3 matches the replica count:

bash
kubectl wait -n drain-lab \
  --for=jsonpath='{.status.disruptionsAllowed}'=0 \
  pdb/drain-demo-pdb \
  --timeout=60s

Sample output:

output
poddisruptionbudget.policy/drain-demo-pdb condition met

kubectl wait supports waiting for an exact JSONPath value.

bash
kubectl get pdb -n drain-lab

Sample output:

output
NAME             MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
drain-demo-pdb   3               N/A               0                     <age>

Inspect and Cordon the Node

List what is running on the node you plan to drain:

bash
kubectl get pods -A \
  --field-selector "spec.nodeName=$TARGET_NODE" \
  -o wide

Sample output (trimmed to lab and node-agent Pods):

output
NAMESPACE       NAME                        READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
calico-system   calico-node-gdtn2           1/1     Running   0          <age> 192.168.56.110   worker01   <none>           <none>
calico-system   csi-node-driver-fj49m       2/2     Running   0          <age> 192.168.5.0      worker01   <none>           <none>
drain-lab       drain-demo-8857dd8cc-4k6vd   1/1     Running   0          <age> 192.168.5.11     worker01   <none>           <none>
drain-lab       drain-demo-8857dd8cc-jpkbv   1/1     Running   0          <age> 192.168.5.9      worker01   <none>           <none>
drain-lab       drain-demo-8857dd8cc-xnsxs   1/1     Running   0          <age> 192.168.5.10     worker01   <none>           <none>
drain-lab       emptydir-pod                 1/1     Running   0          <age> 192.168.5.12     worker01   <none>           <none>
drain-lab       unmanaged-pod                1/1     Running   0          <age> 192.168.5.13     worker01   <none>           <none>
kube-system     kube-proxy-gg8g2            1/1     Running   0          <age> 192.168.56.110   worker01   <none>           <none>

In this lab run all three replicas landed on worker01 while cp-3 was cordoned during preparation.

Before you cordon or drain, note:

  • Whether the Node is Ready and whether spec.unschedulable is already true
  • Pod owners — Deployment, DaemonSet, bare Pod, or static/mirror Pod
  • PodDisruptionBudgets that select those Pods (kubectl get pdb -A)
  • Pods using emptyDir volumes
  • DaemonSet Pods that drain will refuse to delete without --ignore-daemonsets

Mark the node unschedulable when you want to stop new scheduling but keep current Pods running:

bash
kubectl cordon "$TARGET_NODE"

Sample output:

output
node/worker01 cordoned

Verify the status column shows SchedulingDisabled:

bash
kubectl get nodes

Sample output:

output
NAME       STATUS                     ROLES           AGE    VERSION
cp-1       Ready                      control-plane   <age>  v1.36.3
cp-2       Ready                      control-plane   <age>  v1.36.3
cp-3       Ready                      control-plane   <age>  v1.36.3
worker01   Ready,SchedulingDisabled   <none>          <age>  v1.36.3

Existing Pods on the cordoned node keep running. Create a new Pod without a node selector to confirm scheduling skips it:

bash
kubectl run cordon-test -n drain-lab \
  --image=registry.k8s.io/pause:3.10 \
  --restart=Never
bash
kubectl wait -n drain-lab \
  --for=condition=Ready pod/cordon-test \
  --timeout=60s
bash
kubectl get pod cordon-test -n drain-lab -o wide

Sample output:

output
NAME          READY   STATUS    RESTARTS   AGE   IP              NODE   NOMINATED NODE   READINESS GATES
cordon-test   1/1     Running   0          <age> 192.168.242.12   cp-3   <none>           <none>

This test requires another schedulable node with enough capacity. The Pod should not land on $TARGET_NODE; in this run cordon-test scheduled on cp-3.

Cordon marks only the selected node unschedulable; it does not restrict scheduling to worker-role nodes.


Drain the Node

Drain cordons the node (if it is not already cordoned) and evicts Pods the API can move. Modern clusters use the eviction API, which respects PodDisruptionBudgets when possible.

Uncordon first if you cordoned the node only for the scheduling test above:

bash
kubectl uncordon "$TARGET_NODE"

Understand the Initial Blockers

Start without flags to see every blocker drain reports:

bash
kubectl drain "$TARGET_NODE"

Without extra flags, drain stops when it hits DaemonSets, emptyDir volumes, or bare Pods. A bare kubectl drain on a typical worker reports all blockers at once:

output
node/worker01 cordoned
error: unable to drain node "worker01" due to error: [cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): calico-system/calico-node-gdtn2, calico-system/csi-node-driver-fj49m, kube-system/kube-proxy-gg8g2, cannot delete Pods with local storage (use --delete-emptydir-data to override): drain-lab/emptydir-pod, cannot delete Pods that declare no controller (use --force to override): drain-lab/unmanaged-pod], continuing command...
There are pending nodes to be drained:
 worker01
cannot delete DaemonSet-managed Pods (use --ignore-daemonsets to ignore): calico-system/calico-node-gdtn2, calico-system/csi-node-driver-fj49m, kube-system/kube-proxy-gg8g2
cannot delete Pods with local storage (use --delete-emptydir-data to override): drain-lab/emptydir-pod
cannot delete Pods that declare no controller (use --force to override): drain-lab/unmanaged-pod

Although drain stopped before removing the workload Pods, it already cordoned the node. The node remains SchedulingDisabled; a failed drain does not automatically uncordon it. The following commands continue with the node cordoned.

Read the message as a checklist and add flags only for categories you intend to override:

  • --ignore-daemonsets — first flag added in the later commands
  • --delete-emptydir-data — covered in the emptyDir section
  • --force — covered in the unmanaged Pod section

Eviction and warning order can vary because drain processes multiple Pods concurrently.

Handle DaemonSet Pods

Every node in this lab runs CNI and kube-proxy DaemonSets. Drain refuses to delete those Pods because the DaemonSet controller would immediately recreate them on the same node.

Supply --ignore-daemonsets so drain proceeds:

output
Warning: ignoring DaemonSet-managed Pods: calico-system/calico-node-gdtn2, calico-system/csi-node-driver-fj49m, kube-system/kube-proxy-gg8g2

Drain leaves DaemonSet Pods running on the node while evicting eligible workload Pods:

  • They stop if the node is rebooted or kubelet/container networking is stopped
  • See Kubernetes DaemonSets for how those controllers work

Handle emptyDir Data

Pods with emptyDir volumes store data on the node filesystem. Drain blocks eviction unless you confirm the data can be discarded.

Without --delete-emptydir-data, drain reports:

output
cannot delete Pods with local storage (use --delete-emptydir-data to override): drain-lab/emptydir-pod

When ephemeral scratch data is safe to lose:

  • Add --delete-emptydir-data to the complete drain command when you accept losing data stored in emptyDir
  • The unmanaged Pod still requires --force, which is added in the next section
  • Data in emptyDir is removed when the Pod is deleted—it does not travel with the Pod to another node

Treat --delete-emptydir-data as acceptance of data loss for those volumes.

Handle Unmanaged Pods

Bare Pods created directly (no Deployment, ReplicaSet, or Job owner) block drain:

output
cannot delete Pods that declare no controller (use --force to override): drain-lab/unmanaged-pod

Use --force only when you understand the Pod has no controller to recreate it:

  • --force is not a grace-period override for every Pod
  • --force does not disable PodDisruptionBudget checks
  • In this lab, --force is the final override required for the unmanaged lab Pod

The next section runs the complete drain command with a finite timeout so you can observe the intentionally blocking PDB.

Observe PodDisruptionBudget Behaviour

A PDB limits how many Pods from a selector may be unavailable during voluntary disruption. Drain uses the eviction API, so PDB rules apply.

With minAvailable: 3 and three replicas, the budget allows no disruptions:

bash
kubectl get pdb -n drain-lab

Sample output:

output
NAME             MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
drain-demo-pdb   3               N/A               0                     <age>

Run drain with the blocker flags and a finite timeout. --timeout=0s, the default, means no timeout:

bash
kubectl drain "$TARGET_NODE" \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --force \
  --timeout=60s

Drain retries and eventually exits when the PDB blocks every eviction attempt:

output
node/worker01 already cordoned
Warning: ignoring DaemonSet-managed Pods: calico-system/calico-node-gdtn2, calico-system/csi-node-driver-fj49m, kube-system/kube-proxy-gg8g2; deleting Pods that declare no controller: drain-lab/emptydir-pod, drain-lab/unmanaged-pod
evicting pod drain-lab/unmanaged-pod
evicting pod drain-lab/drain-demo-8857dd8cc-jpkbv
evicting pod drain-lab/drain-demo-8857dd8cc-xnsxs
evicting pod drain-lab/emptydir-pod
evicting pod drain-lab/drain-demo-8857dd8cc-4k6vd
error when evicting pods/"drain-demo-8857dd8cc-jpkbv" -n "drain-lab" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"drain-demo-8857dd8cc-xnsxs" -n "drain-lab" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"drain-demo-8857dd8cc-4k6vd" -n "drain-lab" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/emptydir-pod evicted
pod/unmanaged-pod evicted
...
error: unable to drain node "worker01" due to error: [error when evicting pods/"drain-demo-8857dd8cc-xnsxs" -n "drain-lab": global timeout reached: 1m0s, error when evicting pods/"drain-demo-8857dd8cc-4k6vd" -n "drain-lab": global timeout reached: 1m0s, error when evicting pods/"drain-demo-8857dd8cc-jpkbv" -n "drain-lab": global timeout reached: 1m0s], continuing command...

The number of drain-demo Pods on the target node depends on how the scheduler distributed replicas. In this run all three started on worker01 after cp-3 was cordoned during preparation.

Although the command ended with a timeout, it made partial progress:

  • emptydir-pod and unmanaged-pod were removed because no PDB selected them
  • Only the Deployment Pods remained blocked
  • Drain does not roll back Pods that were already evicted

That is normal PDB protection—not a broken cluster.

Relax the budget so one Pod may leave while two remain available:

bash
kubectl patch pdb drain-demo-pdb -n drain-lab \
  -p '{"spec":{"minAvailable":2}}'

Wait until the disruption controller reports one allowed disruption:

bash
kubectl wait -n drain-lab \
  --for=jsonpath='{.status.disruptionsAllowed}'=1 \
  pdb/drain-demo-pdb \
  --timeout=60s

Sample output:

output
poddisruptionbudget.policy/drain-demo-pdb condition met
bash
kubectl get pdb -n drain-lab

Sample output:

output
NAME             MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
drain-demo-pdb   2               N/A               1                     <age>

ALLOWED DISRUPTIONS: 1 means the eviction API currently permits one additional disruption:

  • kubectl drain may submit eviction requests for multiple Pods concurrently
  • After one request succeeds, other requests covered by the same PDB can be rejected and retried
  • Another eviction is commonly permitted after a replacement Pod becomes Ready

Single-replica workloads need extra planning:

  • Draining a node that hosts the only replica can cause an availability gap while its replacement starts
  • A PDB with minAvailable: 1 blocks that voluntary eviction—scale the workload or plan downtime first
  • A configuration that permits the only replica to be disrupted can produce 100% temporary unavailability

Rerun drain with a longer timeout so replacements can become Ready between evictions:

bash
kubectl drain "$TARGET_NODE" \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --force \
  --timeout=300s

Sample output:

output
Warning: ignoring DaemonSet-managed Pods: calico-system/calico-node-gdtn2, calico-system/csi-node-driver-fj49m, kube-system/kube-proxy-gg8g2
evicting pod drain-lab/drain-demo-8857dd8cc-xnsxs
evicting pod drain-lab/drain-demo-8857dd8cc-4k6vd
evicting pod drain-lab/drain-demo-8857dd8cc-jpkbv
error when evicting pods/"drain-demo-8857dd8cc-xnsxs" -n "drain-lab" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
error when evicting pods/"drain-demo-8857dd8cc-4k6vd" -n "drain-lab" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/drain-demo-8857dd8cc-jpkbv evicted
evicting pod drain-lab/drain-demo-8857dd8cc-xnsxs
evicting pod drain-lab/drain-demo-8857dd8cc-4k6vd
error when evicting pods/"drain-demo-8857dd8cc-4k6vd" -n "drain-lab" (will retry after 5s): Cannot evict pod as it would violate the pod's disruption budget.
pod/drain-demo-8857dd8cc-xnsxs evicted
evicting pod drain-lab/drain-demo-8857dd8cc-4k6vd
pod/drain-demo-8857dd8cc-4k6vd evicted
node/worker01 drained

A successful drain confirms that the selected Pods were evicted and removed; it does not guarantee that the Deployment has already restored all three Ready replicas. With minAvailable: 2, drain evicts one Pod at a time while replacements become Ready on cp-3 before the next eviction is permitted.

Wait for the Deployment rollout to complete:

bash
kubectl rollout status deployment/drain-demo \
  -n drain-lab --timeout=120s

Sample output:

output
Waiting for deployment "drain-demo" rollout to finish: 2 of 3 updated replicas are available...
deployment "drain-demo" successfully rolled out

kubectl rollout status waits until the Deployment rollout is complete. Then inspect the drained node and begin maintenance.

After drain succeeds, only DaemonSet (and static mirror) Pods should remain on the node:

bash
kubectl get pods -A \
  --field-selector "spec.nodeName=$TARGET_NODE" \
  -o wide

Sample output:

output
NAMESPACE       NAME                    READY   STATUS    RESTARTS   AGE   IP               NODE       NOMINATED NODE   READINESS GATES
calico-system   calico-node-gdtn2       1/1     Running   0          <age> 192.168.56.110   worker01   <none>           <none>
calico-system   csi-node-driver-fj49m   2/2     Running   0          <age> 192.168.5.0      worker01   <none>           <none>
kube-system     kube-proxy-gg8g2        1/1     Running   0          <age> 192.168.56.110   worker01   <none>           <none>

A node is ready for maintenance only after kubectl drain returns successfully.

To unblock drain when PDB retries persist:

  • Ensure another worker can accept evicted Pods
  • Fix unhealthy replicas so the budget counts Ready Pods correctly
  • Adjust the maintenance plan—scale up, relax the PDB temporarily, or drain during a window when lower availability is acceptable

--disable-eviction forces direct Pod deletion and bypasses PDB handling. Reserve it for disaster recovery, not routine maintenance. PDB design and minAvailable math live in PodDisruptionBudget.


Perform Maintenance and Uncordon

After node/<name> drained appears:

  • Confirm application Pods no longer run on the node (kubectl get pods -A --field-selector "spec.nodeName=$TARGET_NODE" -o wide)
  • Perform OS patches, hardware work, or kubelet troubleshooting on the host
  • Verify systemctl status kubelet and the container runtime on the node
  • Confirm the Node object still reports Ready when the kubelet is healthy

DaemonSet Pods remain on the node while it is cordoned—plan maintenance knowing they stop if you reboot the host or stop kubelet.

When the node is healthy again, allow scheduling:

bash
kubectl uncordon "$TARGET_NODE"

Sample output:

output
node/worker01 uncordoned
bash
kubectl get nodes

Sample output:

output
NAME       STATUS   ROLES           AGE    VERSION
cp-1       Ready    control-plane   <age>  v1.36.3
cp-2       Ready    control-plane   <age>  v1.36.3
cp-3       Ready    control-plane   <age>  v1.36.3
worker01   Ready    <none>          <age>  v1.36.3

The SchedulingDisabled suffix disappears while Ready stays true.

Uncordon does not move Pods back from other nodes. New Pods—created after uncordon or scaled up by controllers—can schedule on the node again. To verify scheduling, create a test Pod with a node selector or watch the next Deployment scale-out land on the restored node.


Troubleshooting

Symptom Likely cause Direction
Drain exits immediately listing DaemonSets Missing --ignore-daemonsets Add flag; expect agents to remain
Drain cites local storage Pod uses emptyDir Add --delete-emptydir-data if data loss is acceptable
Drain cites no controller Bare Pod on the node Delete the Pod, add an owner, or use --force
Drain retries PDB violations Budget or slow replacements Free capacity on other nodes; see PDB section
Drain reaches its global timeout PDB rejection, insufficient replacement capacity, or Pods not terminating before the configured timeout Resolve the reported blocker; increase --timeout only when the operation legitimately needs longer. A timeout of zero means drain waits indefinitely
Pods stay Pending after eviction No schedulable node capacity Add a worker or uncordon another node
Node still SchedulingDisabled after maintenance Forgot uncordon Run kubectl uncordon <node>

What's Next


References


Summary

Node maintenance in Kubernetes is a sequence, not a single command. kubectl cordon prevents ordinary scheduler placements while leaving running workloads untouched—useful for short holds or before you are ready to evict. kubectl drain cordons and evicts API-managed Pods through the eviction API, which respects PodDisruptionBudgets and refuses to delete DaemonSet agents unless you pass --ignore-daemonsets.

The flags matter because drain surfaces real constraints:

  • DaemonSets stay by design
  • emptyDir data disappears with the Pod
  • Bare Pods need --force
  • PDBs limit how many selected Pods may be unavailable simultaneously during voluntary disruptions

When drain retries on budget violations, that is the protection working—fix capacity or replica health rather than bypassing eviction without cause.

After maintenance, kubectl uncordon reopens the node to scheduling. Workloads do not automatically flow back; only new placements consider the node. For removing a host from the cluster entirely, continue with add, remove and rejoin cluster nodes after drain completes.


Frequently Asked Questions

1. What is the difference between kubectl cordon and kubectl drain?

cordon only marks the node unschedulable. Existing Pods keep running. drain cordons the node and evicts API-managed Pods so controllers can recreate them elsewhere, subject to PodDisruptionBudgets and drain flags.

2. Does kubectl drain delete DaemonSet Pods?

kubectl drain does not delete active DaemonSet-managed Pods. Without --ignore-daemonsets, drain stops and reports them. With the flag, drain leaves those Pods on the node while evicting eligible workload Pods.

3. When do I need --delete-emptydir-data with kubectl drain?

Use --delete-emptydir-data when a Pod on the node uses an emptyDir volume and you accept losing that volume's data when the Pod is removed.

4. What does --force do on kubectl drain?

--force allows drain to remove Pods with no controller and Pods whose referenced managing resource no longer exists. It does not mean immediate deletion and does not bypass PDB checks.

5. Do workloads return to a node automatically after uncordon?

No. uncordon only clears the unschedulable bit. Existing Pods are not moved back; only new scheduling attempts can place Pods on the node again.
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)