CRDs & Operators
Extend Kubernetes with custom resources and build a simple operator that manages them.
Time: ~20 minutes Difficulty: Advanced
What You Will Learn
Section titled “What You Will Learn”- CustomResourceDefinitions (CRDs): teaching Kubernetes new resource types
- Creating and managing custom resources with kubectl
- The operator pattern: a controller that watches CRs and creates real resources
- The reconciliation loop: desired state vs actual state
- Printer columns, validation schemas, and status subresources
The Operator Pattern
Section titled “The Operator Pattern”An operator is a controller that watches custom resources and makes the cluster match their desired state. In this demo:
- You define a
WebsiteCRD (what fields a Website has) - You create Website instances (“I want a blog with 2 replicas”)
- The operator watches for Websites and creates Deployments + Services to match
You create: Website CR ──> Operator watches ──> Creates Deployment + ServiceYou update: Website CR ──> Operator notices ──> Updates DeploymentYou delete: Website CR ──> Operator notices ──> Deletes Deployment + ServiceDeploy
Section titled “Deploy”Navigate to the demo directory:
cd demos/crds-and-operatorsStep 1: Register the CRD
Section titled “Step 1: Register the CRD”kubectl apply -f manifests/website-crd.yamlKubernetes now knows about the Website resource type:
kubectl api-resources | grep websitekubectl explain website.specStep 2: Create Website instances (without the operator)
Section titled “Step 2: Create Website instances (without the operator)”kubectl apply -f manifests/namespace.yamlkubectl apply -f manifests/website-samples.yamlThe resources are stored in etcd, but nothing happens yet. No pods, no services:
kubectl get websites -n crd-demokubectl get pods -n crd-demoCRDs are just data. An operator is needed to act on them.
Step 3: Deploy the operator
Section titled “Step 3: Deploy the operator”kubectl apply -f manifests/operator-rbac.yamlkubectl apply -f manifests/operator-script.yamlkubectl apply -f manifests/operator.yamlWatch the operator create Deployments and Services for each Website:
kubectl logs -f deploy/website-operator -n crd-demoIn another terminal, check the resources it created:
kubectl get pods -n crd-demokubectl get svc -n crd-demoYou should see website-my-blog (2 replicas) and website-docs-site (1 replica).
Step 4: Access the websites
Section titled “Step 4: Access the websites”kubectl port-forward svc/website-my-blog 8081:80 -n crd-demo &kubectl port-forward svc/website-docs-site 8082:80 -n crd-demo &Open http://localhost:8081 and http://localhost:8082.
Test the Reconciliation Loop
Section titled “Test the Reconciliation Loop”Change replicas
Section titled “Change replicas”kubectl patch website my-blog -n crd-demo \ --type=merge -p '{"spec":{"replicas":3}}'Wait 10 seconds (the operator polls every 10s), then check:
kubectl get pods -l app=website-my-blog -n crd-demoThe operator scaled the Deployment to 3 replicas.
Change the title
Section titled “Change the title”kubectl patch website my-blog -n crd-demo \ --type=merge -p '{"spec":{"title":"Updated Blog Title"}}'Wait 10 seconds, then refresh http://localhost:8081.
What is Happening
Section titled “What is Happening”manifests/ namespace.yaml # crd-demo namespace website-crd.yaml # CRD: defines the Website resource type website-samples.yaml # Two Website instances (my-blog, docs-site) operator-rbac.yaml # ServiceAccount + Role + RoleBinding for the operator operator-script.yaml # Shell script implementing the reconciliation loop operator.yaml # Deployment running the operatorThe operator is a shell script (for learning purposes). It runs a loop every 10 seconds:
- Lists all Website CRs in the namespace
- For each Website, creates or updates a Deployment with the specified replicas
- Creates a Service for each Website
Real operators use frameworks like:
- Go: controller-runtime / Operator SDK
- Python: kopf
- Java: Java Operator SDK
They use watches (event streams) instead of polling, and they handle deletion, owner references, finalizers, and status updates properly.
Experiment
Section titled “Experiment”-
Create a new Website:
Terminal window kubectl apply -f - <<'EOF'apiVersion: demo.example.com/v1kind: Websitemetadata:name: landing-pagenamespace: crd-demospec:title: "Product Landing Page"replicas: 3color: "#E91E63"EOF -
Check that the CRD validates input:
Terminal window # This should fail (replicas > 5)kubectl apply -f - <<'EOF'apiVersion: demo.example.com/v1kind: Websitemetadata:name: too-manynamespace: crd-demospec:title: "Won't work"replicas: 10EOF -
Use the short name:
Terminal window kubectl get ws -n crd-demo
Cleanup
Section titled “Cleanup”kubectl delete namespace crd-demokubectl delete crd websites.demo.example.comFurther Reading
Section titled “Further Reading”See docs/deep-dive.md for a detailed explanation of CRD versioning, conversion webhooks, owner references, finalizers, the controller-runtime framework, and how production operators like CloudNativePG implement these patterns.
Next Step
Section titled “Next Step”Move on to Service Types to learn the differences between ClusterIP, NodePort, LoadBalancer, and ExternalName.