Phase 4 · ORCHESTRATION & IAC
Pods & Deployments
By the end of today
- Explain why you run Deployments, not bare Pods, in production
- Apply, scale and roll out a Deployment with kubectl
- Roll back a bad image with kubectl rollout undo
Pods run; Deployments keep them running
Yesterday you stood up a kind cluster and drove kubectl for the first time. Today you meet the two objects you’ll type most for the rest of Phase 4, and the whole day hangs off one distinction: a Pod is the thing that runs; a Deployment is the thing that keeps it running.
A Pod is the smallest unit Kubernetes schedules — one or more containers that share a single network address and storage, placed together on one node. Most Pods hold exactly one container. The thing to internalise is that a Pod is disposable: it has no self-healing of its own. If its node reboots, or the Pod is evicted, it is simply gone — nothing recreates it. That’s fine for a one-off debug Pod, and wrong for anything you actually want to stay up.
A Deployment fixes that. It’s a controller: you declare a desired state — “run 3 replicas of this image” — and it works continuously to make reality match. It does this through a ReplicaSet, a lower-level controller whose only job is to keep N identical Pods alive. Kill a Pod and the ReplicaSet notices the count dropped and starts a replacement within seconds. You get self-healing for free, and scaling out is just changing one number.
The Deployment layer on top of the ReplicaSet is what buys you safe change:
- Rolling updates — change the image and the Deployment creates a new ReplicaSet, scaling it up a few Pods at a time while scaling the old one down, so the app never fully drops.
maxSurgeandmaxUnavailablebound how aggressive that swap is. - Rollback — every change is a numbered revision, and the old ReplicaSet stays around scaled to zero. If the new image is broken,
kubectl rollout undoflips straight back to the last good one.
Real world: A Deployment is a thermostat, not a switch. A switch turns the heat on once and forgets; a thermostat holds a target and keeps acting to reach it — the furnace kicks back on every time the room cools. You don’t tell Kubernetes “start this Pod”; you tell it “keep 3 of these running,” and the controller keeps nudging reality toward that number forever.
This is exactly how real platforms run. Spotify runs thousands of microservices on Kubernetes, and effectively none are bare Pods — each service is a Deployment declaring its replica count and image, so a crashed Pod or a drained node self-heals without a human, and a new release rolls out Pod-by-Pod with an instant rollback path if it misbehaves.
Labels and selectors: how the wiring holds
Deployments don’t track their Pods by name — names are random and Pods come and go. They use labels: key-value tags like app: web stamped onto each Pod, and a selector that matches them. The Deployment’s selector.matchLabels and its Pod template’s labels must agree; the ReplicaSet then owns exactly the Pods whose labels match. The same label selector is how a Service will find these Pods tomorrow. Labels are the loose coupling that lets controllers and Services locate Pods without ever hard-coding an identity.
Hands-On Lab
Budget about 25 minutes. This runs entirely locally on the kind cluster you created yesterday — no cloud, no bill. Type each command yourself and read every line. Pod name suffixes, IPs, node names and ages are unique to each run — yours will differ from the samples below.
# 1. Confirm you're pointed at the local kind cluster and its node is Ready.
kubectl get nodes
# Output (node name and AGE will differ):
# NAME STATUS ROLES AGE VERSION
# kind-control-plane Ready control-plane 12m v1.31.0
# 2. web-deploy.yaml — a Deployment: 3 replicas of nginx, wired to its Pods by the app=web label.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginx:1.27-alpine
ports:
- containerPort: 80
# 3. Apply the manifest. kubectl sends the desired state to the API server.
kubectl apply -f web-deploy.yaml
# Output:
# deployment.apps/web created
# 4. See all three objects the one manifest produced: Deployment -> ReplicaSet -> Pods.
kubectl get deploy,rs,pods
# Output (the ReplicaSet hash and Pod suffixes are random — yours will differ):
# NAME READY UP-TO-DATE AVAILABLE AGE
# deployment.apps/web 3/3 3 3 20s
#
# NAME DESIRED CURRENT READY AGE
# replicaset.apps/web-6f8c8d9b7d 3 3 3 20s
#
# NAME READY STATUS RESTARTS AGE
# pod/web-6f8c8d9b7d-2xq4z 1/1 Running 0 20s
# pod/web-6f8c8d9b7d-8kv7n 1/1 Running 0 20s
# pod/web-6f8c8d9b7d-lm9pd 1/1 Running 0 20s
# 5. Filter Pods by the label the selector uses — and see which node/IP each got.
kubectl get pods -l app=web -o wide
# Output (IP and NODE will differ; on a one-node kind cluster all land on the same node):
# NAME READY STATUS RESTARTS AGE IP NODE
# web-6f8c8d9b7d-2xq4z 1/1 Running 0 70s 10.244.0.6 kind-control-plane
# web-6f8c8d9b7d-8kv7n 1/1 Running 0 70s 10.244.0.7 kind-control-plane
# web-6f8c8d9b7d-lm9pd 1/1 Running 0 70s 10.244.0.8 kind-control-plane
# 6. Describe the Deployment — note the selector and the RollingUpdate strategy defaults.
kubectl describe deployment web
# Output (trimmed):
# Name: web
# Selector: app=web
# Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable
# StrategyType: RollingUpdate
# RollingUpdateStrategy: 25% max unavailable, 25% max surge
# 7. Scale out to 5 by changing one number — the ReplicaSet adds two more Pods.
kubectl scale deployment web --replicas=5
kubectl get pods -l app=web
# Output (two new Pods appear; two are still coming up for a second):
# deployment.apps/web scaled
# NAME READY STATUS RESTARTS AGE
# web-6f8c8d9b7d-2xq4z 1/1 Running 0 3m
# web-6f8c8d9b7d-8kv7n 1/1 Running 0 3m
# web-6f8c8d9b7d-lm9pd 1/1 Running 0 3m
# web-6f8c8d9b7d-p4r2t 0/1 ContainerCreating 0 2s
# web-6f8c8d9b7d-x7n9k 0/1 ContainerCreating 0 2s
# 8. Roll out a new image. The Deployment creates a NEW ReplicaSet and swaps Pods gradually.
kubectl set image deployment/web web=nginx:1.28-alpine
kubectl rollout status deployment/web
# Output:
# deployment.apps/web image updated
# Waiting for deployment "web" rollout to finish: 3 out of 5 new replicas have been updated...
# deployment "web" successfully rolled out
# 9. Two revisions now exist — the old ReplicaSet is kept, scaled to zero, for rollback.
kubectl rollout history deployment/web
# Output:
# deployment.apps/web
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# 10. Roll back to revision 1 (the previous image). Kubernetes scales the old ReplicaSet up again.
kubectl rollout undo deployment/web
kubectl rollout status deployment/web
# Output:
# deployment.apps/web rolled back
# deployment "web" successfully rolled out
# 11. Clean up — deleting the Deployment cascades to its ReplicaSet and every Pod.
kubectl delete -f web-deploy.yaml
# Output:
# deployment.apps/web deleted
Read the chain back: one manifest created a Deployment, which created a ReplicaSet, which kept your Pods alive; you scaled by changing a number, shipped a new image Pod-by-Pod, and rolled it back in one command — never touching a Pod directly. That declarative loop is the whole of Kubernetes in miniature.
Common Errors & Fixes
These are the mistakes that trip people up the first week they drive Deployments on kind. Read the error text slowly — parsing it is the actual skill.
Common error: A Deployment applies fine but its Pods never reach Running —
kubectl get podsshowsImagePullBackOff(usually a typo’d image name or tag):NAME READY STATUS RESTARTS AGE web-6f8c8d9b7d-2xq4z 0/1 ImagePullBackOff 0 40sWhy: The scheduler placed the Pod, but the kubelet can’t pull the image — the tag doesn’t exist, the repository name is misspelled, or it’s private and the node has no credentials. Kubernetes retries with a growing backoff, so the Pod sits in
ImagePullBackOff(orErrImagePullon the first attempt) rather than crashing outright.Fix: Read the exact reason with
kubectl describe pod <name>— the Events at the bottom sayFailed to pull image .... Correct theimage:in the manifest and re-apply; the Deployment rolls the fixed spec out automatically. For private images, add animagePullSecret.How you’d spot it in prod: A rollout that stalls at
kubectl rollout statuswith new Pods stuckImagePullBackOffalmost always means the CI pipeline pushed a tag the manifest doesn’t reference, or a registry credential expired — check the image tag against what was actually published before touching anything else.
Common error:
kubectl applyrejects the Deployment because its selector and its Pod template labels don’t match:The Deployment "web" is invalid: spec.template.metadata.labels: Invalid value: map[string]string{"app":"api"}: `selector` does not match template `labels`Why: A Deployment finds its Pods by label selector, so the labels in
spec.selector.matchLabelsand the labels stamped onspec.template.metadata.labelsmust be identical. If they disagree, the ReplicaSet would create Pods it could never select — Kubernetes refuses the object rather than build something that can’t work.Fix: Make the two label blocks match exactly (here, set both to
app: web) and re-apply. Remember the selector is immutable after creation, so if you truly need a different selector you must delete and recreate the Deployment.How you’d spot it in prod: A GitOps or CI apply that fails validation with “selector does not match template labels” is a hand-edit that changed one label block but not the other — diff the two label maps in the manifest before assuming the cluster is at fault.
Common error: A
kubectl getcommand fails immediately with a resource-type error, usually a typo in the kind:error: the server doesn't have a resource type "deploymnet"Why:
kubectlmaps the word you type to a known API resource. A misspelling likedeploymnet, or an object type this cluster’s API doesn’t serve, leaves nothing to map to, so kubectl reports that the server has no such resource type — it never reaches the cluster’s data.Fix: Use a correct name or its short alias —
kubectl get deployments,deploy,rs,po. Runkubectl api-resourcesto list every valid type and its short name on this cluster.How you’d spot it in prod: This error on a resource that “definitely exists” is either a typo or a missing CRD — a custom resource whose controller isn’t installed.
kubectl api-resources | grep <name>tells you instantly which of the two it is.
Kubernetes Pods & Deployments Interview Questions
Cover the answers below and say your own version out loud first — define a Pod versus a Deployment, and what a ReplicaSet adds, before you reveal each answer. Recalling before revealing is what makes these stick when an interviewer asks them cold. The four questions and answers render right after this note.
Go Deeper
Optional extras if you have ~30 more minutes today:
- 5 min — Run
kubectl explain deployment.specand thenkubectl explain deployment.spec.strategyto read the field docs straight from the API server — no browser, always matching your cluster’s version. - 10 min — With the Deployment still up, run
kubectl get pods -win one pane andkubectl delete pod <one-pod>in another. Watch the ReplicaSet start a replacement within seconds — self-healing you can see. - 15 min — Read the Kubernetes Deployment concept docs on rolling updates and revision history, then map each field back to the
scale,set image,rollout statusandrollout undocommands you ran today.
What is the difference between a Pod and a Deployment? Both
A Pod is the smallest unit Kubernetes runs — one or more containers that share a network address and storage, scheduled together onto a node. It's disposable: if the node dies the Pod dies with it and nothing brings it back. A Deployment is a controller that manages Pods for you. You declare how many replicas you want and which image, and it creates a ReplicaSet that keeps exactly that many Pods running, replacing any that crash or vanish. So you almost never create a bare Pod in production — you create a Deployment and let it own the Pods. The Pod is the thing that runs; the Deployment is the thing that keeps it running.
What does a ReplicaSet do, and why not create one directly? Both
A ReplicaSet's one job is to keep a set number of identical Pods running — it watches the cluster, counts Pods matching its label selector, and creates or deletes Pods until the count matches the desired replicas. You rarely create one directly, though. A Deployment sits on top and owns the ReplicaSet, and that layer is what gives you rolling updates and rollback: change the image and the Deployment spins up a new ReplicaSet, scaling the old one down gradually while keeping the revision history. Create a ReplicaSet alone and you lose all of that — you'd be editing Pods by hand. So the rule is simple: manage ReplicaSets through Deployments, never directly.
How does a rolling update work, and how do you roll back? Product
A rolling update replaces Pods gradually instead of all at once, so the app never goes fully down. When you change a Deployment's image it creates a new ReplicaSet and shifts Pods over a few at a time — bounded by maxSurge (how many extra it can add) and maxUnavailable (how many it can take down) — until the new version fully replaces the old. Each change is saved as a numbered revision. If the new image is broken, kubectl rollout undo flips back to the previous ReplicaSet, which is still there scaled to zero. I watch kubectl rollout status during a deploy and undo the moment it stalls, rather than waiting for it to time out.
How do labels and selectors connect a Deployment to its Pods? Both
Labels are key-value tags you attach to objects; a selector is a query that matches them. A Deployment uses them to know which Pods are 'its' Pods. Its spec.selector.matchLabels says, for example, app: web, and its Pod template stamps that same label onto every Pod it creates. The ReplicaSet then owns exactly the Pods whose labels match. The same mechanism is how a Service later finds Pods to send traffic to — it selects on labels too. One catch: the selector is immutable once set, and it must match the template's labels or the Deployment is rejected. So labels are the loose coupling that lets controllers and Services find Pods without hard-coding names.
Mark Day 68 complete
Tomorrow you give those Pods a stable address — Services and cluster networking so traffic reaches them no matter how often a Pod is replaced.
Stuck on today’s lab? Ask in Mission 90 Q&A