Your node went NotReady at 3 AM. Was it a dying disk, a kernel upgrade you scheduled yourself, or a graceful shutdown already doing exactly what it should? Today Kubernetes cannot tell you — NotReady is a boolean wearing a trench coat, and every controller in your cluster reconstructs the real story from a different pile of indirect clues. On September 9, 2026, the Kubernetes blog introduced Node Lifecycle Conditions: five well-known Node conditions — DrainInProgress, Drained, MaintenancePlanned, MaintenanceInProgress, and GracefulNodeShutdownInProgress — that give every node a shared, Kubernetes-owned vocabulary for saying where it is in its lifecycle.
Here is the verdict before the why: start publishing these conditions from your own automation now, but change nothing about how you drain, cordon, or upgrade. In v1.37 the conditions are a status channel with no readers in core Kubernetes — the value lands the moment your dashboards, alerts, and AI operators start consuming the signal instead of inferring it. This post gives you the fleet operator's map: the five conditions and who should publish each one, what v1.37 deliberately does not do, the controller and alerting changes to make on a Cluster API fleet, and the agent-ops payoff that makes this more than a nicer kubectl get nodes.
The five conditions and who publishes them
| Condition | What it reports | Who should publish it on your fleet |
|---|---|---|
DrainInProgress | The node is actively being drained per your chosen drain criteria | Drain automation (upgrade scripts, CAPI remediation flow, autoscaler scale-down) |
Drained | The node has reached your selected drain criteria | Same drain automation, on completion |
MaintenancePlanned | The node is expected to undergo a change in the future | Maintenance scheduler / window planner |
MaintenanceInProgress | The node is actively undergoing maintenance | The controller or runbook step performing the work |
GracefulNodeShutdownInProgress | Graceful Node Shutdown is determined to be in progress | Shutdown reporter (kubelet-adjacent automation today; core-owned later) |
Each condition follows the standard condition contract: True while the lifecycle state is active, False (or removed) when it is not, Unknown when Kubernetes cannot tell. The reason field carries a stable, machine-readable cause; message carries human-readable detail. An authorized maintenance controller might publish MaintenancePlanned=True with reason MaintenanceWindow and a message like "Hardware maintenance is scheduled for this Node" — a shape your alerting and your agents can match on with a string comparison instead of a forensic investigation.
The Monday-morning action list, in full:
- Pick one owner per condition — the component that sets it is the component that clears it.
- Have your drain automation publish
DrainInProgress/Drainedaround work it already does. - Have your maintenance scheduler publish
MaintenancePlannedahead of windows andMaintenanceInProgressduring them. - Rewrite
NotReadypaging to check lifecycle conditions first, and give your agents read access to Node conditions with a check-before-remediate policy.
Nothing on that list requires the v1.37 feature gate, a core upgrade behavior change, or permission from upstream. That is the point — and the next section explains why the design looks that way.
What v1.37 does NOT do (read this before you deploy anything)
The September 9 post, by Ryan Hallisey of NVIDIA, is unusually explicit about the limits of this first release, and misreading them is the fastest way to get burned. Context: v1.37 itself (Garhwal) shipped August 26, two weeks earlier — the conditions rode along as reserved API vocabulary, not as behavior:
- The
NodeLifecycleConditionsfeature gate is effectively a no-op. It is Alpha and disabled by default, it does not restrict who can set the conditions, and no core component reads them. It exists so future built-in consumer behavior can be opted into when it arrives. You do not need to enable it to start publishing today. - No core workload controller changes its behavior. The scheduler, the DaemonSet controller, the Job controller — none of them consult these conditions in v1.37. Publishing
MaintenanceInProgressdoes not pause rollouts or change eviction; it communicates. - Setting and clearing is your job. An administrator or an administrator-authorized controller owns the writes. There is no default publisher filling these in for you.
- Conditions report; they do not operate. Keep driving scheduling and eviction through
kubectl cordon,kubectl drain, taints, and workload-specific controls. The conditions make that work visible; they are not a second control plane for it.
This restraint is deliberate. KEP-5683, led by SIG Node with the Node Lifecycle Working Group and SIG Apps, ships the vocabulary first so the ecosystem of lifecycle projects — maintenance controllers, remediation systems, autoscalers, storage operators — can converge on one status channel before core controllers start depending on it. A shared signal with no consumers yet is worth more than five vendor-specific annotations with five consumers each, because only the shared one can grow readers without growing translators.
Why a shared signal matters: the inference problem
Node lifecycle touches nearly every component in the cluster: kubelet, the node lifecycle controller, workload controllers, the scheduler, autoscalers, storage operators, and whatever external maintenance system your fleet runs. Today each of them reconstructs "what is happening to this node" from a different mix of readiness, taints, Pod state, labels, annotations, and provider APIs — and those signals do not answer the same question.
A taint can influence scheduling or eviction, but it does not attest that a drain is in progress or that your drain criteria were met. A NotReady node does not explain whether the cause is an unexpected failure, a graceful shutdown, or planned maintenance. Labels and annotations from infrastructure providers fill some gaps, but every provider invents its own, so a consumer written against one fleet's conventions breaks on the next.
When independently correct components infer different stories, they make conflicting decisions. The Kubernetes post names three:
- A DaemonSet controller replaces a Pod that the kubelet intentionally terminated during graceful shutdown — fighting the shutdown it should have respected.
- A Job controller waits indefinitely for a terminal Pod phase on a node an administrator is actively removing — patience as a bug.
- A storage operator learns about maintenance only after the drain has already started — the exact ordering its data-safety logic needed to see first.
None of these components is broken. They are blind in the same specific way: no authoritative, Kubernetes-owned place publishes "this node is draining / under maintenance / shutting down gracefully." The five conditions are that place.
What to change on your Cluster API fleet
On a Cluster API-managed fleet — the shape this site cares about, running on owned machines rather than a managed control plane — lifecycle work already happens constantly: KCP rolling upgrades drain control-plane nodes, MachineDeployment rollouts drain workers, MachineHealthCheck remediates unhealthy machines by cordoning, draining, deleting, and replacing them, and the autoscaler drains nodes on scale-down. All of that machinery works today. What it lacks is a shared record of what it is doing while it does it. Four changes close the gap.
1. Assign one owner per condition, in writing. The post's sharpest operational advice is also the easiest to skip: decide which component owns each lifecycle condition, or you will get conflicting writes. A workable default for a CAPI fleet: your drain wrapper (the script or controller that calls the drain, including CAPI's own remediation path) owns DrainInProgress/Drained; your maintenance scheduler owns MaintenancePlanned; the worker performing the maintenance owns MaintenanceInProgress; your shutdown reporting owns GracefulNodeShutdownInProgress. Owner sets it True when the state starts and False — or removes it — when the state ends. No second writer, no "helpful" cleanup cron from another team.
2. Publish around work you already do; define your drain criteria. Drained means "this node reached the drain criteria selected by the administrator" — the criteria are yours to choose, which means choosing them is now a real runbook decision. A reasonable default: all non-DaemonSet, non-mirror pods evicted or completed, volume detachment confirmed, and the node cordoned. Write that down, then have the same automation assert DrainInProgress when eviction starts and flip to Drained when the criteria hold. Maintenance follows the same cadence: MaintenancePlanned when the window is scheduled (a kernel upgrade that follows a drain and a live patch that does not can finally look different in the API), MaintenanceInProgress when the work starts.
3. Use stable reasons; write messages for the 3 AM reader. Automation matches on reason, humans read message — keep that contract. Reasons should come from a small controlled vocabulary per condition (MaintenanceWindow, KubeletUpgrade, KernelSecurityPatch, HardwareReplacement, RemediationDrain, ScaleDownDrain) so alerts and agents can switch on them without regex archaeology. Messages should say what is happening, who started it, and when it should end: "KCP rolling upgrade draining node, started by upgrade pipeline at 02:14 UTC, expected complete by 02:40 UTC." A lastTransitionTime plus a vague message is barely better than the taint you have today.
4. Rewrite NotReady paging and check the MHC interplay. Today many fleets page on NotReady beyond N minutes, which means every planned drain that overruns its window pages somebody for maintenance going to plan. The new shape: page on NotReady only when no lifecycle condition explains it — NotReady with MaintenanceInProgress=True or DrainInProgress=True is a progress signal to watch, not an incident to wake for. Separately, audit how MachineHealthCheck sees nodes your maintenance owns: MHC remediates machines whose nodes match unhealthy conditions for too long, and a maintenance window that holds a node NotReady past the MHC timeout can trigger a remediation fight — the platform deleting a machine your upgrade is mid-drain on. Until MHC (or your remediation chain) consumes lifecycle conditions, keep maintenance windows inside MHC timeouts or pause the relevant health checks for the window, explicitly and visibly.
The agent-ops payoff: stop making agents infer
Here is where this feature stops being a nicer dashboard and starts being infrastructure. Fleets whose roadmap treats AI agents as operators — agents that read cluster state through MCP servers or read-only APIs, diagnose, and (human-gated or not) remediate — currently hand those agents the same inference problem core controllers have, with higher stakes: an agent that misreads a planned drain as a failure does not just log a wrong conclusion, it acts on it. Cordons a node mid-upgrade. Deletes a machine KCP is replacing. Escalates a page for maintenance going to plan.
Lifecycle conditions turn the agent's hardest judgment call into a lookup. Compare the two policies:
- Before: "Node X is NotReady. Check taints, check for terminating pods, check recent events, check whether an upgrade pipeline is running somewhere outside the cluster, guess whether this is failure or intent — then decide."
- After: "Node X is NotReady with
MaintenanceInProgress=True, reasonKubeletUpgrade. This is intent. Watch and report. Node Y is NotReady with no lifecycle condition at all. This is the failure. Investigate."
Three concrete steps make that real. First, give your agents Node condition reads as a first-class capability, not something they reconstruct from five other calls — the condition type, status, reason, and lastTransitionTime should be in the default node summary your agent tooling returns. Second, write the check-before-remediate rule into the agent's policy, not its prompt folklore: no destructive node action (cordon, drain, delete, remediate) while an explaining lifecycle condition is True unless the runbook explicitly covers that combination. Third, keep the status-only guardrail the Kubernetes post insists on: agents consume conditions to decide, but they still act through the same cordon/drain/taint mechanisms humans use — a condition is evidence, never a trigger to bypass the operation path.
The honest caveat: conditions are only as trustworthy as their publishers, and today the publishers are you. An agent that trusts Drained while your drain wrapper sets it optimistically is worse off than one that checked pod state directly. The step 1 ownership map and the step 3 reason vocabulary are what make the agent policy safe — machine-readable state earns machine actors only when a single owner keeps it truthful.
What comes next
The September 9 post sketches the roadmap beyond the vocabulary release, and the most concrete near-term item is a DaemonSet rollout edge case operators will recognize instantly: a node that is broken or under maintenance stays unavailable for reasons unrelated to the new DaemonSet revision, yet still consumes the rollout's availability budget — slowing or blocking progress on healthy nodes. The DaemonSet controller cannot distinguish "the new revision failed" from "an administrator took this node out of service." MaintenanceInProgress gives future work a Kubernetes-owned place to publish that context so rollout ordering, availability accounting, and status reporting can account for it without manual babysitting. That behavior still needs careful design; the condition is the prerequisite, not the fix.
Longer-term, the post is candid that lifecycle coordination may need explicit ownership, locking, and potentially a dedicated API — conditions are the status channel, not the coordination protocol. Follow the work through KEP-5683 and the Node Lifecycle Working Group, SIG Node, and SIG Apps if your fleet's maintenance story depends on where this goes.
The through line is worth naming: Kubernetes is learning to describe intent, not just state. Readiness says what a node looks like; lifecycle conditions say what is being done to it and why. That distinction is exactly what an agent operator — or a tired human at 3 AM — needs most.
Bex.co is the open-source, AI-native Render alternative — push a git repo, get a running HTTPS service on machines you own. Agents are first-class operators there too: machine-readable fleet state is the whole game. Star the repo on GitHub or deploy your first app today.



