CRDs & Operators Basics
This page is a hands-on intro to Custom Resource Definitions and the controllers that act on them. By the end you can define a new API type, create objects of that type, and understand what a controller adds.
Busque em todas as páginas da documentação
This page is a hands-on intro to Custom Resource Definitions and the controllers that act on them. By the end you can define a new API type, create objects of that type, and understand what a controller adds.
kubebuilder, and Docker Engine 29.x to build the controller image.# Quick sanity check
kubectl version
kubectl api-resources | headA CRD registers a new kind. This one adds a namespaced Widget resource in group example.com.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: widgets.example.com
spec:
group: example.com
scope: Namespaced
names:
plural: widgets
singular: widget
kind: Widget
shortNames: ["wdg"]
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
size:
type: stringapiextensions.k8s.io/v1 is the stable, required API for CRDs.scope: Namespaced means each Widget lives in a namespace, like a Pod.storage: true; that is the version persisted to etcd.served: true means the API server will accept and return that version.openAPIV3Schema is mandatory in v1 and controls validation.Once applied, the new kind behaves like a built-in one.
kubectl apply -f widget-crd.yaml
kubectl api-resources | grep widgets
kubectl explain widget.speckubectl apply registers the type cluster-wide.kubectl api-resources now lists widgets, proving the API server knows the kind.kubectl explain reads the schema you supplied, so documentation is generated from the CRD.A CR is an instance of the type, written like any manifest.
apiVersion: example.com/v1
kind: Widget
metadata:
name: blue-widget
spec:
size: largeapiVersion combines the group and version from the CRD.kubectl apply -f blue-widget.yaml stores the object; kubectl get widgets lists it.Structural schemas can enforce allowed values and required fields.
spec:
type: object
required: ["size"]
properties:
size:
type: string
enum: ["small", "medium", "large"]
replicas:
type: integer
minimum: 1
maximum: 10required rejects objects missing the field at admission time.enum limits size to known values, catching typos before they persist.minimum and maximum bound numeric fields without any controller code.Printer columns customize what kubectl get shows.
additionalPrinterColumns:
- name: Size
type: string
jsonPath: .spec.size
- name: Age
type: date
jsonPath: .metadata.creationTimestampversions entry.jsonPath selects the field to display.kubectl get widgets now shows a Size column, improving day-to-day usability.Custom resources use standard RBAC by group and resource name.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: widget-editor
namespace: default
rules:
- apiGroups: ["example.com"]
resources: ["widgets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]apiGroups matches the CRD spec.group.resources matches the plural name.Widget objects.Custom resources support the same lifecycle verbs as built-ins.
kubectl get widgets -o yaml
kubectl describe widget blue-widget
kubectl delete widget blue-widget-o yaml shows the stored object including any status the controller writes.describe surfaces events, useful once a controller emits them.The status subresource splits spec (user intent) from status (controller-reported state).
versions:
- name: v1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec: { type: object }
status:
type: object
properties:
phase: { type: string }subresources.status: {}, updates to .status go through a separate endpoint.A CRD stores data; a controller makes it mean something.
// Reconcile is called whenever a Widget changes.
func (r *WidgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
var w examplev1.Widget
if err := r.Get(ctx, req.NamespacedName, &w); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Drive real state toward w.Spec, then update w.Status.
return ctrl.Result{}, nil
}Reconcile function is the control loop for your type.Kubebuilder generates the project, CRD manifests, and reconciler skeleton.
kubebuilder init --domain example.com --repo example.com/widget-operator
kubebuilder create api --group example --version v1 --kind Widget
make manifestsinit creates the controller-runtime project layout.create api scaffolds both the CRD types and a Reconcile stub.make manifests regenerates CRD YAML from your Go types, keeping them in sync.Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026