Writing Controllers
A Kubernetes controller is a control loop that watches resources and takes action to move the current state toward the desired state. Controllers are the backbone of Kubernetes itself and the standard way to extend platform behavior.
The Controller Pattern
Every controller follows the same pattern: observe, diff, act.
// Simplified controller loop
for {
desired := getDesiredState() // From the resource spec
current := getCurrentState() // From the cluster
if desired != current {
reconcile(desired, current) // Make changes
}
}
The Reconciliation Loop
Controllers receive events (add, update, delete) and reconcile the resource. The reconcile function is idempotent and convergent:
func (r *MyAppReconciler) Reconcile(ctx context.Context,
req ctrl.Request) (ctrl.Result, error) {
// Fetch the custom resource
var myapp myappv1.MyApp
if err := r.Get(ctx, req.NamespacedName, &myapp); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Check if the deployment exists
var deployment appsv1.Deployment
err := r.Get(ctx, req.NamespacedName, &deployment)
if errors.IsNotFound(err) {
// Create the deployment
dep := r.constructDeployment(&myapp)
return ctrl.Result{}, r.Create(ctx, dep)
}
// Update if needed
return ctrl.Result{}, r.Update(ctx, &deployment)
}
Kubebuilder
Kubebuilder scaffolds controller projects with best practices built in:
# Initialize a new project
kubebuilder init --domain example.com --repo github.com/org/mycontroller
# Create an API (resource + controller)
kubebuilder create api --group apps --version v1 --kind MyApp
# Generate manifests (CRDs, RBAC, etc.)
make manifests
# Run the controller locally
make run
# Build and deploy to the cluster
make docker-build docker-push IMG=myregistry/controller:v1
make deploy IMG=myregistry/controller:v1
Testing Controllers
# Kubebuilder generates test scaffolding
make test
# Check controller logs in the cluster
kubectl logs -n mycontroller-system deployment/mycontroller-manager
Controllers should handle errors gracefully by returning requeue results, allowing Kubernetes to retry with exponential backoff.