How to drain a Kubernetes node without downtime

Why a plain drain takes single-replica apps down, and the replicas, budgets and rolling restarts that keep them serving.

Published 1 November 2026 · By Ajay Roy, Guest author · Infrastructure engineer at HCL · Published by Zoorik

Drain a node without downtime: a plain drain leaves downtime, a new pod first keeps two copies serving

Short answer. To drain a Kubernetes node without downtime, each app needs a second Ready replica elsewhere, a PodDisruptionBudget (PDB) allowing one disruption, and spare node capacity. A plain kubectl drain evicts a pod before its replacement starts, so a single-replica app is down until the new copy is Ready.

Why a plain drain takes a single-replica app down

Picture patching a node that runs one of three web API replicas and the only copy of a cache. The other two API pods keep serving.

The cache serves nothing from the moment its pod is evicted. It stays down until a replacement on another node has pulled its image, started and passed its readiness check.

We measured that gap on our dev cluster during a Coral rebalance on 12 October. None of its single-copy apps qualified for the warm handover described below. Each was down for 5 to 82 seconds, and the 6 apps that run two copies were down for 0 seconds.

What a drain does to each pod

kubectl drain marks the node unschedulable, as kubectl cordon does (how cordon and drain differ). It then evicts the node’s pods through the Eviction API. That API refuses any eviction that would break a pod’s PDB.

An evicted pod is marked for deletion, and its endpoint stops receiving Service traffic. Its containers get SIGTERM and the pod’s grace period, 30 seconds by default. Then the kubelet kills any still running.

Kubernetes never moves a pod to another node. It replaces the pod.

In the drain walkthrough on the Disruptions page, the Deployment creates the replacement only after it notices the old pod terminating. Closing that gap means starting the new copy first.

A plain drain leaves a gap with no Ready copy Two rows on one shared time axis with no ticks or numbers, labelled only time. Each row has an old pod lane and a new pod lane. Row Plain drain, 1 replica: the old pod is serving up to a dashed mark labelled evicted, then terminating, out of endpoints. The new pod is in image pull, start from that mark to a dashed mark labelled Ready, then serving. A red bracket from evicted to Ready is labelled no Ready copy: downtime. Row New pod first (rolling restart or warm handover): the new pod is in image pull, start from the same instant while the old pod is still serving, and is Ready at the same mark. The old pod keeps serving past it, up to a dashed mark labelled old pod removed. A bracket from Ready to old pod removed is labelled two copies serving. The only number is the one replica in the first row's name. A plain drain leaves a gap with no Ready copy Plain drain, 1 replicaold podnew podservingterminating, out of endpointsimage pull, startservingno Ready copy: downtimeNew pod first (rolling restart or warm handover)old podnew podservingimage pull, startservingtwo copies servingevictedReadyold pod removedtime
Illustration, not to scale. Downtime is the gap between the old copy leaving the endpoints and the new copy being Ready. Starting the new copy first closes it.

Two kinds of pod wait longer. A StatefulSet’s replacement keeps the old pod’s name (web-0 stays web-0), so it can’t be created until the old pod has terminated completely.

A ReadWriteOnce volume can be mounted read-write by a single node. A replacement on another node can’t share it with the old pod.

Five things each app needs to drain a Kubernetes node without downtime

All five must hold:

  • At least two replicas, on different nodes. Without a PDB, a drain evicts every replica that shares the node at once. Spread them with pod anti-affinity or topology spread constraints.
  • A PDB that allows one disruption. With maxUnavailable: 1, drain evicts one pod and is refused the next. It waits until the replacement counts as healthy, meaning its Ready condition is True. No budget protects a single replica. maxUnavailable: 1 lets its only pod go, and a budget of zero voluntary evictions means the drain never completes.
  • A readiness probe that passes only when the app can serve. Ready is what the PDB and the Deployment wait for, so a pod that reports Ready early ends the overlap too soon.
  • A clean shutdown. On SIGTERM, the app should finish in-flight requests and exit within terminationGracePeriodSeconds. A preStop hook runs before the signal if you need one.
  • Room for the replacement. A new pod that fits on no node stays Pending, and the drain stalls on the next eviction. Add a node first, or let a managed upgrade’s surge node supply it.

