Home DevOps Kubernetes Node Management — Cordon, Drain, Upgrades
Advanced 4 min · July 12, 2026

Kubernetes Node Management — Cordon, Drain, Upgrades

Learn Kubernetes Node Management — Cordon, Drain, Upgrades with plain-English explanations and real examples..

N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
Before you start⏱ 30 min
  • kubectl v1.28+, Kubernetes cluster v1.28+, jq (optional), ssh access to nodes, basic understanding of Kubernetes pods and deployments
✦ Definition~90s read
What is Kubernetes Node Management?

Kubernetes node management commands — cordon, drain, and uncordon — are the essential tools for safely removing a node from service, performing maintenance or upgrades, and returning it to the cluster. They matter because they prevent workload disruption by controlling pod scheduling and eviction. Use them when you need to reboot, patch, or decommission a node without downtime.

Think of Kubernetes Node Management — Cordon, Drain, Upgrades like a tool in your DevOps toolkit — once you understand what it does and when to reach for it, managing Kubernetes clusters becomes second nature.
Plain-English First

Think of Kubernetes Node Management — Cordon, Drain, Upgrades like a tool in your DevOps toolkit — once you understand what it does and when to reach for it, managing Kubernetes clusters becomes second nature.

Welcome to Kubernetes Node Management — Cordon, Drain, Upgrades. We'll break this down from first principles.

Understanding Node States: Ready, NotReady, and SchedulingDisabled

A Kubernetes node can be in several states that affect workload scheduling. The Ready status indicates the node is healthy and can accept pods. NotReady means the node is unreachable or unhealthy — pods may be evicted after a timeout. SchedulingDisabled is a manual state set by cordon that prevents new pods from being scheduled while existing pods continue running. Understanding these states is critical for planning maintenance. When you cordon a node, its spec.unschedulable field is set to true, which the scheduler respects. The node remains Ready but won't accept new pods. This is the first step in a safe drain sequence.

check-node-status.shBASH
1
2
3
4
5
6
kubectl get nodes -o wide
# Output:
# NAME           STATUS                     ROLES    AGE   VERSION
# node-1         Ready,SchedulingDisabled   worker   10d   v1.28.3
# node-2         Ready                      worker   10d   v1.28.3
# node-3         NotReady                   worker   10d   v1.28.3
Output
NAME STATUS ROLES AGE VERSION
node-1 Ready,SchedulingDisabled worker 10d v1.28.3
node-2 Ready worker 10d v1.28.3
node-3 NotReady worker 10d v1.28.3
🔥Node Conditions
A node can have multiple conditions simultaneously. SchedulingDisabled is not a condition but a field. Use kubectl describe node to see all conditions.
📊 Production Insight
In production, a node might show Ready but have degraded performance. Always check resource pressure conditions before draining.
🎯 Key Takeaway
Node states determine scheduling eligibility; cordon sets unschedulable without affecting existing pods.

Cordon: The Safety Gate Before Maintenance

The cordon command marks a node as unschedulable. It's the first step in any node maintenance procedure because it prevents new pods from landing on the node while existing workloads continue. This is crucial for zero-downtime operations. Without cordoning, new pods could be scheduled during the drain, causing race conditions. Cordon is idempotent — running it multiple times has no side effect. Use it before any planned maintenance, even if you're not draining immediately. It gives you a safety window to verify the node's health and plan the drain. The command updates the node's spec.unschedulable field to true.

cordon-node.shBASH
1
2
3
4
5
6
kubectl cordon node-1
# Output: node/node-1 cordoned

# Verify:
kubectl get node node-1 -o jsonpath='{.spec.unschedulable}'
# Output: true
Output
true
💡Cordon Before Drain
Always cordon before drain. Some tools like kubectl drain do this automatically, but explicit cordon gives you a checkpoint.
📊 Production Insight
We once had a CI pipeline that drained nodes without cordoning first. A new deployment scheduled a pod on the draining node, causing a crash loop. Always cordon explicitly.
🎯 Key Takeaway
Cordon is the safety gate that prevents new pods from being scheduled on a node under maintenance.
kubernetes-node-management THECODEFORGE.IO Node Upgrade Workflow Step-by-step process for upgrading a Kubernetes node Cordon Node Mark node as unschedulable Drain Pods Evict pods gracefully with PDB check Upgrade Node Apply OS/kubelet updates Uncordon Node Mark node schedulable again Verify Node Check Ready state and pod scheduling ⚠ Skipping drain can cause pod disruption Always drain before upgrade to respect PDBs THECODEFORGE.IO
thecodeforge.io
Kubernetes Node Management

