K8s Custom Resources: CRD Development
Create Kubernetes Custom Resource Definitions with schema validation, additional printer columns, subresources, and conversion webhooks.
π‘ Quick Answer:
CustomResourceDefinitionextends the Kubernetes API with your own resource types. Define a CRD withkubectl apply, then create instances withkubectl apply. CRDs support schema validation, status subresource, additional printer columns, and versioning. Use CRDs + controllers for the operator pattern β automated management of complex applications.
The Problem
Kubernetes built-in resources donβt cover application-specific needs:
- Representing a database cluster (replicas, backup schedule, version)
- Managing certificates (issuer, renewal, domains)
- Defining network policies at higher abstraction levels
- Application-specific configuration as first-class Kubernetes objects
The Solution
Define a CRD
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.example.com
spec:
group: example.com
names:
kind: Database
listKind: DatabaseList
plural: databases
singular: database
shortNames:
- db
categories:
- all # Shows in kubectl get all
scope: Namespaced
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
required: ["engine", "version", "replicas"]
properties:
engine:
type: string
enum: ["postgresql", "mysql", "mariadb"]
version:
type: string
pattern: '^\d+\.\d+(\.\d+)?$'
replicas:
type: integer
minimum: 1
maximum: 7
storage:
type: object
properties:
size:
type: string
pattern: '^\d+Gi$'
storageClass:
type: string
backup:
type: object
properties:
schedule:
type: string
retention:
type: string
default: "7d"
status:
type: object
properties:
phase:
type: string
readyReplicas:
type: integer
message:
type: string
# Extra columns in kubectl get
additionalPrinterColumns:
- name: Engine
type: string
jsonPath: .spec.engine
- name: Version
type: string
jsonPath: .spec.version
- name: Replicas
type: integer
jsonPath: .spec.replicas
- name: Status
type: string
jsonPath: .status.phase
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
# Enable status subresource
subresources:
status: {}
# scale: # Optional: enable kubectl scale
# specReplicasPath: .spec.replicas
# statusReplicasPath: .status.readyReplicasCreate Custom Resources
apiVersion: example.com/v1
kind: Database
metadata:
name: production-db
namespace: production
spec:
engine: postgresql
version: "16.2"
replicas: 3
storage:
size: 100Gi
storageClass: fast-ssd
backup:
schedule: "0 2 * * *"
retention: "30d"# Apply CRD first
kubectl apply -f database-crd.yaml
# Then create instances
kubectl apply -f production-db.yaml
# Use short name
kubectl get db
# NAME ENGINE VERSION REPLICAS STATUS AGE
# production-db postgresql 16.2 3 Ready 5m
# Describe
kubectl describe db production-db
# Delete
kubectl delete db production-db
# kubectl explain works too
kubectl explain database.specStatus Subresource
# Status is updated separately from spec
# Controller updates status:
kubectl patch database production-db --type=merge --subresource=status \
-p '{"status":{"phase":"Ready","readyReplicas":3,"message":"All replicas healthy"}}'
# Users update spec:
kubectl patch database production-db --type=merge \
-p '{"spec":{"replicas":5}}'
# Status can't be changed with regular kubectl apply
# Only --subresource=status can modify .statusRBAC for Custom Resources
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: database-operator
namespace: production
rules:
- apiGroups: ["example.com"]
resources: ["databases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["example.com"]
resources: ["databases/status"]
verbs: ["get", "update", "patch"]Validation Patterns
# String validation
properties:
name:
type: string
minLength: 3
maxLength: 63
pattern: '^[a-z][a-z0-9-]*$'
# Enum validation
engine:
type: string
enum: ["postgresql", "mysql", "mariadb"]
# Number validation
replicas:
type: integer
minimum: 1
maximum: 7
default: 1
# Nested required fields
spec:
type: object
required: ["engine", "version"]
# Additional properties blocked
spec:
type: object
additionalProperties: false # Rejects unknown fieldsCRD Versioning
versions:
- name: v1
served: true
storage: true # Only ONE version can be storage
schema: ...
- name: v2
served: true
storage: false
schema: ... # v2 has different/extended schema
# With conversion webhook for v1 β v2
conversion:
strategy: Webhook
webhook:
clientConfig:
service:
name: database-converter
namespace: webhook-system
path: /convert
caBundle: <base64-CA>
conversionReviewVersions: ["v1"]List CRDs
# All CRDs in cluster
kubectl get crd
# NAME CREATED AT
# databases.example.com 2026-05-02
# certificates.cert-manager.io 2026-01-15
# CRD details
kubectl describe crd databases.example.com
# API resources includes CRDs
kubectl api-resources | grep example.com
# databases db example.com/v1 true Database
# Delete CRD (DELETES ALL custom resources!)
kubectl delete crd databases.example.comCommon Issues
βno matches for kindβ after CRD apply
CRD not yet established. Wait: kubectl wait --for=condition=Established crd/databases.example.com.
Validation errors on create
Schema doesnβt match. Check: kubectl explain database.spec. Ensure required fields present and types match.
Deleting CRD deletes all instances
By design. Use kubectl delete crd with extreme caution. Consider setting metadata.finalizers on CRs for protection.
Best Practices
- Always define OpenAPI schema β prevents invalid resources
- Enable status subresource β separates user intent (spec) from controller state (status)
- Use additionalPrinterColumns β better
kubectl getoutput - Set categories β
["all"]makes CRs appear inkubectl get all - Version from the start β plan for v1βv2 migration
- RBAC on custom resources β donβt leave them open to everyone
Key Takeaways
- CRDs extend the Kubernetes API with custom resource types
- Schema validation enforces field types, required fields, and patterns
- Status subresource separates spec (user) from status (controller)
- Additional printer columns improve
kubectl getoutput - CRDs + controllers = operator pattern for automated application management

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
