Deployment Tasks — Beginner → Advanced¶
A hands-on ladder for learning Deployments using deployment.yaml in this folder.
Do these AFTER pod-tasks.md and replica-tasks.md — a Deployment manages
ReplicaSets, which manage Pods, so understand those layers first.
The one idea: a Deployment is a ReplicaSet manager that adds rolling updates + rollback. It never touches Pods directly — it creates/scales ReplicaSets, and each ReplicaSet keeps its Pods alive.
Level 0 — Orientation¶
kubectl explain deployment.spec— note the extra fields a ReplicaSet lacks:strategy,revisionHistoryLimit,progressDeadlineSeconds,paused.- Mental model: desired vs actual, but one level up — the Deployment controller reconciles ReplicaSets, each RS reconciles Pods.
- Why it exists: a bare ReplicaSet does NOT roll out template changes to running
pods (you proved this in
replica-tasks.mdTask 5.1). The Deployment fixes that.
Level 1 — Beginner: create & see the layers¶
- Task 1.1 — Apply
- Do:
kubectl apply -f deployment.yaml -
Verify:
kubectl get deploy nginx-deploy→READY 3/3 UP-TO-DATE 3 AVAILABLE 3. -
Task 1.2 — See ALL three layers at once
- Do:
kubectl get deploy,rs,pods -l app=nginx -
Learn: one Deployment → one ReplicaSet (random suffix like
nginx-deploy-7c9f) → 3 Pods (nginx-deploy-7c9f-xxxxx). The Deployment owns the RS; the RS owns the Pods. -
Task 1.3 — Rollout status & history
- Do:
kubectl rollout status deployment/nginx-deploy -
Do:
kubectl rollout history deployment/nginx-deploy→ revision 1, with thechange-causeannotation you set. -
Task 1.4 — Ownership chain
- Do:
kubectl get rs -l app=nginx -o jsonpath='{.items[*].metadata.ownerReferences[*].name}{"\n"}' - Learn: the RS's owner is the Deployment; a Pod's owner is the RS. Two levels of
ownerReferences.
Level 2 — The whole point: rolling updates¶
- Task 2.1 — Roll out a new image (watch it happen)
- In one terminal:
kubectl get rs -w -l app=nginx - In another:
kubectl set image deployment/nginx-deploy nginx=nginx:1.27-alpine - Verify: a NEW ReplicaSet appears and scales up while the OLD one scales down.
-
Learn: this is a rolling update — new RS created, pods shifted gradually.
-
Task 2.2 — Confirm running pods actually changed
- Do:
kubectl get pods -l app=nginx -o jsonpath='{.items[*].spec.containers[*].image}{"\n"}' -
Learn: all pods now run
1.27— the exact thing a bare ReplicaSet could NOT do. -
Task 2.3 — Declarative version
-
Instead of
set image, editdeployment.yaml(image:+ thechange-causeannotation) andkubectl apply -f deployment.yaml. Same rollout, but the file stays the source of truth (prefer this / GitOps). -
Task 2.4 — maxSurge / maxUnavailable in action
- We set
maxSurge: 1, maxUnavailable: 0(zero-downtime). During a rollout,kubectl get pods -l app=nginxbriefly shows 4 pods (3 + 1 surge), never fewer than 3 Ready. TrymaxUnavailable: 1, maxSurge: 0and watch it dip to 2.
Level 3 — Rollback & history¶
- Task 3.1 — Undo the last rollout
- Do:
kubectl rollout undo deployment/nginx-deploy -
Verify: pods go back to the previous image; a rollback is just another rollout (it re-scales the OLD ReplicaSet back up — RS objects are reused, not recreated).
-
Task 3.2 — Roll back to a specific revision
-
Do:
kubectl rollout history deployment/nginx-deploythenkubectl rollout undo deployment/nginx-deploy --to-revision=1 -
Task 3.3 — Why old ReplicaSets stick around
-
Do:
kubectl get rs -l app=nginx— you'll see old RSes at0replicas. They're kept for rollback, bounded byrevisionHistoryLimit(we set 5). Learn: scaled-to-zero RSes are your rollback history. -
Task 3.4 — change-cause labels
- Set
metadata.annotations.kubernetes.io/change-causebefore each apply sorollout historyreads meaningfully instead of<none>.
Level 4 — Scaling, pausing, autoscaling¶
- Task 4.1 — Scale
- Do:
kubectl scale deployment/nginx-deploy --replicas=5(or edit the file + apply). -
Learn: scaling changes replica count but does NOT create a new revision — it's not a template change.
-
Task 4.2 — Pause / resume (batch several edits into one rollout)
- Do:
kubectl rollout pause deployment/nginx-deploy - Make several changes (
set image,set resources...). Nothing rolls yet. - Do:
kubectl rollout resume deployment/nginx-deploy→ ONE rollout applies them all. -
Learn: avoids N separate rollouts when you're making N related changes.
-
Task 4.3 — HorizontalPodAutoscaler
- Do:
kubectl autoscale deployment/nginx-deploy --min=2 --max=6 --cpu-percent=50(needs metrics-server). Learn: HPA editsreplicasfor you based on load. - Gotcha: if HPA owns replicas, DON'T also hard-code
replicasin your applied file or they fight — omitreplicasfrom the manifest when an HPA manages it.
Level 5 — Advanced: strategies & failed rollouts¶
- Task 5.1 — Recreate strategy
- Set
strategy.type: Recreate(remove therollingUpdateblock), apply, then change the image. Verify: ALL old pods terminate first, THEN new ones start (brief downtime). -
Learn: use when two versions can't coexist (exclusive locks, incompatible schema).
-
Task 5.2 — A stuck rollout (bad image)
- Do:
kubectl set image deployment/nginx-deploy nginx=nginx:doesnotexist - Verify:
kubectl rollout statushangs;kubectl get podsshows new pods inImagePullBackOffwhile OLD pods keep serving (becausemaxUnavailable: 0!). -
Learn: a good rollout config means a broken new version does NOT take down the old one. Fix with
kubectl rollout undo. -
Task 5.3 — progressDeadlineSeconds
-
After a stuck rollout, wait past
progressDeadlineSeconds(120s). Checkkubectl describe deployment nginx-deploy→ conditionProgressing=False, reason=ProgressDeadlineExceeded. Learn: it REPORTS failure; it does NOT auto-rollback. -
Task 5.4 — Readiness gates the rollout
- Point
readinessProbe.httpGet.pathat/nope, apply. New pods never become Ready, so the rollout stalls with old pods still serving. Learn: readiness is the rollout's "is the new version healthy?" signal — a core safety mechanism. Revert.
Level 6 — Edge Cases & Production Nuances¶
Same format as pod-tasks.md Level 7: trap → reproduce → diagnose → fix/rule.
EC-1 — Deployment vs ReplicaSet vs Pod: know which to touch¶
- Trap: editing a ReplicaSet the Deployment owns gets reverted — the Deployment controller reconciles the RS back to match its template.
- Rule: with a Deployment, you manage the Deployment only. Never edit its child
RSes or Pods directly; changes there are transient.
kubectl edit deploy/apply, neverkubectl edit rs.
EC-2 — Selector is IMMUTABLE¶
- Trap: you cannot change
spec.selectorafter creation —applyfails withfield is immutable. - Reproduce: change
selector.matchLabels.appto something new,apply. - Fix/rule: to change the selector you must delete and recreate the Deployment. Choose labels carefully up front. (This is also why selector and template labels are usually kept minimal and stable.)
EC-3 — Orphaned ReplicaSets from a selector/label mismatch¶
- Trap: if
template.metadata.labelsstops satisfyingspec.selector, the API rejects it (selector does not match template labels) — same guardrail as a bare RS. - Rule: keep template labels ⊇ selector labels. Adding EXTRA template labels is fine; removing a selector-required one is not.
EC-4 — A rollout that "hangs" is usually readiness or image, not the Deployment¶
- Diagnose (in order):
kubectl rollout status deployment/nginx-deploy # is it progressing?
kubectl get pods -l app=nginx # ImagePullBackOff? CrashLoop? not Ready?
kubectl describe deployment nginx-deploy # conditions: Progressing / Available
kubectl describe pod <new-pod> # events for the failing new pod
- Rule: the Deployment is fine; a new pod can't get Ready. With
maxUnavailable: 0the OLD version keeps serving — so you have time. Fix forward orrollout undo.
EC-5 — Scaling is not a revision; template change is¶
- Trap: you expect
kubectl scaleto show up inrollout history— it doesn't. - Rule: only pod-template changes create a new revision/ReplicaSet.
replicaschanges just resize the current RS. Two different kinds of change; don't conflate them.
EC-6 — revisionHistoryLimit: 0 deletes your rollback ability¶
- Trap: set it to 0 to "clean up" old RSes and you lose ALL rollback history.
- Rule: keep a sane limit (default 10; we use 5). Old scaled-to-zero RSes are cheap and ARE your undo button.
EC-7 — HPA and a hard-coded replicas fight each other¶
- Trap: an HPA manages
replicas, but your Git manifest also setsreplicas: 3. Everyapplyresets the count and the HPA re-adjusts — flapping. - Fix/rule: when an HPA owns scaling, omit
replicasfrom the manifest (or use a server-side-apply field manager that yields it). Let one owner control the count.
EC-8 — Rollout doesn't restart pods when only a ConfigMap/Secret changes¶
- Trap: you update a ConfigMap/Secret the pods consume, but the Deployment template didn't change → no rollout, pods keep the old config (env vars are injected at start; mounted files update eventually but the process may not re-read them).
- Fix: force a rollout:
kubectl rollout restart deployment/nginx-deploy, or bump a template annotation (e.g. a checksum of the config) so the template genuinely changes. - Rule: config changes are invisible to the Deployment unless the template changes.
EC-9 — kubectl rollout restart ≠ delete/recreate¶
- What it does: patches the template with a timestamp annotation, triggering a normal
rolling restart (respects
maxSurge/maxUnavailable, zero-downtime). - Rule: prefer it over
kubectl delete podloops to cycle pods (e.g. to pick up a rotated Secret or clear stuck state) — it's graceful and observable viarollout status.
EC-10 — Deleting a Deployment cascades to RSes and Pods¶
- Trap:
kubectl delete deployment nginx-deployremoves the RSes AND all Pods (foreground cascade) — the whole tree. - Keep the pods:
kubectl delete deployment nginx-deploy --cascade=orphanleaves the current RS + Pods running (rarely what you want, but useful in migrations). - Rule: deleting the top of the tree deletes everything under it by default.
EC-11 — A Deployment ADOPTS a hand-written ReplicaSet and drains it to 0 (WE HIT THIS)¶
- Trap: you have a standalone
nginx-rs(fromreplica.yaml) running 3 pods. You thenkubectl apply -f deployment.yaml. Suddenlykubectl get rsshows yournginx-rsatDESIRED 0— and you never scaled it. The Deployment did. - Why: a Deployment finds "its" ReplicaSets by matching its selector against RS
labels (not just pods). Both objects here use bare
app=nginx:
Deployment nginx-deploy selector: {app: nginx}
ReplicaSet nginx-rs labels: {app: nginx} ← matches → adopted
The Deployment adopts nginx-rs, sees its pod template lacks the current
pod-template-hash, treats it as an old revision, and scales it to 0 — exactly what
it does to any superseded revision.
- Diagnose (the smoking gun is in events):
kubectl get rs nginx-rs -o jsonpath='{.metadata.ownerReferences[*].name}{"\n"}' # -> nginx-deploy
kubectl get events --field-selector involvedObject.name=nginx-deploy | grep nginx-rs
# deployment/nginx-deploy Scaled down replica set nginx-rs from 3 to 2
# ... from 2 to 1 ... from 1 to 0
- Danger:
nginx-rsis now owned by the Deployment.kubectl delete deployment nginx-deploywill cascade-delete your hand-writtennginx-rstoo. - Why the Deployment's OWN RSes are safe: it appends a unique
pod-template-hashto its generated RSes' selectors (app=nginx,pod-template-hash=cd4d84b57), so revisions never collide. Your barenginx-rshad no hash, so it looked like a stray old revision. - Fix/rule: never let a Deployment's selector overlap the labels of a ReplicaSet (or
Pod) you manage separately. Give each workload a unique, stable label set
(
app.kubernetes.io/name+app.kubernetes.io/instance), or don't run a standalone RS and a Deployment with the sameapplabel in the same namespace. This is the same selector-collision family asreplica-tasks.mdEC-1/EC-2, seen from the Deployment side.
EC-12 — Old ReplicaSets stay at 0, they are NOT deleted (this is rollback history)¶
- Trap: after changing the image you see two ReplicaSets and wonder why the old one wasn't cleaned up:
nginx-deploy-79d497f6b7 3 3 3 ← new revision (new image), serving
nginx-deploy-cd4d84b57 0 0 0 ← old revision, kept at 0 (NOT deleted)
- Why: a rollout doesn't delete the old RS — it scales it to 0 and keeps it. That
parked, empty RS still holds the previous pod template (old image), so
rollout undocan restore it by simply scaling it back up. Rollback = re-scale an old RS, not a rebuild. - Diagnose:
kubectl rollout history deployment/nginx-deploy # each old RS = one revision
kubectl get rs -l app=nginx -o custom-columns=\
'RS:.metadata.name,DESIRED:.spec.replicas,IMAGE:.spec.template.spec.containers[0].image'
- When old RSes DO get deleted: bounded by
revisionHistoryLimit(we set 5; default 10). Once you exceed it, the Deployment garbage-collects the OLDEST parked RSes. SettingrevisionHistoryLimit: 0deletes them all immediately → no rollback (see EC-6). - Rule: an empty RS is cheap (no pods, just a small API object) and IS your undo
button. Kubernetes trades a little
kubectl get rsclutter for instant rollback. Don'tkubectl deleteold RSes by hand — you're deleting revision history.
Cheat sheet¶
kubectl apply -f deployment.yaml
kubectl get deploy,rs,pods -l app=nginx # see all three layers
kubectl rollout status deployment/nginx-deploy # is the rollout done?
kubectl rollout history deployment/nginx-deploy # revisions + change-cause
kubectl set image deployment/nginx-deploy nginx=nginx:1.27-alpine
kubectl rollout undo deployment/nginx-deploy [--to-revision=N]
kubectl rollout restart deployment/nginx-deploy # graceful cycle (pick up new config)
kubectl rollout pause|resume deployment/nginx-deploy
kubectl scale deployment/nginx-deploy --replicas=5
kubectl delete -f deployment.yaml
Mental model to lock in¶
- Deployment = ReplicaSet manager; RS = Pod manager; Pod = atomic unit.
- Rolling update = new RS up, old RS down, paced by
maxSurge/maxUnavailable. - Rollback = re-scale an old RS back up (history bounded by
revisionHistoryLimit). - Only template changes make a revision;
replicaschanges are just scaling. - readiness is the rollout's health signal — a bad new version stalls instead of
taking down the old one (with
maxUnavailable: 0). - Config (ConfigMap/Secret) changes need
rollout restart— they don't trigger one.