Skip to content

Kubernetes CRDs & Controller Development with Kubebuilder

CoreConceptAugust 3, 20269 min read

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.

Kubernetes Operator pattern architecture with Custom Resource Definitions and Go controller-runtime
Kubernetes Operator pattern architecture with Custom Resource Definitions and Go controller-runtime

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.

Kubebuilder controller reconciliation loop lifecycle from API event trigger to child resource update
Kubebuilder controller reconciliation loop lifecycle from API event trigger to child resource update

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.

Kubebuilder controller reconciliation loop lifecycle from API event trigger to child resource update
Kubebuilder controller reconciliation loop lifecycle from API event trigger to child resource update

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.

Share:

Related Articles

Containers are the foundation of modern cloud deployment, but default container images often ship with bloated Linux OS

Read

As microservices scale beyond single-server deployments, managing container scheduling, self-healing restarts, network i

Read

Traditional perimeter-based security ('Castle and Moat') assumes that all traffic inside a private network or Kubernetes

Read

Explore this topic

Keep learning

Follow a structured path or browse all courses to go deeper.