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
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
After applying this:
kubectl get databases
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
Apply it:
kubectl apply -f database.yaml
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:
- Watches Custom Resources
- Compares desired state vs actual state
- Reconciles the difference
Without a controller:
- CRD = dead schema
- CR = useless YAML
Controller Reconciliation Loop
Every controller follows this loop:
Observe → Compare → Act → Repeat
Or in plain terms:
while true:
desired_state = CR.spec
actual_state = cluster_reality
if desired_state != actual_state:
fix_it()
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
Real Example: Database Operator
Custom Resource
apiVersion: mycompany.io/v1
kind: Database
metadata:
name: user-db
spec:
engine: postgres
version: "15"
storage: 20Gi
Controller Logic
IF Database CR is created:
- Create StatefulSet
- Create PVC
- Create Service
IF Database CR is deleted:
- Cleanup resources
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
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
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
- “CRD automatically creates resources”
- Writing logic inside YAML
- Ignoring
.statusfields - 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)