Stage 5 · Platform
Operators, CRDs & Controllers
CRD Schema Design
OpenAPI schemas, validation rules, defaulting, pruning, and additional printer columns.
CRD Design Principles
Good CRD design follows Kubernetes API conventions: use spec for desired state, status for observed state, make fields descriptive with good names, and provide sensible defaults. The CRD schema should be self-documenting.
- spec describes the desired state (what the user wants)
- status describes the observed state (what the controller sees)
- Use descriptive field names — users read them in kubectl output
- Provide sensible defaults — minimize required fields
- Use additionalPrinterColumns for kubectl get output
- Enable status subresource for concurrent updates
OpenAPI Schema
The CRD schema uses OpenAPI v3 to validate objects. It defines types, required fields, patterns, enums, and nested objects. Strong schemas prevent invalid resources from reaching the controller.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: webapps.platform.example.com
spec:
group: platform.example.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: ["replicas", "image"]
properties:
replicas:
type: integer
minimum: 1
maximum: 100
image:
type: string
pattern: "^[a-z0-9.-]+:[a-z0-9.-]+(@sha256:[a-f0-9]{64})?$"
env:
type: array
items:
type: object
required: ["name"]
properties:
name:
type: string
pattern: "^[A-Z_][A-Z0-9_]*$"
value:
type: string
valueFrom:
type: object
properties:
secretKeyRef:
type: object
properties:
name:
type: string
key:
type: string
status:
type: object
properties:
readyReplicas:
type: integer
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
enum: ["True", "False", "Unknown"]
message:
type: stringThe schema enforces: replicas is 1-100, image matches a tag or digest pattern, env names are UPPER_SNAKE_CASE, and status conditions have valid statuses. Invalid objects are rejected at admission.
CEL Validation Rules
CEL (Common Expression Language) rules provide complex validation that JSON Schema cannot express: cross-field validation, conditional requirements, and computed constraints. Available since Kubernetes 1.25.
schema:
openAPIV3Schema:
type: object
x-kubernetes-validations:
- rule: "self.spec.replicas <= self.spec.maxReplicas"
message: "replicas must not exceed maxReplicas"
- rule: "has(self.spec.env) ? all(self.spec.env, e, has(e.name) && has(e.value)) : true"
message: "env entries must have both name and value"
- rule: |
!has(self.spec.ingress) || self.spec.ingress.enabled == true
message: "ingress config requires ingress.enabled=true"
properties:
spec:
type: object
properties:
replicas:
type: integer
maxReplicas:
type: integer
env:
type: array
ingress:
type: object
properties:
enabled:
type: boolean
host:
type: stringCEL rules reference multiple fields in the same object. The first rule ensures replicas <= maxReplicas. The second validates all env entries have name and value. The third requires ingress.enabled when ingress config is present.
Defaulting
Defaulting sets field values when they're not specified. Use CEL rules or webhook mutations to apply defaults. This simplifies the user experience — provide sensible defaults and let users override only what they need.
schema:
openAPIV3Schema:
type: object
x-kubernetes-validations:
- rule: |
!has(self.spec.replicas) ? (self.spec.replicas = 2) : true
message: "defaulting replicas to 2"
- rule: |
!has(self.spec.maxReplicas) ? (self.spec.maxReplicas = self.spec.replicas * 3) : true
message: "defaulting maxReplicas to 3x replicas"CEL defaulting sets replicas to 2 if not specified. maxReplicas defaults to 3x replicas. These rules apply during validation — the defaulted values are persisted.
Additional Printer Columns
Additional printer columns add custom output to kubectl get. Show important status fields without requiring kubectl get -o yaml. This improves developer experience.
additionalPrinterColumns:
- name: Replicas
type: integer
jsonPath: .spec.replicas
- name: Ready
type: integer
jsonPath: .status.readyReplicas
- name: Available
type: boolean
jsonPath: ".status.conditions[?(@.type=='Available')].status"
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
- name: Version
type: string
jsonPath: .spec.image
priority: 0
- name: Message
type: string
jsonPath: ".status.conditions[?(@.type=='Available')].message"
priority: 1kubectl get webapps shows: Replicas, Ready, Available, Age, Version. priority: 1 columns show only with -o wide. JSONPath expressions extract values from nested fields and condition arrays.
Status Subresource
The status subresource separates spec (user-managed) from status (controller-managed). Updates to /status don't conflict with spec changes. This enables concurrent updates by users and controllers.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: webapps.platform.example.com
spec:
versions:
- name: v1
served: true
storage: true
subresources:
status: {}
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
status:
type: objectsubresources: status: {} enables the status subresource. Users update /spec, controllers update /status. Changes to /status don't bump the resourceVersion of /spec, preventing conflicts.
Use kubectl apply --dry-run=server to validate objects against the CRD schema. Use kubeconform with CRD schemas for offline validation. Test CEL rules with kubectl apply --dry-run=client to catch validation errors.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.