CoursesKubernetes fundamentalsReplicaSets: keeping copies alive

ReplicaSets: keeping copies alive

How Kubernetes keeps N running.

Beginner8 min · lesson 7 of 24
In plain terms
A ReplicaSet is a manager told “always keep three of these on shift.” One clocks out, it calls a replacement; too many showed up, it sends one home.

A busy coffee shop keeps three baristas on the floor during the lunch rush. One slips away for a break, and the shift supervisor waves in a replacement. Someone extra clocks in, and the supervisor sends them home. The floor stays at three, and nobody out front has to think about it. Kubernetes runs that same kind of supervisor for your app, and it's called a ReplicaSet. First, one word you'll need. A Pod is the smallest thing Kubernetes runs: usually a single copy of your app inside a container, which is a self-contained bundle that holds your app and everything it needs to run. A ReplicaSet has one job. Keep a set number of identical Pods alive.

Keeping the count

You hand the ReplicaSet a number. Say three. From then on it watches and counts, and it quietly fixes anything that drifts off that number. But how does it know which Pods to count as its own? By a label, which is just a small name tag you stick on things. Back in the labels lesson you tagged your Pods with something like app=hello. The ReplicaSet carries a matching note that reads 'watch everything tagged app=hello.' It counts those Pods against your target. Too few, maybe because one crashed or the machine it was running on died (that machine is called a node), and the ReplicaSet creates fresh copies. Too many, and it deletes some. This steady compare-and-fix is called the reconciliation loop, and you'll meet it all over Kubernetes. Look at what's actually running, compare it to what you asked for, then close the gap.

Here's a ReplicaSet written out in full. It's a YAML file. YAML is just a format for plain-text files that spell out what you want, using indentation (the blank space at the start of a line) to show which settings belong to which. You save this to a file on disk, then hand it to Kubernetes.

replicaset.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: hello
labels:
app: hello
spec:
replicas: 3
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: web
image: nginx:1.27

Read it from top to bottom. apiVersion and kind tell Kubernetes you want a ReplicaSet. replicas: 3 is the promise, the number you want kept alive. The selector is that 'watch everything tagged app=hello' note. The template is the cookie cutter. Every Pod the ReplicaSet stamps out gets the shape and the label described there, which is how the copies come out identical every time. The image line, nginx:1.27, names the program to run inside each Pod.

A ReplicaSet's job is narrow and ruthless: keep N pods matching its selector alive. If you delete a pod, it makes another. If you scale the ReplicaSet, it adds or removes pods. You rarely create ReplicaSets by hand because Deployments own them — but understanding ReplicaSets explains why orphans and label mistakes hurt.

The selector is a contract. Change pod labels so they no longer match and the ReplicaSet thinks a replica vanished, then starts a replacement. Prefer editing the template through a Deployment rather than hand-labeling live pods.

Suppose production traffic looks fine but pod count keeps climbing: someone may have relabeled pods out of a ReplicaSet while the controller still wants N matches. Check owners with kubectl get pods -o yaml | findstr ownerReferences.

Try this

Create a small Deployment (which owns a ReplicaSet), delete one pod, and watch the ReplicaSet replace it.

terminal
$ kubectl create deployment rsdemo --image=nginx:1.27 --replicas=2
deployment.apps/rsdemo created
$ kubectl get rs,pods -l app=rsdemo
NAME DESIRED CURRENT READY AGE
replicaset.apps/rsdemo-… 2 2 2 8s
NAME READY STATUS RESTARTS AGE
pod/rsdemo-…-aaaa 1/1 Running 0 8s
pod/rsdemo-…-bbbb 1/1 Running 0 8s
$ POD=$(kubectl get pod -l app=rsdemo -o jsonpath='{.items[0].metadata.name}')
$ kubectl delete pod $POD
pod "rsdemo-…-aaaa" deleted
$ kubectl get pods -l app=rsdemo
NAME READY STATUS RESTARTS AGE
rsdemo-…-bbbb 1/1 Running 0 40s
rsdemo-…-cccc 1/1 Running 0 5s
$ kubectl delete deployment rsdemo
deployment.apps "rsdemo" deleted

