Module 1.5: CRDs & Operators - Extending Kubernetes
Complexity:
[MEDIUM]- New to CKA 2025Time to Complete: 35-45 minutes
Prerequisites: Module 1.1 (Control Plane understanding)
What You’ll Be Able to Do
Section titled “What You’ll Be Able to Do”After this module, you will be able to:
- Create Custom Resource Definitions and explain how they extend the Kubernetes API
- Deploy an operator and manage its custom resources
- Explain the operator pattern (custom controller + CRD) and when it’s appropriate
- Debug CRD validation errors and operator reconciliation failures
Why This Module Matters
Section titled “Why This Module Matters”CRDs and Operators are new to the CKA 2025 curriculum.
Kubernetes ships with built-in resources: Pods, Deployments, Services. But what if you need a “Database” resource that automatically handles backups, failover, and scaling? What if you want a “Certificate” resource that auto-renews from Let’s Encrypt?
Custom Resource Definitions (CRDs) let you create new resource types. Operators are controllers that manage these custom resources. Together, they’re how the Kubernetes ecosystem extends beyond the core.
This is how Prometheus, Cert-Manager, ArgoCD, and hundreds of other tools integrate with Kubernetes. Understanding CRDs and Operators is essential for working with modern Kubernetes.
The Building Blocks Analogy
Think of Kubernetes like a LEGO set. It comes with standard blocks (Pods, Services). CRDs are custom blocks you design yourself—a “Database” block, a “Certificate” block. Operators are instruction manuals that know how to combine these blocks into working systems. You don’t build manually; the operator follows the instructions automatically.
What You’ll Learn
Section titled “What You’ll Learn”By the end of this module, you’ll be able to:
- Understand what CRDs and Operators are
- Create and manage Custom Resource Definitions
- Create instances of custom resources
- Understand the Operator pattern
- Work with common operators (cert-manager, prometheus)
Part 1: Custom Resource Definitions (CRDs)
Section titled “Part 1: Custom Resource Definitions (CRDs)”1.1 What Is a CRD?
Section titled “1.1 What Is a CRD?”A CRD extends the Kubernetes API with a new resource type.
Built-in Resources: Custom Resources (via CRDs):├── Pod ├── Certificate (cert-manager)├── Deployment ├── Prometheus (prometheus-operator)├── Service ├── PostgreSQL (postgres-operator)├── ConfigMap ├── VirtualService (istio)└── ... └── YourOwnResourceOnce a CRD is created, you can use kubectl to manage the new resource type just like built-in resources:
# Built-in resourcekubectl get pods
# Custom resource (after CRD is installed)kubectl get certificateskubectl get prometheuseskubectl get postgresqls1.2 CRD Structure
Section titled “1.2 CRD Structure”apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: crontabs.stable.example.com # <plural>.<group>spec: group: stable.example.com # API group versions: - name: v1 # API version served: true # Enable this version storage: true # Store in etcd schema: openAPIV3Schema: # Validation schema type: object properties: spec: type: object properties: cronSpec: type: string image: type: string replicas: type: integer scope: Namespaced # or Cluster names: plural: crontabs # kubectl get crontabs singular: crontab # kubectl get crontab kind: CronTab # Kind in YAML shortNames: - ct # kubectl get ct1.3 Creating a CRD
Section titled “1.3 Creating a CRD”# Apply the CRDkubectl apply -f crontab-crd.yaml
# Verify it was createdkubectl get crd crontabs.stable.example.com
# Now you can create instanceskubectl get crontabs# No resources found (expected - we haven't created any yet)Did You Know?
CRDs are stored in etcd just like built-in resources. Once created, they become first-class API citizens. The API server handles validation, RBAC, and watch mechanisms automatically.
Part 2: Creating Custom Resources
Section titled “Part 2: Creating Custom Resources”Pause and predict: You’ve just applied a CRD that defines a new “Database” resource type. You then create a Database custom resource with
replicas: 3. Will Kubernetes automatically create 3 pods for your database? Why or why not?
2.1 Custom Resource Instance
Section titled “2.1 Custom Resource Instance”Once the CRD exists, you can create instances:
apiVersion: stable.example.com/v1kind: CronTabmetadata: name: my-cron-job namespace: defaultspec: cronSpec: "* * * * */5" image: my-awesome-cron-image replicas: 3kubectl apply -f my-crontab.yamlkubectl get crontabskubectl get ct # Using shortNamekubectl describe crontab my-cron-job2.2 Custom Resource Operations
Section titled “2.2 Custom Resource Operations”All standard kubectl operations work:
# Createkubectl apply -f crontab.yaml
# Listkubectl get crontabs -A
# Describekubectl describe crontab my-cron-job
# Editkubectl edit crontab my-cron-job
# Deletekubectl delete crontab my-cron-job
# Watchkubectl get crontabs -w
# Get as YAMLkubectl get crontab my-cron-job -o yamlPart 3: The Operator Pattern
Section titled “Part 3: The Operator Pattern”3.1 What Is an Operator?
Section titled “3.1 What Is an Operator?”A CRD alone doesn’t do anything—it’s just data storage. An Operator is a controller that:
- Watches for changes to custom resources
- Takes action to reconcile desired state with actual state
┌────────────────────────────────────────────────────────────────┐│ Operator Pattern ││ ││ You create: ││ ┌─────────────────────────────────────────┐ ││ │ apiVersion: databases.example.com/v1 │ ││ │ kind: PostgreSQL │ ││ │ spec: │ ││ │ version: "15" │ ││ │ replicas: 3 │ ││ │ storage: 100Gi │ ││ └─────────────────────────────────────────┘ ││ │ ││ ▼ ││ ┌─────────────────────────────────────────┐ ││ │ Operator (Controller) │ ││ │ │ ││ │ Watches PostgreSQL resources │ ││ │ Creates: │ ││ │ • StatefulSet with 3 replicas │ ││ │ • PVCs for 100Gi storage │ ││ │ • Services for connections │ ││ │ • Secrets for credentials │ ││ │ • ConfigMaps for configuration │ ││ │ │ ││ │ Manages: │ ││ │ • Automatic failover │ ││ │ • Backups │ ││ │ • Version upgrades │ ││ └─────────────────────────────────────────┘ ││ │└────────────────────────────────────────────────────────────────┘What would happen if: The cert-manager operator pod crashes but the CRDs remain. Can you still create Certificate custom resources? Will existing certificates continue to serve TLS traffic?
3.2 Operator Components
Section titled “3.2 Operator Components”An operator typically includes:
- CRD(s) - Define the custom resource types
- Controller - Deployment running the reconciliation logic
- RBAC - Permissions to manage resources
- Webhooks (optional) - Validation and mutation
3.3 Reconciliation Loop
Section titled “3.3 Reconciliation Loop”┌─────────────────────────────────────────────────────────────┐│ Reconciliation Loop ││ ││ ┌─────────┐ ││ │ Watch │◄─────────────────────────────────────────┐ ││ └────┬────┘ │ ││ │ Event: PostgreSQL resource changed │ ││ ▼ │ ││ ┌─────────┐ │ ││ │ Read │ Get current state from cluster │ ││ └────┬────┘ │ ││ │ │ ││ ▼ │ ││ ┌─────────┐ │ ││ │ Compare │ Current state vs. Desired state │ ││ └────┬────┘ │ ││ │ │ ││ ▼ │ ││ ┌─────────┐ │ ││ │ Act │ Create/Update/Delete resources │ ││ └────┬────┘ │ ││ │ │ ││ └─────────────────────────────────────────────►─┘ ││ Repeat forever ││ │└─────────────────────────────────────────────────────────────┘War Story: The Self-Healing Database
A company used a PostgreSQL operator. At 3 AM, the primary database node failed. Without human intervention, the operator detected the failure, promoted a replica to primary, updated service endpoints, and notified the team via Slack. By the time the on-call engineer checked, everything was running. Total downtime: 90 seconds. Without the operator, it would have been a 30-minute manual failover.
Part 4: Working with Real Operators
Section titled “Part 4: Working with Real Operators”4.1 cert-manager
Section titled “4.1 cert-manager”cert-manager automates TLS certificate management:
# Install cert-manager (includes CRDs)kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml
# Check CRDs createdkubectl get crd | grep cert-manager# certificates.cert-manager.io# clusterissuers.cert-manager.io# issuers.cert-manager.io# ...# Create a Certificate resourceapiVersion: cert-manager.io/v1kind: Certificatemetadata: name: myapp-tls namespace: defaultspec: secretName: myapp-tls-secret issuerRef: name: letsencrypt-prod kind: ClusterIssuer dnsNames: - myapp.example.comThe cert-manager operator watches this Certificate and:
- Requests a certificate from Let’s Encrypt
- Completes the ACME challenge
- Stores the certificate in the specified Secret
- Auto-renews before expiration
4.2 Prometheus Operator
Section titled “4.2 Prometheus Operator”# Check Prometheus CRDskubectl get crd | grep monitoring.coreos.com# prometheuses.monitoring.coreos.com# servicemonitors.monitoring.coreos.com# alertmanagers.monitoring.coreos.com# Create a Prometheus instanceapiVersion: monitoring.coreos.com/v1kind: Prometheusmetadata: name: main namespace: monitoringspec: replicas: 2 serviceAccountName: prometheus serviceMonitorSelector: matchLabels: team: frontend4.3 Discovering What’s Installed
Section titled “4.3 Discovering What’s Installed”# List all CRDs in clusterkubectl get crd
# See all custom resources of a typekubectl get certificates -A
# Check if operator is runningkubectl get pods -A | grep operatorkubectl get pods -A | grep -E "cert-manager|prometheus"Part 5: CRD Deep Dive
Section titled “Part 5: CRD Deep Dive”Stop and think: What happens if you delete a CRD while there are still custom resources of that type in the cluster? Do the custom resources survive, or are they deleted too?
5.1 Schema Validation
Section titled “5.1 Schema Validation”CRDs can enforce validation:
schema: openAPIV3Schema: type: object required: - spec properties: spec: type: object required: - cronSpec - image properties: cronSpec: type: string pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$' image: type: string replicas: type: integer minimum: 1 maximum: 10 default: 1Now invalid resources are rejected:
# This would fail validationkubectl apply -f bad-crontab.yaml# Error: spec.replicas: Invalid value: 15: must be <= 105.2 Additional Printer Columns
Section titled “5.2 Additional Printer Columns”Show custom columns in kubectl get:
versions: - name: v1 additionalPrinterColumns: - name: Schedule type: string jsonPath: .spec.cronSpec - name: Replicas type: integer jsonPath: .spec.replicas - name: Age type: date jsonPath: .metadata.creationTimestampkubectl get crontabs# NAME SCHEDULE REPLICAS AGE# my-cron-job * * * * */5 3 5m5.3 Subresources
Section titled “5.3 Subresources”Enable status and scale subresources:
versions: - name: v1 subresources: status: {} # Enable /status subresource scale: # Enable kubectl scale specReplicasPath: .spec.replicas statusReplicasPath: .status.replicas# Now this workskubectl scale crontab my-cron-job --replicas=5Part 6: Namespaced vs. Cluster-Scoped
Section titled “Part 6: Namespaced vs. Cluster-Scoped”6.1 Scope Types
Section titled “6.1 Scope Types”# Namespaced (default)scope: Namespaced# Resources exist within a namespace# kubectl get crontabs -n myapp
# Cluster-scopedscope: Cluster# Resources are cluster-wide (like Nodes, PVs)# kubectl get clusterissuers (cert-manager example)6.2 When to Use Each
Section titled “6.2 When to Use Each”| Scope | Use When | Examples |
|---|---|---|
| Namespaced | Resource belongs to a team/app | Certificate, Database, Application |
| Cluster | Resource is shared/global | ClusterIssuer, StorageProfile |
Part 7: Exam-Relevant Operations
Section titled “Part 7: Exam-Relevant Operations”7.1 Check What CRDs Exist
Section titled “7.1 Check What CRDs Exist”# List all CRDskubectl get crd
# Get details about a CRDkubectl describe crd certificates.cert-manager.io
# See the full CRD definitionkubectl get crd certificates.cert-manager.io -o yaml7.2 Work with Custom Resources
Section titled “7.2 Work with Custom Resources”# List custom resourceskubectl get <resource-name> -A
# Get specific resourcekubectl get certificate my-cert -o yaml
# Edit custom resourcekubectl edit certificate my-cert
# Delete custom resourcekubectl delete certificate my-cert7.3 Find API Resources
Section titled “7.3 Find API Resources”# List all resource types (including custom)kubectl api-resources
# Filter by groupkubectl api-resources --api-group=cert-manager.io
# Show if namespacedkubectl api-resources --namespaced=trueDid You Know?
Section titled “Did You Know?”-
OperatorHub.io catalogs hundreds of operators. Search for what you need before building your own.
-
Operator SDK and Kubebuilder are frameworks for building operators in Go. You can also build operators in Python, Ansible, or Helm.
-
OLM (Operator Lifecycle Manager) manages operator installation and upgrades. It’s what OpenShift uses to install operators.
-
Finalizers let custom resources prevent deletion until cleanup is complete. This is how operators ensure databases are properly backed up before deletion.
Common Mistakes
Section titled “Common Mistakes”| Mistake | Problem | Solution |
|---|---|---|
| Creating CR before CRD | ”resource not found” | Always install CRD first |
| Wrong API group/version | YAML validation fails | Check CRD for correct apiVersion |
| Forgetting operator install | CR does nothing | CRD alone doesn’t act; need operator |
| Deleting CRD with existing CRs | Data loss | Delete CRs first, then CRD |
| Wrong scope assumption | Resource in wrong place | Check CRD scope: Namespaced vs Cluster |
-
You join a new team and run
kubectl get pods -Abut only see a handful of system pods. A colleague says “the databases are managed by an operator.” How would you discover what CRDs are installed, find the database custom resources, and determine which operator manages them?Answer
Start with `kubectl get crd` to list all Custom Resource Definitions in the cluster — this shows you every custom type available. Look for database-related names (e.g., `postgresqls.acid.zalan.do` or `databases.example.com`). Then list instances: `kubectl get-A` (e.g., `kubectl get postgresqls -A`). To find the operator managing them, run `kubectl get pods -A | grep operator` or check the CRD's API group and search for pods in kube-system or a dedicated namespace. You can also run `kubectl api-resources` which shows all resource types including their API group, helping you trace which operator project owns them. -
A developer creates a
Certificatecustom resource for cert-manager, but after 10 minutes the certificate Secret hasn’t been created. The CRD exists andkubectl get certificate my-certshows the resource. What are the three things you should check, in order?Answer
First, check if the cert-manager operator pods are running: `kubectl get pods -n cert-manager`. A CRD without its operator is just data storage — no controller is watching to act on the Certificate CR. Second, if the operator is running, check the operator logs: `kubectl logs -n cert-manager deploy/cert-manager` for reconciliation errors, such as missing ClusterIssuer, DNS challenge failures, or rate limiting from Let's Encrypt. Third, describe the Certificate resource: `kubectl describe certificate my-cert` and check the Status and Events sections for specific error messages like "issuer not found" or "ACME challenge failed." The operator writes status conditions to the CR, which is your most direct diagnostic. -
Your team wants to create a “BackupPolicy” CRD that should apply to entire clusters, not individual namespaces. A junior engineer sets
scope: Namespaced. What problems will this cause, and what should the scope be?Answer
With `scope: Namespaced`, every team would need to create their own BackupPolicy in each namespace, leading to duplication and inconsistency. More critically, cluster-wide backup policies (like "back up all PVs every 6 hours") don't logically belong to any single namespace. The scope should be `Cluster`, making BackupPolicy resources cluster-wide — similar to how cert-manager uses `ClusterIssuer` for cluster-wide certificate issuers vs `Issuer` for namespace-scoped ones. With cluster scope, names must be globally unique, you don't use `-n namespace` with kubectl commands, and RBAC requires ClusterRoles (not Roles) to manage them. Choose Namespaced for resources owned by a team or application, and Cluster for shared infrastructure policies. -
You accidentally run
kubectl delete crd certificates.cert-manager.io. What happens to all the Certificate custom resources in the cluster, and can you recover them?Answer
Deleting a CRD cascades and deletes ALL custom resources of that type across all namespaces. Every Certificate CR in the cluster is immediately removed from etcd. The TLS Secrets that were already created by cert-manager still exist (they're regular Kubernetes Secrets, not custom resources), so existing TLS traffic continues working. However, no new certificates can be issued, and existing certificates won't auto-renew since the Certificate CRs are gone. Recovery requires reinstalling cert-manager (which recreates the CRD) and then recreating all Certificate CRs. This is why the "Common Mistakes" section warns to delete CRs first, then the CRD — and why you should always have your CRs defined in version control (GitOps) for disaster recovery.
Hands-On Exercise
Section titled “Hands-On Exercise”Task: Create a simple CRD and custom resources.
Steps:
- Create a CRD for a “Website” resource:
cat > website-crd.yaml << 'EOF'apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: websites.stable.example.comspec: group: stable.example.com versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object required: - url properties: url: type: string replicas: type: integer default: 1 additionalPrinterColumns: - name: URL type: string jsonPath: .spec.url - name: Replicas type: integer jsonPath: .spec.replicas - name: Age type: date jsonPath: .metadata.creationTimestamp scope: Namespaced names: plural: websites singular: website kind: Website shortNames: - wsEOF
kubectl apply -f website-crd.yaml- Verify the CRD:
kubectl get crd websites.stable.example.comkubectl api-resources | grep website- Create a Website instance:
cat > my-website.yaml << 'EOF'apiVersion: stable.example.com/v1kind: Websitemetadata: name: company-site namespace: defaultspec: url: https://example.com replicas: 3EOF
kubectl apply -f my-website.yaml- Work with the custom resource:
# List websiteskubectl get websiteskubectl get ws # Short name
# Describekubectl describe website company-site
# Get as YAMLkubectl get website company-site -o yaml
# Editkubectl edit website company-site- Create another website:
cat > blog.yaml << 'EOF'apiVersion: stable.example.com/v1kind: Websitemetadata: name: blogspec: url: https://blog.example.com replicas: 2EOF
kubectl apply -f blog.yamlkubectl get ws- Explore installed operators (if any):
# Check for cert-managerkubectl get crd | grep cert-manager
# Check for prometheus operatorkubectl get crd | grep monitoring.coreos.com
# List all CRDskubectl get crd- Cleanup:
kubectl delete website company-site blogkubectl delete crd websites.stable.example.comrm website-crd.yaml my-website.yaml blog.yamlSuccess Criteria:
- Can create a CRD
- Can create custom resource instances
- Can query custom resources with kubectl
- Understand the relationship between CRD and CR
- Know how to find CRDs in a cluster
Practice Drills
Section titled “Practice Drills”Drill 1: CRD Exploration (Target: 3 minutes)
Section titled “Drill 1: CRD Exploration (Target: 3 minutes)”Explore existing CRDs in your cluster:
# List all CRDskubectl get crd
# Get details on a specific CRDkubectl get crd <crd-name> -o yaml | head -50
# List instances of a CRDkubectl get <resource-name> -A
# Describe a CRDkubectl describe crd <crd-name>Drill 2: Create a Simple CRD (Target: 5 minutes)
Section titled “Drill 2: Create a Simple CRD (Target: 5 minutes)”# Create CRDcat << 'EOF' | kubectl apply -f -apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: apps.example.comspec: group: example.com names: kind: App listKind: AppList plural: apps singular: app shortNames: - ap scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: image: type: string replicas: type: integerEOF
# Verify CRD existskubectl get crd apps.example.com
# Create an instancecat << 'EOF' | kubectl apply -f -apiVersion: example.com/v1kind: Appmetadata: name: my-appspec: image: nginx:1.25 replicas: 3EOF
# Query using short namekubectl get ap
# Cleanupkubectl delete app my-appkubectl delete crd apps.example.comDrill 3: CRD with Validation (Target: 5 minutes)
Section titled “Drill 3: CRD with Validation (Target: 5 minutes)”cat << 'EOF' | kubectl apply -f -apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: databases.stable.example.comspec: group: stable.example.com names: kind: Database plural: databases singular: database shortNames: - db scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object required: - spec properties: spec: type: object required: - engine - version properties: engine: type: string enum: - postgres - mysql - mongodb version: type: string storage: type: string default: "10Gi"EOF
# Try to create invalid resource (should fail)cat << 'EOF' | kubectl apply -f -apiVersion: stable.example.com/v1kind: Databasemetadata: name: invalid-dbspec: engine: oracle # Not in enum! version: "14"EOF
# Create valid resourcecat << 'EOF' | kubectl apply -f -apiVersion: stable.example.com/v1kind: Databasemetadata: name: prod-dbspec: engine: postgres version: "14"EOF
# Cleanupkubectl delete database prod-dbkubectl delete crd databases.stable.example.comDrill 4: Find Operator-Managed Resources (Target: 3 minutes)
Section titled “Drill 4: Find Operator-Managed Resources (Target: 3 minutes)”# Find CRDs from popular operatorskubectl get crd | grep -E "cert-manager|prometheus|istio|argocd"
# If cert-manager is installedkubectl get certificates -Akubectl get clusterissuers
# If prometheus operator is installedkubectl get servicemonitors -Akubectl get prometheusrules -A
# General: Find all custom resourceskubectl api-resources --api-group="" | head -20kubectl api-resources | grep -v "^NAME"Drill 5: CRD Status Subresource (Target: 5 minutes)
Section titled “Drill 5: CRD Status Subresource (Target: 5 minutes)”cat << 'EOF' | kubectl apply -f -apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: tasks.work.example.comspec: group: work.example.com names: kind: Task plural: tasks scope: Namespaced versions: - name: v1 served: true storage: true subresources: status: {} schema: openAPIV3Schema: type: object properties: spec: type: object properties: command: type: string status: type: object properties: phase: type: string completedAt: type: stringEOF
# Create taskcat << 'EOF' | kubectl apply -f -apiVersion: work.example.com/v1kind: Taskmetadata: name: build-jobspec: command: "make build"EOF
# View the taskkubectl get task build-job -o yaml
# Cleanupkubectl delete task build-jobkubectl delete crd tasks.work.example.comDrill 6: Troubleshooting - CRD Not Found (Target: 3 minutes)
Section titled “Drill 6: Troubleshooting - CRD Not Found (Target: 3 minutes)”# Try to create a resource for non-existent CRDcat << 'EOF' | kubectl apply -f -apiVersion: nonexistent.example.com/v1kind: Widgetmetadata: name: testspec: size: largeEOF
# Error: no matches for kind "Widget"
# Diagnosekubectl get crd | grep widget # Nothingkubectl api-resources | grep -i widget # Nothing
# Solution: CRD must be created before resources# Create the CRD first, then the resourceDrill 7: Challenge - Create Your Own CRD
Section titled “Drill 7: Challenge - Create Your Own CRD”Design and implement a CRD for a “Backup” resource with:
- Group:
backup.example.com - Required fields:
source,destination,schedule(cron format) - Optional field:
retention(integer, default 7) - Validation:
schedulemust be a string
# YOUR TASK: Create the CRD and a sample Backup resourceSolution
cat << 'EOF' | kubectl apply -f -apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: backups.backup.example.comspec: group: backup.example.com names: kind: Backup plural: backups shortNames: - bk scope: Namespaced versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object required: - spec properties: spec: type: object required: - source - destination - schedule properties: source: type: string destination: type: string schedule: type: string retention: type: integer default: 7EOF
cat << 'EOF' | kubectl apply -f -apiVersion: backup.example.com/v1kind: Backupmetadata: name: daily-db-backupspec: source: /data/postgres destination: s3://backups/postgres schedule: "0 2 * * *" retention: 14EOF
kubectl get bkkubectl delete backup daily-db-backupkubectl delete crd backups.backup.example.comDrill 8: Deploy a Basic Educational Operator (Target: 10 minutes)
Section titled “Drill 8: Deploy a Basic Educational Operator (Target: 10 minutes)”To truly understand operators, let’s deploy a basic educational operator. We’ll use a Bash script that acts as the controller for a Website CRD. The script’s while loop simulates the operator’s continuous reconciliation process, actively managing Kubernetes Deployments based on your Custom Resources.
Stop and think: What happens if you scale the deployment directly using
kubectl scale deployment my-portfolio-site --replicas=5? How will the operator react during its next loop?
# 1. First, create the Website CRDcat << 'EOF' | kubectl apply -f -apiVersion: apiextensions.k8s.io/v1kind: CustomResourceDefinitionmetadata: name: websites.stable.example.comspec: group: stable.example.com scope: Namespaced names: plural: websites singular: website kind: Website shortNames: [ws] versions: - name: v1 served: true storage: true schema: openAPIV3Schema: type: object properties: spec: type: object properties: image: {type: string, default: "nginx:alpine"} replicas: {type: integer, default: 1}EOF
# 2. Create the Operator Script# This represents the controller's reconciliation loopcat << 'EOF' > website-operator.sh#!/bin/bashecho "Starting Website Operator..."while true; do # Find all Website custom resources for ws in $(kubectl get websites -o jsonpath='{.items[*].metadata.name}' 2>/dev/null); do image=$(kubectl get website $ws -o jsonpath='{.spec.image}') replicas=$(kubectl get website $ws -o jsonpath='{.spec.replicas}')
# Reconcile: Ensure a Deployment exists with the desired state kubectl create deployment $ws-site --image=$image --replicas=$replicas --dry-run=client -o yaml | kubectl apply -f - >/dev/null 2>&1 echo "Reconciled Website: $ws -> Image: $image, Replicas: $replicas" done sleep 5doneEOFchmod +x website-operator.sh
# 3. Run the operator in the background./website-operator.sh &OPERATOR_PID=$!
# 4. Create a Custom Resourcecat << 'EOF' | kubectl apply -f -apiVersion: stable.example.com/v1kind: Websitemetadata: name: my-portfoliospec: image: "nginx:alpine" replicas: 2EOF
# 5. Observe the reconciliation (Wait a few seconds for the loop)sleep 6kubectl get deploymentskubectl get pods
# 6. Test the Reconciliation Loop# The operator should fight back if we delete the managed deploymentecho "Deleting the managed deployment to simulate a failure..."kubectl delete deployment my-portfolio-site
# 7. Check again in 5-10 secondssleep 6kubectl get deployments# The operator recreated it! This is the reconciliation loop in action.
# 8. Clean upkill $OPERATOR_PIDkubectl delete website my-portfoliokubectl delete deployment my-portfolio-sitekubectl delete crd websites.stable.example.comrm website-operator.shNext Module
Section titled “Next Module”Module 1.6: RBAC - Role-Based Access Control for securing your cluster.