✦ For everyone, free.

Practical knowledge for real and everyday life

Home

Kubernetes Custom Resource Management

Kubernetes Custom Resource Management extends Kubernetes' capabilities by enabling tailored resource definitions for specialized workloads and infrastructure needs.

Kubernetes Custom Resource Management is the practice of operating individual instances of a custom resource type once its CRD is installed — creating, updating, deleting, and garbage-collecting specific objects such as a particular PostgresCluster named orders-db — distinct from managing the CRD's own schema and lifecycle, and concerned instead with instance-level concerns like finalizers, ownership, labeling conventions, and safe deletion.


Instance Lifecycle Basics

Creating and Updating Instances

apiVersion: databases.example.com/v1
kind: PostgresCluster
metadata:
  name: orders-db
  namespace: production
  labels:
    app.kubernetes.io/managed-by: postgres-operator
    team: payments
spec:
  replicas: 3
  storageSize: 100Gi
kubectl apply -f orders-db.yaml
kubectl patch postgrescluster orders-db -n production --type merge -p '{"spec":{"replicas":5}}'

Instance updates flow through the same admission chain — schema validation, CEL rules, webhooks — as any other write, and once accepted, trigger the owning controller's reconcile loop to converge actual cluster state toward the newly declared spec.

Standard Labeling Conventions

metadata:
  labels:
    app.kubernetes.io/name: postgrescluster
    app.kubernetes.io/instance: orders-db
    app.kubernetes.io/managed-by: postgres-operator

Applying the recommended Kubernetes labels consistently across custom resource instances allows generic tooling, dashboards, and cost-attribution systems to group and query custom resources alongside built-in ones, using the same label keys regardless of resource kind.


Finalizers and Controlled Deletion

Why Finalizers Are Necessary

A custom resource instance frequently has external state associated with it that the API server itself knows nothing about, a cloud provider snapshot, an external DNS record, a billing account; a finalizer prevents the object from being fully removed from etcd until the owning controller has performed that external cleanup.

metadata:
  finalizers:
    - databases.example.com/cleanup-backups
if !cluster.DeletionTimestamp.IsZero() {
    if containsFinalizer(cluster, "databases.example.com/cleanup-backups") {
        if err := deleteExternalBackups(ctx, cluster); err != nil {
            return ctrl.Result{}, err
        }
        removeFinalizer(cluster, "databases.example.com/cleanup-backups")
        r.Update(ctx, cluster)
    }
}

The Deletion Timestamp Grace Period

kubectl delete postgrescluster orders-db -n production

A DELETE request against an object with a finalizer does not remove it immediately; instead, the API server sets metadata.deletionTimestamp and the object remains visible (in a terminating state) until every finalizer is removed by its owning controller, meaning an object stuck in this state for an unexpectedly long time typically indicates the controller's cleanup logic is failing or its finalizer removal path is unreachable.

Object Removed deletionTimestamp set finalizers =

Ownership and Garbage Collection

ownerReferences Between Instances

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: orders-db-primary
  ownerReferences:
    - apiVersion: databases.example.com/v1
      kind: PostgresCluster
      name: orders-db
      uid: 3b1e2c4a-...
      controller: true
      blockOwnerDeletion: true

Objects a controller creates on behalf of a custom resource instance (a StatefulSet, a Service, a Secret) carry an ownerReferences entry pointing back to that instance, which is what lets the built-in garbage collector automatically remove all of them the moment the owning PostgresCluster is deleted, without the controller needing bespoke cleanup logic for its own generated objects, as distinct from external, non-Kubernetes state which still requires a finalizer.

blockOwnerDeletion Semantics

Setting blockOwnerDeletion: true prevents the owner from being deleted via foreground cascading deletion until the dependent object itself is removed, an ordering guarantee occasionally needed when a dependent must finish its own cleanup before the parent instance can be considered fully gone.


Multi-Instance Operational Practices

Bulk Inspection Across Instances

kubectl get postgresclusters --all-namespaces -o custom-columns=NAME:.metadata.name,NS:.metadata.namespace,READY:.status.conditions[?(@.type==\"Available\")].status

Because custom resources support the same label selectors, field selectors (where indexed), and output formatting as built-in resources, fleet-wide inspection and bulk operations work identically once the CRD is installed, without any custom resource type requiring bespoke tooling for routine querying.

Instance-Level RBAC Delegation

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: payments-team-postgres-editor
  namespace: production
subjects:
  - kind: Group
    name: payments-team
roleRef:
  kind: Role
  name: postgrescluster-editor
  apiGroup: rbac.authorization.k8s.io

Delegating instance management to specific teams via namespace-scoped RoleBindings is the standard multi-tenant pattern, granting a team full control over their own custom resource instances within their namespace without any access to another team's instances or to the CRD's schema itself.


Relationship to CRD Management and Scope Management

Custom resource management operates entirely at the level of the objects a CRD makes possible, presupposing the design decisions already fixed by CRD scope management (namespaced versus cluster) and governed in structure by CRD spec structure; where those areas define what instances can look like and where they can exist, custom resource management is the daily operational discipline of creating, updating, safely deleting, and delegating access to the instances themselves.

PostgresCluster StatefulSet Service Secret