- Why CRDs are only useful when paired with controllers.
- How the reconciliation loop works and its guiding principles.
- Controller architecture and how informs, queues, and reconcilers interact.
- The canonical Reconcile function pattern and a practical Go example.
- How to report status with conditions and
observedGeneration. - How to implement finalizers to guarantee cleanup.
- How to pick an operator framework.
This lesson focuses on controller patterns and implementation best practices. Use a framework (Kubebuilder, Operator SDK, or Metacontroller) to avoid boilerplate and concentrate on business logic.
kubectl apply.
A developer tries:


- Watch / Observe — receive notifications (via an Informer) when resources change.
- Compare — compute the delta between desired state (CR.spec) and actual state (cluster or external system).
- Act — create, update, or delete resources to close the gap.
- Update status — write current progress and conditions back to CR.status.
- Level-triggered (not edge-triggered): react to state, not individual events. If an event is missed, the next reconcile sees the current state and fixes it.
- Idempotency: repeated reconciles must be safe and not cause duplicate or conflicting actions.
- Eventual consistency: the system converges toward the desired state over time; transient failures are retried.
Progressing or Provisioning in status. Reconciles continue until the database is Ready.

Controller architecture
Controllers are composed of a small stack of cooperating components:
- API Server — the source of truth where desired state is persisted.
- Informer — watches API server changes and keeps a local cache to reduce API traffic.
- Work Queue — deduplicates events, rate-limits retries, and ensures single-running reconcile per resource.
- Reconciler — your custom logic that compares and acts on resources.


- Fetch the resource from the API server (it might have been deleted).
- If
metadata.deletionTimestampis set, run finalizer/cleanup logic and return. - Compare spec to actual state and make necessary actions (create/update/delete).
- Update
status(conditions,observedGeneration, etc.) via the status subresource.
type(e.g.,Available,Progressing,Degraded)status(one of"True","False","Unknown")reasonandmessagefor human-readable contextlastTransitionTimefor timingobservedGeneration(optional) to indicate whichmetadata.generationthe controller has processed
observedGeneration so consumers can compare metadata.generation with status.observedGeneration to know whether the current status reflects the latest spec.

metadata.deletionTimestamp but retains the object while finalizers are present. Your controller must:
- Detect
deletionTimestamp. - Perform external cleanup (delete cloud resources, credentials, networking).
- Remove the finalizer from the CR and update it.
- Only then will the API server remove the object from etcd.
If cleanup fails (for example, cloud APIs are down), the finalizer will remain and the resource will stay in
Terminating state. Implement retries with backoff and robust logging so you can recover or manually resolve stuck objects.
Pick the framework that matches your language skills, testing needs, and integration surface.
Key takeaways
- CRDs alone are just declarative data stored in etcd; controllers make CRs do work.
- Reconciliation must be level-triggered, idempotent, and target eventual consistency.
- Use
status.conditionsandobservedGenerationto communicate progress and the reconciliation state. - Finalizers ensure external resources are cleaned up before the CR is removed.
- Use a framework (Kubebuilder, Operator SDK, Metacontroller) to avoid boilerplate and follow Kubernetes best practices.

- Kubernetes CustomResourceDefinitions
- controller-runtime (controller pattern and helpers)
- Kubebuilder book
- Operator SDK
- Metacontroller documentation
- Conditions guidance: API conventions — conditions