Cloud Native 15 min read

Building Kubernetes Controllers with Controller Runtime and Kubebuilder

This article explains how to simplify Kubernetes controller development by using the controller-runtime library and the Kubebuilder scaffolding tool, covering manager and webhook concepts, providing full Go examples, YAML configurations, and step‑by‑step commands for building, deploying, and testing a custom Foo controller.

Cloud Native Technology Community
Cloud Native Technology Community
Cloud Native Technology Community
Building Kubernetes Controllers with Controller Runtime and Kubebuilder

The previous article introduced the low‑level client‑go Informer pattern for writing Kubernetes controllers, which requires a lot of boilerplate code for leader election, webhooks, and error handling. In most cases developers can avoid these details by using the controller‑runtime library, which wraps the Informer logic and provides higher‑level abstractions.

Controller‑runtime introduces three core concepts:

Manager : a component that owns the lifecycle of one or more controllers within a single process.

Controller : created with ctrl.NewControllerManagedBy(mgr).For(&api.Foo{}).Complete(&reconciler{}) , it watches a specific CRD type and calls the Reconcile method.

Webhook : created with ctrl.NewWebhookManagedBy(mgr).For(&api.Foo{}) , it registers validation and mutation logic for the CRD.

The sample Go program below shows a minimal controller‑runtime controller that implements a simple Reconcile method, sets up a manager with leader election, registers the Foo CRD scheme, and adds both a controller and a webhook to the manager.

package main

import (
    "context"
    "fmt"
    "os"
    api "github.com/zhaohuabing/k8scontrollertutorial/pkg/custom/apis/foo/v1alpha1"
    _ "k8s.io/client-go/plugin/pkg/client/auth/gcp"
    ctrl "sigs.k8s.io/controller-runtime"
    "sigs.k8s.io/controller-runtime/pkg/client"
    "sigs.k8s.io/controller-runtime/pkg/log"
    "sigs.k8s.io/controller-runtime/pkg/log/zap"
)

var (
    setupLog = ctrl.Log.WithName("setup")
)

type reconciler struct {
    client.Client
}

func (r *reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx).WithValues("foo", req.NamespacedName)
    log.V(1).Info("reconciling foo")
    var foo api.Foo
    if err := r.Get(ctx, req.NamespacedName, &foo); err != nil {
        log.Error(err, "unable to get foo")
        return ctrl.Result{}, err
    }
    fmt.Printf("Sync/Add/Update for foo %s\n", foo.GetName())
    return ctrl.Result{}, nil
}

func main() {
    ctrl.SetLogger(zap.New())
    mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{LeaderElection: true, LeaderElectionID: "sample-controller", LeaderElectionNamespace: "kube-system"})
    if err != nil {
        setupLog.Error(err, "unable to start manager")
        os.Exit(1)
    }
    if err = api.AddToScheme(mgr.GetScheme()); err != nil {
        setupLog.Error(err, "unable to add scheme")
        os.Exit(1)
    }
    err = ctrl.NewControllerManagedBy(mgr).
        For(&api.Foo{}).
        Complete(&reconciler{Client: mgr.GetClient()})
    if err != nil {
        setupLog.Error(err, "unable to create controller")
        os.Exit(1)
    }
    err = ctrl.NewWebhookManagedBy(mgr).
        For(&api.Foo{}).
        Complete()
    if err != nil {
        setupLog.Error(err, "unable to create webhook")
        os.Exit(1)
    }
    setupLog.Info("starting manager")
    if err = mgr.Start(ctrl.SetupSignalHandler()); err != nil {
        setupLog.Error(err, "problem running manager")
        os.Exit(1)
    }
}

When the manager starts, it checks whether the Foo struct implements the Validator and Defaulter interfaces. If so, controller‑runtime automatically creates a webhook server. The following Go methods implement validation and mutation logic for the Foo CRD:

