Deployments That Keep Pods Running
The controller you actually use. Replicas, the ReplicaSet underneath, scaling up and down, and what happens when you delete a pod a Deployment owns.
The controller you actually use. Replicas, the ReplicaSet underneath, scaling up and down, and what happens when you delete a pod a Deployment owns.
A pod you created by hand has one fatal property: delete it, or lose the node it was on, and it is gone permanently. Nothing in the cluster considers its absence a problem, because nothing was ever told the pod should exist — only that it should be created.
The Deployment is the object that fixes that, and it is the one you will write for almost every workload you ever run. By the end of this lesson you will have created one, watched it heal itself, scaled it with a number, and seen exactly what sits between it and the pods it owns.
The shift is small to write and large in consequence. Instead of "make me this pod", a Deployment says "three pods that look like this should always exist".
That sentence is a piece of desired state, so a controller can be responsible for it. When one pod disappears, the count no longer matches and the controller creates a replacement — the same reconciliation loop, applied to a number.
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: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80kubectl apply -f deployment.yaml
kubectl get deploymentsNAME READY UP-TO-DATE AVAILABLE AGE
web 3/3 3 3 12skubectl get podsNAME READY STATUS RESTARTS AGE
web-6c9f7d4b58-7xk2m 1/1 Running 0 14s
web-6c9f7d4b58-mn4pq 1/1 Running 0 14s
web-6c9f7d4b58-vz8lt 1/1 Running 0 14sThree pods you did not name. The names are the Deployment's name, then a hash identifying this version of the pod template, then a random suffix per pod — which is your first hint that pod names are not things to depend on.
Almost all of the confusion about Deployments is in three fields, so they are worth separating carefully.
replicas is how many pods you want. A number, and changing it is
how you scale.
template is a pod, described exactly as in the pod lesson —
metadata and spec, with containers, volumes and everything else.
It is a stamp, not a pod: the Deployment uses it to make pods, and
changing it triggers a rollout.
selector is how the Deployment recognises which pods are its
own. It is a label query, and it must match the labels in the
template.
That last field is the one people trip over, because it looks redundant. It is not: the Deployment does not remember which pods it made. It finds them, every reconciliation, by asking "which pods carry these labels?" Labels are the only link.
Bad — the selector does not match the template, and the object is rejected:
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: webserver # differentGood — the selector matches the labels the template applies:
spec:
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: webThe API server catches this exact case — selector does not match template labels — which is the friendly version. The unfriendly
version is two Deployments whose selectors both match the other's
pods: each sees too many pods, each deletes some, and they fight
indefinitely while your application flaps. Keep every Deployment's
labels genuinely unique to it.
Now the payoff. Delete a pod:
kubectl delete pod web-6c9f7d4b58-7xk2m
kubectl get podsNAME READY STATUS AGE
web-6c9f7d4b58-mn4pq 1/1 Running 3m
web-6c9f7d4b58-vz8lt 1/1 Running 3m
web-6c9f7d4b58-jd5rw 0/1 ContainerCreating 1sA replacement appeared within a second, with a new name. Nobody intervened: the count dropped to two, the ReplicaSet controller noticed, and it created one.
Watch it live, which is more convincing than reading it:
kubectl get pods --watch
# in another terminal:
kubectl delete pod -l app=web --allDelete all three and three more appear. The desired state never changed; only reality did, briefly.
This is also what happens without your involvement. A node is drained for an upgrade and its pods are evicted — the Deployment recreates them on other nodes. A node runs out of memory and the kubelet kills a pod — a replacement is scheduled. A node dies entirely — after a timeout, its pods are marked gone and rescheduled elsewhere.
Scaling is editing a number:
# edit deployment.yaml: replicas: 5
kubectl apply -f deployment.yaml
kubectl get podsOr imperatively, for an experiment or an emergency:
kubectl scale deployment web --replicas=5Remember the trap from the manifests lesson: the imperative version leaves your file saying 3, and the next routine apply undoes it. Fine for five minutes during an incident, followed by an edit to the file.
Scaling down removes pods rather than stopping them, and Kubernetes chooses which — preferring pods that are not ready, on over-provisioned nodes, or newest. You do not get to pick, which is another reason nothing should be attached to a particular pod's identity.
kubectl scale deployment web --replicas=0 # stop everything,
# keep the objectScaling to zero is a genuinely useful state: the Deployment still exists, its configuration is intact, and nothing is running or costing anything.
Look carefully at your pod names and there is a piece you have not been told about:
web-6c9f7d4b58-7xk2m
^^^ ^^^^^^^^^^ ^^^^^
| | └── random, per pod
| └── the ReplicaSet: this version of the template
└── the DeploymentA Deployment does not create pods. It creates a ReplicaSet, and the ReplicaSet creates pods.
Manages versions of the pod template, and the rollout from one to the next.
Keeps N pods matching one exact template. There is one per template version, which is what makes a rollback possible.
Interchangeable, randomly named, and replaced rather than repaired.
kubectl get replicasetsNAME DESIRED CURRENT READY AGE
web-6c9f7d4b58 3 3 3 8mThe reason for the extra layer becomes obvious the moment you change the image:
kubectl set image deployment/web nginx=nginx:1.27
kubectl get replicasetsNAME DESIRED CURRENT READY AGE
web-6c9f7d4b58 0 0 0 9m
web-7d8b5f9c64 3 3 3 20sA new ReplicaSet for the new template, scaled up to three; the old one kept at zero. That is how a rollout works — one ReplicaSet scaled up while another scales down — and how a rollback works, by scaling the old one back up. Keeping the old ReplicaSets is keeping your history.
kubectl rollout history deployment/web
kubectl rollout undo deployment/webThe rollout lesson takes this apart properly. For now, the useful model: you edit the Deployment, the Deployment manages ReplicaSets, ReplicaSets manage pods. You should never create a ReplicaSet yourself, for the same reason you rarely create a pod — the layer above does it better.
Four columns, and knowing what each means makes diagnosis much faster:
NAME READY UP-TO-DATE AVAILABLE AGE
web 2/3 3 2 4m2/3 READY with 3 UP-TO-DATE means all three pods are the right
version and one is not ready — so look at that pod. 3/3 READY with
1 UP-TO-DATE means a rollout is in progress.
kubectl get deployment web
kubectl describe deployment web # events and conditions
kubectl rollout status deployment/web # blocks until settled
kubectl get pods -l app=web # the pods themselves
kubectl logs -l app=web --tail=20 # all their logs at oncekubectl logs -l is worth knowing: it reads from every pod matching a
label, which is what you want when three replicas exist and any of
them might hold the error.
Deployment covers stateless applications, which is most of them. Three siblings exist for cases it does not fit, and it is enough to recognise them:
Stateless apps. Almost everything.
Pods are interchangeable, randomly named, with shared storage or none at all.
Stable names and per-pod storage.
Pod 0 keeps being pod 0, with its own disk. This is why databases on Kubernetes are a bigger topic than applications.
Exactly one pod per node.
Log collectors, metrics agents, network plugins — anything that has to be everywhere rather than somewhere.
Run to completion.
Once, or on a schedule. Success means exiting zero, not staying up.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3 # how many pods
selector:
matchLabels:
app: web # MUST match the template's labels
template: # a pod stamp, not a pod
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27-alpine
ports:
- containerPort: 80# Everyday
kubectl apply -f deployment.yaml
kubectl get deployments
kubectl get pods -l app=web
kubectl describe deployment web # events and conditions
kubectl rollout status deployment/web # wait until settled
kubectl logs -l app=web --tail=20 # every replica's logs
# Scaling (put it in the file afterwards)
kubectl scale deployment web --replicas=5
kubectl scale deployment web --replicas=0 # off, but still defined
# Changing the image (a rollout)
kubectl set image deployment/web nginx=nginx:1.27
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
# Proving self-healing
kubectl get pods --watch
kubectl delete pod -l app=web --all # they all come back
# The layers
# Deployment -> ReplicaSet (one per template version) -> Pods
# pod name = deployment-replicasethash-random
# never create a ReplicaSet yourself
# Reading the columns
# READY ready pods / desired
# UP-TO-DATE pods on the current template
# AVAILABLE ready long enough to count
# Which controller
# Deployment stateless, interchangeable pods
# StatefulSet stable identity + per-pod storage (databases)
# DaemonSet exactly one pod per node (agents)
# Job/CronJob run to completion, once or scheduledYour pods now survive. What they do not have is a usable address: every replacement pod gets a new IP, and there are three of them, so nothing can sensibly connect to your application yet.
The Service is the answer, and it is the next lesson — one stable name
in front of a changing set of pods, found by exactly the labels you
have just been setting. Before moving on, scale your Deployment up and
down a few times and watch kubectl get pods --watch. Seeing pods
appear and vanish while the Deployment stays put is the mental model
worth having.