Skip to main content

Events, Reasons and Conditions

What the operator publishes about one episode, on two objects: the claim reports the request, the slot reports the machine. Read the claim for the timeline of an attempt and the slot for what the machine did; kubectl describe is the entry point for both.

The durable record is not the event stream. Events expire with the cluster's Event TTL — about one hour by default — while NodeClaim.status.conditions, NodeClaim.status.reason and Node.status.lastClaim live as long as their object does, and the slot's digest outlives the claim that was collected by its TTL. So:

You wantReadIt lasts
what just happened, in orderkubectl describe nodeclaim <claim>about an hour
what the machine didkubectl describe node.infra.homecluster.dev <slot>about an hour
what is true about the last attemptstatus.reason, status.conditions, Node.status.lastClaimas long as the objects do
the shape of the pool a day laterkubectl get groups, kubectl describe node.infra.homecluster.dev (recentTransitions)the field's own bound
# One attempt's timeline, oldest first
kubectl describe nodeclaim worker-01-7f4b9c -n homelab-autoscaler-system

# The claim stream of the pool
kubectl get events -n homelab-autoscaler-system \
--field-selector involvedObject.kind=NodeClaim --sort-by=.lastTimestamp

# One claim's events
kubectl get events -n homelab-autoscaler-system \
--field-selector involvedObject.kind=NodeClaim,involvedObject.name=worker-01-7f4b9c

# The slot's events: what the machine was asked to do
kubectl describe nodes.infra.homecluster.dev worker-01 -n homelab-autoscaler-system

Claim events​

Every one of the Claim* rows below is published on the NodeClaim, carries the action nodeclaim, and is written at the one chokepoint that owns the phase writes — after the status write landed. A move the phase table refuses, a write the API server refused, and a re-apply that changed nothing publish nothing, so a timeline never reports a transition the cluster does not have. Repeats of one fact patch a single Event's count and lastTimestamp instead of adding an object per pass, so a resync loop cannot storm the stream; kubectl get events -o wide shows the action. The last two rows are the slot reconciler's: published on the claim that carries the count, with the action PowerOff.

ReasonTypeWhenThe note carries
ClaimCreatedNormalthe claim exists — the first write of a fresh claim, for every creator: the provider, a hand-applied claim, a replayrequestedBy, the call and its delta / nodes, the group, the attempt number, the deadline and the budget
ClaimBootingNormalpending → bootingthe startup Job of the attempt (or the label selector that finds it), and that the boot is committed and no longer cancelable
ClaimReadyNormalbooting → readythe Kubernetes node that passed the readiness gate, and the time from spec.requestedAt; the move clears spec.deadlineAt on a second write behind the status one (claim.Recorder.Move, then clearBootDeadline)
ClaimFailedWarningany move to failedstatus.reason and the number behind it: the deadline that passed, the Job that died, the attempts spent, the next attempt's number
ClaimCancelledNormala live claim a caller cancelled reached its end (CancelledByDecreaseTargetSize, NodeDeleted, DeletedByHand)which call asked, its delta / nodes, whether a machine had been bought, and how long the attempt lasted
ClaimCompletedNormala move to completed with no cancel behind itthat the machine is observed off, the duration, and the shutdown Job that powered it off when one is readable
ClaimSupersededNormala move to failed with reason: Supersededthe newer claim's name — the request that owns the slot now
ShutdownAttemptLimitReachedWarninga scale-down has spent its 3 shutdown attempts on a machine still observed up, and the controller stops firing itthe attempts, the bound, and how long the pause lasts
ShutdownAttemptStreakResetWarningthe reset grace passed and the controller fires the shutdown againthe bound spent, the grace, and that a fixed script is picked up without a hand edit

ClaimCancelled and ClaimSuperseded are Normal, not Warning: a cancel is a request landing, and a superseded extra claim is the one-live-claim-per-slot rule holding.

Slot events​

These are published on the Node CR — the slot — and say what the machine was asked to do and what happened to it. The Action column is what kubectl get events -o wide prints.

