Skip to main content
We can define platform APIs with CustomResourceDefinitions (CRDs) so users create custom resources (CRs). That validates YAML with the API server, but validation alone doesn’t make things happen. If you create a Database CR today, nothing will be provisioned automatically — the YAML just sits in etcd until a controller acts on it. Controllers are the missing piece that bridge declared intent to real-world resources. In this lesson we will:
  • Explain why CRDs are useful only when paired with controllers.
  • Describe the reconciliation loop and its governing principles.
  • Describe controller architecture and the typical reconcile function shape.
  • Cover status reporting with conditions and observedGeneration.
  • Implement finalizers for cleanup on deletion.
  • Compare common operator frameworks so you can pick the right one.
The image outlines learning objectives related to CRDs and controller architecture, including understanding CRDs' need for controllers and explaining the reconciliation loop.
This is a longer lesson because controllers are one of the most fundamental concepts in Kubernetes. Deployments, StatefulSets, Services, and every custom operator all depend on controllers to make the desired state real. Scenario: a shiny Database CRD A platform team builds a Database CRD with enum validations, printer columns, and a solid OpenAPI schema. They tell developers: create a database with kubectl apply.
The image illustrates a concept where a platform team interacts with a database CRD (Custom Resource Definition) through processes like enum validation, printer columns, and schema management.
The developer applies a YAML:
The image shows a diagram with a "Platform Team" informing "Developers" that they can now create databases using kubectl apply.
Command:
It appears successful, but there is still no PostgreSQL instance, no network configuration, and no status updates. Without a controller, the CR is only persisted in etcd — nothing reconciles it to reality.
The image is a diagram highlighting the limitations of CRDs as etcd data, including issues like no database creation, lack of status updates, absence of drift correction, and no cleanup on delete.
Controller = the software that makes desired state real. A helpful analogy:
The image explains the roles of CRD (Menu), CR (Order), and Controller (Kitchen) in a Kubernetes context, using a restaurant analogy.
  • CRD is the menu: it defines what can be requested.
  • CR (the object you apply) is the order you place.
  • Controller is the kitchen: it actually prepares the meal and reports progress.
The Reconciliation Loop Reconciliation is the core control logic controllers run repeatedly to converge actual state to desired state. Typical phases:
  1. Watch / observe — get notifications for creates/updates/deletes.
  2. Compare — compute desired state (from spec) vs actual state (external resources, cloud APIs, cluster objects).
  3. Act — create/update/delete resources to close the gap.
  4. Report — update the CR’s status (conditions, observedGeneration, phase).
Examples for a Database CR:
  • If the external DB does not exist, create it.
  • If the size changed, resize the DB.
  • If the CR is deleted, perform cleanup.
  • Update status to show provisioning progress or readiness.
Follow three guiding principles for safe and robust reconcilers:
  • Level-triggered: react to current state, not transient events. If an event is missed, the next reconcile still inspects state and corrects drift.
  • Idempotency: running Reconcile multiple times should produce the same final result as running it once—always check current state first.
  • Eventual consistency: the system can take multiple reconciles to converge; expect retries and backoff.
The image outlines the key principles of "The Reconciliation Loop: Desired vs Actual," emphasizing level-triggered processes, eventual consistency, and idempotent operations. It suggests reconciling based on current state rather than events, allowing systems to converge over time and handle repeated operations with consistent results.
Use status to inform users: a five-minute provisioning operation can report Progressing while the controller continues to reconcile and later update Ready. Controller internals: four collaborating layers Most operator frameworks implement this architecture for you; your job is to implement the Reconciler (the business logic).
  1. API server — source of truth and where desired state is stored.
  2. Informer — watches API server and maintains a local cache to reduce API traffic.
  3. Work queue — the informer enqueues resource keys; queue deduplicates events so the reconciler runs for the latest state.
  4. Reconciler — your implementation: read resource, compare desired vs actual, act, and return a result (requeue/no-requeue/error).
The image is a diagram of a controller architecture consisting of three components: API Server, Informer, and Work Queue, each with specific roles described alongside.
The image illustrates a controller architecture composed of four components: an API Server, an Informer, a Work Queue, and a Reconciler, each with specific functions outlined.
The framework handles watching, queuing, retries, and worker scaling so you can focus on making the reconciler idempotent and correct.
The image illustrates a "Controller Architecture" with focus on resilience, consistency, and scalability, explaining the benefits of the design. It highlights that the framework manages watching, queuing, and retries, while the user writes the reconciler.
Reconcile function pattern (conceptual Go example) You don’t need to memorize Go—focus on the flow. This example mirrors the signature used by controller-runtime: Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error).
Best practices for your reconcile function:
  • Always fetch the latest object from the API server or cache.
  • Handle deletion via finalizers and short-circuit if the object is gone.
  • Make operations idempotent — check external state before making changes.
  • Use the status subresource to update fields like observedGeneration and conditions.
Status and conditions Status is how your controller reports progress and health. Kubernetes conditions are a well-established pattern: each condition includes type, status (True/False/Unknown), reason, and message. Common condition types:
  • Available / Ready — resource is ready to serve.
  • Progressing — creation/update is in progress.
  • Degraded — running but not operating correctly.
observedGeneration pattern When a user modifies the spec, Kubernetes increments metadata.generation. Your controller sets status.observedGeneration to the generation it last reconciled. Comparing metadata.generation and status.observedGeneration tells users whether the controller has acted on the latest change.
The image explains the "observedGeneration" pattern, where you compare metadata.generation with status.observedGeneration to check if changes are processed.
Finalizers and deletion flow Finalizers allow controllers to perform cleanup before Kubernetes permanently removes a CR. When a user deletes a CR, the API server sets deletionTimestamp but keeps the object until finalizers are cleared—this is the controller’s opportunity to delete external resources (cloud instances, secrets, networks). Typical finalizer handling:
Deletion flow summary:
  1. User runs kubectl delete.
  2. API server sets deletionTimestamp and retains the object.
  3. Controller observes the timestamp, performs cleanup, and removes finalizers.
  4. When no finalizers remain, the API server deletes the object.
If cleanup fails (for example, cloud API outages), the object will remain in a Terminating state while finalizers persist. Implement robust retries, exponential backoff, and comprehensive logging so cleanup eventually completes and you avoid orphaned resources.
Operator frameworks Most teams avoid writing controller boilerplate from scratch. Common frameworks let you focus on business logic:
The image is a comparison chart of three operator frameworks: Kubebuilder, Operator SDK, and Metacontroller, highlighting their features and best use cases.
Below is a concise comparison to help you choose: Key takeaways
The image outlines four key takeaways about controllers, CRDs, reconciliation, status, and finalizers in a technology context. Each point is numbered and highlighted with a colored icon.
  • CRDs without controllers are just stored data — controllers make the desired state become real.
  • Design your Reconcile to be idempotent and level-triggered: always read current state and act only when needed.
  • Use status, conditions, and observedGeneration to transparently communicate what the controller has processed.
  • Implement finalizers to clean up external resources; handle errors with retries and backoff to avoid stuck Terminating objects.
Additional resources

Watch Video