✦ For everyone, free.

Practical knowledge for real and everyday life

Home

Kubernetes Custom Resource Lifecycle

Kubernetes Custom Resource Lifecycle manages creation, updates, and deletion of custom resources, extending Kubernetes API for infrastructure and operations.

Kubernetes Custom Resource Lifecycle is the ordered sequence of states a single custom resource instance passes through from initial submission to final removal from etcd, encompassing admission, persistence, reconciliation, readiness, update handling, deletion initiation, finalization, and garbage collection as a single coherent state progression rather than a set of independent operational tasks.


Phase One: Admission

Schema Validation and Mutation

Before an object is ever persisted, it passes through the same admission chain as any Kubernetes resource: schema and CEL validation, then any registered mutating webhooks (which may inject defaults or normalize fields), then any validating webhooks, in that fixed order.

apiVersion: databases.example.com/v1
kind: PostgresCluster
metadata:
  name: orders-db
spec:
  replicas: 3

A request rejected at this phase never reaches the next; the object does not exist in any form and no controller is ever made aware of the attempted creation.


Phase Two: Persistence

Writing to etcd via the Storage Version

Once admission succeeds, the object is converted (if necessary) to the CRD's designated storage version and written to etcd, at which point it becomes visible to LIST and WATCH calls, and metadata.generation is initialized to 1.

metadata.generation = 1

Phase Three: Initial Reconciliation

The Controller Observes Creation

The owning controller's informer receives an ADDED event, triggering the first reconcile pass; because the reconcile loop is level-triggered, this pass reads the full current spec rather than treating it as an incremental change, and typically creates the dependent objects (a StatefulSet, a Service, a Secret) needed to bring the desired state into existence.

func (r *PostgresClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    var cluster databasesv1.PostgresCluster
    r.Get(ctx, req.NamespacedName, &cluster)
    r.ensureStatefulSet(ctx, &cluster)
    r.ensureService(ctx, &cluster)
    cluster.Status.ObservedGeneration = cluster.Generation
    return ctrl.Result{}, r.Status().Update(ctx, &cluster)
}

Reaching Ready State

Subsequent reconcile passes update status.conditions as dependent objects converge toward their own ready state (a StatefulSet reporting all replicas available, for instance), until the custom resource's own Available condition transitions to True, marking the instance operationally ready.

status:
  observedGeneration: 1
  conditions:
    - type: Available
      status: "True"
      reason: AllReplicasReady

Phase Four: Update Handling

Spec Changes Increment Generation

kubectl patch postgrescluster orders-db --type merge -p '{"spec":{"replicas":5}}'

Any change to spec increments metadata.generation, triggering a new MODIFIED watch event and a fresh reconcile pass; because status.observedGeneration was previously set to the prior generation, comparing the two values lets both the controller and any external client detect that reconciliation of the new desired state is still in progress.

Reconciling generation > status.observedGeneration

Status-Only Updates Do Not Advance Generation

Because status is a separate subresource, controller-written status updates do not increment metadata.generation, meaning the generation counter reflects only user-declared intent, not the controller's own progress, preserving a clean signal of "how many times has intent changed" distinct from "how many times has the controller written status."


Phase Five: Deletion Initiation

Setting the Deletion Timestamp

kubectl delete postgrescluster orders-db

A DELETE request against an object holding one or more finalizers does not remove it; the API server instead sets metadata.deletionTimestamp, and the object remains listable and watchable in a terminating state, still subject to reconciliation but now understood by the controller as "being torn down" rather than "being created or updated."

if !cluster.DeletionTimestamp.IsZero() {
    return r.handleDeletion(ctx, &cluster)
}

Phase Six: Finalization

External Cleanup Before Removal

The controller's deletion-handling path performs any necessary external cleanup (deleting cloud snapshots, deregistering external DNS, revoking issued credentials) and only then removes its finalizer entries from metadata.finalizers.

func (r *PostgresClusterReconciler) handleDeletion(ctx context.Context, cluster *databasesv1.PostgresCluster) (ctrl.Result, error) {
    if err := r.deleteExternalBackups(ctx, cluster); err != nil {
        return ctrl.Result{}, err
    }
    controllerutil.RemoveFinalizer(cluster, "databases.example.com/cleanup-backups")
    return ctrl.Result{}, r.Update(ctx, cluster)
}

Phase Seven: Garbage Collection and Final Removal

Dependents Removed via ownerReferences

Once every finalizer is cleared, the API server permanently removes the object from etcd, and the built-in garbage collector concurrently removes every dependent object whose ownerReferences pointed to it, completing the lifecycle with no remaining trace of either the custom resource instance or its generated dependents.

metadata:
  ownerReferences:
    - apiVersion: databases.example.com/v1
      kind: PostgresCluster
      name: orders-db
      controller: true

Relationship to Custom Resource Management and the Extension Model

The lifecycle described here is the temporal thread running through every concern covered under custom resource management — labeling, ownership, finalizers — and it is the concrete, phase-by-phase instantiation of the abstract reconciliation pattern defined by the broader Kubernetes extension model: admission, persistence, reconciliation, update, deletion, finalization, and garbage collection are the fixed sequence every custom resource instance passes through, regardless of which specific Operator or controller implementation manages it.

Admission Reconcile Ready Delete GC