Kubernetes Workloads: ReplicaSets and Deployments
A ReplicaSet maintains a target number of running Pods. When a Pod disappears, the ReplicaSet creates a new one. Changing the pod template has no effect on existing Pods - the RS only uses the updated template when it next needs to create one. A Deployment wraps a ReplicaSet and solves that gap: when the pod template changes, a Deployment finds or creates a ReplicaSet matching the new template, scales it up, and scales the old one down to 0. Old Pods are deleted and new ones take their place. Old ReplicaSets are retained at 0 replicas for rollback. kubectl rollout undo restores the Deployment's pod template to the previous revision, then the normal rolling update handles the scaling. How many old ReplicaSets are kept is controlled by .spec.revisionHistoryLimit, which defaults to 10. Once that limit is exceeded, the oldest ones are deleted.
This challenge builds both layers by hand. The ReplicaSet tasks show what the controller does and what it cannot do. Task 3 produces the mixed-image state that a Deployment is designed to prevent.
Task 1 - Create a ReplicaSet
A ReplicaSet uses a label selector to identify the Pods it manages. The selector must match the labels on the pod template.
Steps:
- Create a ReplicaSet named
cachewith.spec.replicas: 2, selectorapp: cache, one container using imagenginx
Run kubectl get rs cache - DESIRED and READY both show 2. Run kubectl get pods -l app=cache to list the two Pods - their names are cache- followed by a random suffix generated by the ReplicaSet controller. Run kubectl get pod <pod-name> -o yaml | grep -A6 ownerReferences to see .metadata.ownerReferences pointing back to the cache ReplicaSet.
Task 2 - Self-Healing
When a Pod disappears, the ReplicaSet detects the count dropped below .spec.replicas and creates a replacement. A bare Pod has no controller watching it - if deleted, it stays gone.
Steps:
- Delete one of the Pods managed by the
cacheReplicaSet
Run kubectl get pods -l app=cache - the replacement Pod has a different name from the one deleted. The ReplicaSet controller detected the count dropped below .spec.replicas and created a replacement within seconds. The surviving Pod was not affected.
Task 3 - Template Changes Do Not Affect Existing Pods
A ReplicaSet applies its pod template only to Pods it creates from that point on. The template change does not affect existing Pods.
Steps:
- Edit the
cacheReplicaSet object and change its pod template image fromnginxtonginx:alpine - Delete one of the running Pods
Run kubectl get pods -l app=cache -o yaml | grep "image:" - one Pod shows nginx, the other shows nginx:alpine. Both Pods have the same label and are managed by the same ReplicaSet, but run different images. A Deployment avoids this by using a separate ReplicaSet per template version - old Pods are deleted and new ones are created from the new RS.
Task 4 - Create a Deployment
A Deployment owns a ReplicaSet, and that ReplicaSet owns the Pods. The ownership chain is recorded in .metadata.ownerReferences on each object.
Steps:
- Create a Deployment named
frontendwith.spec.replicas: 3, selectorapp: frontend, one container using imagenginx
Run kubectl get rs -l app=frontend - one ReplicaSet exists, named frontend-<hash> where the hash is derived from the pod template. Pod names follow the pattern frontend-<hash>-<suffix>. Run kubectl get rs -l app=frontend -o yaml | grep -A6 ownerReferences to see the RS pointing to the Deployment, and kubectl get pods -l app=frontend -o yaml | grep -A6 ownerReferences to see each Pod pointing to the RS.
Task 5 - Scale the Deployment
Scaling changes .spec.replicas without touching the pod template. The existing ReplicaSet grows to match the new count - no rollout is triggered and no new RS is created.
Steps:
- Scale the
frontendDeployment to 5 replicas
Run kubectl get pods -l app=frontend - 5 Pods, all with the same frontend-<hash>- prefix. The same ReplicaSet that held 3 Pods now holds 5 - no new RS was created, because the pod template did not change. Run kubectl get rs -l app=frontend to confirm only one RS exists and its DESIRED and READY columns both show 5.
Task 6 - Rolling Update and Rollback
A Deployment retains old ReplicaSets at 0 replicas as rollout history. kubectl rollout history lists the available revisions. Setting the kubernetes.io/change-cause annotation after each rollout populates the CHANGE-CAUSE column and makes the history readable. .spec.revisionHistoryLimit caps how many revisions are kept before the oldest are deleted.
Steps:
- Update
frontend's image tonginx:alpine - Update
frontend's image tonginx:stable-alpine - Roll back to revision
1using--to-revision=1(or whichever revision runs plainnginxin case you played with revisions) - Set
.spec.revisionHistoryLimitto3
Rolling back to an old revision does not restore its revision number. The RS is reused and stamped with the next available number - the old number disappears from history. If you played with extra rollouts and are unsure which revision runs plain nginx, inspect each candidate with kubectl rollout history deployment/frontend --revision=N.
Run kubectl describe deployment frontend - the NewReplicaSet field names the currently active RS and OldReplicaSets lists everything retained at 0.
Optional: set the kubernetes.io/change-cause annotation on frontend after each image update to populate the CHANGE-CAUSE column in kubectl rollout history.
- After updating to
nginx:alpine:kubernetes.io/change-cause="update to nginx:alpine" - After updating to
nginx:stable-alpine:kubernetes.io/change-cause="update to nginx:stable-alpine"