# kubernetes/sample-controller: The Official Kubernetes Custom Controller Reference

> sample-controller is the official Kubernetes reference implementation for building custom controllers that watch a CustomResourceDefinition; it demonstrates how to wire client-go informers, a rate-limited work queue, and the code-generator toolchain in a working Go project.

**kubernetes/sample-controller** — Repository for sample controller. Complements sample-apiserver

- Repository: https://github.com/kubernetes/sample-controller
- Stars: 3,518 · Forks: 1,215
- Language: Go
- License: Apache-2.0
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/kubernetes-sample-controller

## What sample-controller Teaches and Who Needs It

Writing custom Kubernetes controllers is among the most common tasks for platform engineers who extend cluster behavior with custom resources. The kubernetes/sample-controller repository is the official reference implementation for exactly this task. It is published as a staged, read-only mirror from the main kubernetes/kubernetes repository; contributions including issues and pull requests should go to kubernetes/kubernetes rather than to this repository.

The project implements a controller that watches a single custom resource type called Foo, defined through a CustomResourceDefinition. It demonstrates three operations every controller author must master: registering a new custom resource type, creating and listing instances of that type, and reacting to create, update, and delete events through a reconciliation loop. This makes it the canonical starting point for anyone writing a single-resource controller in Go.

The Foo resource type is a placeholder. The architecture, the generated code, and the reconciliation loop are the substance. The project is intended for Go developers who want a working, correct example to read and adapt, not for end users deploying a finished product.

## The Informer and Work Queue Architecture

The controller depends on the client-go library (k8s.io/client-go/tools/cache), which supplies the informer mechanism that most production Kubernetes controllers use. Informers maintain a local cache of API objects backed by a watch on the Kubernetes API server, so the controller does not need to poll for changes. When an object is added, updated, or deleted, the informer fires event handler callbacks.

The sample-controller uses a standard work queue pattern on top of these informers. Event handlers do not process objects directly. They enqueue a key (in namespace/name form) for the changed object. A separate worker loop pulls keys from the queue and calls a reconciliation function that reads current state, compares it to desired state, and acts accordingly. A rate limiter on the queue prevents runaway retries on repeated failures.

The detailed explanation of how the controller interacts with each client-go mechanism, including the relationship between the shared informer factory, the event handler registration, and the work queue, is documented in docs/controller-client-go.md within the repository. That file is the companion reading for anyone studying this reference.

## Generating Typed Client Code with update-codegen.sh

Because writing the typed client, informers, listers, and deep-copy functions by hand is error-prone and repetitive, sample-controller relies on code-generator (k8s.io/code-generator) to produce them automatically. The script at ./hack/update-codegen.sh runs the generator suite and produces two output locations: pkg/generated/ for the typed client and informers, and pkg/apis/samplecontroller/v1alpha1/zz_generated.deepcopy.go for the deep-copy implementation.

The generated files must not be edited by hand. When setting up a new controller based on this example, engineers should run update-codegen.sh against their own API types rather than copying the generated output. To make the generators accessible, the code-generator repository needs to be present at a path the script can find. Running go mod vendor to populate the vendor directory is one supported approach.

The go.mod file pins the code-generator to a specific commit that matches the k8s.io/client-go and k8s.io/api versions. Changing those versions without updating the code-generator pin can cause subtle type mismatches in the generated informer factories. This version coupling is one of the less-obvious maintenance costs when adapting the sample to a long-lived project.

## Building and Running the Controller Against a Cluster

The sample-controller binary is built with the standard Go toolchain and requires no additional build dependencies beyond Go modules. The README states that the Kubernetes cluster must be version 1.9 or later because the controller manages apps/v1 Deployment objects, which reached stability in that release.

Clone the repository and build:

```bash
git clone https://github.com/kubernetes/sample-controller
cd sample-controller
```

Build the binary and run it against an existing cluster:

```bash
go build -o sample-controller .
./sample-controller -kubeconfig=$HOME/.kube/config
```

With the controller running, create the CRD and a sample Foo resource:

```bash
kubectl create -f artifacts/examples/crd-status-subresource.yaml
kubectl create -f artifacts/examples/example-foo.yaml
kubectl get deployments
```

The controller creates a Deployment for each Foo resource. After applying example-foo.yaml, kubectl get deployments should show the Deployment the controller created on the cluster. The -kubeconfig flag is optional when running inside the cluster with an appropriate service account.

## Defining Foo Resource Types with Spec Structs

Every custom resource type follows the same pattern visible in the sample-controller source. A Go struct with JSON struct tags defines the shape of the resource's Spec. The JSON tags are required on every user-facing field; without them, the Kubernetes generators treat the field as internal and exclude it from the generated CRD schema.