The PDB for a three-replica Deployment labelled app: <app>:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: <app>-pdb
spec:
  maxUnavailable: 1
  selector:
    matchLabels:
      app: <app>
  unhealthyPodEvictionPolicy: AlwaysAllow

The PDB docs recommend maxUnavailable because it “automatically responds to changes in the number of replicas”.

The last line is explained under hung drains. Pod disruption budget best practices covers choosing the numbers.

How to drain a node in Kubernetes, step by step

  1. See what runs on the node and what its budgets allow. An app with one replica will restart. A budget with 0 under ALLOWED DISRUPTIONS will block the drain.
kubectl get pods --all-namespaces --field-selector spec.nodeName=<node-name> -o wide
kubectl get poddisruptionbudgets --all-namespaces
  1. Cordon the node. New pods stop landing on it: kubectl cordon <node-name>.
  2. Move single-replica Deployments off it with the rolling restart in the next section.
  3. Drain it. If every pod there has a controller, this evicts all but DaemonSet and mirror pods. It also deletes the data in the evicted pods’ emptyDir volumes. Check first that none holds data you need.
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data --timeout=30m
  1. Do the maintenance. If you keep the node, run kubectl uncordon <node-name> to make it schedulable again.

The kubectl drain flags

kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data is the form most drains need. Drain won’t proceed without the first flag while a DaemonSet pod is on the node. It won’t proceed without the second while a pod uses emptyDir.

The flags, from the kubectl reference for Kubernetes v1.37:

FlagWhat it doesDefault
--ignore-daemonsets
Skips DaemonSet pods. Drain never deletes them: their controller ignores the unschedulable mark and would replace them on the same node.
Off
--delete-emptydir-data
Evicts pods that use emptyDir. That local data is deleted.
Off
--timeout
How long drain waits before giving up.
Not set. Zero means no limit.
--grace-period
Seconds each pod gets to shut down.
-1, the pod’s own setting
--force
Also deletes pods no controller manages, which otherwise stop the drain. Nothing recreates them.
Off
--disable-eviction
Deletes pods instead of evicting them, which bypasses PDBs.
Off

Set a kubectl drain timeout whenever you script a drain. Drain retries refused evictions until every pod is gone or the timeout is reached. Without one, a blocked PDB holds the job open for good.

When drain gives up, the node stays cordoned. Uncordon it if you’re abandoning the work. --dry-run=client previews a drain, printing what would be sent without sending it.

A rolling restart moves a single-replica Deployment first

A rolling restart gives a single-replica Deployment the overlap a plain drain can’t. It starts the new pod first, on another node because yours is cordoned.

So the restart can run 1 + 1 = 2 pods and must keep 1 − 0 = 1 available. The old pod stays until the new one is Ready.

kubectl rollout restart deployment/<name> -n <namespace>
kubectl rollout status deployment/<name> -n <namespace>

When rollout status reports success, the Ready pod is on another node.

Before you drain, sort the node’s apps:

  • Drain an app with two or more replicas as it is, once it meets the five needs.
  • Restart a single-replica Deployment with rolling updates first.
  • Expect any other single-replica app to restart.

A restart gives no overlap to a StatefulSet, a pod with a ReadWriteOnce volume or a Deployment with the Recreate strategy. Recreate kills all existing pods before it creates new ones. Those restart however you drain them, so move them in a quiet hour, or run a second replica where the app supports one.

