Cordon changes scheduling eligibility; drain coordinates eviction and waits for Pod termination. Neither command guarantees that work survives maintenance. Application shutdown, replacement capacity, storage and disruption budgets determine that outcome. Treat a drained node as an observed state with documented exceptions, rather than a promise inferred from one command name.

Cordon has scheduling exceptions. Drain includes cordon and proceeds through deletion filters, supported eviction and termination waits. Partial results, disruption budgets, remaining workloads, replacement capacity and storage all need inspection.
Cordon is a placement restriction, not an empty-node certificate. Drain is not atomic and does not prove successful writes or replacement. The retained diagnostics were not run in this review.

Cordon changes future placement

Cordon sets the node’s unschedulable field. Existing Pods continue running, and ordinary new scheduling avoids that node. Drain performs cordoning itself, so a separate cordon can create an earlier observation window but is not mandatory. Uncordoning permits scheduling again; it does not force immediate placement or repair an unhealthy kubelet.

The scheduling restriction has exceptions. DaemonSet Pods tolerate the unschedulable condition, and a Pod assigned directly through nodeName bypasses normal scheduler selection. Other Pods with the relevant toleration can also qualify. Check actual remaining workloads and avoid describing cordon as a firewall against every possible Pod arrival.

Drain has filters and independent operations

Kubectl first identifies Pods and checks deletion filters. Bare Pods or missing managing controllers can require force; emptyDir data requires acknowledgement of its loss; DaemonSet Pods require the ignore option. Mirror Pods are skipped because deleting their API representation cannot stop the underlying static workload managed by kubelet.

These preliminary checks are not an atomic transaction. Once eligible evictions begin, some can succeed while others wait or fail. Drain can also leave the node cordoned after failure. Re-read node and Pod state before retrying, rather than assuming an unsuccessful command means that nothing changed.

Ignore-daemonsets leaves DaemonSet Pods running. Delete-emptydir-data permits loss of those ephemeral contents; it does not migrate them. Force allows handling unmanaged Pods and does not bypass PodDisruptionBudgets. Confirm ownership, data needs and replacement behavior before choosing any of these options. Statelessness alone does not establish that removing a lone instance is harmless.

Budgets govern voluntary eviction

When supported, drain uses the Eviction API, which checks applicable disruption budgets. Direct deletion through disable-eviction bypasses that protection. The distinction matters: adding force cannot cure a budget rejection, while bypassing eviction can remove replicas whose continued operation is needed for quorum or availability.

A healthy Pod can be blocked when its budget allows no disruption. The default IfHealthyBudget policy also constrains eviction of Running but unhealthy Pods when the guarded application is already disrupted; it does not invariably require every unhealthy Pod to become Ready. AlwaysAllow changes that unhealthy-Pod decision. Select the policy deliberately from application recovery behavior.

Drain waits for termination and retries some rejected evictions. Its timeout defaults to unlimited, but authentication errors, server failures and other conditions can still end the command. Graceful termination gives the application a window to stop; it cannot prove completed writes or successful replacement. A finite operational deadline helps expose blocked maintenance.

When it breaks

A zero-disruption budget is a useful lead, not permission to weaken the budget immediately. Inspect the selected Pods, desired and current healthy counts, replacement capacity and storage topology. Restoring an unhealthy replica or adding safe capacity may be preferable to reducing protection. Concurrent drains compete for the same budgets and can intensify the problem.

If maintenance cannot proceed, identify the exact remaining Pod and reason. Do not substitute disable-eviction simply to obtain a successful terminal result. Check application health and remaining static or DaemonSet workloads before powering down; a successful ordinary drain deliberately leaves some categories behind.

Check it quickly

These retained commands were not run in this review. Use a disposable, explicitly selected cluster and the intended namespace. The client dry run still reads cluster state, but neither performs evictions nor validates whether the server will admit them. Its ignore and emptyDir flags suppress those particular preflight refusals.

kubectl get node node-1 -o jsonpath='{.spec.unschedulable}{"\n"}'
kubectl drain node-1 --dry-run=client --ignore-daemonsets --delete-emptydir-data
kubectl get pods --field-selector spec.nodeName=node-1 -o custom-columns=NAME:.metadata.name,OWNER:.metadata.ownerReferences[0].kind
kubectl get pdb -A -o custom-columns=NAME:.metadata.name,MIN:.spec.minAvailable,MAX:.spec.maxUnavailable,ALLOWED:.status.disruptionsAllowed

The node field shows cordon state. The Pod listing covers only the current namespace and displays only the first owner reference; neither an empty owner column nor a displayed owner proves the complete controller relationship. Compare all namespaces and the controlling reference before acting. The budget listing is cluster-wide but omits namespaces and selectors. Match each budget to the actual workload, then verify a real, authorized drain separately; this inspection cannot certify its success.

Sources