Drain: Evicting Pods Gracefully

The drain command evicts all pods from a node while respecting PodDisruptionBudgets (PDBs) and terminationGracePeriodSeconds. It first cordons the node (if not already), then evicts pods one by one. DaemonSet pods are not evicted by default — you must use --ignore-daemonsets or --delete-emptydir-data flags. Drain waits for PDBs to allow at least one replica to remain available. If a PDB blocks eviction, drain will hang until timeout. This is intentional to protect application availability. Use --force to bypass PDBs (dangerous in production). Drain also respects pod priority — higher priority pods are evicted last.

drain-node.shBASH
1
2
3
4
5
6
7
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data --timeout=300s
# Output:
# node/node-1 cordoned
# evicting pod "nginx-abc123"
# evicting pod "api-server-def456"
# ...
# node/node-1 drained
Output
node/node-1 cordoned
evicting pod "nginx-abc123"
evicting pod "api-server-def456"
...
node/node-1 drained
⚠ PDB Blocking Drain
If a PDB requires minAvailable=2 and only 2 replicas exist, drain will hang. Use --disable-eviction to force delete pods (not recommended).
📊 Production Insight
During a cluster upgrade, a drain hung for 10 minutes because a PDB required 3 replicas but only 3 existed. We had to temporarily scale up the deployment to unblock.
🎯 Key Takeaway
Drain evicts pods gracefully, respecting PDBs and termination grace periods, but can hang if PDBs are too restrictive.

Uncordon: Returning the Node to Service

After maintenance, uncordon marks the node as schedulable again. It sets spec.unschedulable to false. Pods are not automatically rescheduled onto the node — they will be scheduled only when new pods are created or when existing pods are rescheduled due to other reasons. This is a common misconception. If you want to rebalance workloads, you may need to manually delete pods on other nodes or use descheduler. Uncordon is safe to run at any time, even if the node is not fully ready. However, ensure the node's kubelet is healthy and all system components are running before uncordoning.

uncordon-node.shBASH
1
2
3
4
5
6
kubectl uncordon node-1
# Output: node/node-1 uncordoned

# Verify:
kubectl get node node-1 -o jsonpath='{.spec.unschedulable}'
# Output: false
Output
false
💡Post-Uncordon Check
After uncordon, verify node health with kubectl get nodes and check that the node's Ready status is True.
📊 Production Insight
After a kernel upgrade, we uncordoned a node but forgot to check kubelet logs. The node was Ready but had a broken CNI plugin, causing new pods to fail. Always validate node health before uncordoning.
🎯 Key Takeaway
Uncordon makes the node schedulable again but does not automatically reschedule existing pods onto it.

Drain Flags and Options You Must Know

The kubectl drain command has several flags that control behavior. --ignore-daemonsets is almost always needed because DaemonSet pods are not evicted by default. --delete-emptydir-data allows eviction of pods using emptyDir volumes — without it, drain will refuse to evict such pods. --force bypasses PDBs and termination grace periods — use only as a last resort. --grace-period sets the duration for pod termination. --timeout specifies how long to wait before giving up. --pod-selector allows draining only pods matching a label. Understanding these flags prevents unexpected failures. For example, forgetting --ignore-daemonsets will cause drain to hang indefinitely.

drain-with-flags.shBASH
1
2
3
4
5
6
kubectl drain node-1 \
  --ignore-daemonsets \
  --delete-emptydir-data \
  --grace-period=60 \
  --timeout=300s \
  --force=false
⚠ Force Flag Danger
Using --force can cause abrupt pod termination, leading to data corruption or service disruption. Only use when node is already down and pods must be removed.
📊 Production Insight
A junior engineer once ran drain without --ignore-daemonsets on a node with 50 DaemonSet pods. The command hung for 30 minutes until timeout. We now enforce a script that includes required flags.
🎯 Key Takeaway
Drain flags control eviction behavior; always use --ignore-daemonsets and --delete-emptydir-data in production.

PodDisruptionBudget: The Gatekeeper of Availability

PodDisruptionBudget (PDB) is a Kubernetes resource that limits the number of pods that can be voluntarily disrupted at a time. It ensures that a minimum number of pods remain available during voluntary disruptions like node drains. PDBs are defined per application and specify minAvailable or maxUnavailable. When draining a node, the eviction API checks PDBs. If evicting a pod would violate the PDB, the eviction is rejected. This can cause drain to hang. To avoid this, ensure your PDBs are configured with enough buffer. For example, if you have 3 replicas, set maxUnavailable: 1 to allow one pod to be disrupted at a time.

