✦ For everyone, free.

Practical knowledge for real and everyday life

Home

Kubernetes CRD Schema Management

Kubernetes CRD Schema Management defines and enforces the structure of custom resources, ensuring consistency and reliability in Kubernetes-based applications.

Kubernetes CRD Schema Management is the practice of authoring, generating, testing, and maintaining the OpenAPI validation schema embedded in a Custom Resource Definition, encompassing the tooling that derives schemas from source code, the defaulting and pruning behavior the API server applies at admission time, and the workflow for evolving a schema without silently breaking objects already stored under it.


Authoring Approaches

Hand-Written OpenAPI Schemas

A schema can be written directly as OpenAPI v3 YAML, giving full manual control but requiring the author to keep the schema synchronized by hand with whatever code actually consumes the resource.

schema:
  openAPIV3Schema:
    type: object
    properties:
      spec:
        type: object
        properties:
          replicas:
            type: integer
            minimum: 1
            maximum: 10
          storageClassName:
            type: string

Generated Schemas from Source Annotations

Tooling such as controller-gen (used by the Kubebuilder and Operator SDK ecosystems) derives the OpenAPI schema directly from Go struct tags and marker comments, keeping the schema mechanically synchronized with the controller's actual data model.

type PostgresClusterSpec struct {
    // +kubebuilder:validation:Minimum=1
    // +kubebuilder:validation:Maximum=10
    Replicas int32 `json:"replicas"`

    // +kubebuilder:validation:Enum=gp3;io2;standard
    StorageClassName string `json:"storageClassName,omitempty"`
}
controller-gen crd:crdVersions=v1 paths="./api/..." output:crd:dir=./config/crd/bases

Defaulting Behavior

Schema-Level Defaults

schema:
  openAPIV3Schema:
    type: object
    properties:
      spec:
        type: object
        properties:
          retentionDays:
            type: integer
            default: 30

A default value declared in the schema is applied by the API server itself at admission time whenever the field is omitted from a submitted object, meaning defaulting happens once, at creation, and is persisted into the stored object rather than being recomputed on every read.

Defaulting Webhooks vs. Schema Defaults

Simple, static defaults belong in the schema; defaults that depend on other field values or external state (computing a default replica count based on a referenced node pool's size, for instance) require a mutating admission webhook instead, since schema-level default values cannot express conditional logic.

Stored Value = { Submitted Value, if present ; Default, otherwise }

Pruning and Preservation

Automatic Pruning Under Structural Schemas

With a structural schema, any field submitted that is not declared anywhere in the schema is silently dropped by the API server before the object is stored, rather than being rejected or persisted as unknown data, which protects the resource's shape but also means a typo in a field name fails silently rather than with a validation error unless additionalProperties: false is explicitly set to reject unknowns instead of pruning them.

schema:
  openAPIV3Schema:
    type: object
    properties:
      spec:
        type: object
        additionalProperties: false
        properties:
          replicas:
            type: integer

Cross-Field and Business-Rule Validation

CEL-Based Validation Rules

Common Expression Language rules embedded via x-kubernetes-validations express constraints that span multiple fields or encode business logic beyond simple type and range checks, evaluated synchronously at admission time without a separate webhook round trip.

schema:
  openAPIV3Schema:
    type: object
    properties:
      spec:
        type: object
        x-kubernetes-validations:
          - rule: "self.minReplicas <= self.maxReplicas"
            message: "minReplicas must not exceed maxReplicas"
          - rule: "!has(oldSelf.storageClassName) || self.storageClassName == oldSelf.storageClassName"
            message: "storageClassName is immutable after creation"

The second rule above demonstrates immutability enforcement by comparing the incoming object (self) against the previously stored object (oldSelf), a pattern that previously required a validating webhook and is now expressible directly in the schema.


Testing Schema Changes

Dry-Run Validation

kubectl apply --dry-run=server -f updated-postgrescluster.yaml

Server-side dry-run submits an object through the full admission chain, including schema validation and any registered webhooks, without persisting it, making it the standard way to verify a schema change accepts or correctly rejects representative sample objects before rolling the change out.

Schema Compatibility Testing in CI

Operator projects commonly maintain a corpus of representative custom resource YAML files exercised in CI against each schema revision, catching accidental breaking changes (a tightened enum, a newly required field) against realistic objects before the schema reaches a production cluster where existing stored objects could be invalidated.


Relationship to CRD Management, Spec Structure, and Version Management

Schema management is the content-authoring discipline underlying the structural fields described in CRD spec structure, and it is the layer where the safe-evolution rules from broader CRD management (additive-only changes, structural schema requirements) and the sequencing rules from version management are actually put into practice: a schema change is where those policies either hold or are violated, making rigorous authoring and testing of the schema itself the point where CRD lifecycle discipline is enforced in the day-to-day of Operator development.

Go markers / YAML Generated schema API server Prune / default