Skip to content

Status Conditions and ObservedGeneration

Reference documentation for the status reconciliation logic that updates conditions, readyReplicas, and observedGeneration on the Memcached CR after every reconciliation cycle.

Source: internal/controller/status.go, internal/controller/memcached_controller.go

Overview

After reconciling the Deployment and Service, the reconciler updates the Memcached CR's status subresource with three pieces of information:

  1. observedGeneration — the CR's metadata.generation at the time of reconciliation, so operators can detect stale status.
  2. readyReplicas — the number of ready pods from the owned Deployment, surfaced at the CR level for convenience.
  3. Conditions — three standard conditions (Available, Progressing, Degraded) derived from the Deployment's status.

Status is always updated via the status subresource (r.Status().Update) to avoid conflicts with spec writes.


ObservedGeneration

status.observedGeneration is set to metadata.generation on every successful reconciliation. This follows the Kubernetes convention for detecting whether the controller has processed the latest spec.

ScenarioMeaning
observedGeneration == metadata.generationController has acted on the latest spec
observedGeneration < metadata.generationController has not yet processed the latest spec change

The field is updated unconditionally on every reconcile — not just when the spec changes — so it always reflects the most recent reconciliation pass.


ReadyReplicas

status.readyReplicas mirrors the owned Deployment's status.readyReplicas.

Deployment StatereadyReplicas Value
Deployment existsdep.Status.ReadyReplicas
Deployment not found0

This field is also exposed as the Ready printer column via the +kubebuilder:printcolumn marker on the Memcached type, so kubectl get memcached displays it directly.


Conditions

Three conditions are computed on every reconciliation and applied using apimachinery/pkg/api/meta.SetStatusCondition, which correctly handles lastTransitionTime — the timestamp only changes when the condition's Status value changes.

Each condition includes ObservedGeneration matching the CR's metadata.generation at the time of computation (set in computeConditions).

Available

Indicates whether the Memcached instance has minimum availability.

StatusReasonWhen
TrueAvailableDeployment has readyReplicas >= 1 or desired replicas is 0
FalseUnavailableDeployment has readyReplicas == 0 and desired > 0
FalseUnavailableDeployment does not exist yet

Message format: "<ready>/<desired> replicas are ready"

Progressing

Indicates whether a rollout or scaling operation is in progress.

StatusReasonWhen
TrueProgressingDeployment does not exist yet
TrueProgressingupdatedReplicas < desired (rollout in progress)
TrueProgressingtotalReplicas != desired (scaling in/out)
FalseProgressingCompleteupdatedReplicas == desired and totalReplicas == desired

Message format:

  • When Deployment is nil: "Waiting for deployment to be created"
  • When progressing: "Rollout in progress: <updated>/<desired> replicas updated"
  • When complete: "All <desired> replicas are updated"

Degraded

Indicates whether the instance has fewer ready replicas than desired, or whether referenced Secrets are missing.

StatusReasonWhen
TrueSecretNotFoundOne or more referenced Secrets are missing
TrueDegradedreadyReplicas < desired and desired > 0
TrueDegradedDeployment does not exist and desired > 0
FalseNotDegradedreadyReplicas == desired and no missing Secrets
FalseNotDegradeddesired == 0 (intentionally scaled to zero)

SecretNotFound takes precedence over replica-based degraded status. When any referenced Secret (SASL credentials or TLS certificate) cannot be fetched, the Degraded condition is set to SecretNotFound regardless of replica counts.

Message format:

  • When Secrets missing: "Referenced Secrets not found: <name1>, <name2>"
  • When Deployment is nil: "Waiting for deployment to be created"
  • When degraded: "Only <ready>/<desired> replicas are ready"
  • When not degraded: "All <desired> desired replicas are ready"

Edge Cases

Scaled to Zero

When spec.replicas is 0, the cluster is intentionally empty:

ConditionStatusReasonRationale
AvailableTrueAvailableNo availability requirement
ProgressingFalseProgressingCompleteNothing to progress
DegradedFalseNotDegradedDesired state is met

Nil Deployment

If the Deployment does not exist when status is computed (e.g., first reconciliation before reconcileDeployment creates it, or a race condition):

ConditionStatusReasonRationale
AvailableFalseUnavailableNo replicas serving traffic
ProgressingTrueProgressingCreation is an in-progress operation
DegradedTrueDegraded0 ready replicas with desired > 0 (default 1)

Condition Constants

Defined in internal/controller/status.go:

Type Constants

ConstantValue
ConditionTypeAvailable"Available"
ConditionTypeProgressing"Progressing"
ConditionTypeDegraded"Degraded"