ReasonTypeActionWhen
ScaleUpRequestedNormalPowerOnthe startup transition started: the slot was claimed and the power-on was asked for
ScaleUpCompletedNormalPowerOnthe startup Job completed and the transition into ready finished
ScaleUpFailedWarningPowerOnthe startup Job failed, timed out, or the transition was force-cleaned
ScaleUpDeferredNormalPowerOnthe boot is deferred: the coordination lock is held by the opposite operation, and the requeue is paced to the lock's own deadline
ScaleDownRequestedNormalPowerOffthe shutdown transition started: the node was cordoned and the power-off was asked for
ScaleDownCompletedNormalPowerOffthe shutdown Job completed and the machine is observed off
ScaleDownFailedWarningPowerOffthe shutdown Job failed, timed out, or the transition was force-cleaned
ScaleDownDeferredNormalPowerOffthe shutdown is deferred behind a lock; the intent stays on the live claim, so nothing has to be asked for again
ReadinessGateTimeoutWarningUncordonthe gate's 4 minutes expired: the node was uncordoned anyway (a cordoned machine is invisible to scale-down and is paid for forever) and the claim failed with that reason
DeclaredNodeLabelsUnwrittenWarningUncordonthe gate refused the node because the labels and annotations its slot declares could not be written, so the machine stays cordoned while the write is retried. Recorded once per gate, not per retry
NodeLabelsAppliedNormalApplyNodeLabelsa write of the slot's declared keys landed on the node object: + added, ~ rewritten, - removed
SkippedReservedNodeLabelKeyWarningApplyNodeLabelsa declared key in a namespace its owner controls was skipped at write time, for a slot that predates the admission rule. Fired when the set of refused keys changes
NodeCapacityMismatchWarningCheckDeclaredCapacitythe registered machine's status.allocatable sits more than 5% from spec.nodeCapacity on a declared resource, naming the resource and both values. Only declared resources are compared
AutoscalerDeletionCandidateNormalAutoscalerDecisionthe node carries DeletionCandidateOfClusterAutoscaler: cluster-autoscaler is evaluating it for scale-down
AutoscalerDeletionRequestedNormalAutoscalerDecisionthe node carries ToBeDeletedByClusterAutoscaler: it was marked for deletion and is draining

The last two are reports of a decision the autoscaler made, not of an action this operator took: it never adds, edits, or removes a cluster-autoscaler marker. Date the decision from the condition's message (observed at ...), not from its lastTransitionTime — both markers are recorded with Status: True, so the candidate→requested transition keeps the candidate's time.

Messages are flattened onto one line and cut at 512 characters: a Job failure or a webhook denial carries a multi-line payload, and an Event is a real object kept in etcd.

Claim reasons​

NodeClaim.status.reason is the one-line outcome, mirrored into Node.status.lastClaim.reason. The set is open, and the names are stable:

ReasonWhat happened
DeadlineExceededspec.deadlineAt passed while the claim was pending or booting; the machine is force-shut-down
StartupJobFailedthe startup Job of the attempt exited non-zero, or could not be created
StartupJobTimeoutthe startup Job of the attempt ran out of time
ShutdownJobFailedthe shutdown Job of a scale-down exited non-zero, so the machine it was to power off is still up
ShutdownJobTimeoutthe shutdown Job of a scale-down ran out of time
StuckTransitionthe lifecycle transition was stuck past 15m and was force-cleaned
ReadinessGateTimeoutthe machine booted and joined but never cleared the pool's readiness gate
AttemptLimitExceededthe slot spent its 3 consecutive attempts; no machine is bought for it until one lands
CancelledByDecreaseTargetSizeDecreaseTargetSize deleted a pending claim
NodeDeletedDeleteNodes named this slot's machine for deletion
MachineGonethe node object of a ready claim disappeared and the machine has been observed off for a boot window
SlotDeletedthe Node CR the claim asked for is going away
DeletedByHandan operator deleted the claim, and the finalizer powered its machine off
Supersededa second live claim was found on one slot, and this one is the extra

Each is explained, with what to do, in A Claim Failed.

Conditions​

Conditions are the durable half. They are stamped by the same writes as the events, and they answer "what is true" rather than "what happened".

On the claim:

ConditionTrue when
Readythe claim's machine can take the pool's work
MachineCommittedthe startup Job exists, i.e. from pending → booting on. On a terminal move it is read off the phase the claim moved from, so a dead claim says whether there is a machine to clean up. It is a record for you, never an instruction to a reconciler
MachineObservedOffthe machine of a ready claim is observed off. Its lastTransitionTime is the instant the loss was first recorded, and the MachineGone bound is measured from it; a reboot clears it and the claim stays ready

On the slot, written by the lifecycle transitions:

ConditionTrue when
StartupProgressinga startup transition started and the machine is being powered on
ShutdownProgressinga shutdown transition started and the machine is being powered off
Readythe slot entered the ready state of the state machine: the machine is on and operational
ShutdownFalse, with reason NodeShutdown, when the slot entered the shut-down state: the machine is off
JobFaileda lifecycle Job of the attempt exited non-zero, naming the transition it moved the slot on
ForceCleanupa stuck transition was force-cleaned past 15m

Plus the two the observation writes:

ConditionWhat it records
ClusterAutoscalerMarkedForDeletionthe last cluster-autoscaler marker this operator read on the node, with the node's taints, spec.unschedulable and the instant it was observed
NodeReadinessTaintsthe taint keys this slot's readiness gate is waiting for, from the Group's spec.nodeReadinessTaints

The Group carries one condition of its own, Loaded: the pool is published to the provider. Its lastTransitionTime dates the moment that status last changed, not the last reconcile pass.