Kubernetes Course
ReplicaSets
One Pod is great. One Pod in production is terrifying. What happens when it crashes? What happens when the node it lives on dies? This lesson is about ReplicaSets — the object that keeps a fixed number of your Pods alive at all times, no matter what goes wrong. We'll create one, break it on purpose, and watch Kubernetes heal itself in real time.
Use Play with Kubernetes at labs.play-with-k8s.com or run minikube start. Confirm you're good with kubectl get nodes — you should see at least one node in Ready state.
The Problem With a Single Pod
In Lesson 8 you created a Pod directly. It worked. But think about what happens next:
A single Pod has no safety net. A ReplicaSet is that safety net.
What Is a ReplicaSet?
A ReplicaSet is an object that says: "I want exactly N copies of this Pod running at all times." Kubernetes reads that number, counts the actual running Pods, and continuously makes reality match what you declared.
Too few Pods? It creates more. Too many? It deletes some. A Pod crashes? It creates a replacement straight away — automatically, without anyone having to do anything.
This automatic fixing of broken state is called self-healing — and it's one of Kubernetes' most powerful features.
How Does a ReplicaSet Know Which Pods Are "Its" Pods?
This trips up a lot of beginners. The ReplicaSet doesn't own Pods by name — names change every time a new Pod is created. It finds its Pods using label selectors.
You tell the ReplicaSet: "count any Pod that has the label app: payment-api." The ReplicaSet counts those Pods. If the count is below 3, it creates a new Pod — and stamps it with that same label so it gets counted next time.
This label-based approach is actually clever. Because it means if you manually create a Pod with the same label, the ReplicaSet will count it — and delete one of its own Pods to keep the total at 3. More on this in the hands-on section below.
Writing Your First ReplicaSet YAML
The scenario: Your payment API is running as a single Pod. A production incident last week proved you need at least 3 copies running at all times for redundancy. Your lead says to turn this into a ReplicaSet today. Here's the YAML.
apiVersion: apps/v1 # ReplicaSets use the apps/v1 API group — not the core v1
kind: ReplicaSet # The object type
metadata:
name: payment-api-rs # Name of the ReplicaSet itself
labels:
app: payment-api # Label on the ReplicaSet object (not the Pods)
spec:
replicas: 3 # We want exactly 3 Pods running at all times
selector: # How this ReplicaSet finds its Pods — via labels
matchLabels:
app: payment-api # Count any Pod that has this label
template: # The blueprint for every Pod this ReplicaSet creates
metadata:
labels:
app: payment-api # MUST match the selector above — or Kubernetes rejects it
spec:
containers:
- name: payment-api # Container name inside each Pod
image: nginx:1.25 # Image to use — nginx as our stand-in
ports:
- containerPort: 80 # Port the container listens on
resources:
requests:
memory: "128Mi" # Minimum memory needed to schedule this Pod
cpu: "250m" # 0.25 of one CPU core
limits:
memory: "256Mi" # Kill the container if it exceeds this
cpu: "500m" # Throttle the container if it exceeds this
app: payment-api. This is the link between the ReplicaSet and the Pods it manages.Deploying and Watching It Start
The scenario: You saved the YAML above as payment-rs.yaml. Let's apply it and watch all three Pods spin up at the same time.
# Create the ReplicaSet
kubectl apply -f payment-rs.yaml
# Watch all three Pods come to life — -w streams live updates
kubectl get pods -w
# See the ReplicaSet itself
kubectl get replicaset
# Short form also works
kubectl get rs
replicaset.apps/payment-api-rs created NAME READY STATUS RESTARTS AGE payment-api-rs-x2p9k 0/1 ContainerCreating 0 1s payment-api-rs-mn7ql 0/1 ContainerCreating 0 1s payment-api-rs-k8sj2 0/1 ContainerCreating 0 1s payment-api-rs-x2p9k 1/1 Running 0 7s payment-api-rs-mn7ql 1/1 Running 0 8s payment-api-rs-k8sj2 1/1 Running 0 9s NAME DESIRED CURRENT READY AGE payment-api-rs 3 3 3 15s
Pod names are auto-generated — payment-api-rs-x2p9k, payment-api-rs-mn7ql, payment-api-rs-k8sj2. The ReplicaSet name is the prefix, and Kubernetes appends a random suffix. You'll never know these names in advance — you don't need to.
All three started simultaneously — the Scheduler found three nodes (or scheduled multiple Pods on one node if you only have one) and the kubelet on each pulled the image and started the container at the same time.
DESIRED / CURRENT / READY on the ReplicaSet output — DESIRED is what you declared (3), CURRENT is how many Pods exist right now (3), READY is how many have passed their readiness check (3). When all three match, you're healthy.
Breaking It On Purpose — Watch Self-Healing Happen
This is the most satisfying moment in any Kubernetes demo. We're going to delete one of the Pods manually — simulating a crash — and watch the ReplicaSet create a replacement in real time.
The scenario: It's 11 PM. An engineer accidentally deletes the wrong Pod in production. Before they even have time to panic, Kubernetes has already created a fresh one. Here's what that looks like from the terminal.
# First — open a second terminal and run this to watch Pods live
kubectl get pods -w
# In your first terminal — grab the name of one of your Pods
kubectl get pods
# Delete one Pod by name (replace with your actual Pod name)
kubectl delete pod payment-api-rs-x2p9k
# Watch what happens in the second terminal...
# The deleted Pod shows Terminating, then a brand new Pod appears
NAME READY STATUS RESTARTS AGE payment-api-rs-x2p9k 1/1 Running 0 4m payment-api-rs-mn7ql 1/1 Running 0 4m payment-api-rs-k8sj2 1/1 Running 0 4m payment-api-rs-x2p9k 1/1 Terminating 0 4m12s payment-api-rs-7qr8n 0/1 Pending 0 0s payment-api-rs-7qr8n 0/1 ContainerCreating 0 0s payment-api-rs-7qr8n 1/1 Running 0 5s NAME READY STATUS RESTARTS AGE payment-api-rs-7qr8n 1/1 Running 0 5s payment-api-rs-mn7ql 1/1 Running 0 4m17s payment-api-rs-k8sj2 1/1 Running 0 4m17s
The moment payment-api-rs-x2p9k started Terminating, the ReplicaSet Controller noticed: "I need 3, I now only have 2, I need to create 1 more." A new Pod — payment-api-rs-7qr8n — appeared in Pending within milliseconds.
Total time from delete to replacement running: about 5 seconds. That's the gap users might experience if traffic isn't spread across multiple Pods and doesn't have retries. With 3 Pods and a Service in front, users feel nothing at all — requests just go to the other two while the third is replaced.
The new Pod has a completely new name and a new IP address. The ReplicaSet doesn't care — it matches by label, and this new Pod has app: payment-api stamped on it, so it gets counted.
Scaling Up and Down
The scenario: Black Friday is tomorrow. Your traffic is forecast to triple. You need to go from 3 replicas to 8 immediately. Here are two ways to do it.
# Option 1 — scale using kubectl directly (fastest, good for emergencies)
# This updates the replicas field in etcd without touching your YAML file
kubectl scale replicaset payment-api-rs --replicas=8
# Check the result
kubectl get pods
kubectl get rs
# Option 2 — edit the YAML file to change replicas: 3 to replicas: 8
# then re-apply it (better for teams — keeps the file as the source of truth)
kubectl apply -f payment-rs.yaml
# Scale back down after Black Friday
kubectl scale replicaset payment-api-rs --replicas=3
replicaset.apps/payment-api-rs scaled NAME READY STATUS RESTARTS AGE payment-api-rs-7qr8n 1/1 Running 0 6m payment-api-rs-mn7ql 1/1 Running 0 10m payment-api-rs-k8sj2 1/1 Running 0 10m payment-api-rs-p3xw2 1/1 Running 0 4s payment-api-rs-q9kd7 1/1 Running 0 4s payment-api-rs-r5nm1 1/1 Running 0 4s payment-api-rs-v2tc4 1/1 Running 0 4s payment-api-rs-yw8bj 1/1 Running 0 4s NAME DESIRED CURRENT READY AGE payment-api-rs 8 8 8 10m
Five new Pods appeared almost instantly. The ReplicaSet compared "I want 8, I have 3" and created 5 more in one go. The Scheduler placed them across available nodes simultaneously.
Important note about kubectl scale vs editing YAML: kubectl scale changes the live state in etcd but doesn't update your YAML file. If a colleague then runs kubectl apply -f payment-rs.yaml, it will revert back to 3 because the file still says 3. In teams, always update the file and apply it — so the file remains the single source of truth.
The Label Experiment — How ReplicaSets Really Count
Here's a fun experiment that proves exactly how label-based counting works. We'll create a standalone Pod with the same label as our ReplicaSet — and watch what happens.
The scenario: You're at 3 replicas. You manually create a fourth Pod with the same label. The ReplicaSet now counts 4 Pods matching its selector — but it only wants 3. So it deletes one. Let's try it.
# Make sure you're back at 3 replicas first
kubectl scale replicaset payment-api-rs --replicas=3
# Create a standalone Pod with the SAME label as the ReplicaSet selector
kubectl run intruder-pod --image=nginx:1.25 --labels="app=payment-api"
# Watch what happens — the ReplicaSet sees 4 Pods, wants 3, deletes one
kubectl get pods -w
NAME READY STATUS RESTARTS AGE payment-api-rs-7qr8n 1/1 Running 0 8m payment-api-rs-mn7ql 1/1 Running 0 12m payment-api-rs-k8sj2 1/1 Running 0 12m intruder-pod 0/1 Pending 0 0s intruder-pod 0/1 ContainerCreating 0 0s intruder-pod 1/1 Running 0 4s payment-api-rs-k8sj2 1/1 Terminating 0 12m NAME READY STATUS RESTARTS AGE payment-api-rs-7qr8n 1/1 Running 0 8m payment-api-rs-mn7ql 1/1 Running 0 12m intruder-pod 1/1 Running 0 5s
The moment intruder-pod started running with label app: payment-api, the ReplicaSet counted 4 matching Pods. It wanted 3. So it immediately deleted one of its own Pods — payment-api-rs-k8sj2 was terminated.
This is why labels matter enormously. If you accidentally give a test Pod the same label as a production ReplicaSet's selector, Kubernetes will destroy one of your production Pods immediately. This is a real mistake that has caused real incidents. Always be careful with labels on Pods you create manually.
Inspecting Your ReplicaSet
The scenario: Something feels off. Pods seem to be restarting more than expected. You want to see the full picture of your ReplicaSet — events, Pod status, selector rules.
# Full details on the ReplicaSet — shows selector, Pod template, events
kubectl describe replicaset payment-api-rs
# See which Pods belong to this ReplicaSet using label filtering
kubectl get pods -l app=payment-api
# The -l flag means "filter by label" — this is exactly how the ReplicaSet counts Pods
# Try changing the label value and see how the results change
kubectl get pods -l app=payment-api,environment=staging
Name: payment-api-rs
Namespace: default
Selector: app=payment-api
Labels: app=payment-api
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: app=payment-api
Containers:
payment-api:
Image: nginx:1.25
Port: 80/TCP
Limits:
cpu: 500m
memory: 256Mi
Requests:
cpu: 250m
memory: 128Mi
Events:
Type Reason Age Message
---- ------ ---- -------
Normal SuccessfulCreate 14m Created pod: payment-api-rs-x2p9k
Normal SuccessfulCreate 14m Created pod: payment-api-rs-mn7ql
Normal SuccessfulCreate 14m Created pod: payment-api-rs-k8sj2
Normal SuccessfulDelete 8m Deleted pod: payment-api-rs-k8sj2
Normal SuccessfulCreate 8m Created pod: payment-api-rs-7qr8n
The Events section shows the history of this ReplicaSet — every Pod it ever created, and every Pod it ever deleted. You can see the entire story of what happened during the label experiment above: it deleted k8sj2 and created 7qr8n.
kubectl get pods -l app=payment-api — the -l flag filters Pods by label, which is exactly the same logic the ReplicaSet uses internally to count. This is a powerful command for debugging any label-related issues.
An Honest Word — You Rarely Use ReplicaSets Directly
Everything you've just done with a ReplicaSet is genuinely useful to understand. But in day-to-day production work, you'll rarely create a ReplicaSet directly.
Instead you use a Deployment — which creates and manages a ReplicaSet for you automatically. The reason is simple: ReplicaSets alone have no understanding of updates. If you change the container image in your ReplicaSet YAML and re-apply it, the existing Pods don't change — they keep running the old image until they're individually deleted and replaced. That's not how you want to manage a production service.
A Deployment wraps a ReplicaSet and adds rolling updates, rollback support, and update history on top. We cover Deployments thoroughly in Lesson 10. Everything you learned here carries over completely — a Deployment is just a smarter controller on top of a ReplicaSet.
| ReplicaSet | Deployment | |
|---|---|---|
| Keeps N replicas running | ✓ Yes | ✓ Yes |
| Self-healing | ✓ Yes | ✓ Yes |
| Rolling updates | ✗ No | ✓ Yes |
| Rollback support | ✗ No | ✓ Yes |
| Use in production | Only if you have a very specific reason | Always — this is the standard |
Cleaning Up
# Delete the ReplicaSet — this also deletes all the Pods it manages
kubectl delete replicaset payment-api-rs
# Or delete using the YAML file
kubectl delete -f payment-rs.yaml
# Clean up the intruder Pod if it's still around
kubectl delete pod intruder-pod
# Verify everything is gone
kubectl get pods
kubectl get rs
replicaset.apps "payment-api-rs" deleted NAME READY STATUS RESTARTS AGE payment-api-rs-7qr8n 1/1 Terminating 0 18m payment-api-rs-mn7ql 1/1 Terminating 0 18m intruder-pod 1/1 Terminating 0 10m No resources found in default namespace.
When you delete a ReplicaSet, Kubernetes deletes all the Pods it owns at the same time — it's a cascading delete. This is different from deleting individual Pods: delete one Pod and the ReplicaSet recreates it. Delete the ReplicaSet itself and all its Pods go with it. If you ever need to delete a ReplicaSet but keep the Pods running (unusual but possible), use kubectl delete rs payment-api-rs --cascade=orphan.
When something goes wrong with a Deployment in production — Pods stuck terminating, unexpected deletions, weird rolling update behaviour — understanding what the underlying ReplicaSet is doing is the key to diagnosing it. Every Deployment creates a ReplicaSet, and when you run kubectl describe deployment or look at events, you're seeing ReplicaSet behaviour underneath.
Also — the label experiment above is genuinely something that causes production incidents. Knowing how label selectors work, and why accidentally matching labels can cause Pods to get deleted, is the kind of knowledge that separates good engineers from great ones.
replicas: 3 to replicas: 5 and re-apply with kubectl apply -f. Watch 2 new Pods appear.kubectl get pods -w. In the second, delete a Pod. Watch the replacement appear in the first terminal in real time.kubectl get pods -l app=payment-api and compare the output to kubectl get pods. Notice anything different? Try changing the label value to something that doesn't match and see what happens.Practice Questions
Type from memory — don't scroll up.
1. A ReplicaSet doesn't track its Pods by name. What does it use instead to find and count the Pods it manages?
2. Your ReplicaSet has replicas: 3. You manually delete 2 Pods. How many Pods will be running 30 seconds later?
3. ReplicaSets are great for keeping Pods running but have no support for rolling updates or rollbacks. Which object should you use instead for production deployments?
Knowledge Check
Pick the best answer.
1. One of your 3 ReplicaSet Pods crashes and exits. What does the ReplicaSet do?
2. Your ReplicaSet has replicas: 3 and selector app: payment-api. You manually create a new Pod with the label app: payment-api. What happens?
3. You run kubectl delete replicaset payment-api-rs. What happens to the 3 Pods it was managing?
Up Next · Lesson 10
Deployments
The object you'll use every single day in production. Deployments give you rolling updates, rollback in one command, and update history — all built on top of the ReplicaSet you just mastered.