Reason Constants

ConstantValue
ConditionReasonAvailable"Available"
ConditionReasonUnavailable"Unavailable"
ConditionReasonProgressing"Progressing"
ConditionReasonProgressingComplete"ProgressingComplete"
ConditionReasonDegraded"Degraded"
ConditionReasonNotDegraded"NotDegraded"
ConditionReasonSecretNotFound"SecretNotFound"

Reconciliation Method

reconcileStatus(ctx, mc *Memcached, missingSecrets []string) on MemcachedReconciler performs the status update. The missingSecrets parameter is the list of Secret names that could not be fetched during deployment reconciliation (returned by reconcileDeployment).

go
func (r *MemcachedReconciler) reconcileStatus(ctx context.Context, mc *memcachedv1alpha1.Memcached, missingSecrets []string) error {
    // 1. Fetch the current Deployment (nil if not found)
    // 2. Compute conditions from Deployment status and missingSecrets
    // 3. Apply conditions via meta.SetStatusCondition
    // 4. Set readyReplicas from Deployment (0 if nil)
    // 5. Set observedGeneration from mc.Generation
    // 6. Update via r.Status().Update(ctx, mc)
}

Error Handling

Error ScenarioBehavior
Deployment fetch fails (not NotFound)Error wrapped and returned for requeue
Status update conflictError returned, controller-runtime requeues with backoff
Status update transient failureError returned, controller-runtime requeues with backoff

Status update errors do not affect resource reconciliation — the Deployment and Service are reconciled before status, so they converge independently.


Reconcile Integration

The Reconcile method calls reconcileStatus after all resource reconciliation:

go
func (r *MemcachedReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // 1. Fetch Memcached CR
    // 2. missingSecrets, err := reconcileDeployment  ← resource convergence + secret resolution
    // 3. reconcileService                             ← resource convergence
    // 4. reconcileStatus(ctx, mc, missingSecrets)     ← status update (last)
}

reconcileDeployment returns the list of missing Secret names alongside the error, so they can be forwarded to reconcileStatus for the SecretNotFound condition.

This ordering ensures:

  • Resources converge even if status update fails on a subsequent requeue.
  • Status reflects the state after the latest resource reconciliation.
  • A failed status update returns an error, triggering a requeue to retry.

Reconciliation Flow

text
  Memcached CR created/updated


  ┌─────────────────────────────┐
  │  Reconcile                  │
  │  1. Fetch Memcached CR      │
  │  2. reconcileDeployment     │
  │  3. reconcileService        │
  └────────────┬────────────────┘


  ┌─────────────────────────────┐
  │  reconcileStatus            │
  │                             │
  │  1. Fetch Deployment        │
  │     ├─ Found → use status   │
  │     └─ NotFound → nil       │
  │                             │
  │  2. computeConditions       │
  │     (with missingSecrets)   │
  │     ├─ Available            │
  │     ├─ Progressing          │
  │     └─ Degraded /           │
  │        SecretNotFound       │
  │                             │
  │  3. meta.SetStatusCondition │
  │     (preserves transition   │
  │      timestamps)            │
  │                             │
  │  4. Set readyReplicas       │
  │  5. Set observedGeneration  │
  │  6. r.Status().Update()     │
  └─────────────────────────────┘

Status Example

A Memcached CR with 3 replicas where 2 are ready:

yaml
apiVersion: memcached.c5c3.io/v1alpha1
kind: Memcached
metadata:
  name: my-cache
  namespace: default
  generation: 2
spec:
  replicas: 3
status:
  observedGeneration: 2
  readyReplicas: 2
  conditions:
    - type: Available
      status: "True"
      reason: Available
      message: "2/3 replicas are ready"
      lastTransitionTime: "2024-01-15T10:30:00Z"
      observedGeneration: 2
    - type: Progressing
      status: "True"
      reason: Progressing
      message: "Rollout in progress: 3/3 replicas updated"
      lastTransitionTime: "2024-01-15T10:29:00Z"
      observedGeneration: 2
    - type: Degraded
      status: "True"
      reason: Degraded
      message: "Only 2/3 replicas are ready"
      lastTransitionTime: "2024-01-15T10:29:00Z"
      observedGeneration: 2

Fully Ready State

yaml
status:
  observedGeneration: 2
  readyReplicas: 3
  conditions:
    - type: Available
      status: "True"
      reason: Available
      message: "3/3 replicas are ready"
    - type: Progressing
      status: "False"
      reason: ProgressingComplete
      message: "All 3 replicas are updated"
    - type: Degraded
      status: "False"
      reason: NotDegraded
      message: "All 3 desired replicas are ready"