Kubernetes CRDs & Controller Development with Kubebuilder
Kubernetes provides core declarative primitives — such as Pod, Service, and Deployment. However, as cloud-native applications grow complex, managing stateful applications (like database clusters, message brokers, or ML training pipelines) requires extending the Kubernetes API with domain-specific automation.
Custom Resource Definitions (CRDs) allow developers to register new object types directly in the Kubernetes API. The Operator Pattern pairs a CRD with a custom Go controller running an automated reconciliation loop. Kubebuilder is the official Go framework powering Kubernetes operators like Cert-Manager and Istio. This guide details Kubebuilder scaffolding, OpenAPI v3 schema generation, and Go controller-runtime reconciliation loops.
Mental Model: Kubernetes Operator Pattern & Controller-Runtime
The Operator Pattern encodes human operational knowledge (provisioning, scaling, backup, recovery) into software daemons running inside Kubernetes.
An Operator consists of two parts:
1. Custom Resource Definition (CRD): Defines the declarative API schema (e.g., kind: PostgresCluster).
2. Custom Controller: A Go process that watches the Kubernetes API server for create/update/delete events on that CRD.
When a user applies a PostgresCluster YAML, the controller's Reconciliation Loop (Reconcile(ctx, req)) evaluates the difference between desired state (e.g., replicas: 3) and live cluster pods. The controller provisions StatefulSets, ConfigMaps, and Services automatically until the cluster matches the target specification. For GitOps control plane integration, review mastering gitops argocd fluxcd kubernetes and building high throughput apis go gin framework.
Quick reference
- Operator Pattern codifies domain-specific operational logic into automated Go software controllers.
- Custom Resource Definitions (CRDs) extend the Kubernetes API server with new custom resource kinds.
- Go controller-runtime library manages API informers, event queues, and client caching.
- Reconciliation loops execute continuously to eliminate state drift between Spec and Status.
- Powers major cloud-native projects including Prometheus Operator, Strimzi Kafka, and Cert-Manager.
Remember this
Develop custom Kubernetes operators using Kubebuilder to automate complex stateful application lifecycles.
Defining Custom Resource Definitions (CRDs) & OpenAPI v3 Schemas
Kubebuilder uses Go struct tags and controller-gen markers to auto-generate OpenAPI v3 validation schemas.
In api/v1alpha1/postgrescluster_types.go, define the Spec (desired state) and Status (observed state) structs:
1type PostgresClusterSpec struct {2 // +kubebuilder:validation:Minimum=13 // +kubebuilder:validation:Maximum=104 Replicas int32 `json:"replicas"` 5 StorageGB int32 `json:"storageGB"` 6}7 8type PostgresClusterStatus struct {9 ReadyReplicas int32 `json:"readyReplicas"` 10 Phase string `json:"phase"` 11}Running make manifests parses Go comments (+kubebuilder:validation) and generates production YAML CRD manifests (config/crd/bases/...).
Quick reference
- Go struct tags (
json:"replicas") define YAML field naming conventions. - Controller-gen markers (
+kubebuilder:validation:Minimum=1) generate OpenAPI v3 validation rules. - Status struct isolates observed runtime state from user-configured Spec desired state.
- Supports subresources (+kubebuilder:subresource:status) for fast status field updates.
- Generates deepcopy methods (zz_generated.deepcopy.go) required by k8s runtime interface.
Remember this
Annotate Go struct fields with Kubebuilder markers to auto-generate OpenAPI v3 CRD validation schemas.
Writing the Reconciliation Loop (Reconcile Method)
The core logic of every Kubebuilder operator resides in the Reconcile(ctx reconcile.Request) method.
1func (r *PostgresClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {2 var cluster datav1.PostgresCluster3 if err := r.Get(ctx, req.NamespacedName, &cluster); err != nil {4 return ctrl.Result{}, client.IgnoreNotFound(err)5 }6 7 // Reconcile underlying StatefulSet8 desiredSts := r.constructStatefulSet(&cluster)9 if err := r.CreateOrUpdate(ctx, desiredSts); err != nil {10 return ctrl.Result{Requeue: true}, err11 }12 13 // Update observed Status14 cluster.Status.ReadyReplicas = desiredSts.Status.ReadyReplicas15 r.Status().Update(ctx, &cluster)16 return ctrl.Result{RequeueAfter: 30 * time.Second}, nil17}Returning ctrl.Result{Requeue: true} instructs the controller manager to re-trigger reconciliation if transient API errors occur.
Quick reference
- Reconcile method receives Namespace/Name keys whenever observed CRD instances change.
- Use client.IgnoreNotFound(err) to handle deleted resources cleanly without error loops.
- CreateOrUpdate helper functions sync child StatefulSets and Services to match Spec.
- Update status subresource separately (r.Status().Update) to prevent speculative trigger loops.
- Return RequeueAfter durations for periodic health checks or background maintenance tasks.
Remember this
Implement idempotent Reconcile logic to create child resources and update Status subresources.
Testing & Deploying Operators with Kustomize & RBAC Roles
Kubebuilder auto-generates minimal RBAC (Role-Based Access Control) permissions using // +kubebuilder:rbac comments above the Reconcile method.
When make manifests runs, controller-gen outputs ClusterRole and ClusterRoleBinding YAML files granting the operator pod exact permissions to create/update child StatefulSets and Services without granting excessive cluster admin privileges.
Test operators locally using make run (which connects local Go controller code to a remote KinD/EKS cluster). Build production OCI container images using make docker-build docker-push and deploy using make deploy via Kustomize.
Quick reference
- +kubebuilder:rbac markers generate least-privilege RBAC ClusterRoles automatically.
- make run launches local Go controller process connected to remote Kubernetes context for debugging.
- Kustomize overlays generate multi-environment deployment configs (dev, staging, production).
- envtest library runs unit tests against a real in-memory API server (etcd + kube-apiserver).
- Includes Prometheus metric endpoints (/metrics) out of the box for controller monitoring.
Remember this
Use controller-gen RBAC markers and envtest unit testing to build and deploy secure Kubernetes operators.
Key takeaway
To test Kubebuilder, run kubebuilder init --domain my.domain --repo my.domain/app. Create an API (kubebuilder create api), run make run, and apply custom resource YAML files.
Related Articles
Explore this topic