Перейти до вмісту
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:
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:
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
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
“CRD automatically creates resources”
Writing logic inside YAML
Ignoring .status fields
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.
For further actions, you may consider blocking this person and/or reporting abuse
Залишити відповідь