The Anatomy of a Go Operator
When you run kubebuilder init --domain example.com --repo github.com/example/my-operator, it creates a standard directory structure. Understanding this layout is essential — it tells you where to look when something breaks and where to add new functionality.
my-operator/
├── main.go # Entry point: sets up Manager, registers controllers
├── go.mod # Go module definition and dependencies
├── Makefile # Build, test, deploy targets
├── Dockerfile # Container image for the operator
├── api/
│ └── v1alpha1/
│ ├── myresource_types.go # YOUR CRD struct definition (edit this)
│ └── zz_generated.deepcopy.go # Auto-generated, do not edit
├── controllers/
│ ├── myresource_controller.go # YOUR reconcile logic lives here
│ └── suite_test.go # Integration test setup
└── config/
├── crd/ # Generated CRD YAML (from your Go struct)
├── rbac/ # Generated RBAC roles for your controller
├── manager/ # Deployment manifest for the operator itself
└── default/ # Kustomize overlays combining the above
Here is what each piece does in practice:
main.go — The entry point. It creates a Manager object, which is the shared runtime that manages the API client connection, the in-memory cache, and the lifecycle of all controllers. You register your controllers with the Manager here. You rarely need to edit this file after initial setup.
api/v1alpha1/myresource_types.go — This is where you define your Custom Resource. You write a Go struct with fields like Spec and Status, add Kubebuilder annotation comments (called markers), and then run make generate manifests. The tooling reads your struct and automatically generates the CRD YAML in config/crd/. This is the contract between your users and your operator.
controllers/myresource_controller.go — This is the brain. It contains the Reconcile function where all your business logic lives. This is where you spend 90% of your development time.
config/ — Contains all the raw Kubernetes YAML manifests generated by the tool. These are managed by Kustomize (a YAML templating tool built into kubectl). You apply these to a cluster to deploy your operator. You generally do not edit these files by hand — they are regenerated by make manifests.
A common interview question is: "How does Kubebuilder generate CRD YAML?" The answer is markers — specially formatted Go comments like // +kubebuilder:validation:Minimum=1 that the controller-gen tool reads at build time to produce CRD YAML with validation rules, printer columns, and subresource definitions. This is the same mechanism the Kubernetes project itself uses for its built-in types.