Skip to main content
Now that you can author a CustomResourceDefinition (CRD), you should apply a few production-ready design patterns to avoid future downtime, migration pain, and poor operator UX. This article covers four essential patterns:
  • API versioning (use served and storage flags correctly)
  • Enabling the status subresource to separate desired state from observed state
  • Adding printer columns for concise kubectl get output
  • Enabling the scale subresource for kubectl scale and HPA integration
A presentation slide titled "Learning Objectives" showing a vertical, numbered list of Kubernetes CRD-related goals. Items include API versioning, using the status subresource, adding printer columns for kubectl, enabling the scale subresource for HPA, and applying a CRD design checklist.
Following these patterns ensures every CRD you create is maintainable, extensible, and operator-friendly. Real-world motivation A team created a 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.
A slide titled "CRDs Without Patterns Become Technical Debt" showing two panels: "CRD: Database" (existing resources) and "CRD: Database V2" (migrated resources) filled with document icons and arrows pointing from the old to the new. Below are small icons and labels indicating tasks like manually migrating resources and updating references across the codebase.
Common pitfalls when design patterns are missing
  • 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 kubectl UX: default kubectl get shows only NAME and AGE, forcing repetitive commands to inspect health, size, or replica counts.
Kubernetes provides built-in CRD patterns to address each of these points. Below are focused recommendations and examples. API versioning Kubernetes version names usually follow 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.
Only one version may have storage: true at a time. Table: Version flags and purpose Recommended lifecycle strategy
  1. Start development with v1alpha1 — set served: true, storage: true.
  2. When making improvements, add v1beta1 with served: true. When promoting it to storage, set v1beta1: storage: true and change v1alpha1: storage: false. The API server will convert stored objects to the new storage version where possible.
  3. Promote v1beta1 → v1 using the same pattern.
If schema changes are incompatible across versions, implement a conversion webhook to transform objects between versions. For simple additive changes (optional fields, defaults), webhooks are often unnecessary. See the Kubernetes docs on conversion webhooks for details: https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion Example version flags:
You can mark versions as deprecated to show a migration warning:
Status subresource The 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
The slide titled "Status Subresource: Separate Desired From Actual" compares two scenarios: without status subresource (users and controllers both edit spec and status, causing race conditions) and with status subresource (users edit spec, controllers edit status, giving clean separation of concerns). The left box is red and the right box is green to emphasize the problem vs. solution.
Enable the status subresource in a version entry by adding an empty status object under subresources:
Observed generation pattern Use metadata.generation and status.observedGeneration to help users and controllers know whether the controller has processed the latest spec update:
  • metadata.generation increments on each change to spec.
  • Controller sets status.observedGeneration to the generation it last reconciled.
Example status snippet:
Remember: 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:
With printer columns:
Add columns under additionalPrinterColumns in each version. Each column requires a name, type, and a jsonPath to extract the value (you can read from spec and status):
This small addition greatly improves the UX for operators and platform engineers. Scale subresource If your custom resource represents a scalable workload, expose the scale subresource to enable:
  • kubectl scale support, e.g. kubectl scale databases orders-db --replicas=3
  • HPA (Horizontal Pod Autoscaler) integration using metrics
Map your CR’s spec and status fields to standard K8s scale expectations:
Example CLI usage:
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.
Quick checklist and key takeaways Links and references
Start with versioning and status separation early. These two patterns avoid the most painful production issues (breaking changes and controller/user race conditions).

Watch Video