ReplicaSets
Keeping N identical pods alive with a selector.
Delete a running Pod and it comes back within seconds. Kubernetes wasn't watching that one specific Pod, waiting to resurrect it. Something counted the Pods, noticed the total had dropped below what you asked for, and stamped out a fresh one to fill the gap. That something is usually a ReplicaSet. A Pod is Kubernetes' smallest unit of work, one or more containers that start and stop together, and a ReplicaSet is a small controller with exactly one job: keep N copies of a Pod running, no more and no fewer.
A ReplicaSet has no list of which Pods belong to it. It goes by a badge instead: Pods carrying the right label count toward the total, and Pods without it are none of its business. That badge check is called the selector, and it is the machinery underneath everything else in this lesson. You'll rarely create a ReplicaSet by hand, because a Deployment builds them for you, but this is the object where the 'keep N alive' guarantee physically lives. Worth understanding on its own.
The loop, and the three fields that feed it
A ReplicaSet is defined by three fields. replicas is the number of Pods you want. template is the shape of each Pod it should create, the same spec you'd write for a standalone Pod. selector is a label query, a filter like app=web, that tells the ReplicaSet which Pods in the namespace count as its own. Behind these three fields runs a loop that never really stops, and it behaves like a thermostat. A thermostat reads the room temperature, compares it to the number you dialed in, switches the heat on or off, then reads again a moment later. The ReplicaSet controller reads how many Pods currently match its selector, compares that to replicas, creates or deletes Pods to close the gap, then reads again. That loop is why a crashed Pod comes back on its own. The live count fell below desired, so the next pass builds a replacement. Nobody wrote a recovery script. Self-healing falls out of the counting.
That loop lives inside kube-controller-manager, the control-plane process that runs most of Kubernetes' built-in controllers. Think of the control plane as the cluster's brain, the part that makes decisions rather than doing the work. So how does the loop notice a dead Pod so fast? Not by asking over and over. It works like a mailroom that gets a buzz the moment a package lands, instead of walking down to the loading dock every minute to check. The controller keeps an open subscription to Pod and ReplicaSet changes through the API server (the one component everything in the cluster talks to), so a Pod deletion pings the loop almost the instant it happens. That's why the replacement for a deleted or crashed Pod feels immediate instead of showing up on some timer. A failed node is the one case that really is on a timer. The control plane waits the better part of a minute of silence before it marks the node NotReady, and the default not-ready and unreachable tolerations then hold that node's Pods in place for a further five minutes before they are evicted. Only at that point does the matching count drop and the ReplicaSet build replacements on other nodes, so Pods sitting on a dead node for five-plus minutes is normal, not a broken cluster.
apiVersion: apps/v1kind: ReplicaSetmetadata:name: webspec:replicas: 3selector:matchLabels:app: webtemplate:metadata:labels:app: web # MUST match the selector abovespec:containers:- name: appimage: nginx:1.27ports:- containerPort: 80
kubectl apply -f web-rs.yamlkubectl get rs web
replicaset.apps/web createdNAME DESIRED CURRENT READY AGEweb 3 3 3 12s
DESIRED is your replicas, the target. CURRENT is how many the controller has actually created. READY is how many of those pass their health checks and can serve traffic. When all three line up, the loop is at rest and does nothing. So break it on purpose and watch it heal.
kubectl get pods -l app=webkubectl delete pod web-7v2kd # use a name from the list abovekubectl get pods -l app=web
NAME READY STATUS RESTARTS AGEweb-7v2kd 1/1 Running 0 40sweb-9xlpm 1/1 Running 0 40sweb-q4t8n 1/1 Running 0 40spod "web-7v2kd" deletedNAME READY STATUS RESTARTS AGEweb-9xlpm 1/1 Running 0 55sweb-q4t8n 1/1 Running 0 55sweb-pm6rd 0/1 ContainerCreating 0 2s
The deleted Pod is gone, and a brand new one, web-pm6rd, is already on its way up. It carries a different random suffix because it's a genuinely new Pod, not the old one revived. It appeared the instant the matching count dropped to two.
The selector is an ownership contract
The selector does more than filter what you see. It draws the ownership boundary. A ReplicaSet owns exactly the Pods whose labels match its selector, and Kubernetes records that ownership by stamping every Pod the ReplicaSet creates with an ownerReference pointing back at it. Two behaviors fall out of this, and both catch people off guard. First, adoption: if a loose Pod with matching labels already exists, the ReplicaSet counts it toward replicas instead of making a new one. Second, orphaning: relabel a running Pod so it no longer matches, and the ReplicaSet loses sight of it, its owned count drops, and it builds a replacement while the relabeled Pod keeps running with nothing managing it. You can read that ownership link straight off any Pod.
kubectl get pod web-9xlpm -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'
ReplicaSet/web
Why you almost never write one by hand
A ReplicaSet keeps Pods alive and does nothing else. It has no concept of a version. Change the image in its template and the running Pods don't budge. Only Pods created after the edit get the new image, so rolling out an update would mean deleting Pods by hand to force fresh ones. That gap is exactly what a Deployment fills. A Deployment is a higher-level object that creates and steers ReplicaSets for you, adding rolling updates and one-command rollback (that's the next lesson). Look under any Deployment and you'll find a ReplicaSet with a hash in its name.
kubectl get rs -l app=web
NAME DESIRED CURRENT READY AGEweb-5c9f7b8d64 3 3 3 6mweb-7d4f9c6b55 0 0 0 21m
The suffix like 5c9f7b8d64 is a pod-template-hash, a fingerprint the Deployment adds so each version of the template gets its own ReplicaSet. The one parked at zero is the previous version, kept around empty so a rollback is instant. During a live rollout you'd catch both moving at once, the new one climbing toward three and the old one draining toward zero.
kubectl scale rs/web-5c9f7b8d64 --replicas=5 on it and the change works for a few seconds, then snaps back to three. The Deployment runs its own reconcile loop, it sees a ReplicaSet that disagrees with its spec, and it corrects the ReplicaSet right back. People burn real time here, staring at a change that 'won't take.' Make the change on the Deployment instead (kubectl scale deployment/web --replicas=5) and it flows down to the ReplicaSet and stays.If the labels in template don't match spec.selector, you never get as far as stray Pods: the API server rejects the object on apply, with selector does not match template labels. Orphaning is a separate thing, and it comes from relabeling a Pod that is already running.
Scaling a ReplicaSet is blunt. Prefer Deployment for user-facing apps so revisions stay tracked.
The hash in a Deployment-managed ReplicaSet name is how rollouts keep old and new sets distinct.
Try this
Apply a ReplicaSet with three replicas, delete one Pod, and confirm the count comes back. Then read the ownerReference off a surviving Pod to see what is holding that count at three. Pod name suffixes are random, so these commands read a name out of the cluster instead of hardcoding one.
kubectl apply -f web-rs.yamlkubectl get rs web# grab whichever Pod name your cluster generated, then delete that onePOD=$(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')kubectl delete pod "$POD"kubectl get pods -l app=web# ask a Pod that is still there who owns itPOD=$(kubectl get pods -l app=web -o jsonpath='{.items[0].metadata.name}')kubectl get pod "$POD" -o jsonpath='{.metadata.ownerReferences[0].kind}/{.metadata.ownerReferences[0].name}{"\n"}'kubectl get rs -l app=web
Takeaway
A ReplicaSet keeps N identical pods alive by label selector. Deployments own ReplicaSets so you get rollouts; raw ReplicaSets are the mechanism underneath.
app label on one running Pod from web to web-debug so you can poke at it in isolation. The ReplicaSet still has replicas: 3. What happens?