pdb-example.yamlYAML
1
2
3
4
5
6
7
8
9
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: nginx-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: nginx
🔥PDB Best Practice
Set maxUnavailable: 1 for multi-replica deployments. For single-replica, consider using minAvailable: 0 during maintenance windows.
📊 Production Insight
We had a PDB with minAvailable: 3 for a 3-replica deployment. During a rolling upgrade, drain could not evict any pod, stalling the entire cluster upgrade. We now set maxUnavailable: 1 for all stateless apps.
🎯 Key Takeaway
PDBs protect application availability by limiting concurrent pod disruptions; misconfigured PDBs can block drains.

Node Upgrades: A Step-by-Step Workflow

Upgrading a Kubernetes node (e.g., kubelet, container runtime, or OS) requires a careful workflow to avoid downtime. The standard process: 1) Cordon the node to prevent new pods. 2) Drain the node to evict existing pods. 3) Perform the upgrade (e.g., package update, reboot). 4) Verify node health (kubelet, node status). 5) Uncordon the node. This sequence ensures zero pod disruption. For control plane nodes, the process is similar but requires special attention to etcd and API server quorum. Always upgrade one node at a time, especially in production. Automate this with tools like kubeadm upgrade node or cluster-api.

node-upgrade-workflow.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Step 1: Cordon
kubectl cordon node-1

# Step 2: Drain
kubectl drain node-1 --ignore-daemonsets --delete-emptydir-data

# Step 3: Upgrade (example for Ubuntu)
ssh node-1 'sudo apt update && sudo apt upgrade -y kubelet kubeadm kubectl && sudo systemctl restart kubelet'

# Step 4: Verify
kubectl get node node-1

# Step 5: Uncordon
kubectl uncordon node-1
💡Rolling Upgrade Pattern
Upgrade nodes one at a time. For large clusters, use a rolling upgrade strategy with a tool like kOps or AKS upgrade.
📊 Production Insight
During a kernel upgrade, we forgot to drain a node and rebooted. The node came back with a different kernel version, causing a CNI incompatibility. Always drain before rebooting.
🎯 Key Takeaway
Node upgrades follow a strict cordon-drain-upgrade-verify-uncordon cycle to maintain availability.
kubernetes-node-management THECODEFORGE.IO Node Management Stack Layered components for safe node operations Control Plane API Server | Scheduler | Controller Manager Node Agent kubelet | kube-proxy Pod Lifecycle PodDisruptionBudget | Eviction API Maintenance Tools kubectl cordon | kubectl drain | kubectl uncordon Automation Scripts | Cluster Autoscaler | Node Problem Detector THECODEFORGE.IO
thecodeforge.io
Kubernetes Node Management

Automating Node Management with Scripts and Tools

Manual node management doesn't scale. In production, automate cordon/drain/uncordon with scripts or tools like Ansible, Terraform, or cluster-api. A common pattern is to write a bash script that iterates over nodes, cordons, drains, performs maintenance, verifies, and uncordons. Use kubectl with --context for multi-cluster support. For cloud-managed clusters (EKS, AKS, GKE), use their node management features (e.g., AWS Node Termination Handler). For on-prem, consider using a tool like kured (Kubernetes Reboot Daemon) that automates node reboots based on reboot-required files.

automate-node-maintenance.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
set -euo pipefail

NODE=$1

echo "Cordoning $NODE"
kubectl cordon "$NODE"

echo "Draining $NODE"
kubectl drain "$NODE" --ignore-daemonsets --delete-emptydir-data --timeout=300s

echo "Performing maintenance on $NODE"
ssh "$NODE" 'sudo apt upgrade -y kubelet && sudo systemctl restart kubelet'

echo "Waiting for node to be Ready"
until kubectl wait --for=condition=Ready node/"$NODE" --timeout=60s; do
  sleep 5
done

echo "Uncordoning $NODE"
kubectl uncordon "$NODE"
echo "Done"
🔥Idempotency
Ensure your automation is idempotent. Running the script on an already drained node should be safe.
📊 Production Insight
We automated node reboots with kured but initially didn't set a reboot window. Nodes rebooted during peak traffic, causing cascading failures. Always schedule maintenance windows.
🎯 Key Takeaway
Automate node management to reduce human error and scale operations; use scripts or dedicated tools.

