✦ For everyone, free.

Practical knowledge for real and everyday life

Home

Kubernetes Custom Resource Status Handling

Kubernetes Custom Resource Status Handling tracks resource health with structured status fields, ensuring consistent state across clusters.

Kubernetes Custom Resource Status Handling is the discipline of designing and implementing what a controller writes into a custom resource's status field, covering the conditions pattern, phase fields, transition-time management, avoidance of status flapping, and the practical constraints on status size and update frequency that shape how much and how often a controller should report.


Conditions as the Primary Status Pattern

Anatomy of a Condition

status:
  conditions:
    - type: Available
      status: "True"
      reason: AllReplicasReady
      message: "3/3 replicas are ready and serving traffic"
      lastTransitionTime: "2024-06-01T10:00:00Z"
      observedGeneration: 4

Each condition entry pairs a type (a specific aspect being reported, such as Available, Progressing, or Degraded) with a tri-state status (True, False, or Unknown), a machine-readable reason in PascalCase suitable for programmatic branching, a human-readable message, and lastTransitionTime, which must only update when status itself changes, not on every reconcile pass that leaves the condition's truth value unchanged.

func setCondition(conditions *[]metav1.Condition, newCond metav1.Condition) {
    existing := meta.FindStatusCondition(*conditions, newCond.Type)
    if existing != nil && existing.Status == newCond.Status {
        newCond.LastTransitionTime = existing.LastTransitionTime
    }
    meta.SetStatusCondition(conditions, newCond)
}

Why Multiple Independent Conditions Beat a Single Phase

Reporting Available, Progressing, and Degraded as three independent conditions, rather than collapsing status into a single phase enum, allows a resource to correctly represent states that a single value cannot, such as "available, but currently rolling out a change" (Available: True, Progressing: True) versus "not yet available" (Available: False, Progressing: True), which a flat phase field would force into a single ambiguous value.

State Space = 2 n , n = number of conditions

Avoiding Status Flapping

The Cost of Unstable Status

If a controller's reconcile logic is sensitive to transient conditions (a brief network blip during a health check, a momentarily unready pod during a rolling update), naively reporting Available: False on every such blip produces status that flaps rapidly between True and False, which is disruptive to any dependent automation watching that condition, such as a kubectl wait call or a GitOps promotion gate.

Debounce and Hysteresis Patterns

if consecutiveFailures < 3 {
    return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
setCondition(&cluster.Status.Conditions, metav1.Condition{
    Type:   "Available",
    Status: metav1.ConditionFalse,
    Reason: "HealthCheckFailedRepeatedly",
})

Requiring a condition to be observed consistently across several reconcile passes before flipping its reported status, rather than reacting to a single failed observation, is the standard technique for producing stable status that reflects sustained state changes rather than noise.


observedGeneration and Staleness

Reporting Which Generation Was Reconciled

status:
  observedGeneration: 4
  conditions:
    - type: Available
      status: "True"

Every controller that writes status should also write status.observedGeneration set to the metadata.generation it actually reconciled, so that any consumer of the status (a human, kubectl wait, or a dependent automation) can detect whether the displayed status reflects the current desired state or is stale because a more recent spec change has not yet been processed.

kubectl get postgrescluster orders-db -o jsonpath='{.metadata.generation} vs {.status.observedGeneration}'

Size and Update-Frequency Constraints

etcd Object Size Limits

Because status is stored as part of the same object in etcd, which enforces a default maximum object size of 1.5MB, status must remain a compact summary, not a full log of everything a controller has observed; embedding verbose historical event data or large nested structures in status risks approaching that limit and degrading LIST/WATCH performance for every client of that resource type, not just the one instance affected.

Update Frequency and API Server Load

return ctrl.Result{RequeueAfter: 30 * time.Second}, nil

Writing status on every reconcile pass regardless of whether anything changed generates unnecessary etcd writes and watch events across every client watching the resource; comparing the computed status against the currently stored status before issuing an update, and skipping the write when nothing has changed, is standard practice to avoid needlessly amplifying write load at scale.


Aggregating Status from Dependent Objects

Deriving Status from Owned Resources

sts := &appsv1.StatefulSet{}
r.Get(ctx, client.ObjectKey{Name: cluster.Name, Namespace: cluster.Namespace}, sts)
ready := sts.Status.ReadyReplicas == *sts.Spec.Replicas

A custom resource's own status is typically derived by inspecting the status of the built-in resources it owns (a StatefulSet's readyReplicas, a Service's endpoint count) rather than independently determined, meaning custom resource status handling is frequently a translation and aggregation layer summarizing several underlying built-in resources' status into the higher-level conditions a user actually cares about.


Relationship to Custom Resource Lifecycle and Subresource Management

Status handling is the specific content-design discipline applied within the status subresource mechanism described under CRD subresource management, and it produces the observable signal that marks the transition between the reconciliation and readiness phases of the custom resource lifecycle: a well-designed status implementation, stable, generation-aware, and appropriately compact, is what makes every later phase of that lifecycle legible to humans and automation alike, while a poorly designed one leaves even a functionally correct controller appearing unreliable or opaque.

Available Progressing Degraded status.conditions[]