The Spec is arbitrary key-value data. Adapting the Foo type to a real resource means replacing the placeholder fields with the ones your application needs. The README illustrates this with a database resource example that has Databases, Users, and Version fields, each with the required json tag. The critical point is that omitting a json tag does not cause a compile error but does prevent the field from appearing in the CRD's OpenAPI schema, which can cause silent validation gaps.

The go.mod module path for import purposes is k8s.io/sample-controller. When vendoring, note the README instruction to use go mod vendor to create the vendor directory so the code-generator can find its dependencies.

## Validation Rules and the Status Subresource

The example CRD at artifacts/examples/crd.yaml demonstrates field validation using a structured schema. The schema enforces that spec.replicas must be an integer with a minimum value of 1 and a maximum value of 10. Structured schema validation is mandatory for apiextensions.k8s.io/v1 CRDs, and engineers adapting the sample can use this location to add OpenAPI-compatible validation for their own resource fields.

The variant CRD at artifacts/examples/crd-status-subresource.yaml enables the /status subresource. With this enabled, the controller can call UpdateStatus to write only to the status portion of the resource, leaving the spec portion unchanged. This separation between spec (user intent) and status (observed state) is part of the Kubernetes API conventions; mixing spec and status updates in a single call violates those conventions and can cause reconciliation loops where the controller triggers itself repeatedly.

The /scale subresource is also listed as supported. The CustomResourceSubresources feature that backs both subresources reached general availability in Kubernetes 1.16 according to the README.

## Limitations of the Reference Implementation

sample-controller is a deliberate simplification. It manages exactly one custom resource type and provides no examples for multi-resource controllers, admission webhooks, conversion webhooks, or admission policies. There is a note about CRD versioning and evolving the v1alpha API to a stable v1, but no working example of a versioning migration is included.

The Foo resource type has no meaningful production behavior. It creates Deployments as a proxy mechanism to demonstrate the reconciliation pattern, not to implement a real workload abstraction. Engineers building controllers for databases, message queues, or network primitives will need to replace essentially all of the business logic in controller.go while keeping the structural skeleton around it.

The repository is also staging-only: it is a read-only automated mirror. If a bug is found in the controller or code-generator integration, the fix must go to kubernetes/kubernetes, not here. There are no GitHub releases; versioning follows the Kubernetes release cycle through the staging mechanism.

## Alternative: Kubebuilder, and Maintenance Notes

The standard alternative to building directly on sample-controller patterns is Kubebuilder, the scaffold generator maintained by Kubernetes SIG API Machinery. Kubebuilder wraps the controller-runtime library on top of client-go and generates a project skeleton with admission webhook scaffolding and Makefile targets. The trade-off is that Kubebuilder's abstraction layer hides the informer and work queue wiring that sample-controller exposes directly. Engineers who need to debug low-level cache invalidation or informer resync behavior will find that reading sample-controller first makes those abstractions legible.

The last push to the sample-controller staging repository was on 2026-09-25. The project is licensed under Apache-2.0. Because it is a staged mirror of the Kubernetes codebase, its Go module dependencies track the main Kubernetes dependency tree rather than following their own independent versioning.

## Conclusion

Engineers who need a correct, readable starting point for a Kubernetes custom controller should clone sample-controller and read the informer wiring before writing a single line of business logic. Engineers who want webhook scaffolding, generated Makefile targets, and controller-runtime abstractions should use Kubebuilder instead. Before adapting the code, confirm that your cluster is Kubernetes 1.9 or later, and verify that the code-generator version pinned in go.mod aligns with your target version of k8s.io/client-go.

## FAQ

### How to create your own controller?

Clone sample-controller and run ./hack/update-codegen.sh to generate typed client, informers, listers, and deep-copy functions for your resource types. Then replace the Foo type definition in pkg/apis/samplecontroller/v1alpha1 with your own type and adapt the reconciliation logic in controller.go.

### What does the code-generator produce for a custom controller?

Running update-codegen.sh generates a typed client, informers, and listers in pkg/generated/, plus a deep-copy function at pkg/apis/samplecontroller/v1alpha1/zz_generated.deepcopy.go. These files must not be edited by hand.

### Can sample-controller be deployed directly as a production controller?

No. The README presents it explicitly as a learning reference, not a production-ready controller. The Foo resource type creates Deployments only to demonstrate the reconciliation pattern, and engineers are expected to replace the business logic with their own resource handling.

## Sources

- [Issues](https://github.com/kubernetes/sample-controller/issues)
- [kubernetes/sample-controller on GitHub](https://github.com/kubernetes/sample-controller)
- [License: Apache-2.0](https://github.com/kubernetes/sample-controller/blob/master/LICENSE)
- [README](https://github.com/kubernetes/sample-controller/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kubernetes-sample-controller
