Kubernetes Extension Model
Kubernetes Extension Model extends Kubernetes with custom controllers, enabling advanced automation and integration beyond core capabilities.
Kubernetes Extension Model is the underlying architectural pattern that makes Kubernetes extensibility coherent across its many surfaces: a declarative control loop built on watch-based synchronization, where new behavior is added by introducing new desired-state objects and new controllers that reconcile them, rather than by branching or modifying the core API server's imperative request-handling code.
The Reconciliation Pattern as the Model's Core
Desired State, Observed State, and Convergence
Every extension built on the Kubernetes model follows the same structural loop: a controller reads the desired state expressed in a resource's spec, compares it against the actual observed state of the system, and issues the minimal set of actions needed to converge the two, then repeats this indefinitely rather than executing once and exiting.
for {
desired := getDesiredState(resource.Spec)
actual := getObservedState()
if !equal(desired, actual) {
applyChanges(diff(desired, actual))
}
resource.Status = actual
}
Level-Triggered, Not Edge-Triggered
The model is deliberately level-triggered: a controller re-evaluates full current state on every reconcile pass rather than reacting only to the specific delta of an individual event, which makes the system self-healing against missed events, controller restarts, or out-of-order delivery, at the cost of requiring reconcile logic to be idempotent and safe to run redundantly.
The Watch Mechanism
List-Watch as the Data Plane of Extension
Controllers do not poll the API server for changes; they establish a long-lived watch connection that streams ADDED, MODIFIED, and DELETED events for a resource type, seeded by an initial LIST call and a resource version used to resume the watch stream after a disconnect without missing events.
kubectl get postgresclusters --watch -o json
The Informer and Local Cache
Client libraries wrap the raw watch mechanism in an Informer, which maintains a local, eventually-consistent cache of the watched resources and dispatches change notifications to registered handler functions, so that reconcile logic reads from a fast local cache rather than issuing a live API call on every reconcile invocation.
informer := cache.NewSharedIndexInformer(listWatch, &v1.PostgresCluster{}, 0, cache.Indexers{})
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: enqueue,
UpdateFunc: func(old, new interface{}) { enqueue(new) },
DeleteFunc: enqueue,
})
Status as a First-Class Part of the Model
Spec/Status Separation
The extension model formalizes a strict separation between spec, which represents user or higher-level-controller intent and is never written by the reconciling controller, and status, which the controller writes to report observed state, current conditions, and any errors encountered during reconciliation, keeping the direction of control flow unambiguous.
apiVersion: databases.example.com/v1
kind: PostgresCluster
status:
conditions:
- type: Available
status: "True"
lastTransitionTime: "2024-06-01T12:00:00Z"
readyReplicas: 3
Status Subresource Isolation
Registering a /status subresource on a CRD enforces this separation at the API level: updates to spec and updates to status go through separate endpoints with separate RBAC verbs, preventing a client with permission to report status from also being able to alter user intent.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
spec:
versions:
- name: v1
subresources:
status: {}
Composability of the Model
Owner References and Garbage Collection
Objects created by a controller as a consequence of reconciling a higher-level resource are linked back via ownerReferences, allowing the built-in garbage collector to automatically delete dependent objects when their owner is deleted, without the controller needing to implement its own cleanup logic.
metadata:
ownerReferences:
- apiVersion: databases.example.com/v1
kind: PostgresCluster
name: orders-db
uid: 3b1e2c4a-...
controller: true
Layered Reconciliation
Because every extension, whether a built-in controller managing ReplicaSets from Deployments, or a custom Operator managing StatefulSets from a PostgresCluster, follows the identical reconcile-loop model, extensions compose naturally in layers: a higher-level custom resource's controller can itself create and depend on lower-level built-in resources, each independently reconciled by its own controller, without any extension needing special knowledge of how another layer is implemented.
Why This Model Was Chosen
Uniformity Over Ad Hoc Extension
By requiring every extension to express itself as a Kubernetes object reconciled by a watch-driven controller, the model avoids the need for a distinct plugin API, configuration format, or lifecycle contract per extension type; a CRD-backed Operator, a built-in controller, and an aggregated API server's supporting logic are all instances of the same underlying pattern, learnable once and applied everywhere.
Relationship to Extensibility Areas and Operator Scope
The Extension Model is the shared substrate beneath every specific extensibility area — CRDs, admission webhooks, aggregated APIs, and Operators — and understanding it explains why those areas behave consistently: reconciliation, watch-based synchronization, and spec/status separation are not features unique to any one extension mechanism, but the common contract every extension mechanism in Kubernetes is built to satisfy.