- API versioning (use
servedandstorageflags correctly) - Enabling the
statussubresource to separate desired state from observed state - Adding printer columns for concise
kubectl getoutput - Enabling the
scalesubresource forkubectl scaleand HPA integration

Database CRD with only a single v1 version and no status subresource. Six months later they needed to require a backup field. Changing the v1 schema to make backup required would have invalidated all stored resources. Lacking a prepared upgrade path, they had to declare a new CRD database.v2, migrate resources manually, and update every consumer — a multi-week effort that proper versioning and status separation would have avoided.

- No versioning: adding required or incompatible fields can make stored objects invalid and can break clients.
- No status separation: users may accidentally overwrite controller-managed status fields, causing race conditions and loss of controller state.
- Poor
kubectlUX: defaultkubectl getshows onlyNAMEandAGE, forcing repetitive commands to inspect health, size, or replica counts.
v1alpha1, v1beta1, v1. Each entry in the CRD versions array supports two key flags:
served: true— API server accepts requests for that version.storage: true— server stores objects in etcd using that version.
storage: true at a time.
Table: Version flags and purpose
Recommended lifecycle strategy
- Start development with
v1alpha1— setserved: true, storage: true. - When making improvements, add
v1beta1withserved: true. When promoting it to storage, setv1beta1: storage: trueand changev1alpha1: storage: false. The API server will convert stored objects to the new storage version where possible. - Promote
v1beta1→v1using the same pattern.
status subresource prevents user edits from accidentally overwriting controller-managed status fields. By enabling status, Kubernetes exposes separate endpoints:
- Update spec: PUT /apis/<group>/…/namespaces/<ns>/<plural>/<name>
- Update status: PUT /apis/<group>/…/namespaces/<ns>/<plural>/<name>/status

status object under subresources:
metadata.generation and status.observedGeneration to help users and controllers know whether the controller has processed the latest spec update:
metadata.generationincrements on each change tospec.- Controller sets
status.observedGenerationto the generation it last reconciled.
status snippet:
spec is the desired state; status is the observed state produced by controllers.
Printer columns
Printer columns improve the kubectl get experience by showing useful fields (size, phase, replicas) in a tabular view.
Default output:
additionalPrinterColumns in each version. Each column requires a name, type, and a jsonPath to extract the value (you can read from spec and status):
scale subresource to enable:
kubectl scalesupport, e.g.kubectl scale databases orders-db --replicas=3- HPA (Horizontal Pod Autoscaler) integration using metrics
spec and status fields to standard K8s scale expectations:
labelSelectorPath is optional and only required when the HPA needs to identify which Pods belong to the CR instance.
Putting it all together — production-ready CRD example
This consolidated CRD demonstrates versioning, per-version subresources, printer columns, short names, and OpenAPI v3 validation. Note: openAPIV3Schema, subresources, and additionalPrinterColumns live under each versions entry.
Links and references
- Kubernetes CRD versioning and conversion webhooks: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/
- Status and subresources: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#status-subresource
- Additional printer columns: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#additional-printer-columns
- Scale subresource and HPA: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#scale-subresource
Start with versioning and status separation early. These two patterns avoid the most painful production issues (breaking changes and controller/user race conditions).