> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# CRD Design Patterns Versioning Status and Printer Columns

> Instructions for creating production ready Kubernetes CRDs covering versioning, status separation, printer columns, and scale to improve upgrades, UX, and HPA integration

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.](https://mintcdn.com/kodekloud-c4ac6d9a/seqLTE3JlSrVAtw9/images/Prep-Course-Certified-Cloud-Native-Platform-Engineering-CNPE/Platform-APIs-and-Self-Service-Capabilities/CRD-Design-Patterns-Versioning-Status-and-Printer-Columns/kubernetes-crd-learning-objectives.jpg?fit=max\&auto=format\&n=seqLTE3JlSrVAtw9\&q=85\&s=f35ade824b718a6984942e62fc6c1279)

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.](https://mintcdn.com/kodekloud-c4ac6d9a/seqLTE3JlSrVAtw9/images/Prep-Course-Certified-Cloud-Native-Platform-Engineering-CNPE/Platform-APIs-and-Self-Service-Capabilities/CRD-Design-Patterns-Versioning-Status-and-Printer-Columns/crd-database-migration-technical-debt.jpg?fit=max\&auto=format\&n=seqLTE3JlSrVAtw9\&q=85\&s=688e3e1d9b7575a3d45d5e0d850fa558)

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

| Flag | Purpose |
| - | - |
| `served: true` | Accept traffic for this API version |
| `storage: true` | Persist objects in etcd in this version format |
| `deprecated: true` | Mark older versions to warn users (optional) |

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](https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/#webhook-conversion)

Example version flags:

```yaml theme={null}
# version entry snippet
served: true
storage: true
```

You can mark versions as deprecated to show a migration warning:

```yaml theme={null}
# deprecation example in a version entry
served: true
storage: true
deprecated: true
deprecationWarning: "This version will be removed in v2.0; please migrate to v1."
```

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.](https://mintcdn.com/kodekloud-c4ac6d9a/seqLTE3JlSrVAtw9/images/Prep-Course-Certified-Cloud-Native-Platform-Engineering-CNPE/Platform-APIs-and-Self-Service-Capabilities/CRD-Design-Patterns-Versioning-Status-and-Printer-Columns/status-subresource-separate-desired-actual.jpg?fit=max\&auto=format\&n=seqLTE3JlSrVAtw9\&q=85\&s=e585396862ff41d00c4c6e358b25836f)

Enable the status subresource in a version entry by adding an empty `status` object under `subresources`:

```yaml theme={null}
subresources:
  status: {}
```

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:

```yaml theme={null}
status:
  phase: Ready
  conditions:
    - type: Available
      status: "True"
      reason: ProvisioningComplete
      lastTransitionTime: "2024-01-15T10:30:00Z"
  observedGeneration: 3
  endpoint: db.prod.svc:5432
```

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:

```bash theme={null}
NAME        AGE
orders-db   5m
```

With printer columns:

```bash theme={null}
NAME        SIZE    STATUS   AGE
orders-db   medium  Ready    5m
```

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`):

```yaml theme={null}
additionalPrinterColumns:
  - name: Size
    type: string
    jsonPath: .spec.size
  - name: Status
    type: string
    jsonPath: .status.phase
  - name: Age
    type: date
    jsonPath: .metadata.creationTimestamp
```

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:

```yaml theme={null}
subresources:
  status: {}
  scale:
    specReplicasPath: .spec.replicas
    statusReplicasPath: .status.replicas
    labelSelectorPath: .status.selector
```

Example CLI usage:

```bash theme={null}
kubectl scale databases orders-db --replicas=3
kubectl autoscale databases orders-db --min=1 --max=5
```

`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.

```yaml theme={null}
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: databases.platform.acme.io
spec:
  group: platform.acme.io
  scope: Namespaced
  names:
    plural: databases
    singular: database
    kind: Database
    shortNames:
      - db

  versions:
    - name: v1
      served: true
      storage: true
      subresources:
        status: {}
        scale:
          specReplicasPath: .spec.replicas
          statusReplicasPath: .status.replicas
          labelSelectorPath: .status.selector
      additionalPrinterColumns:
        - name: Size
          type: string
          jsonPath: .spec.size
        - name: Status
          type: string
          jsonPath: .status.phase
        - name: Age
          type: date
          jsonPath: .metadata.creationTimestamp
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              required:
                - size
              properties:
                size:
                  type: string
                  enum:
                    - small
                    - medium
                    - large
                replicas:
                  type: integer
                  minimum: 0
            status:
              type: object
              properties:
                phase:
                  type: string
                replicas:
                  type: integer
                observedGeneration:
                  type: integer
```

Quick checklist and key takeaways

| Pattern | Why it matters | How to implement |
| - | - | - |
| Versioning | Avoid invalidating stored objects and ease migrations | Use `served` and `storage`; implement conversion webhooks for incompatible changes |
| Status subresource | Prevent user/controller race conditions | `subresources: status: {}` and use `status.observedGeneration` |
| Printer columns | Improve operator UX and speed of debugging | `additionalPrinterColumns` with `jsonPath` to `spec`/`status` |
| Scale subresource | Enable `kubectl scale` and HPA | `subresources: scale` with `specReplicasPath` / `statusReplicasPath` |
| Schema validation | Guard inputs and document expected fields | `openAPIV3Schema` under `versions` |

Links and references

* Kubernetes CRD versioning and conversion webhooks: [https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definition-versioning/](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](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](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](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).

> **[Watch Video](https://learn.kodekloud.com/user/courses/prep-course-certified-cloud-native-platform-engineer-cnpe/module/756ffaae-767b-4743-9724-c05d3fbf9a18/lesson/6eac32ae-9924-4589-80b4-b64798b0da30)**


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.