Skip to content

Phase 4 · ORCHESTRATION & IAC

Pods & Deployments

Day 68 of 90 ~55 min 0/20 in phase Builds on Day 67

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

Section 1 of 5 · ~3 min

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. maxSurge and maxUnavailable bound 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 undo flips straight back to the last good one.
A Deployment owns a ReplicaSet, which keeps three identical Pods running; all four objects are wired together by the app=web label selector. Deployment web desired: 3 replicas ReplicaSet keeps count = 3 Pod (app=web) Pod (app=web) Pod (app=web)
One Deployment owns one ReplicaSet, which keeps three label-matched Pods alive — kill a Pod and it's replaced.

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

Section 2 of 5 · ~3 min

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

Section 3 of 5 · ~3 min

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 pods shows ImagePullBackOff (usually a typo’d image name or tag):

NAME                   READY   STATUS             RESTARTS   AGE
web-6f8c8d9b7d-2xq4z   0/1     ImagePullBackOff   0          40s

Why: 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 (or ErrImagePull on the first attempt) rather than crashing outright.

Fix: Read the exact reason with kubectl describe pod <name> — the Events at the bottom say Failed to pull image .... Correct the image: in the manifest and re-apply; the Deployment rolls the fixed spec out automatically. For private images, add an imagePullSecret.

How you’d spot it in prod: A rollout that stalls at kubectl rollout status with new Pods stuck ImagePullBackOff almost 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 apply rejects 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.matchLabels and the labels stamped on spec.template.metadata.labels must 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 get command fails immediately with a resource-type error, usually a typo in the kind:

error: the server doesn't have a resource type "deploymnet"

Why: kubectl maps the word you type to a known API resource. A misspelling like deploymnet, 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. Run kubectl api-resources to 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

Section 4 of 5 · ~1 min

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

Section 5 of 5 · ~1 min

Optional extras if you have ~30 more minutes today:

  • 5 min — Run kubectl explain deployment.spec and then kubectl explain deployment.spec.strategy to 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 -w in one pane and kubectl 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 status and rollout undo commands 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