Lesson 4.1: Conditions and Status Management

Introduction

In Module 3, you learned basic status updates. Now let’s implement proper status management using conditions - the Kubernetes-standard way to report resource state. Conditions provide structured, machine-readable status that both humans and automation can understand.

What are Conditions?

Conditions are structured status information that follows a standard pattern:

graph TB
    CONDITION[Condition]
    
    CONDITION --> TYPE[Type: Ready]
    CONDITION --> STATUS[Status: True/False/Unknown]
    CONDITION --> REASON[Reason: PodReady]
    CONDITION --> MESSAGE[Message: All pods ready]
    CONDITION --> LAST_TRANSITION[LastTransitionTime]
    
    style CONDITION fill:#FFB6C1
    style STATUS fill:#90EE90

Condition Structure

type Condition struct {
    Type               string    // e.g., "Ready", "Progressing"
    Status             string    // "True", "False", "Unknown"
    Reason             string    // Short reason code
    Message            string    // Human-readable message
    LastTransitionTime time.Time // When status changed
    ObservedGeneration int64     // Generation this applies to
}

Common Condition Types

Kubernetes defines standard condition types:

graph LR
    READY[Ready]
    PROGRESSING[Progressing]
    DEGRADED[Degraded]
    STALLED[Stalled]
    
    READY --> TRUE[True: Resource ready]
    READY --> FALSE[False: Not ready]
    
    PROGRESSING --> TRUE2[True: Work in progress]
    PROGRESSING --> FALSE2[False: Not progressing]
    
    style READY fill:#90EE90
    style PROGRESSING fill:#FFB6C1
  • Ready: Resource is ready to serve traffic/work
  • Progressing: Work is actively being done
  • Degraded: Resource is working but in degraded state
  • Stalled: Progress has stopped

Status Subresource

Remember from Module 1 and Module 3: status is a subresource.

graph LR
    RESOURCE[Custom Resource] --> SPEC[spec]
    RESOURCE --> STATUS[status subresource]
    
    SPEC --> USER[User writes]
    STATUS --> CONTROLLER[Controller writes]
    
    STATUS --> CONDITIONS[conditions]
    STATUS --> PHASE[phase]
    STATUS --> OBSERVED[observedGeneration]
    
    style STATUS fill:#FFB6C1
    style CONDITIONS fill:#90EE90

Condition Lifecycle

Conditions transition through states:

stateDiagram-v2
    [*] --> Unknown
    Unknown --> True: Resource ready
    Unknown --> False: Resource not ready
    True --> False: Resource degraded
    False --> True: Resource recovered
    True --> [*]
    False --> [*]

Implementing Conditions

Step 1: Add Conditions to Status