Takeaway

ReplicaSets keep N matching pods alive via selectors. Prefer Deployments to manage them, and never casually relabel live pods out of a selector unless you intend to orphan them.

The selector and template labels have to match
Look at the two spots where app: hello shows up, once under selector and once inside the template's labels. They have to be the same. If they don't match, Kubernetes rejects the file, because you'd be asking the ReplicaSet to watch for Pods it never actually creates. It's one of the most common first-day mistakes, and the error message doesn't always point straight at the cause.

Apply the file and check what you got. Here 'apply' means hand this file to Kubernetes and tell it to make reality match what the file describes. 'rs' is just short for ReplicaSet.

terminal
$ kubectl apply -f replicaset.yaml
replicaset.apps/hello created
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
hello 3 3 3 12s

DESIRED is the number you asked for. CURRENT is how many exist right now. READY is how many are fully up and able to take traffic. Right after you apply, you might catch CURRENT and READY a step behind DESIRED for a second or two while the Pods start. That short gap is normal. Starting a container takes a beat, and READY only ticks up once the Pod can actually serve. Give it a moment and all three columns settle on three.

Now break something on purpose. Delete one of the three Pods and watch what the ReplicaSet does about it. The -l flag means 'only show Pods carrying this label.'

terminal
$ kubectl get pods -l app=hello
NAME READY STATUS RESTARTS AGE
hello-4x2qd 1/1 Running 0 40s
hello-9klmn 1/1 Running 0 40s
hello-p7qrs 1/1 Running 0 40s
$ kubectl delete pod hello-4x2qd
pod "hello-4x2qd" deleted
$ kubectl get pods -l app=hello
NAME READY STATUS RESTARTS AGE
hello-9klmn 1/1 Running 0 70s
hello-p7qrs 1/1 Running 0 70s
hello-vv8dz 0/1 ContainerCreating 0 3s

You deleted hello-4x2qd, and a brand-new Pod, hello-vv8dz, is already coming up in its place. You never ran a command to recreate it. The count fell to two, the ReplicaSet noticed within a moment, and it started a replacement on its own. You could delete another, and the same thing would happen again. That's self-healing, and it's the reason a single Pod can be treated as throwaway while your app as a whole stays up.

One thing worth seeing: who actually made that replacement. The part doing the counting is the ReplicaSet controller, and it logs every move it makes. Ask it to describe itself and read the Events at the bottom.

terminal
$ kubectl describe rs hello
Name: hello
Selector: app=hello
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 90s replicaset-controller Created pod: hello-4x2qd
Normal SuccessfulCreate 90s replicaset-controller Created pod: hello-9klmn
Normal SuccessfulCreate 90s replicaset-controller Created pod: hello-p7qrs
Normal SuccessfulCreate 8s replicaset-controller Created pod: hello-vv8dz

That last line is the reconciliation loop caught in the act. Eight seconds ago, right after you deleted hello-4x2qd, the replicaset-controller created hello-vv8dz to get back to three. No magic, just a controller counting and acting.

Three Pods is not three working Pods

A ReplicaSet guards the count and only the count. It never checks whether the Pods inside are healthy. Say a typo slipped into the image line and you wrote nginx:1.99, a tag that was never published. Apply it and the ReplicaSet still does its job: three Pods. They just cannot start.

terminal
$ kubectl get pods -l app=hello
NAME READY STATUS RESTARTS AGE
hello-2mn4x 0/1 ImagePullBackOff 0 45s
hello-8xk2p 0/1 ImagePullBackOff 0 45s
hello-qz9wl 0/1 ImagePullBackOff 0 45s
$ kubectl get rs
NAME DESIRED CURRENT READY AGE
hello 3 3 0 45s