// Validation webhook
func (f *Foo) ValidateCreate() error {
    if f.Spec.Replicas != nil && *f.Spec.Replicas < 0 {
        return fmt.Errorf("replicas should be non‑negative")
    }
    return nil
}
func (f *Foo) ValidateUpdate(old runtime.Object) error {
    if f.Spec.Replicas != nil && *f.Spec.Replicas < 0 {
        return fmt.Errorf("replicas should be non‑negative")
    }
    return nil
}
func (f *Foo) ValidateDelete() error { return nil }

// Mutation webhook
func (f *Foo) Default() {
    if f.Spec.Replicas == nil {
        f.Spec.Replicas = new(int32)
        *f.Spec.Replicas = 1
    }
}

Corresponding ValidatingWebhookConfiguration and MutatingWebhookConfiguration YAML files tell the API server to route Foo create/update/delete requests to the webhook server:

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
  name: foo
webhooks:
- name: foo.samplecontroller.k8s.io
  clientConfig:
    service:
      namespace: default
      name: sample-controller-webhook-server
      path: /validate-samplecontroller-k8s-io-v1alpha1-foo
  rules:
  - apiGroups: ["samplecontroller.k8s.io"]
    apiVersions: ["v1alpha1"]
    resources: ["foos"]
    operations: ["CREATE", "UPDATE", "DELETE"]
    scope: Namespaced
  sideEffects: None
  admissionReviewVersions: ["v1"]
---
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: foo
webhooks:
- name: foo.samplecontroller.k8s.io
  clientConfig:
    service:
      namespace: default
      name: sample-controller-webhook-server
      path: /validate-samplecontroller-k8s-io-v1alpha1-foo
  rules:
  - apiGroups: ["samplecontroller.k8s.io"]
    apiVersions: ["v1alpha1"]
    resources: ["foos"]
    operations: ["CREATE"]
    scope: Namespaced
  sideEffects: None
  admissionReviewVersions: ["v1"]

While controller‑runtime reduces boilerplate, Kubebuilder further automates project scaffolding. After installing the kubebuilder CLI, the following commands generate a full project structure, CRD definitions, and controller skeletons:

kubebuilder init --project-name kubebuilderexample --domain zhaohuabing.com --repo github.com/zhaohuabing/kubebuilderexample
kubebuilder create api --group samplecontroller --version v1alpha1 --kind Foo

Developers then edit api/v1alpha1/foo_types.go to add spec and status fields, run make manifests to generate the CRD YAML, and install it with make install . The controller image is built and pushed using:

make docker-build docker-push IMG=zhaohuabing/sample-controller:kubebuilder

Finally, the controller is deployed to the cluster with:

make deploy IMG=zhaohuabing/sample-controller:kubebuilder

Sample logs show the manager starting, leader election, and the controller processing a Foo resource:

2023-04-07T06:57:46Z INFO setup starting manager
2023-04-07T06:58:10Z INFO Starting Controller {"controller":"foo","controllerGroup":"samplecontroller.zhaohuabing.com","controllerKind":"Foo"}
reconcile foo foo-sample

In summary, the series covered three approaches to building Kubernetes controllers—raw Informer, controller‑runtime, and Kubebuilder—each offering a trade‑off between flexibility and developer convenience. Controller‑runtime provides a concise API with built‑in webhook support, while Kubebuilder adds project generation and further reduces manual setup.

References:

Kubernetes controller‑runtime (https://github.com/kubernetes-sigs/controller-runtime)

Kubebuilder quick start (https://book.kubebuilder.io/quick-start.html)

Source code for the controller‑runtime example (https://github.com/zhaohuabing/k8scontrollertutorial/tree/main/pkg/custom/controller_runtime)

Source code for the Kubebuilder example (https://github.com/zhaohuabing/kubebuilderexample)

cloud-nativekubernetesGoCRDWebhookController RuntimeKubeBuilder
Cloud Native Technology Community
Written by

Cloud Native Technology Community

The Cloud Native Technology Community, part of the CNBPA Cloud Native Technology Practice Alliance, focuses on evangelizing cutting‑edge cloud‑native technologies and practical implementations. It shares in‑depth content, case studies, and event/meetup information on containers, Kubernetes, DevOps, Service Mesh, and other cloud‑native tech, along with updates from the CNBPA alliance.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.