🏗️ Extending Kubernetes: Building Your Own Superpowers
Imagine Kubernetes is like a giant LEGO castle. It’s already amazing! But what if you could create your own special LEGO pieces that do exactly what you want? That’s what “Extending Kubernetes” is all about!
🎭 The Story: The Magic Kingdom of K8s
Once upon a time, there was a magical kingdom called Kubernetes. The kingdom had guards (controllers), messengers (the API), and rulebooks (resources). But the wise rulers knew something important: every kingdom has different needs.
So they created a way for anyone to add new magic spells to the kingdom. Let’s learn how!
📦 Custom Resource Definitions (CRDs)
What is a CRD?
Think of Kubernetes like a toy box. It comes with standard toys: Pods, Services, Deployments. But what if you want a toy that doesn’t exist yet?
A Custom Resource Definition (CRD) is like drawing a blueprint for a brand new toy. Once you create the blueprint, Kubernetes knows how to handle your new toy!
Real-Life Example
Let’s say you run a pizza shop. Kubernetes doesn’t know what a “Pizza” is. But YOU can teach it!
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: pizzas.food.example.com
spec:
group: food.example.com
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
toppings:
type: array
items:
type: string
size:
type: string
scope: Namespaced
names:
plural: pizzas
singular: pizza
kind: Pizza
Now you can create pizzas in Kubernetes!
apiVersion: food.example.com/v1
kind: Pizza
metadata:
name: margherita
spec:
toppings:
- cheese
- tomato
- basil
size: large
Why CRDs Matter
| Without CRDs | With CRDs |
|---|---|
| Only use built-in resources | Create ANY resource you need |
| Limited flexibility | Unlimited possibilities |
| One-size-fits-all | Custom-fit for YOUR app |
🤖 Custom Controllers
What is a Controller?
Imagine you have a robot helper. You tell it: “Always make sure there are 3 cookies on the plate.” If someone eats a cookie, the robot bakes a new one!
A Controller is that robot. It watches something and takes action to keep things the way they should be.
The Control Loop
graph TD A["👀 Watch"] --> B["🔍 Compare"] B --> C{Match?} C -->|No| D["🔧 Act"] C -->|Yes| E["😊 Happy"] D --> A E --> A
Every controller does this dance:
- Watch - See what’s happening
- Compare - Is it what we want?
- Act - Fix it if needed
- Repeat - Forever!
Simple Controller Example
// Simplified controller logic
func (c *PizzaController) Reconcile(
ctx context.Context,
req Request) (Result, error) {
// 1. Get the Pizza resource
pizza := &Pizza{}
err := c.Get(ctx, req.NamespacedName, pizza)
// 2. Check: Does it have sauce?
if !pizza.HasSauce() {
// 3. Act: Add sauce!
pizza.AddSauce()
c.Update(ctx, pizza)
}
return Result{}, nil
}
🎩 The Operator Pattern
What is an Operator?
Remember CRDs (the blueprint) and Controllers (the robot)? An Operator is both together! It’s the complete package.
Think of it like this:
- CRD = Recipe card for a cake
- Controller = Baker who follows the recipe
- Operator = Recipe card + Baker = Complete bakery!
Why Operators are Magical
Operators encode human knowledge into software. Instead of a person manually setting up a database, the Operator does it automatically!
graph TD A["Human Expert"] -->|Teaches| B["Operator"] B -->|Manages| C["Complex App"] C -->|Reports to| B B -->|Heals| C
Real Operator Example: Database
Without Operator:
- Human creates Pod
- Human configures storage
- Human sets up replication
- Human handles backups
- Human fixes problems at 3 AM 😴
With Operator:
- Create one “Database” resource
- Operator handles EVERYTHING! 🎉
apiVersion: databases.example.com/v1
kind: PostgresCluster
metadata:
name: my-database
spec:
replicas: 3
storage: 100Gi
backups:
schedule: "0 2 * * *"
🌐 Kubernetes API Fundamentals
The API is the Heart
Everything in Kubernetes talks through the API. It’s like the main post office of our kingdom. Every letter (request) goes through it!
How the API Works
graph TD A["You"] -->|kubectl| B["API Server"] C["Controller"] -->|Watch| B D["Scheduler"] -->|Query| B E["Kubelet"] -->|Report| B B -->|Store| F["etcd"]
API Request Example
When you run kubectl get pods, here’s what happens:
- Request goes to API Server
- API Server checks: “Are you allowed?” (Authentication)
- API Server checks: “Can you do this?” (Authorization)
- API Server checks: “Is this valid?” (Admission)
- API Server returns the answer!
RESTful Magic
The API uses REST. Think of it like addresses:
| Action | HTTP Method | URL |
|---|---|---|
| List pods | GET | /api/v1/pods |
| Create pod | POST | /api/v1/pods |
| Get one pod | GET | /api/v1/pods/my-pod |
| Update pod | PUT | /api/v1/pods/my-pod |
| Delete pod | DELETE | /api/v1/pods/my-pod |
📚 API Groups and Versions
Why Groups?
Imagine a huge library. If ALL books were in one pile, finding anything would be impossible! So we organize them into sections.
API Groups are like library sections:
| Group | What’s Inside |
|---|---|
core (no group) |
Pods, Services, ConfigMaps |
apps |
Deployments, StatefulSets |
batch |
Jobs, CronJobs |
networking.k8s.io |
Ingress, NetworkPolicy |
your.company.com |
Your custom stuff! |
API Versions
Software changes! Versions help manage this:
- v1alpha1 - “Just testing this idea” 🧪
- v1beta1 - “Pretty good, try it!” 🔬
- v1 - “Rock solid, use it!” 💎
Reading an API Path
/apis/apps/v1/namespaces/default/deployments/nginx
│ │ │ │ │ │ │
│ │ │ │ │ │ └─ Resource name
│ │ │ │ │ └─ Resource type
│ │ │ │ └─ Namespace name
│ │ │ └─ "namespaces" keyword
│ │ └─ API version
│ └─ API group
└─ API prefix
👁️ Watch and Informers
The Watch Mechanism
Instead of asking “Any changes?” every second (polling), Kubernetes uses Watch. It’s like subscribing to notifications!
Polling (Bad):
You: "Any new emails?"
Server: "No"
(1 second later)
You: "Any new emails?"
Server: "No"
(Forever...)
Watch (Good):
You: "Tell me when there's a new email"
Server: "OK! *ding* You have mail!"
What are Informers?
An Informer is a smart helper that:
- Watches resources for changes
- Caches data locally (super fast lookups!)
- Notifies your code when things change
graph TD A["API Server"] -->|Watch Stream| B["Informer"] B -->|Store| C["Local Cache"] B -->|Notify| D["Your Code"] D -->|Fast Read| C
Informer Example
// Create an informer for Pods
informer := cache.NewSharedInformer(
&cache.ListWatch{
ListFunc: func(options v1.ListOptions) {
return client.CoreV1().
Pods("").List(options)
},
WatchFunc: func(options v1.ListOptions) {
return client.CoreV1().
Pods("").Watch(options)
},
},
&corev1.Pod{},
time.Minute,
)
// Add event handlers
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
fmt.Println("New pod created!")
},
UpdateFunc: func(old, new interface{}) {
fmt.Println("Pod updated!")
},
DeleteFunc: func(obj interface{}) {
fmt.Println("Pod deleted!")
},
})
🚪 Admission Webhooks
What are Admission Webhooks?
Imagine a bouncer at a club. Before you enter, they check:
- Are you on the list? (Authentication - already done)
- Are you dressed properly? (Admission!)
Admission Webhooks are custom bouncers you create!
Two Types of Webhooks
graph LR A["Request"] --> B["Validating"] B -->|OK/Reject| C{Pass?} C -->|Yes| D["Mutating"] D -->|Modify| E["Save to etcd"] C -->|No| F["Rejected!"]
1. Validating Webhook - Says YES or NO
- “Is this Pod request valid?”
- “Does it follow our rules?”
- Cannot change the request
2. Mutating Webhook - Changes things
- “Let me add a label to this Pod”
- “Let me inject a sidecar container”
- Modifies before saving
Real Example: Validating Webhook
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-validator
webhooks:
- name: validate.pods.example.com
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
clientConfig:
service:
name: webhook-service
namespace: webhook-system
path: "/validate-pods"
admissionReviewVersions: ["v1"]
sideEffects: None
Real Example: Mutating Webhook
// Webhook handler that adds labels
func mutatePod(
ar *admissionv1.AdmissionReview,
) *admissionv1.AdmissionResponse {
pod := &corev1.Pod{}
json.Unmarshal(ar.Request.Object.Raw, pod)
// Add a label
patch := []map[string]interface{}{{
"op": "add",
"path": "/metadata/labels/injected",
"value": "true",
}}
patchBytes, _ := json.Marshal(patch)
return &admissionv1.AdmissionResponse{
Allowed: true,
Patch: patchBytes,
PatchType: func() *admissionv1.PatchType {
pt := admissionv1.PatchTypeJSONPatch
return &pt
}(),
}
}
🎛️ Dynamic Admission Control
What Makes it “Dynamic”?
Regular admission happens inside the API server. Dynamic means you can add or remove admission rules WITHOUT restarting anything!
The Admission Flow
graph TD A["API Request"] --> B["Authentication"] B --> C["Authorization"] C --> D["Mutating Webhooks"] D --> E["Schema Validation"] E --> F["Validating Webhooks"] F --> G["Persist to etcd"]
Why Dynamic is Powerful
| Static (Old Way) | Dynamic (New Way) |
|---|---|
| Compile into API server | Deploy as separate service |
| Restart to change | Change anytime |
| Same for all clusters | Customize per cluster |
Common Use Cases
1. Policy Enforcement
"All Pods MUST have resource limits"
"No containers can run as root"
"Images must come from our registry"
2. Automatic Injection
"Add logging sidecar to all Pods"
"Inject environment variables"
"Add default security settings"
3. Validation
"Database names must match pattern"
"Storage requests can't exceed 1TB"
"Labels must include 'team' and 'env'"
Simple Policy Example
# Using a policy engine like Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
rules:
- name: check-team-label
match:
resources:
kinds:
- Pod
validate:
message: "Pods must have 'team' label"
pattern:
metadata:
labels:
team: "?*"
🎯 Putting It All Together
Here’s how all the pieces connect:
graph TD A["You Create CRD"] --> B["CRD Defines New Resource"] B --> C["You Write Controller"] C --> D["Controller + CRD = Operator"] D --> E["Operator Watches via Informers"] E --> F["API Server Processes Requests"] F --> G["Admission Webhooks Validate/Mutate"] G --> H["Resource Saved!"]
The Complete Picture
- CRDs - Define what new things exist
- Controllers - Make things happen automatically
- Operators - Package CRDs + Controllers together
- API - How everyone communicates
- Groups/Versions - Organize and evolve the API
- Watch/Informers - Efficiently track changes
- Admission Webhooks - Enforce rules and inject defaults
- Dynamic Admission - Change rules without restarts
🚀 You Did It!
You now understand how to extend Kubernetes! You can:
✅ Create custom resources with CRDs ✅ Build controllers that act automatically ✅ Package them as Operators ✅ Understand the API structure ✅ Use Watch and Informers efficiently ✅ Create admission webhooks for validation ✅ Implement dynamic admission control
Remember: Kubernetes is DESIGNED to be extended. The creators wanted you to build on top of it. So go create something amazing! 🎉
“The best way to predict the future is to invent it.” - Alan Kay
In Kubernetes, you don’t just predict what resources you need - you INVENT them!