CURRENT is three, so the ReplicaSet is satisfied. But READY is zero, so not one Pod can take traffic: the count is met and your app is down. When a Pod is stuck, the first move is always the same. Describe it and read the Events, where Kubernetes records what went wrong.

terminal
$ kubectl describe pod hello-2mn4x
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 46s default-scheduler Successfully assigned default/hello-2mn4x to node-1
Normal Pulling 45s kubelet Pulling image "nginx:1.99"
Warning Failed 43s kubelet Failed to pull image "nginx:1.99": not found: manifest unknown
Warning Failed 43s kubelet Error: ErrImagePull
Normal BackOff 17s (x2 over 42s) kubelet Back-off pulling image "nginx:1.99"
Warning Failed 17s (x2 over 42s) kubelet Error: ImagePullBackOff

The Events say it plainly: it tried to pull nginx:1.99, the registry had no such tag, and it keeps backing off to retry, which is what ImagePullBackOff means. Fix the tag to nginx:1.27 and re-apply. One catch: the ReplicaSet won't replace those three broken Pods on its own, since as far as it's concerned they still count. Delete them, and it rebuilds fresh ones from the corrected template, and READY climbs to three. A ReplicaSet keeps Pods existing, not working, and that is why you watch READY, not just CURRENT.

Why you won't build one yourself

A ReplicaSet only knows how to keep the count. It has no idea how to move your app to a new version without dropping requests, and no way to roll back a change that went bad. So in real life you almost never write one by hand. You write a Deployment, which is the next lesson, and the Deployment builds and manages a ReplicaSet for you. Peek inside any running Deployment and you'll find a ReplicaSet underneath, doing the exact counting you just watched. That's what makes Deployments stop feeling like magic. A version update is really the Deployment starting a fresh ReplicaSet and shifting Pods from the old one to the new one, a few at a time. For now, the thing to hold onto is simple. That patient counting is the floor every higher-level tool stands on.

The self-healing loop
1count Pods withthe labelhow many right now?2compare to thetargetyou asked for 33create or deleteto matchclose the gap4a Pod dies, a newone appearsself-healing, on its own
A ReplicaSet keeps a set number of labelled Pods alive by counting and fixing on a loop. You'll normally use a Deployment (next), which drives a ReplicaSet and adds version control on top.
Quick check
01A ReplicaSet keeps three Pods, but a bad image tag leaves them all in ImagePullBackOff. What does kubectl get rs show, and what does it tell you?
Correct — A ReplicaSet guarantees the count, not health. CURRENT 3 means three Pods exist; READY 0 means none can take requests, so you watch READY.
Incorrect — No. It creates the Pods regardless; they exist and count as CURRENT. The image only fails once the kubelet tries to pull it.
Incorrect — No. It never lowers its target on its own. It keeps three Pods present and retries pulling the image.
Incorrect — No. Existing is not the same as ready. A Pod stuck pulling its image is CURRENT but not READY.
02The lesson says you almost never write a ReplicaSet by hand and use a Deployment instead. What can a plain ReplicaSet NOT do on its own?
Incorrect — Keeping the count steady is exactly a ReplicaSet's one job, so this isn't the missing piece.
Correct — a bare ReplicaSet has no rolling-update or rollback logic, so a Deployment wraps one to add both.
Incorrect — The template sets each new Pod's labels itself, so this isn't why you'd reach for a Deployment.
Incorrect — A ReplicaSet happily runs many Pods; the replicas number is precisely how many it keeps alive.
03You fix the image tag in a ReplicaSet's template from nginx:1.99 back to nginx:1.27 and re-apply, but the three Pods are still stuck in ImagePullBackOff. What gets them running the corrected image?
Incorrect — The broken Pods still count toward the target, so the ReplicaSet leaves them in place and won't swap them.
Incorrect — Cycling to 0 would work, but 'only supported way' is false — the lesson's direct fix is deleting the Pods.
Incorrect — Re-applying the template doesn't touch Pods that already exist and still satisfy the count.
Correct — once the old Pods are gone, the ReplicaSet stamps out new ones from the fixed template.

Related