A single-replica app restarts unless a rolling restart moves it first A decision tree read top to bottom, with the questions on the left and the three outcomes on the right. Question 1, Two or more replicas? If yes: Drain as it is, once it meets the five needs, a Ready copy keeps serving, with a green mark. If no, one replica, go to question 2, A Deployment with rolling updates and no ReadWriteOnce volume? If yes: Rolling restart first, the new pod is Ready on another node before the old one goes, with a green mark. If no: Restarts however you drain, StatefulSet, Recreate or ReadWriteOnce volume, move it in a quiet hour, or run a second replica. The numbers are 2 replicas, 1 replica and 5 needs. A single-replica app restarts unless a rolling restart moves it first Two or more replicas?A Deployment with rolling updatesand no ReadWriteOnce volume?Drain as it isonce it meets the five needsa Ready copy keeps servingRolling restart firstthe new pod is Ready on another nodebefore the old one goesRestarts however you drainStatefulSet, Recreate or ReadWriteOnce volumemove it in a quiet hour, or run a second replicayesyesno, one replicano
DaemonSet pods stay on the node, and a pod with no controller stops the drain unless it is forced off, after which nothing recreates it.

When a drain hangs, and whether to ignore the PDB

A stuck drain is waiting on an eviction that keeps being refused. The usual causes:

  • A PDB that allows zero voluntary evictions, such as maxUnavailable of 0, or minAvailable of 100% or of the replica count.
  • Pods under a budget that aren’t Ready, such as a crash-looping app. Under the default IfHealthyBudget policy, drain can evict them only while the app isn’t already disrupted. unhealthyPodEvictionPolicy: AlwaysAllow, stable since Kubernetes 1.31, lets drain evict them, and the Disruptions page recommends it.
  • A replacement stuck Pending for lack of room.

Adding a replica so the budget allows one eviction is usually the better fix.

For a single-instance stateful app, the PDB docs suggest a maxUnavailable of 0, with an agreement that the operator asks first. When ready for the downtime, the app owner deletes the PDB, then recreates it.

Kubernetes node maintenance on AKS and EKS waits on your PDBs

Managed upgrades drain nodes for you, so the per-app rules above still decide whether an upgrade is quiet.

AKS rolling node pool upgrades

On Azure Kubernetes Service (AKS), a rolling node pool upgrade adds a surge node first, one node by default. Microsoft recommends a surge of 33% for production pools. AKS then cordons and drains old nodes one at a time, or as many at once as there are surge nodes.

Each node gets a drain timeout of 30 minutes by default, settable from 5 minutes to 24 hours. If it runs out with pods still running, the upgrade stops until a later PUT operation resumes it. A wait after each drain is settable too, up to 30 minutes and 0 by default.

Set the timeout, in minutes, per node pool:

az aks nodepool update \
  --resource-group <resource-group> \
  --cluster-name <cluster-name> \
  --name <node-pool> \
  --drain-timeout 45

EKS managed node group updates

On Amazon Elastic Kubernetes Service (EKS), a managed node group update with the default strategy creates new nodes first. Once a new node is Ready, it cordons an old node and drains it. EKS waits 60 seconds after evicting every pod before it terminates the node.

Pods that don’t leave within 15 minutes fail the update with a PodEvictionFailure error, unless you set the force flag. That flag, on the update-nodegroup-version request, deletes them.

AWS names an aggressive PDB, or several PDBs pointing at one pod, among the causes. So forcing deletes pods a budget was holding. That carries the same risk as --disable-eviction.

