Skip to content

Kubernetes Operators & Custom Resource Definitions (CRDs)

DodaTech 3 min read

In this tutorial, you'll learn about Kubernetes Operators & Custom Resource Definitions (CRDs). We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Kubernetes Operators extend the Kubernetes API with Custom Resource Definitions and controllers that automate application management tasks like backups upgrades and scaling.

What You'll Learn

This tutorial covers creating CRDs, building operators with the Operator SDK, implementing reconciliation loops, managing operator lifecycle with OLM, and real operator examples.

Why It Matters

Manual application management does not scale. Operators encode human operational knowledge into software, automating complex tasks like database backups, certificate renewal, and cluster upgrades.

Real-World Use

The Prometheus Operator manages thousands of monitoring stacks across Kubernetes clusters. Zalando built the Postgres Operator to automate database provisioning and failover for 2000 PostgreSQL instances.

Custom Resource Definitions

CRDs extend the Kubernetes API with custom resource types.

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: databases.example.com
spec:
  group: example.com
  names:
    kind: Database
    plural: databases
    singular: database
    shortNames:
    - db
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          spec:
            type: object
            required:
            - engine
            - version
            properties:
              engine:
                type: string
                enum: [postgres, mysql]
              version:
                type: string
              replicas:
                type: integer
                minimum: 1
                maximum: 10
# Create CRD
kubectl apply -f database-crd.yaml

# List CRDs
kubectl get crds

# Create a custom resource
kubectl apply -f - <<EOF
apiVersion: example.com/v1
kind: Database
metadata:
  name: my-db
spec:
  engine: postgres
  version: "15"
  replicas: 3
EOF

Building an Operator with Operator SDK

Initialize the Project

# Install Operator SDK
curl -LO https://github.com/operator-framework/operator-sdk/releases/download/v1.34.0/operator-sdk_linux_amd64
chmod +x operator-sdk_linux_amd64
sudo mv operator-sdk_linux_amd64 /usr/local/bin/operator-sdk

# Create operator project
operator-sdk init --domain example.com --repo github.com/example/database-operator

# Create API and controller
operator-sdk create api --group example --version v1 --kind Database --resource --controller

Implement the Controller

The controller watches Database custom resources and reconciles the desired state.

func (r *DatabaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    log := log.FromContext(ctx)

    var db examplev1.Database
    if err := r.Get(ctx, req.NamespacedName, &db); err != nil {
        return ctrl.Result{}, client.IgnoreNotFound(err)
    }

    // Ensure StatefulSet exists
    sts := &appsv1.StatefulSet{}
    sts.Name = db.Name
    sts.Namespace = db.Namespace
    _, err := ctrl.CreateOrUpdate(ctx, r.Client, sts, func() error {
        sts.Spec.Replicas = &db.Spec.Replicas
        sts.Spec.Template.Spec.Containers = []corev1.Container{{
            Name:  "database",
            Image: fmt.Sprintf("postgres:%s", db.Spec.Version),
        }}
        return ctrl.SetControllerReference(&db, sts, r.Scheme)
    })

    return ctrl.Result{}, err
}

Deploy the Operator

# Build and push image
make docker-build docker-push IMG=registry.example.com/database-operator:v1

# Deploy to cluster
make deploy IMG=registry.example.com/database-operator:v1

Testing the Operator

# Deploy a sample custom resource
kubectl apply -f config/samples/example_v1_database.yaml

# Check that the StatefulSet was created
kubectl get statefulsets

# View operator logs
kubectl -n database-operator-system logs deployment/database-operator-controller-manager

Practice Questions

  1. What is the relationship between a CRD and a Controller? The CRD defines the API schema. The Controller watches CRD instances and creates or manages the underlying Kubernetes resources.

  2. What does the reconciliation loop do? It continuously compares the desired state from the custom resource with the actual cluster state and makes changes to converge them.

  3. How does ctrl.CreateOrUpdate work? It creates a resource if it does not exist, or updates it to match the desired specification if it already exists.

  4. Why use the Operator Lifecycle Manager? OLM manages operator installation, upgrades, and dependencies across cluster namespaces.

  5. What is the purpose of the OpenAPI schema in a CRD? It validates custom resource fields and provides documentation through kubectl describe.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro