Kubernetes Operators Basics
10 examples to get you started with K8s Operators - 7 basic and 3 intermediate.
Prerequisites
- Install Go 1.26.x or later from go.dev/dl.
- Install kubebuilder (latest - verify at build): follow kubebuilder.io.
- Install
kubectland a local cluster (kind, minikube, or Docker Desktop Kubernetes). - Install
kustomize(bundled with recent kubectl) formake deploy. - Verify tools:
go version,kubebuilder version,kubectl cluster-info.
Basic Examples
1. Scaffold a kubebuilder Project
Create a module with API group webapp.example.com and a Guestbook kind.
mkdir guestbook-operator && cd guestbook-operator
kubebuilder init --domain example.com --repo example.com/guestbook-operator
kubebuilder create api --group webapp --version v1 --kind Guestbook --resource --controllerinitgeneratesmain.go, Makefile, and Dockerfile scaffold.create apiaddsapi/v1/guestbook_types.goandinternal/controller/guestbook_controller.go.- Run
go mod tidyafter scaffolding to resolve controller-runtime modules.
Related: CRD Design & kubebuilder Scaffolding - API markers and codegen
2. Generate CRD and RBAC Manifests
Use controller-gen via the project Makefile to emit install YAML.
make manifests
ls config/crd/bases/make manifestsrunscontroller-genwith+kubebuildermarkers from Go types.- Output lands in
config/crd/bases/andconfig/rbac/. - Regenerate after every API struct change before
kubectl apply.
Related: CRD Design & kubebuilder Scaffolding - validation markers
3. Install CRDs into a Local Cluster
Apply generated CRDs and RBAC to kind or minikube.
make install
kubectl get crd | grep webappmake installapplies CRD bases fromconfig/crd.- Confirm
guestbooks.webapp.example.comappears before starting the manager. - CRD schema errors surface here; fix Go markers and regenerate.
Related: controller-runtime Manager, Client & Scheme - Scheme registration
4. Minimal Reconciler Skeleton
The generated controller implements Reconcile with a typed client.
func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var gb webappv1.Guestbook
if err := r.Get(ctx, req.NamespacedName, &gb); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// TODO: ensure desired child resources exist
return ctrl.Result{}, nil
}req.NamespacedNameidentifies the object that triggered work.- Return
client.IgnoreNotFound(err)when the CR was deleted. - Empty
ctrl.Result{}means success with no forced requeue.
Related: Writing a Reconciler: ctrl.Request & Reconcile Loop - requeue and errors
5. Register the Controller with the Manager
Wire the reconciler in main.go with the Manager's client and scheme.
if err = (&controller.GuestbookReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Guestbook")
os.Exit(1)
}mgr.GetClient()is a caching client safe for reads inside reconcile.SetupWithManagerregisters watches and connects the workqueue.- Leader election and metrics bind to the same Manager instance.
Related: controller-runtime Manager, Client & Scheme - Manager lifecycle
6. Run the Manager Locally
Start the operator against your kubeconfig with leader election disabled for dev.
make runmake runexecutesgo run ./cmd/main.gowith envtest-free cluster access.- Apply a sample CR in another terminal:
kubectl apply -f config/samples/. - Watch manager logs for reconcile lines when the sample changes.
Related: Leader Election, Metrics & Operator Observability - health and metrics endpoints
7. Apply a Sample Custom Resource
Use the generated sample manifest to trigger reconciliation.
apiVersion: webapp.example.com/v1
kind: Guestbook
metadata:
name: guestbook-sample
namespace: default
spec:
frontendSize: 2kubectl apply -f config/samples/webapp_v1_guestbook.yaml
kubectl get guestbook guestbook-sample -o yaml- Samples live under
config/samples/aftercreate api. - Status subresource updates appear under
.statusonce you implement them. - Edit
specand confirm the reconciler runs again (level-driven).
Related: Watches, Predicates, Owner References & Finalizers - event filtering
Intermediate Examples
8. Set Owner References on Child Deployments
Tie a child Deployment lifecycle to the parent Guestbook CR.
dep := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{
Name: gb.Name + "-frontend", Namespace: gb.Namespace,
}}
if err := controllerutil.SetControllerReference(&gb, dep, r.Scheme); err != nil {
return ctrl.Result{}, err
}- Owner references enable Kubernetes garbage collection when the CR deletes.
SetControllerReferencerequires the parent to have a controller flag set.- One controller per owned kind per namespace is the common pattern.
Related: Watches, Predicates, Owner References & Finalizers - GC and watches
9. Add a Deletion Finalizer
Block CR removal until external cleanup finishes.
if !controllerutil.ContainsFinalizer(&gb, finalizerName) {
controllerutil.AddFinalizer(&gb, finalizerName)
return ctrl.Result{}, r.Update(ctx, &gb)
}
if !gb.DeletionTimestamp.IsZero() {
// teardown external resources, then:
controllerutil.RemoveFinalizer(&gb, finalizerName)
return ctrl.Result{}, r.Update(ctx, &gb)
}- Finalizers prevent object removal until your code clears them.
- Always requeue after adding or removing finalizers via
Update. - Pair finalizers with idempotent teardown logic.
Related: Watches, Predicates, Owner References & Finalizers - teardown safety
10. Run envtest Integration Tests
Test reconcile logic without a live cluster using envtest.
make testtestEnv := &envtest.Environment{
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
}
cfg, err := testEnv.Start()- kubebuilder projects include envtest wiring in
suite_test.go. make testrunsgo testwith CRDs loaded into a local control plane.- Add reconcile assertions with the envtest client before shipping.
Related: Operator Testing with envtest & fake client - fake client patterns
Stack versions: This page was written for Go 1.26.x (Green Tea GC default, go fix modernizers - verify patch at build), chi (latest - verify at build), gin (latest - verify at build), echo (latest - verify at build), google.golang.org/grpc (latest - verify at build), sigs.k8s.io/controller-runtime (latest - verify at build), kubebuilder (latest - verify at build), tinygo (latest - verify board targets at build), wazero (latest - verify at build), and golangci-lint (latest - verify linter set at build).