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

Source: Dev.to | Jan 18, 2026 (UTC)

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:

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:

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:

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:

Example:

kind: VirtualService spec: hosts: - myapp http: - route: - destination: host: myapp-v2

Enter fullscreen mode

Exit fullscreen mode

Istio controllers convert this into:

Istio doesn’t modify Kubernetes.

It extends it properly.

Why Companies Build Products Using CRDs

CRDs allow:

Used by:

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:

Do not use CRDs for:

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.

For further actions, you may consider blocking this person and/or reporting abuse

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *

Читайте також