DEV Community

SHARON SHAJI
SHARON SHAJI

Posted on

Kubernetes Custom Resources, Custom Resource Definition (CRD) & Controllers

Kubernetes is not powerful because of Pods or Services.

It’s powerful because it can be extended without modifying Kubernetes itself.

That extensibility comes from Custom Resources + CRDs + Controllers.

If you don’t understand these three properly, you’re not really using Kubernetes — you’re just deploying YAML.


Why CRDs Exist

Kubernetes solves generic infrastructure problems:

  • Scheduling
  • Networking
  • Storage
  • Scaling

But real companies have domain-specific problems:

  • “Deploy a model”
  • “Provision a database”
  • “Create a service mesh rule”
  • “Manage certificates”

Instead of hardcoding these into Kubernetes, Kubernetes gives us a way to define our own APIs.

That framework is:

  • CRD → defines a new API type
  • Custom Resource → instance of that API
  • Controller → logic that makes it real

Mental Model (Important)

Kubernetes itself does nothing with your CRDs

It only stores them.

If you create a CRD without a controller, Kubernetes will happily accept it and do absolutely nothing.

That’s not a bug. That’s the design.


What Is a Custom Resource (CR)?

A Custom Resource is a new object type in Kubernetes.

Just like:

  • Pod
  • Service
  • Deployment

You can create your own:

  • Database
  • Application
  • MLModel
  • Certificate
  • Gateway

Example:

kubectl get databases
kubectl apply -f myapp.yaml
Enter fullscreen mode Exit fullscreen mode

Kubernetes doesn’t care what Database means.


What Is a CRD (CustomResourceDefinition)?

A CRD defines a new API schema inside Kubernetes.

Think of a CRD as:

“Hey Kubernetes, here is a new kind of object. Please store it.”

CRD defines:

  • API group
  • Version
  • Kind
  • Schema (validation)

Simple CRD Example

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: databases.mycompany.io
spec:
  group: mycompany.io
  scope: Namespaced
  names:
    plural: databases
    singular: database
    kind: Database
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                engine:
                  type: string
                version:
                  type: string
                storage:
                  type: string
Enter fullscreen mode Exit fullscreen mode

After applying this:

kubectl get databases
Enter fullscreen mode Exit fullscreen mode

Kubernetes now recognizes Database as a valid object — nothing more.


Creating a Custom Resource (CR)

apiVersion: mycompany.io/v1
kind: Database
metadata:
  name: user-db
spec:
  engine: postgres
  version: "15"
  storage: 20Gi
Enter fullscreen mode Exit fullscreen mode

Apply it:

kubectl apply -f database.yaml
Enter fullscreen mode Exit fullscreen mode

Result:

  • Stored in etcd
  • No Pod created
  • No database started

This is where most beginners get confused.


Why Controllers Are Mandatory

A Controller is the brain.

It:

  1. Watches Custom Resources
  2. Compares desired state vs actual state
  3. Reconciles the difference

Without a controller:

  • CRD = dead schema
  • CR = useless YAML

Controller Reconciliation Loop

Every controller follows this loop:

Observe → Compare → Act → Repeat
Enter fullscreen mode Exit fullscreen mode

Or in plain terms:

while true:
  desired_state = CR.spec
  actual_state = cluster_reality
  if desired_state != actual_state:
    fix_it()
Enter fullscreen mode Exit fullscreen mode

This loop never stops.


How CRD + Controller Work Together

User
 |
 | kubectl apply
 v
Kubernetes API Server
 |
 v
etcd (stores CR)
 |
 v
Controller watches CR
 |
 v
Controller creates / updates:
  - Pods
  - Services
  - PVCs
  - ConfigMaps
Enter fullscreen mode Exit fullscreen mode

Real Example: Database Operator

Custom Resource

apiVersion: mycompany.io/v1
kind: Database
metadata:
  name: user-db
spec:
  engine: postgres
  version: "15"
  storage: 20Gi
Enter fullscreen mode Exit fullscreen mode

Controller Logic

IF Database CR is created:
  - Create StatefulSet
  - Create PVC
  - Create Service

IF Database CR is deleted:
  - Cleanup resources
Enter fullscreen mode Exit fullscreen mode

Kubernetes doesn’t know databases.

Your controller does.


Desired vs Actual State

Desired State (CR):
  Database:
    engine: postgres
    replicas: 1

Actual State (Cluster):
  StatefulSet: missing

Controller Action:
  Create StatefulSet
Enter fullscreen mode Exit fullscreen mode

If someone deletes the StatefulSet manually?

➡️ Controller recreates it.


CRDs vs Deployments

Feature Deployment CRD
Built-in Yes No
Controller included Yes Only if you write one
Logic location Kubernetes You

A Deployment is just a CRD + controller shipped by Kubernetes.


Istio Uses CRDs

Istio extends Kubernetes using CRDs like:

  • VirtualService
  • DestinationRule
  • Gateway

Example:

kind: VirtualService
spec:
  hosts:
  - myapp
  http:
  - route:
    - destination:
        host: myapp-v2
Enter fullscreen mode Exit fullscreen mode

Istio controllers convert this into:

  • Envoy config
  • Traffic routing
  • Load balancing

Istio doesn’t modify Kubernetes.

It extends it properly.


Why Companies Build Products Using CRDs

CRDs allow:

  • Kubernetes-native APIs
  • Declarative behavior
  • kubectl-first UX
  • Strong reconciliation guarantees

Used by:

  • ArgoCD
  • Crossplane
  • Cert-Manager
  • Prometheus Operator
  • Istio

All follow the same pattern.


Common Mistakes

  1. “CRD automatically creates resources”
  2. Writing logic inside YAML
  3. Ignoring .status fields
  4. Treating CRDs as config files

CRDs are APIs, not configs.


When Should You Use CRDs?

Use CRDs when:

  • You manage lifecycle, not just deployment
  • You need reconciliation
  • You’re building a platform

Do not use CRDs for:

  • Static configs
  • One-time jobs
  • Simple values

Final Takeaway

CRDs define WHAT you want

Controllers define HOW it happens

Kubernetes enforces the loop

If you understand this, you understand Kubernetes beyond YAML.

Top comments (0)