By the end you'll know
- What a Deployment actually manages, and the ReplicaSet hiding underneath it
- How to scale an app, and ship a new version without taking it down
- What happens when a release is broken, and how to roll it back in one command
- Which of the other workload types to use for databases, per-node agents and scheduled tasks
What a Deployment does
In Lesson 3 you saw that a bare pod doesn't come back once it's deleted. A Deployment fixes that. You tell it what you want, "3 copies of this app, running this image", and it keeps that true: it replaces pods that disappear, adds or removes pods when you change the count, and swaps in a new version a few pods at a time when you change the image.
It doesn't manage pods directly, though. There's a middle layer:
- The Deployment holds your intent: the image, the number of copies, and how to update.
- A ReplicaSet keeps an exact number of identical pods running. The Deployment creates one ReplicaSet per version of your app.
- The pods are the actual running copies.
You almost never create a ReplicaSet yourself. The Deployment does it for you, which is why you'll see them in listings without having written one.
Try it: create a Deployment
Start a cluster if you don't have one running:
kind create cluster --name learn
Save this as deploy.yaml. It's the same shape you read in Lesson 4, with three copies:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 3
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: nginx:1.27
Apply it and look at all three layers at once:
kubectl apply -f deploy.yaml
kubectl get deployments,replicasets,pods
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/hello 3/3 3 3 20s
NAME DESIRED CURRENT READY AGE
replicaset.apps/hello-6d5f9c7b8d 3 3 3 20s
NAME READY STATUS RESTARTS AGE
pod/hello-6d5f9c7b8d-4kqzt 1/1 Running 0 20s
pod/hello-6d5f9c7b8d-b7m2x 1/1 Running 0 20s
pod/hello-6d5f9c7b8d-tx9wl 1/1 Running 0 20s
Look at the names: the ReplicaSet is hello plus a code, and each pod is the ReplicaSet's name plus another code. You can tell which version every pod belongs to just from its name. Your codes will be different.
Scale it
Change replicas: 3 to replicas: 5 in deploy.yaml and apply again:
kubectl apply -f deploy.yaml
kubectl get pods
Two new pods appear. Change it back to 3 and apply, and two are removed. There's also a shortcut, kubectl scale deployment hello --replicas=5, but it changes the cluster without changing your file, so the two drift apart. Editing the file and applying it keeps your file as the record of what's running, which matters a lot once your files live in Git.
Ship a new version
Change the image in deploy.yaml from nginx:1.27 to nginx:1.28, apply, and watch:
kubectl apply -f deploy.yaml
kubectl rollout status deployment/hello
Waiting for deployment "hello" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "hello" rollout to finish: 2 out of 3 new replicas have been updated...
deployment "hello" successfully rolled out
That's a rolling update. The Deployment created a new ReplicaSet for nginx:1.28 and moved pods over gradually, starting new ones before removing old ones, so there were always pods serving. Now look at the ReplicaSets:
kubectl get replicasets
NAME DESIRED CURRENT READY AGE
hello-6d5f9c7b8d 0 0 0 5m
hello-7b9c4f6d5c 3 3 3 40s
The old one is still there, scaled to 0. Keep that in mind for the next section.
How gradual is "gradual"? Two settings control it. maxSurge is how many extra pods can exist above your count during the update, and maxUnavailable is how many of your count can be missing. Both default to 25%, rounded so that at least one pod can move at a time. With 3 replicas, that works out to "start 1 new pod, then remove 1 old one", over and over.
Break it on purpose
Now ship a version that doesn't exist. Change the image to nginx:9.99 and apply:
kubectl apply -f deploy.yaml
kubectl get pods
NAME READY STATUS RESTARTS AGE
hello-5c8f7d9b6a-q2wrt 0/1 ImagePullBackOff 0 30s
hello-7b9c4f6d5c-8hn4v 1/1 Running 0 6m
hello-7b9c4f6d5c-kd7sp 1/1 Running 0 6m
hello-7b9c4f6d5c-x5vzm 1/1 Running 0 6m
One new pod is stuck in ImagePullBackOff, the failure you met in Lesson 3. But look at the rest: all three old pods are still running. The rolling update only removes an old pod after a new one is ready, and the new one never becomes ready, so the update stalls and your app keeps working on the old version. A bad release doesn't take the site down. It just never finishes.
Roll it back:
kubectl rollout undo deployment/hello
kubectl get pods
The broken pod disappears, and you're back on nginx:1.28. The rollback was instant because the previous ReplicaSet was still there, scaled to 0, ready to grow again. You can see every version the Deployment remembers with:
kubectl rollout history deployment/hello
One catch: rollout undo changed the cluster, but your deploy.yaml still says nginx:9.99. Fix the file too, or the next apply will ship the broken version again. In a team that keeps its files in Git, the usual way to roll back is to revert the change in Git and let it apply, so the file and the cluster always agree.
In my real cluster
CivicGrid's API runs as a Deployment, and shipping a new version is a one-line change in Git: I update the image tag in my GitOps (Git-driven deployment) repository, and Argo CD applies it, which starts exactly this kind of rolling update. Because a new pod starts before the old one stops, every update briefly needs room for one extra pod. On my single, fairly full node, that headroom is one of the reasons I freed up memory by turning Grafana off. If there were no room, the new pod would wait in Pending, and the old one would keep serving until it fit.
Other ways to run pods
A Deployment fits most apps, but not all. Kubernetes has a few other workload types, each managing pods in a different way:
| Type | Use it for | Real example |
|---|---|---|
| Deployment | Apps where any copy is as good as another, and copies come and go freely | A web app or API |
| StatefulSet | Apps where each copy needs a stable name and its own storage that survives restarts | Databases, Prometheus |
| DaemonSet | Exactly one copy on every node, including new nodes as they join | Log collectors, monitoring agents, network plugins |
| Job | A task that runs until it finishes, then stops | A data import or a database migration |
| CronJob | A Job on a schedule | A nightly backup or a weekly report |
The question to ask is: does it matter which copy is which? If not, use a Deployment. If each copy has its own identity or its own data, use a StatefulSet. If it's about the nodes rather than the app, use a DaemonSet. If it should finish, use a Job, or a CronJob if it should repeat.
See them in your own cluster:
kubectl get deployments,statefulsets,daemonsets,jobs,cronjobs -A
Even an empty kind cluster has some: coredns runs as a Deployment, and kube-proxy and kindnet run as DaemonSets, one pod per node, because every node needs its own networking.
In my real cluster
My production cluster uses most of these. The CivicGrid API is a Deployment. Prometheus and Alertmanager run as StatefulSets, because each keeps its own data. The Cilium network plugin runs as a DaemonSet, one per node. And CivicGrid's scheduled tasks run as CronJobs.
Clean up
kind delete cluster --name learn
Check yourself
What does a Deployment create to keep the right number of pods running?
You change a Deployment's image and apply. What happens to the running pods?
A new release has a typo in the image name. What happens to your app?
Which command takes a Deployment back to its previous version?
You need one copy of a log collector on every node. Which workload type fits?
Why is editing replicas in your file and applying it better than kubectl scale?
Key terms
- Deployment: keeps a set number of identical pods running, and updates them gradually.
- ReplicaSet: the layer a Deployment uses to keep an exact number of pods for one version.
- Rolling update: replacing pods with a new version a few at a time, so the app keeps running.
- Rollback: returning to a previous version, with
kubectl rollout undo. - StatefulSet, DaemonSet, Job, CronJob: the other workload types, for stateful apps, per-node agents, finite tasks and scheduled tasks.
Go deeper
- Deployments (opens in a new tab) in the official docs, including every rollout setting.
- Workloads (opens in a new tab) gives an overview of every workload type.