Handling Edge Cases: When Drain Fails

Drain can fail for several reasons: PDB violations, pods with local storage (emptyDir without --delete-emptydir-data), unmanaged pods (not backed by a controller), or pods with strict inter-pod anti-affinity. When drain fails, it will output the reason. Common fixes: adjust PDBs, add --delete-emptydir-data, or delete unmanaged pods manually. For pods with anti-affinity, you may need to temporarily scale down the deployment. In extreme cases, use --force but be aware of the risks. Always investigate the root cause rather than blindly forcing. A failed drain is a signal that your cluster configuration needs attention.

debug-drain-failure.shBASH
1
2
3
4
5
6
7
8
9
# Drain fails with:
kubectl drain node-1 --ignore-daemonsets
# error: unable to drain node "node-1", aborting command...
# There are pending pods in node "node-1":
#   pod "my-pod" (unmanaged)

# Solution: Delete the unmanaged pod
kubectl delete pod my-pod --grace-period=0 --force
kubectl drain node-1 --ignore-daemonsets
⚠ Unmanaged Pods
Pods not created by a controller (e.g., bare pods) cannot be evicted by drain. They must be deleted manually or recreated as part of a Deployment.
📊 Production Insight
We once had a cron job that created bare pods for batch processing. These pods blocked drains. We migrated them to Jobs, which are evictable.
🎯 Key Takeaway
Drain failures are often due to PDBs, local storage, or unmanaged pods; diagnose before forcing.

Best Practices for Production Node Management

  1. Always cordon before drain, even if drain does it automatically. 2) Use PDBs with maxUnavailable: 1 for all stateless workloads. 3) Set terminationGracePeriodSeconds appropriately (e.g., 30s for web apps, 120s for batch jobs). 4) Use --ignore-daemonsets and --delete-emptydir-data in all drain commands. 5) Test drain on a non-production cluster first. 6) Monitor cluster during maintenance with alerts on node status and pod evictions. 7) Have a rollback plan: if a node fails to come back after upgrade, have a process to replace it. 8) Use cluster autoscaler to handle capacity during node drains.
deployment-with-pdb.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      terminationGracePeriodSeconds: 30
      containers:
      - name: nginx
        image: nginx:1.25
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: web-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: web
💡Graceful Termination
Set terminationGracePeriodSeconds to match your application's shutdown time. Too short causes abrupt kills; too long delays drains.
📊 Production Insight
We set terminationGracePeriodSeconds: 120 for a legacy app that needed to flush buffers. This prevented data loss during drains but increased drain time. Trade-offs are inevitable.
🎯 Key Takeaway
Production best practices include PDBs, proper grace periods, and automation to ensure safe node management.

Troubleshooting Common Node Management Issues

Common issues: Node stuck in NotReady after reboot — check kubelet logs (journalctl -u kubelet). Drain hangs — check PDBs and pod eviction events (kubectl get events --field-selector involvedObject.kind=Pod). Uncordon doesn't schedule pods — check node conditions and taints. Pods evicted but not rescheduled — check scheduler logs or node resources. Always use kubectl describe node to get a full picture. For persistent issues, consider replacing the node rather than debugging. In cloud environments, terminating and recreating the node is often faster.

troubleshoot-node.shBASH
1
2
3
4
5
6
7
8
9
10
11
# Check node conditions
kubectl describe node node-1 | grep -A5 Conditions

# Check kubelet logs
ssh node-1 'journalctl -u kubelet --since "5 minutes ago"'

# Check eviction events
kubectl get events --field-selector involvedObject.kind=Pod --sort-by='.lastTimestamp'

