Project Setup with kubebuilder
Table of Contents
Introduction
kubebuilder is the scaffolding CLI maintained by the Kubernetes SIGs team. It generates a working Go project β go.mod, main.go, Makefile, RBAC manifests, and CRD generation tooling β from a few commands. The goal isn't to hide complexity; it's to give you a consistent, idiomatic project structure so you can focus on reconcile logic rather than boilerplate.
This article walks through initializing appstack-operator, understanding every file that gets generated, and running the controller locally against a kind cluster.
Installing the Toolchain
Go
kubebuilder
kubebuilder doesn't have a brew formula. Download it directly from the releases page or use the command above.
kind (local Kubernetes)
controller-gen
controller-gen is the code generation tool that reads Go struct markers and outputs CRD YAML and DeepCopy methods. kubebuilder installs it automatically via the Makefile, but you can install it manually:
Initializing the Project
The --domain flag sets the API group domain. Your CRDs will live under apps.htunn.io/v1alpha1. The --repo flag sets the Go module name in go.mod.
Expected output:
Understanding the Generated Structure
Makefile Targets
The generated Makefile is the interface you'll use every day:
The PROJECT File
kubebuilder writes a PROJECT file that tracks what APIs and controllers you've scaffolded:
When you run kubebuilder create api, it appends to this file. It's also used by kubebuilder to validate compatibility when you add new resources.
Scaffolding the API and Controller
kubebuilder will prompt:
Answer y to both. This generates:
The PROJECT file is updated:
Understanding main.go
The generated cmd/main.go is the entry point. It's worth reading in full before touching any controller logic:
Key things to understand in this file:
The scheme: A scheme maps Go types (AppStack) to Kubernetes API group/version/kind strings. You register both built-in types and your custom types in init(). Every API call goes through the scheme.
The Manager: The ctrl.Manager starts all your registered controllers, manages the client cache, serves the metrics endpoint, and handles leader election. You start it with mgr.Start(), which blocks until a signal is received.
SetupWithManager: Each controller calls this to register itself with the manager. This is where you configure watches (covered in the controller article).
Running the Operator Locally
Before writing any reconcile logic, verify the scaffolded project builds and runs:
Install the CRD
Expected:
Run the Controller
This runs go run ./cmd/main.go with the current kubeconfig. The controller connects to your kind cluster, registers event watches, and begins its goroutines. You should see output like:
Apply a Test Resource
While make run is running, open a second terminal:
The controller will call Reconcile(). In the logs you'll see:
Nothing happens yet because the reconcile body is empty in the scaffold. That logic is what the next articles build.
The Development Loop
The day-to-day loop when developing an operator:
Keeping make run running while you modify resources in another terminal is the most efficient development flow. You don't need to rebuild or redeploy between changes β just restart make run when you change Go code.
Useful Debug Commands
Last updated