In the previous chapter, you learned that Pods are the atomic unit of computation in Kubernetes — ephemeral wrappers around containers that share network and storage. But here's the critical question: if Pods are designed to be disposable, how do you run a production application that must stay available 24/7? You need automation — something that watches your Pods and ensures the right number always exists. Enter Kubernetes controllers: the automation agents that transform fragile Pods into a robust, self-healing production system.
3.2.1 The Need for Controllers: Why Pods Alone Are Insufficient
Imagine you open a coffee shop. You hire one barista, but what happens when they get sick? The shop closes. So you hire three baristas — but now you must constantly count heads, call replacements, and manage shift changes without ever closing. Doing this manually is exhausting. You need a manager.
Controllers are that manager. A controller is a control loop that watches the shared state of the cluster through the API Server and makes changes to move the current state toward the desired state. You declare "I want three replicas of my web application," and the controller tirelessly ensures reality matches your declaration. If a Pod crashes, the controller creates a replacement. If you update to a new container image, the controller orchestrates a graceful transition.
This pattern — the reconciliation loop — is the beating heart of Kubernetes: observe the current state, compare it to the desired state, and take action to close the gap.
Analogy: The Restaurant Franchise
A Deployment is the corporate headquarters of a restaurant franchise chain. The corporate office decides: "We want exactly ten locations open, each serving the same menu." The ReplicaSet is the regional manager whose sole job is counting: "I see nine locations, corporate wants ten — I need to open one more." The DaemonSet is the mandatory health inspector who visits every restaurant location — exactly one per location, no exceptions. When the corporate office rolls out a new interior design, the Deployment manages the renovation so only a few locations close at a time while the rest keep serving. And if the new design is a disaster? Corporate reverts every location back to the previous design.
Visual Description: The franchise operation diagram shows a hierarchy: at the top, Corporate Office (Deployment) issues directives to Regional Managers (ReplicaSets), who oversee individual Restaurant Locations (Pods). When a new menu launches, the Deployment creates a new ReplicaSet while gradually scaling down the old one — traffic shifts one location at a time. A rollback arrow shows the reverse process when something goes wrong.
⚠️ Common Misconception: "If I create Pods directly, they'll stay running forever." Standalone Pods are the most fragile objects in Kubernetes. A node failure or resource pressure deletes them permanently. Only controllers provide resilience through automated replacement.
3.2.2 ReplicaSets: Ensuring the Right Location Count
A ReplicaSet maintains a stable set of replica Pods running at any given time. You specify how many copies you want, and the ReplicaSet ensures that exact number exists. If too many are running, it deletes extras. If too few are running, it creates replacements.
How does a ReplicaSet know which Pods belong to it? Through label selectors. The ReplicaSet defines labels (for example, app=web-app), and any Pod matching those labels is counted as part of the set. Here's a ReplicaSet manifest:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: web-rs
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
The template section contains the Pod specification the ReplicaSet uses to create replacements. Standalone ReplicaSets are rarely used — they form the foundation for Deployments, which add rolling updates and rollback. But you can see them when debugging:
kubectl get rs -l app=web-app
# NAME DESIRED CURRENT READY AGE
# web-app-7c4b8d9f2 3 3 3 2h
# web-app-3a9e1c5d7 0 0 0 1d # old revision
3.2.3 Deployments: The Corporate Office Orchestrating Everything
A Deployment is the most common controller for stateless applications. It manages ReplicaSets and Pods, automatically creating a ReplicaSet when you deploy. When you update the Deployment — changing the image, resources, or environment variables — it creates a new ReplicaSet and orchestrates a gradual transition.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: web-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
Rolling Update Strategy: Renovating Without Closing
The rolling update strategy creates a new ReplicaSet and gradually shifts traffic from old Pods to new ones:
maxSurge: How many extra Pods above the desired count can exist during the updatemaxUnavailable: How many Pods can be below the desired count during the update
Here's the math for replicas: 3, maxSurge: 1, maxUnavailable: 0:
| Step | Old Pods | New Pods | Total | Available |
|---|---|---|---|---|
| Initial | 3 | 0 | 3 | 3 |
| New Pod starts | 3 | 1 | 4 | 4 |
| Old Pod terminated | 2 | 1 | 3 | 3 |
| New Pod starts | 2 | 2 | 4 | 4 |
| New Pod starts | 1 | 3 | 4 | 4 |
| Old Pod terminated | 0 | 3 | 3 | 3 |
This ensures zero downtime — at least 3 Pods are always available. The trade-off is slightly higher resource consumption during the update (peaking at 4 Pods).
⚠️ Common Misconception: "Rolling updates are always zero-downtime." They are only zero-downtime with proper readiness probes and maxUnavailable set correctly. Without readiness probes, Kubernetes may send traffic to a Pod that hasn't finished initializing.
Rollback: When the New Design Fails
Every Deployment keeps a history of its ReplicaSets. The old ReplicaSet is scaled to zero but preserved — your safety net. If a new version introduces a bug, revert instantly:
# View the rollout history
kubectl rollout history deployment/web-app
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 kubectl set image deployment/web-app nginx=nginx:1.26
# 3 kubectl set image deployment/web-app nginx=nginx:1.27
# Roll back to the previous revision
kubectl rollout undo deployment/web-app
# Roll back to a specific revision
kubectl rollout undo deployment/web-app --to-revision=2
# Pause a rollout for canary testing
kubectl rollout pause deployment/web-app
kubectl rollout resume deployment/web-app
The revisionHistoryLimit controls how many old ReplicaSets to retain (default: 10). Too low and you lose rollback capability; too high and you waste resources.
🛑 PAUSE & RECALL — 2 minutes
Without looking back, answer these:
- In the restaurant franchise analogy, what role does the Deployment play? What role does the ReplicaSet play?
- If you have
replicas: 5,maxSurge: 2, andmaxUnavailable: 1, what is the maximum total Pods during an update? What is the minimum guaranteed available? - Why does a Deployment keep old ReplicaSets around instead of deleting them?
Rate your confidence (0-4).
3.2.4 DaemonSets: One Inspector Per Location
A DaemonSet ensures one Pod runs on every node (or a subset of nodes). Unlike a Deployment where you specify a replica count, a DaemonSet automatically adds a Pod to every qualifying node. It's perfect for cluster-wide infrastructure services: log collectors (Fluent Bit), monitoring agents (node-exporter), network plugins (CNI), and storage drivers.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: node-exporter
spec:
selector:
matchLabels:
app: node-exporter
template:
metadata:
labels:
app: node-exporter
spec:
containers:
- name: node-exporter
image: prom/node-exporter:latest
ports:
- containerPort: 9100
By default, DaemonSets respect node taints — they won't run on control plane nodes unless they explicitly tolerate those taints. You can restrict DaemonSets using nodeSelector or nodeAffinity:
spec:
template:
spec:
nodeSelector:
node-type: monitoring-eligible
tolerations:
- key: "dedicated"
operator: "Equal"
value: "monitoring"
effect: "NoSchedule"
3.2.5 Jobs and CronJobs
Jobs are for workloads that run to completion and then stop — batch processing, data migration, report generation:
apiVersion: batch/v1
kind: Job
metadata:
name: data-migration
spec:
completions: 5
parallelism: 2
backoffLimit: 3
ttlSecondsAfterFinished: 3600
template:
spec:
restartPolicy: OnFailure
containers:
- name: processor
image: my-batch-app:v1
command: ["python", "migrate.py"]
Key parameters: completions (total successful runs needed), parallelism (simultaneous Pods), backoffLimit (retries before failure), ttlSecondsAfterFinished (auto-cleanup). Check completion with kubectl get job data-migration.
⚠️ Common Misconception: Jobs use
restartPolicy: OnFailureorNever, notAlways. A Job's Pod is expected to terminate.
CronJobs schedule Jobs using cron syntax:
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-report
spec:
schedule: "0 2 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 5
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: reporter
image: report-generator:v2
command: ["python", "generate_report.py"]
concurrencyPolicy: Forbid skips a run if the previous hasn't finished — essential for non-overlapping tasks like database cleanup. Allow permits overlap; Replace cancels the running instance.
3.2.6 GKE in Practice
GKE Note: GKE manages several DaemonSets automatically:
fluentbit-gke(logs),gke-metrics-agent(monitoring),netd(networking), andpdcsi-node(storage) inkube-system. On GKE Autopilot, these are fully managed — Google handles all updates, and you cannot modify them. Autopilot also enforces that DaemonSet containers specify resource requests and prohibits privileged DaemonSets.
Workload Identity is GKE's mechanism for Pods to authenticate to GCP services without storing service account keys. A Kubernetes ServiceAccount is bound to a GCP IAM ServiceAccount, and Pods receive short-lived OAuth tokens automatically. When your Deployment needs to read from Cloud Storage or write to Pub/Sub, Workload Identity eliminates static credentials entirely.
🤔 TRY BEFORE YOU SEE
You have a 5-node GKE cluster (3 worker nodes, 2 control plane nodes). You deploy a Deployment with replicas: 4 and a DaemonSet with default tolerations. Predict: How many Pods does each controller create, and on which node types? If you add 2 more worker nodes, what changes?
Reveal: The Deployment creates 4 Pods distributed by the Scheduler across any worker node. The DaemonSet creates 3 Pods, one on each worker node — control plane taints block it by default. When adding 2 new workers, the Deployment stays at 4 Pods (unless scaled), while the DaemonSet automatically creates 2 new Pods.
Lab: LAB-3.2 — Deploying and Managing Applications (75 min)
Part 1: Create and Scale a Deployment (10 min)
kubectl create namespace lab-3-2
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: lab-3-2
spec:
replicas: 3
selector:
matchLabels:
app: web-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: nginx
image: nginx:1.24
ports:
- containerPort: 80
EOF
kubectl get deployment,pods -n lab-3-2 -l app=web-app
kubectl scale deployment web-app --replicas=5 -n lab-3-2
Part 2: Rolling Update and the Rollback Rescue (25 min)
# Check rollout history
kubectl rollout history deployment/web-app -n lab-3-2
# Update to a working new version
kubectl set image deployment/web-app nginx=nginx:1.25 --record -n lab-3-2
kubectl rollout status deployment/web-app -n lab-3-2
# Deploy a BROKEN image (simulating a bad release)
kubectl set image deployment/web-app nginx=nginx:nonexistent --record -n lab-3-2
kubectl get pods -n lab-3-2 -l app=web-app -w
# Observe ImagePullBackOff errors
# PERFORM THE ROLLBACK RESCUE
kubectl rollout undo deployment/web-app -n lab-3-2
kubectl rollout status deployment/web-app -n lab-3-2
kubectl get pods -n lab-3-2 -l app=web-app
Part 3: Deploy a DaemonSet (20 min)
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: simple-agent
namespace: lab-3-2
spec:
selector:
matchLabels:
app: simple-agent
template:
metadata:
labels:
app: simple-agent
spec:
containers:
- name: agent
image: busybox:stable
command: ['sh', '-c', 'sleep 3600']
resources:
requests:
cpu: 10m
memory: 16Mi
EOF
# Verify one Pod per worker node
kubectl get daemonset -n lab-3-2
kubectl get pods -n lab-3-2 -o wide -l app=simple-agent
Part 4: Create and Verify a CronJob (20 min)
cat <<EOF | kubectl apply -f -
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello-cron
namespace: lab-3-2
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
jobTemplate:
spec:
ttlSecondsAfterFinished: 60
template:
spec:
restartPolicy: OnFailure
containers:
- name: hello
image: busybox:stable
command: ['sh', '-c', 'echo "Hello at $(date)"']
EOF
# Wait 2 minutes, then verify
kubectl get cronjob -n lab-3-2
kubectl get jobs -n lab-3-2
kubectl logs -n lab-3-2 -l job-name=$(kubectl get jobs -n lab-3-2 -o jsonpath='{.items[0].metadata.name}')
# Clean up
kubectl delete namespace lab-3-2
🛑 PAUSE & RECALL — 3 minutes
Close your eyes and picture the franchise operation:
- You deploy a new version and Pods enter
ImagePullBackOff. What exactkubectlcommand recovers? - A DaemonSet has 3 Pods but your cluster now has 5 worker nodes. What happens automatically?
- A CronJob with
concurrencyPolicy: Forbidhas a still-running instance when the next schedule fires. What happens, and why is this safer thanAllow?
Rate your confidence (0-4).
Chapter Summary
This chapter introduced Kubernetes workload controllers — the automation layer transforming ephemeral Pods into production-grade systems. ReplicaSets ensure the correct Pod count through label selectors. Deployments build on ReplicaSets to provide declarative rolling updates with maxSurge and maxUnavailable parameters that control update safety. DaemonSets guarantee one Pod per node for infrastructure services like logging and monitoring. Jobs and CronJobs handle completion-based and scheduled workloads. The kubectl rollout undo command provides a fast escape hatch when releases fail. On GKE, managed DaemonSets handle system components automatically, and Workload Identity removes the need for static GCP credentials.
📇 KEY CONCEPT CARDS
- Q: What is the relationship between Deployment, ReplicaSet, and Pod?
A: Deployment manages ReplicaSets. Each update creates a new ReplicaSet; the Deployment scales it up while scaling the old one down. Each ReplicaSet manages Pods through label selectors. Hierarchy: Deployment → ReplicaSet → Pod.
- Q: How do
maxSurgeandmaxUnavailablework during rolling updates?
A:maxSurgecontrols extra Pods above desired count during updates.maxUnavailablecontrols how many Pods can be below the desired count. SettingmaxUnavailable: 0guarantees you never drop below the desired replica count.
- Q: What is the difference between a Deployment and a DaemonSet?
A: A Deployment runs a specified number of replicas distributed by the Scheduler — you control the count. A DaemonSet runs exactly one Pod per eligible node — Kubernetes controls the count based on node count.
- Q: How does a Deployment rollback work, and why is it fast?
A: Old ReplicaSets are scaled to zero but retained. To roll back, Kubernetes scales the old ReplicaSet up and the current one down. The old container image is typically cached on nodes, so Pods restart immediately without pulling.