# Check taints
kubectl get node node-1 -o jsonpath='{.spec.taints}'
🔥Node Replacement
If a node is consistently problematic, replace it. In cloud, terminate the instance and let the autoscaler create a new one.
📊 Production Insight
A node had a failing SSD that caused intermittent kubelet crashes. We spent hours debugging before replacing it. Now we replace nodes with hardware issues immediately.
🎯 Key Takeaway
Troubleshoot node issues by checking kubelet logs, events, and node conditions; replace if necessary.
Cordon vs Drain Key differences in node maintenance operations Cordon Drain Purpose Prevent new pod scheduling Evict existing pods gracefully Pod Impact Existing pods continue running Pods are terminated or moved PDB Respect No effect on PDBs Respects PodDisruptionBudget Reversibility Undone by uncordon Undone by uncordon (pods may need resche Use Case Pre-maintenance safety gate Actual pod eviction for upgrades THECODEFORGE.IO
thecodeforge.io
Kubernetes Node Management

Advanced: Using Descheduler for Workload Rebalancing

After uncordoning a node, pods don't automatically move to it. The descheduler is a Kubernetes add-on that evicts pods based on policies to rebalance workloads. For example, after a node returns from maintenance, the descheduler can evict pods from other nodes to fill the empty node. This is useful for optimizing resource utilization. However, descheduler must be used carefully — it can cause unnecessary pod churn. Configure it with policies like LowNodeUtilization and HighNodeUtilization. Always run descheduler in dry-run mode first. In production, we run descheduler every 15 minutes with a MaxNoOfPodsToEvictPerNode limit.

descheduler-policy.yamlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: "descheduler/v1alpha2"
kind: "DeschedulerPolicy"
profiles:
  - name: LowNodeUtilization
    pluginConfigs:
    - name: "LowNodeUtilization"
      args:
        thresholds:
          cpu: 20
          memory: 20
        targetThresholds:
          cpu: 50
          memory: 50
    plugins:
      deschedule:
        enabled:
          - "LowNodeUtilization"
⚠ Descheduler Risks
Descheduler can cause cascading evictions. Always set MaxNoOfPodsToEvictPerNode and test in staging first.
📊 Production Insight
We enabled descheduler without limits and it evicted 20% of pods in one cycle, causing a brief performance degradation. Now we limit evictions to 10 pods per node per cycle.
🎯 Key Takeaway
Descheduler rebalances pods after node maintenance but must be configured with limits to avoid disruption.
⚙ Quick Reference
12 commands from this guide
FileCommand / CodePurpose
check-node-status.shkubectl get nodes -o wideUnderstanding Node States
cordon-node.shkubectl cordon node-1Cordon
drain-node.shkubectl drain node-1 --ignore-daemonsets --delete-emptydir-data --timeout=300sDrain
uncordon-node.shkubectl uncordon node-1Uncordon
drain-with-flags.shkubectl drain node-1 \Drain Flags and Options You Must Know
pdb-example.yamlapiVersion: policy/v1PodDisruptionBudget
node-upgrade-workflow.shkubectl cordon node-1Node Upgrades
automate-node-maintenance.shset -euo pipefailAutomating Node Management with Scripts and Tools
debug-drain-failure.shkubectl drain node-1 --ignore-daemonsetsHandling Edge Cases
deployment-with-pdb.yamlapiVersion: apps/v1Best Practices for Production Node Management
troubleshoot-node.shkubectl describe node node-1 | grep -A5 ConditionsTroubleshooting Common Node Management Issues
descheduler-policy.yamlapiVersion: "descheduler/v1alpha2"Advanced

Key takeaways

1
Cordon before drain
Always cordon a node before draining to prevent new pods from being scheduled during eviction.
2
Respect PodDisruptionBudgets
PDBs protect application availability; configure them with maxUnavailable: 1 for stateless workloads.
3
Automate node management
Use scripts or tools to cordon, drain, upgrade, and uncordon nodes to reduce human error.
4
Handle drain failures gracefully
Diagnose failures by checking PDBs, unmanaged pods, and local storage; avoid --force unless necessary.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
What is the difference between cordon and drain?
Q02JUNIOR
Why does drain hang and how do I fix it?
Q03JUNIOR
Can I uncordon a node before it's fully ready?
Q01 of 03JUNIOR

What is the difference between cordon and drain?

ANSWER
Cordon marks a node as unschedulable, preventing new pods from being scheduled. Drain evicts existing pods from the node, respecting PDBs and grace periods. Cordon is a prerequisite for drain.
FAQ · 6 QUESTIONS

Frequently Asked Questions

01
What is the difference between cordon and drain?
02
Why does drain hang and how do I fix it?
03
Can I uncordon a node before it's fully ready?
04
How do I drain a node without downtime?
05
What happens if I drain a node with a single-replica deployment?
06
Should I use `--force` when drain fails?
N
Naren Founder & Principal Engineer

20+ years shipping production infrastructure and CI/CD at scale. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 12, 2026
last updated
2,073
articles · all by Naren
🔥

That's Kubernetes. Mark it forged?

4 min read · try the examples if you haven't

Previous
Kubernetes Ingress Controllers — NGINX, Traefik, AWS ALB
25 / 38 · Kubernetes
Next
Kubernetes Backup and Disaster Recovery