✦ For everyone, free.

Practical knowledge for real and everyday life

Home

Kubernetes Controller Ownership Management

Kubernetes Controller Ownership Management ensures proper resource management by defining ownership relationships within Kubernetes clusters.

Kubernetes Controller Ownership Management is the practice of correctly establishing and interpreting ownerReferences between a custom resource instance and the built-in objects a controller creates on its behalf, governing which controller is authoritative over a given dependent object, how cascading deletion propagates, and how the garbage collector determines what to remove when an owner disappears.


Establishing Ownership

SetControllerReference

if err := controllerutil.SetControllerReference(cluster, statefulSet, r.Scheme); err != nil {
    return ctrl.Result{}, err
}

The SetControllerReference helper populates ownerReferences on a dependent object, setting controller: true to mark the owning PostgresCluster as the authoritative controller, and enforces at most one controller-owner per object, since Kubernetes permits multiple non-controller owner references but only a single controller: true reference, reflecting the assumption that exactly one controller is responsible for a given object's lifecycle.

metadata:
  ownerReferences:
    - apiVersion: databases.example.com/v1
      kind: PostgresCluster
      name: orders-db
      uid: 3b1e2c4a-5f6d-4e8a-9c1b-7d2e3f4a5b6c
      controller: true
      blockOwnerDeletion: true
| { ref ownerReferences : ref.controller = true } | 1

Cross-Namespace Ownership Is Disallowed

An ownerReferences entry can only point to an owner in the same namespace as the dependent object (or to a cluster-scoped owner, for a namespaced dependent); the API server rejects a cross-namespace owner reference at admission, meaning a controller managing resources across multiple namespaces cannot rely on ownerReferences alone to link them and must instead track that relationship through the custom resource's own spec or status fields plus explicit reconcile logic.


Cascading Deletion Propagation

Foreground, Background, and Orphan Policies

kubectl delete postgrescluster orders-db --cascade=foreground
kubectl delete postgrescluster orders-db --cascade=background
kubectl delete postgrescluster orders-db --cascade=orphan

foreground deletion sets a deletion timestamp on the owner but delays its actual removal until every dependent object with blockOwnerDeletion: true has itself been deleted; background (the default) deletes the owner immediately and lets the garbage collector asynchronously clean up dependents afterward; orphan removes only the owner, stripping the ownerReferences link from dependents so they are left behind, functioning independently going forward.

blockOwnerDeletion in Practice

ownerReferences:
  - controller: true
    blockOwnerDeletion: true

Setting blockOwnerDeletion: true, which SetControllerReference does by default, ensures a foreground cascading delete waits for that specific dependent's removal before finalizing the owner's own deletion, an ordering guarantee relevant when a dependent object performs its own cleanup (via a finalizer) that logically must complete before the owner is considered gone.


Garbage Collector Behavior

Watching for Owner Deletion

The garbage collector controller, running as part of the kube-controller-manager, maintains its own graph of ownership relationships across the cluster by watching every resource type's ownerReferences, and reacts to an owner's deletion by queuing every object referencing it (via background propagation by default) for deletion, without any involvement from the custom resource's own controller.

kubectl get statefulsets,services,secrets -l app.kubernetes.io/instance=orders-db

Verifying that dependent objects are actually removed after deleting a custom resource instance, as above, is a useful sanity check when troubleshooting orphaned resources that may indicate a missing or incorrect ownerReferences entry rather than a garbage collector malfunction.

Handling Pre-Existing, Unowned Objects

existing := &corev1.ConfigMap{}
if err := r.Get(ctx, key, existing); err == nil && len(existing.OwnerReferences) == 0 {
    // adopt: existing object was created outside this controller's control
}

When a controller discovers an object matching what it would otherwise create, but lacking an owner reference (perhaps created manually or by a prior, differently versioned controller), reconcile logic must explicitly decide whether to adopt it by adding the appropriate owner reference, or to treat it as a naming conflict and report an error, since silently overwriting an unowned object risks destroying state the controller did not itself create.


Ownership Across Multiple Controllers

Non-Controller Owner References

ownerReferences:
  - apiVersion: databases.example.com/v1
    kind: PostgresCluster
    name: orders-db
    controller: false
  - apiVersion: backup.example.com/v1
    kind: BackupPolicy
    name: standard-daily
    controller: true

An object can carry multiple owner references as long as at most one sets controller: true; this allows an object such as a shared Secret to be garbage-collected when either of two owning resources is deleted (through OR semantics across the reference list) while still designating a single controller as authoritative for reconciling its actual content.


Relationship to Custom Resource Management and Reconciliation

Ownership management is the specific mechanism that makes the automatic dependent-object cleanup described under custom resource management possible, and it is a precondition for the idempotent get-then-create-or-update reconciliation pattern to function correctly at scale: without correct ownerReferences, a controller has no reliable way to distinguish objects it is responsible for from unrelated cluster state, and the garbage collector has no way to keep a custom resource's generated dependents from accumulating indefinitely after the resource itself is removed.

PostgresCluster (owner) StatefulSet (dependent) ownerReferences: controller=true