Kubebuilder turns custom resources into controller projects
Kubebuilder packages the development work around a Kubernetes API and its controller. Its reconciliation model explains which operational decisions the SDK can standardize and which remain application-specific.
A CustomResourceDefinition can give a database a Kubernetes object, but it cannot decide how that database should recover or reconfigure itself. The August 10 Kubebuilder introduction addresses the development work between defining that object and shipping its controller. The SDK creates project structure and API definitions around controller-runtime, with supporting integration tests and deployment artifacts.
I consider custom controllers one of the strongest reasons to understand Kubernetes' API machinery. They let an application expose operational intent through an API suited to its own domain. That benefit depends on more than accepting YAML. The implementation has to converge actual application state toward the declaration and communicate what has happened after the API accepts the request.
An accepted object is an asynchronous request
The controller explanation in Kubebuilder v1.0.0 describes controllers as asynchronous implementations of resource APIs. Writing a resource to storage precedes the controller's work. A successful create request therefore establishes that Kubernetes accepted the object, while the controller may encounter operational problems later. Status is how the implementation reports the resulting actual state.
That distinction is essential when designing an application-specific API. A request for more database replicas may require work which cannot finish inside the API request. The announcement names scaling with resharding and backup scheduling as examples of application operations. The API needs to describe the requested result, while the controller implements the sequence of changes needed to approach it.
Kubebuilder's documentation defines reconciliation in terms of the object's namespace and name. It does not pass the state of a particular event into the reconcile function as the authoritative instruction. The implementation reads observed state and compares it with the current specification. This permits another reconciliation to begin from whatever state exists when it runs.
The effect is visible when a user changes a specification again before previous work finishes. The documentation's rollout example replaces an earlier request with a newer configuration after a problem is discovered. A level-based controller works toward the current request. Completing every obsolete intermediate specification would make a queue of historical requests compete with the declaration which is now stored in the API.
Watches identify work to reconsider
A controller normally watches the resource type it reconciles. It also needs to observe objects whose state affects that resource. The documentation uses a ReplicaSet and its Pods to explain the relationship. A Pod change can require reconsidering the ReplicaSet even though the user has not edited the ReplicaSet specification.
That is why a controller which only handles creation of its custom resource is incomplete for many application APIs. Once a dependent object changes, the desired state may no longer match the cluster. The watch arrangement must identify which parent resource needs reconciliation. Otherwise, the custom object can remain unchanged while the application state it represents diverges.
The documentation distinguishes resources a controller creates from other related resources it observes. Ownership references help map events on created objects back to their owner. Labels and selectors help the controller find the objects associated with its resource. Related objects can require different mapping. A Node change, for example, can affect several DaemonSet objects rather than one owning object.
These relationships belong in the controller design. Generating an API type does not tell the SDK which external changes make an application's state stale. A useful reconciliation function and an appropriate set of watches have to agree about the state being maintained. The resource name placed on the work queue should identify the declaration whose desired state must be reconsidered.
The SDK standardizes supporting development work
The v1.0.0 project README describes a workflow which creates resource APIs, implements reconciliation and watches additional resources. It then calls for updating the generated integration tests with the new fields and business logic. Container publication follows that implementation work. The generated project is a starting point for application behavior, with supporting mechanisms already arranged.
The announcement gives the initial commands for creating that project and its API.
kubebuilder init
kubebuilder create api
These commands create the project and API structure for subsequent implementation. The announcement also describes targets for installing and running the API, building and pushing its container, and deployment. Providing those artifacts together makes publication part of the development workflow. A controller requires both executable behavior and the Kubernetes objects which permit and configure its operation.
The README's preferences explain the project's approach to reuse. It favors Go interfaces and libraries before code generation, with initialization of stubs below generation. Forking and modifying boilerplate is explicitly discouraged. That ordering seeks to keep common controller behavior maintainable through shared libraries while reserving generated code for repeated structures which those libraries cannot express directly.
I find that preference consistent with the value of reconciliation itself. Shared runtime machinery can provide a common way to invoke operational logic without determining what that logic must do. Application-specific decisions remain visible in the implementation and its tests. The SDK removes repeated project work while leaving the resource's semantics for its author to define.
Status completes the operational API
The controller documentation requires status updates because errors can happen after the user's API request returns. Status must also reflect changes which the user did not initiate, including changes caused by failures in the cluster. A custom API that accepts intent but never reports the observed result leaves the user without an API-level account of whether the controller achieved it. Acceptance and completion are separate observations in this asynchronous model.
This makes testing status behavior part of testing the controller's meaning. Reconciliation may update only status when no cluster mutation is needed. Conversely, creating a dependent object does not establish that it is ready for use. The ReplicaSet example reports running Pods separately from creating them, then reconciles again as those Pods change state. An application controller needs an equally explicit distinction between requested work and the state it can currently observe.