ReplicaSets: keeping copies alive
How Kubernetes keeps N running.
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.
apiVersion: apps/v1kind: ReplicaSetmetadata:name: hellolabels:app: hellospec:replicas: 3selector:matchLabels:app: hellotemplate:metadata:labels:app: hellospec:containers:- name: webimage: 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.
$ kubectl create deployment rsdemo --image=nginx:1.27 --replicas=2deployment.apps/rsdemo created$ kubectl get rs,pods -l app=rsdemoNAME DESIRED CURRENT READY AGEreplicaset.apps/rsdemo-… 2 2 2 8sNAME READY STATUS RESTARTS AGEpod/rsdemo-…-aaaa 1/1 Running 0 8spod/rsdemo-…-bbbb 1/1 Running 0 8s$ POD=$(kubectl get pod -l app=rsdemo -o jsonpath='{.items[0].metadata.name}')$ kubectl delete pod $PODpod "rsdemo-…-aaaa" deleted$ kubectl get pods -l app=rsdemoNAME READY STATUS RESTARTS AGErsdemo-…-bbbb 1/1 Running 0 40srsdemo-…-cccc 1/1 Running 0 5s$ kubectl delete deployment rsdemodeployment.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.
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.
$ kubectl apply -f replicaset.yamlreplicaset.apps/hello created$ kubectl get rsNAME DESIRED CURRENT READY AGEhello 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.'
$ kubectl get pods -l app=helloNAME READY STATUS RESTARTS AGEhello-4x2qd 1/1 Running 0 40shello-9klmn 1/1 Running 0 40shello-p7qrs 1/1 Running 0 40s$ kubectl delete pod hello-4x2qdpod "hello-4x2qd" deleted$ kubectl get pods -l app=helloNAME READY STATUS RESTARTS AGEhello-9klmn 1/1 Running 0 70shello-p7qrs 1/1 Running 0 70shello-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.
$ kubectl describe rs helloName: helloSelector: app=helloReplicas: 3 current / 3 desiredPods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed...Events:Type Reason Age From Message---- ------ ---- ---- -------Normal SuccessfulCreate 90s replicaset-controller Created pod: hello-4x2qdNormal SuccessfulCreate 90s replicaset-controller Created pod: hello-9klmnNormal SuccessfulCreate 90s replicaset-controller Created pod: hello-p7qrsNormal 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.
$ kubectl get pods -l app=helloNAME READY STATUS RESTARTS AGEhello-2mn4x 0/1 ImagePullBackOff 0 45shello-8xk2p 0/1 ImagePullBackOff 0 45shello-qz9wl 0/1 ImagePullBackOff 0 45s$ kubectl get rsNAME DESIRED CURRENT READY AGEhello 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.
$ kubectl describe pod hello-2mn4x...Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Scheduled 46s default-scheduler Successfully assigned default/hello-2mn4x to node-1Normal Pulling 45s kubelet Pulling image "nginx:1.99"Warning Failed 43s kubelet Failed to pull image "nginx:1.99": not found: manifest unknownWarning Failed 43s kubelet Error: ErrImagePullNormal 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.