A stuck drain pauses an AKS upgrade and fails an EKS update Two rows, each a chain of three boxes read left to right, with the drain boxes in one column and outlined as the subject. AKS rolling node pool upgrade: surge node added, 1 by default, then cordon and drain old nodes, as many at once as surge nodes, then once drained a wait of 0 to 30 min, 0 by default, and an arrow labelled next node loops back to the drain box. Under the drain box a dashed arrow, pods still running at the drain timeout (30 min by default, 5 min to 24 h), leads to a dashed box, upgrade stops until resumed. EKS managed node group update, default strategy: new node Ready, then cordon and drain an old node, then once drained node terminated, 60 s after the drain. Under the drain box a dashed arrow, pods still there at 15 min, leads to a dashed box, update fails: PodEvictionFailure, unless forced, which deletes them. A stuck drain pauses an AKS upgrade and fails an EKS update AKS rolling node pool upgradesurge node added1 by defaultcordon and drain old nodesas many at once as surge nodeswait0 to 30 min0 by defaultthendrainednext nodepods still running at the drain timeout30 min by default, 5 min to 24 hupgrade stops until resumedEKS managed node group update, default strategynew node Readycordon and drain an old nodenode terminated60 s after the drainthendrainedpods still there at 15 minupdate fails: PodEvictionFailureunless forced, which deletes them
Both managed upgrades add capacity before they drain, and a drain that a PDB keeps refusing stops at the timeout in both.

How Coral drains nodes

Coral optimizes Kubernetes on AKS and EKS. One of its levers for Kubernetes cost optimization is draining spare nodes so your autoscaler can remove them. Its drains follow the rules above, plus a step for single-replica apps:

  • Check first: before a consolidation drain, the agent checks the live cluster. Every pod on the node must fit on another Ready node, with its affinity, taints, spread, ports and zones.
  • One node at a time: consolidation cordons and drains one node at a time. If a drain can’t finish in time, the node goes back into service.
  • Warm handover: for a Deployment or an Argo Rollout, even one that runs a single copy, Coral scales it up by one. It waits until the new pod is Ready on another node, evicts the old pod within its PDB, then scales back. A Ready copy keeps serving throughout. No process state moves, and open connections to the old pod end.
  • Eviction within the PDB: where warm handover isn’t used, Coral evicts the pod through the Eviction API, within its PDB.
  • Held nodes: a node stays when a pod on it can’t move safely. Examples are a PDB that allows no disruption, emptyDir or hostPath data, or a pod marked do-not-disrupt. Node list → “What’s holding them” names each reason and its fix.

When a single-copy app still restarts

Warm handover isn’t used for these, among others:

  • StatefulSets and Jobs
  • the Recreate strategy
  • ReadWriteOnce volumes and GPU pods
  • a Deployment whose replica count Argo CD or Flux sets

An HPA-scaled app gets a handover only in a rebalance, where Coral raises the autoscaler’s minimum instead. All of these fall back to eviction, so a single-copy app among them gets a short restart, as under a plain drain.

That is why every single-copy app restarted on 12 October. Argo CD sets the replica count of some, and the rest are StatefulSets or use the Recreate strategy.

A bare pod with no controller keeps its node unless it is marked safe to evict. A pod annotated karpenter.sh/do-not-disrupt: "true" is never moved, and its node is kept.

Upgrades and rebalances

Coral skips cordoned, NotReady and deleting nodes, and never drains a node someone else cordoned. During an upgrade window, we recommend pausing node optimization and the Evictor, which moves pods off lightly used nodes.

A rebalance replaces the nodes you choose with cheaper machines, and it never goes past a PDB. Each handover copy must stay Ready and serving, with no restarts, before the old one goes. A node that can’t be emptied in time is kept.

Sources: Kubernetes, “kubectl drain” (opens in a new tab); Kubernetes, “Disruptions” (opens in a new tab); Kubernetes, “Specifying a Disruption Budget for your Application” (opens in a new tab); Kubernetes, “Pod Lifecycle” (opens in a new tab); Kubernetes, “Deployments” (opens in a new tab); Kubernetes, “Persistent Volumes” (opens in a new tab); Microsoft Learn, “Configure rolling upgrades for Azure Kubernetes Service (AKS) node pools” (opens in a new tab); AWS, “Understand each phase of node updates” (opens in a new tab).

Find your own number

One line in your cloud console. One PDF report with your estimated saving at list price.

Estimate your saving

Talk to the builders

Thirty minutes with the people who build Coral.

Book a demo (opens in a new tab)

Or write to founders@zoorik.com