// DatabaseStatus defines the observed state of Database
type DatabaseStatus struct {
    // Conditions represent the latest observations
    Conditions []metav1.Condition `json:"conditions,omitempty"`
    
    // Phase is a simple status indicator
    Phase string `json:"phase,omitempty"`
    
    // ObservedGeneration tracks the generation this status applies to
    ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

Step 2: Helper Functions

import (
    "k8s.io/apimachinery/pkg/api/meta"
    metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// setCondition sets a condition on the Database
func (r *DatabaseReconciler) setCondition(db *databasev1.Database, conditionType string, status metav1.ConditionStatus, reason, message string) {
    condition := metav1.Condition{
        Type:               conditionType,
        Status:             status,
        Reason:             reason,
        Message:            message,
        LastTransitionTime: metav1.Now(),
        ObservedGeneration: db.Generation,
    }
    
    meta.SetStatusCondition(&db.Status.Conditions, condition)
}

// getCondition gets a condition by type
func (r *DatabaseReconciler) getCondition(db *databasev1.Database, conditionType string) *metav1.Condition {
    return meta.FindStatusCondition(db.Status.Conditions, conditionType)
}

Step 3: Update Conditions in Reconcile

func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    // ... read Database ...
    
    // Check StatefulSet status
    statefulSet := &appsv1.StatefulSet{}
    err := r.Get(ctx, client.ObjectKey{
        Name:      db.Name,
        Namespace: db.Namespace,
    }, statefulSet)
    
    if errors.IsNotFound(err) {
        r.setCondition(db, "Ready", metav1.ConditionFalse, "StatefulSetNotFound", "StatefulSet not found")
        r.setCondition(db, "Progressing", metav1.ConditionTrue, "Creating", "Creating StatefulSet")
        return ctrl.Result{}, r.Status().Update(ctx, db)
    }
    
    // Check if ready
    if statefulSet.Status.ReadyReplicas == *statefulSet.Spec.Replicas {
        r.setCondition(db, "Ready", metav1.ConditionTrue, "AllReplicasReady", "All replicas are ready")
        r.setCondition(db, "Progressing", metav1.ConditionFalse, "ReconciliationComplete", "Reconciliation complete")
    } else {
        r.setCondition(db, "Ready", metav1.ConditionFalse, "ReplicasNotReady", 
            fmt.Sprintf("%d/%d replicas ready", statefulSet.Status.ReadyReplicas, *statefulSet.Spec.Replicas))
        r.setCondition(db, "Progressing", metav1.ConditionTrue, "Scaling", "Waiting for replicas to be ready")
    }
    
    // Update status
    db.Status.ObservedGeneration = db.Generation
    return ctrl.Result{}, r.Status().Update(ctx, db)
}

Status Update Strategies

Strategy 1: Update on Every Reconcile

// Update status every time
return ctrl.Result{}, r.Status().Update(ctx, db)

Pros: Always current
Cons: Can cause conflicts with rapid updates

Strategy 2: Update Only on Changes

// Only update if conditions changed
if conditionsChanged {
    return ctrl.Result{}, r.Status().Update(ctx, db)
}

Pros: Reduces conflicts
Cons: More complex logic

Strategy 3: Periodic Updates

// Update status periodically
if time.Since(lastStatusUpdate) > 30*time.Second {
    return ctrl.Result{}, r.Status().Update(ctx, db)
}

Pros: Reduces API calls
Cons: Status may be slightly stale

Condition State Machine

For complex resources, use a state machine:

stateDiagram-v2
    [*] --> Pending
    Pending --> Creating: Start creation
    Creating --> Ready: Creation complete
    Creating --> Failed: Creation failed
    Ready --> Updating: Update requested
    Updating --> Ready: Update complete
    Updating --> Failed: Update failed
    Failed --> Ready: Recovery successful
    Ready --> Deleting: Delete requested
    Deleting --> [*]

Reporting Progress

Use Progressing condition to show progress:

// During creation
r.setCondition(db, "Progressing", metav1.ConditionTrue, "CreatingStatefulSet", "Creating StatefulSet")

// After StatefulSet created
r.setCondition(db, "Progressing", metav1.ConditionTrue, "WaitingForPods", "Waiting for pods to be ready")

// When complete
r.setCondition(db, "Progressing", metav1.ConditionFalse, "ReconciliationComplete", "Reconciliation complete")

Error Reporting

Report errors with conditions:

if err != nil {
    r.setCondition(db, "Ready", metav1.ConditionFalse, "Error", err.Error())
    r.setCondition(db, "Progressing", metav1.ConditionFalse, "Error", "Reconciliation failed")
    return ctrl.Result{}, r.Status().Update(ctx, db)
}

Key Takeaways

  • Conditions provide structured, standard status reporting
  • Use standard condition types (Ready, Progressing, etc.)
  • LastTransitionTime tracks when status changed
  • ObservedGeneration tracks which spec generation status applies to
  • Update conditions based on actual resource state
  • Use state machines for complex workflows
  • Report progress with Progressing condition
  • Report errors clearly with conditions

Understanding for Building Operators

When implementing conditions:

  • Use meta.SetStatusCondition for updates
  • Track observed generation
  • Update on state changes
  • Use standard condition types
  • Provide clear reasons and messages
  • Handle conflicts gracefully

References

Official Documentation

Further Reading

  • Kubernetes Operators by Jason Dobies and Joshua Wood - Chapter 5: Status and Conditions
  • Programming Kubernetes by Michael Hausenblas and Stefan Schimanski - Chapter 6: Status Management
  • Kubernetes API Conventions - Status

Next Steps

Now that you understand status management, let’s learn about finalizers for graceful cleanup.