CRD and Operator
CustomResourceDefinition (CRD) and the Operator pattern are the core of Kubernetes extensibility, allowing users to define custom resources and implement automated operational logic.
Overview
CustomResourceDefinition (CRD) allows users to extend the Kubernetes API and define their own resource types. The Operator pattern combines CRDs with controller logic to achieve automated application management. The CKA exam requires a basic understanding of CRDs and the Operator pattern.
Part 1: CRD (CustomResourceDefinition)
1. CRD Concepts
CRD is Kubernetes' extension mechanism that allows users to define new resource types (such as Database, Backup, Application). The Kubernetes API Server provides full RESTful API support for these custom resources.
After creating a custom resource:
- API Server automatically generates RESTful endpoints: /apis/<group>/<version>/namespaces/<ns>/<resource-plural>/
- Supports kubectl operations (get, create, delete, etc.)
- Supports RBAC access control
- Stored in etcd
CRD vs. Native Resources
| Feature | Native Resources (Pod, Service...) | CRD |
|---|---|---|
| Definition location | Kubernetes source code | CRD YAML file |
| Validation logic | Built-in | OpenAPI v3 schema |
| Controller | Built-in controller | Custom controller (Operator) |
| Storage | etcd | etcd |
2. CRD YAML Structure
2.1 Basic CRD Example
# crd-database.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.example.com # Required format: <plural>.<group>
spec:
group: example.com # API group
names:
plural: databases # Plural name (used by kubectl)
singular: database # Singular name
shortNames: # Short names
- db
kind: Database # Resource type (used for the kind field in YAML)
listKind: DatabaseList # List type
scope: Namespaced # Scope: Namespaced or Cluster
versions:
- name: v1 # API version
served: true # Whether served by API Server
storage: true # Whether used as the etcd storage version
schema: # OpenAPI v3 validation Schema
openAPIV3Schema:
type: object
required:
- spec
properties:
spec:
type: object
required:
- engine
- version
properties:
engine:
type: string
enum:
- mysql
- postgres
- mongodb
version:
type: string
pattern: '^\d+\.\d+\.\d+$'
replicas:
type: integer
minimum: 1
maximum: 10
default: 1
storage:
type: string
pattern: '^\d+(Gi|Ti)$'
default: "10Gi"
adminUser:
type: string
default: "admin"
backup:
type: object
properties:
enabled:
type: boolean
default: false
schedule:
type: string
pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$'
description: "Cron schedule for backups"
2.2 Creating a Custom Resource Using a CRD
# database-sample.yaml
apiVersion: example.com/v1
kind: Database
metadata:
name: my-production-db
labels:
environment: production
team: backend
spec:
engine: postgres
version: "14.5"
replicas: 3
storage: 100Gi
adminUser: dbadmin
backup:
enabled: true
schedule: "0 2 * * *"
# Apply the CRD
kubectl apply -f crd-database.yaml
# View the CRD
kubectl get crd
kubectl get crd databases.example.com -o yaml
# Use the custom resource
kubectl apply -f database-sample.yaml
# Work with custom resources (exactly like native resources)
kubectl get databases
kubectl get db # Using the shortName
kubectl get databases --all-namespaces
kubectl describe database my-production-db
kubectl delete database my-production-db
kubectl get databases -o wide
kubectl get databases -o yaml
2.3 Additional CRD Feature Configuration
# CRD advanced configuration example
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.example.com
spec:
# ... basic configuration ...
versions:
- name: v1
# ... schema ...
additionalPrinterColumns: # Extra columns for kubectl get
- name: Engine
type: string
jsonPath: .spec.engine
description: Database engine type
- name: Version
type: string
jsonPath: .spec.version
- name: Replicas
type: integer
jsonPath: .spec.replicas
- name: Status
type: string
jsonPath: .status.phase
subresources: # Subresources
status: {} # Enable /status subresource
scale: # Enable /scale subresource
specReplicasPath: .spec.replicas
statusReplicasPath: .status.replicas
conversion: # Version conversion
strategy: None # or Webhook
# With additionalPrinterColumns:
kubectl get databases
# NAME ENGINE VERSION REPLICAS STATUS
# my-production-db postgres 14.5 3 Running
# View status subresource
kubectl get database my-production-db -o json | jq '.status'
2.4 CRD Version Management
# CRD supports multiple coexisting versions, enabling smooth upgrades through version conversion
# View CRD versions
kubectl get crd databases.example.com -o json | jq '.spec.versions[].name'
# Access a specific version
kubectl get databases.v1.example.com
3. Operator Pattern
3.1 Operator Concept
The Operator is a pattern for packaging, deploying, and managing Kubernetes applications. It extends the API through CRDs and uses custom controllers to maintain the desired state of custom resources.
Operator = CRD + Controller + Domain Knowledge
How it works:
1. User creates a custom resource (e.g., a Database instance)
2. The Operator controller watches for changes to that resource
3. The controller creates/manages underlying resources (StatefulSet, Service, PVC...) based on business logic
4. The controller continuously adjusts the actual state to match the user's desired state
3.2 Operator vs. Traditional Deployment
| Feature | Traditional Approach | Operator Approach |
|---|---|---|
| Deployment | Manually create multiple YAMLs | Create a single CR instance |
| Scaling | Manually modify Deployment | Modify the CR's replicas field |
| Upgrades | Manually update image versions | Modify the CR's version field |
| Backup/Restore | Manually execute scripts | Operator handles automatically |
| Failure Recovery | Manually troubleshoot and fix | Operator automatically detects and recovers |
| Rollback | Manual execution | Operator automates |
3.3 Operator Workflow
┌──────────────────────┐
│ etcd (CR storage) │
└──────────┬───────────┘
│ watch
┌──────────▼───────────┐
│ Reconciler Loop │
│ ───────────────── │
│ 1. Read CR (desired) │
│ 2. Query actual state│
│ 3. Compare diff │
│ 4. Execute adjustment│
│ 5. Update CR Status │
└──────────────────────┘
4. Using Existing Operators
4.1 etcd-operator
# Install etcd-operator
kubectl apply -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/service_account.yaml
kubectl apply -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/rbac/cluster-role.yaml
kubectl apply -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/rbac/cluster-role-binding.yaml
kubectl apply -f https://raw.githubusercontent.com/coreos/etcd-operator/master/example/deployment.yaml
# View the Operator
kubectl get pods -l name=etcd-operator
# Create an etcd cluster
cat <<EOF | kubectl apply -f -
apiVersion: "etcd.database.coreos.com/v1beta2"
kind: "EtcdCluster"
metadata:
name: "example-etcd-cluster"
spec:
size: 3
version: "3.5.15"
EOF
# View the etcd cluster
kubectl get etcdclusters
kubectl get pods -l etcd_cluster=example-etcd-cluster
# Scale the etcd cluster
kubectl patch etcdcluster example-etcd-cluster --type='json' -p='[{"op": "replace", "path": "/spec/size", "value": 5}]'
# Change etcd version (Operator auto rolling upgrade)
kubectl patch etcdcluster example-etcd-cluster --type='json' -p='[{"op": "replace", "path": "/spec/version", "value": "3.5.15"}]'
4.2 prometheus-operator
# Use Helm to install kube-prometheus-stack (includes prometheus-operator)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm install prometheus prometheus-community/kube-prometheus-stack
# View the operator
kubectl get pods -l app.kubernetes.io/name=prometheus-operator
# View custom resources (prometheus-operator registers many CRDs)
kubectl get crd | grep monitoring.coreos.com
# prometheuses.monitoring.coreos.com
# alertmanagers.monitoring.coreos.com
# servicemonitors.monitoring.coreos.com
# podmonitors.monitoring.coreos.com
# prometheusrules.monitoring.coreos.com
# thanosrulers.monitoring.coreos.com
# Use ServiceMonitor to configure scraping
cat <<EOF | kubectl apply -f -
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app-monitor
spec:
selector:
matchLabels:
app: my-app
endpoints:
- port: metrics
interval: 15s
EOF
# View ServiceMonitor
kubectl get servicemonitors
4.3 cert-manager Operator
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml
# View cert-manager Pods
kubectl get pods -n cert-manager
# Registered custom resources
kubectl get crd | grep cert-manager
# certificates.cert-manager.io
# issuers.cert-manager.io
# clusterissuers.cert-manager.io
# Create a certificate
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: selfsigned-issuer
spec:
selfSigned: {}
---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: my-tls-cert
spec:
dnsNames:
- example.com
secretName: my-tls-secret
issuerRef:
name: selfsigned-issuer
kind: Issuer
EOF
# View certificates
kubectl get certificates
kubectl get certificate my-tls-cert -o yaml
kubectl get secret my-tls-secret
5. OLM (Operator Lifecycle Manager)
OLM is a package manager for Operators, responsible for installation, upgrades, and lifecycle management of Operators.
# Install OLM
curl -sL https://github.com/Operator-framework/operator-lifecycle-manager/releases/download/v0.25.0/install.sh | bash -s v0.25.0
# View OLM components
kubectl get pods -n olm
# View available Operators (from OperatorHub)
kubectl get packagemanifests -n olm
# Install an Operator (via Subscription)
cat <<EOF | kubectl apply -f -
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: prometheus
namespace: operators
spec:
channel: stable
name: prometheus
source: operatorhubio-catalog
sourceNamespace: olm
EOF
# View installed Operators
kubectl get operators
kubectl get subscriptions
# View Operator versions
kubectl get clusterserviceversion
kubectl get csv -n operators
6. kubebuilder / operator-sdk (Tools for Building Operators)
# Install operator-sdk
export ARCH=$(case $(uname -m) in x86_64) echo -n amd64 ;; aarch64) echo -n arm64 ;; esac)
export OS=$(uname | awk '{print tolower($0)}')
curl -sL https://github.com/operator-framework/operator-sdk/releases/download/v1.33.0/operator-sdk_${OS}_${ARCH} -o operator-sdk
chmod +x operator-sdk && sudo mv operator-sdk /usr/local/bin/
# Create an Operator project
operator-sdk init --domain example.com --repo github.com/example/database-operator
# Create API (CRD)
operator-sdk create api --group example --version v1 --kind Database --resource --controller
# Generate CRD manifests
make generate
make manifests
CKA Exam Key Points
- CRD basic structure -- Know the
group,names,scope,versions, andschemafields - kubectl operations on CRD resources -- CRD resources are used with kubectl just like native resources
- Operator = CRD + Controller -- Understand the core idea of the Operator pattern
additionalPrinterColumns-- Customizekubectl getoutput columns- CRD
scope-- The difference betweenNamespacedandCluster
🧪 Complete Hands-on Example: Create a CRD and Use an Operator
Scenario Description
Create a CRD named databases.example.com, then install prometheus-operator (kube-prometheus-stack) using Helm, and verify that the custom resource and Operator are working properly.
Prerequisites
- A running Kubernetes cluster
- kubectl configured
- Helm v3 installed
Steps
Step 1: Create the CRD YAML
cat <<'EOF' > ~/crd-database.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: databases.example.com
spec:
group: example.com
names:
plural: databases
singular: database
shortNames:
- db
kind: Database
listKind: DatabaseList
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:
- mysql
- postgres
version:
type: string
replicas:
type: integer
minimum: 1
maximum: 10
default: 1
EOF
Step 2: Apply the CRD
kubectl apply -f ~/crd-database.yaml
# customresourcedefinition.apiextensions.k8s.io/databases.example.com created
# Verify the CRD
kubectl get crd
# NAME CREATED AT
# databases.example.com 2026-05-27T10:00:00Z
kubectl get crd databases.example.com -o yaml
Step 3: Create a custom resource
cat <<'EOF' | kubectl apply -f -
apiVersion: example.com/v1
kind: Database
metadata:
name: my-production-db
spec:
engine: postgres
version: "14.5"
replicas: 3
EOF
# database.example.com/my-production-db created
# Use kubectl to work with the custom resource (same as native resources)
kubectl get databases
# NAME ENGINE VERSION AGE
# my-production-db postgres 14.5 10s
kubectl get db # Using the shortName
# NAME ENGINE VERSION AGE
# my-production-db postgres 14.5 10s
kubectl describe database my-production-db
Step 4: Install prometheus-operator (kube-prometheus-stack)
# Add the prometheus repository
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# Install kube-prometheus-stack (includes prometheus-operator)
helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring --create-namespace
# NAME: prometheus
# LAST DEPLOYED: Tue May 27 10:00:00 2026
# NAMESPACE: monitoring
# STATUS: deployed
# REVISION: 1
Step 5: View CRDs registered by the Operator
# View CRDs registered by prometheus-operator
kubectl get crd | grep monitoring.coreos.com
# alertmanagerconfigs.monitoring.coreos.com
# alertmanagers.monitoring.coreos.com
# podmonitors.monitoring.coreos.com
# prometheuses.monitoring.coreos.com
# prometheusrules.monitoring.coreos.com
# servicemonitors.monitoring.coreos.com
# View Operator Pods
kubectl get pods -n monitoring
# NAME READY STATUS RESTARTS AGE
# prometheus-kube-prometheus-operator-xxxxxxxxx-xxxxx 1/1 Running 0 2m
# prometheus-kube-state-metrics-xxxxxxxxx-xxxxx 1/1 Running 0 2m
# prometheus-prometheus-node-exporter-xxxxx 1/1 Running 0 2m
Step 6: Use the Operator's CRD to create a ServiceMonitor
cat <<'EOF' | kubectl apply -f -
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: example-app-monitor
namespace: monitoring
spec:
selector:
matchLabels:
app: example-app
endpoints:
- port: metrics
interval: 30s
EOF
# servicemonitor.monitoring.coreos.com/example-app-monitor created
# View the ServiceMonitor
kubectl get servicemonitors -n monitoring
# NAME AGE
# example-app-monitor 10s
Verification Results
# Verify the CRD custom resource
kubectl get databases
# NAME ENGINE VERSION AGE
# my-production-db postgres 14.5 5m
# Verify the Operator is working
kubectl get pods -n monitoring | grep operator
# prometheus-kube-prometheus-operator-xxxxxxxxx-xxxxx 1/1 Running 0 5m
# Verify Prometheus managed by the Operator
kubectl get prometheus -n monitoring
# NAME VERSION REPLICAS AGE
# prometheus-kube-prometheus-prometheus v2.xx.x 1 5m
Exam Tips
- CRD operations are exactly the same as native resources:
kubectl get/create/delete/describe <crd-resource> - The CRD's
metadata.namemust follow the<plural>.<group>format (e.g.,databases.example.com) scopedetermines whether the resource is Namespaced or Cluster-level- Operator = CRD + Controller; related CRDs are automatically registered when an Operator is installed
- The CKA exam only requires a basic understanding of CRDs/Operators, not writing Operator code