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.
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.