Sign in
← Kubernetes: workloads
5 min · easy · Lesson 1 of 11

ReplicaSet: keeping N replicas

The controller that restores the right number of Pod copies on its own, and why it's almost never written by hand.

A controller that restores the needed number of Pod copies on its own, and why it's almost never written by hand.

Why you need a ReplicaSet

A Pod is the smallest unit Kubernetes runs, one copy of an application. If the node goes down or the process is killed by the OOM killer (a Linux kernel mechanism that stops processes when memory runs low), a Pod without a controller simply stops existing. Nobody brings it back. That's fine for a one-off task, but a web service needs something that keeps the right number of copies running.

A ReplicaSet is a controller that keeps exactly N copies of a Pod running. A controller here is a process that constantly checks reality against the plan. If there are fewer Pods than needed, the ReplicaSet creates new ones from the template. If there are more, it deletes the extra ones. It doesn't matter who deleted a Pod: a person by hand or a node that went down. The ReplicaSet brings up a replacement right away.

Manifest fields

ReplicaSet · spec.selector matchLabels: app: web template · metadata.labels labels: app: web matching lines
The controller looks for its Pods by the line from selector, and the template has to match it.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: web-rs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27.3-alpine
          ports:
            - containerPort: 80

apiVersion: apps/v1: a ReplicaSet lives in the apps group, not in core. If you write plain v1, kubectl answers like this:

error: resource mapping not found for name: "web-rs" namespace: "" from "web-rs.yaml": no matches for kind "ReplicaSet" in version "v1"
ensure CRDs are installed first

The CRD hint here is a red herring: the real issue is apiVersion. The same applies to Deployment and StatefulSet.

spec.replicas: how many copies to keep. You can set it to 0: then the controller removes all the Pods, but the object itself stays. Handy for a temporary shutdown.

spec.selector: how the controller finds its Pods. matchLabels is a set of key-value pairs. All conditions are combined with AND. A label is a key-value tag on a resource. The entry app: web means "treat every Pod with the label app=web in this namespace as mine".

The selector must match the labels in template.metadata.labels. If the selector has app: web and the template has app: webapp, apply fails:

The ReplicaSet "web-rs" is invalid: spec.template.metadata.labels: Invalid value: {"app":"webapp"}: `selector` does not match template `labels`

The controller creates Pods from the template, then looks for them by the selector and has to find them. You can't change the selector of an existing ReplicaSet: the API answers such an edit with field is immutable. You need a different selector, which means you need a new ReplicaSet.

spec.template: the Pod template the controller uses to create copies. Inside is an ordinary Pod spec, without apiVersion and kind. The controller comes up with the Pod name itself: the ReplicaSet's name plus a random suffix, for example web-rs-jmtmd.

Whose Pods are these

Every Pod a ReplicaSet manages carries a reference to its owner in the metadata.ownerReferences field:

kubectl get pod web-rs-jmtmd -o jsonpath='{.metadata.ownerReferences}'
[{"apiVersion":"apps/v1","blockOwnerDeletion":true,"controller":true,"kind":"ReplicaSet","name":"web-rs","uid":"7ed57918-..."}]

This reference shows who's responsible for the Pod. Delete the ReplicaSet with kubectl delete rs web-rs, and its Pods get deleted along with it.

There's a subtlety. A ReplicaSet treats every Pod with matching labels as its own, even one it didn't create. The controller adopts such an unowned Pod: it sets its ownerReferences and counts it toward spec.replicas.

If a Pod labeled app: web was already running before the ReplicaSet appeared, the controller adopts it and creates one fewer copy than planned. If such a Pod shows up later, there will be more replicas than planned, and the controller deletes the extra one. It doesn't delete a random one: first Pods that aren't scheduled or ready yet, then the newest ones. That's why the Pod that disappears is usually the one you just created by hand.

A ReplicaSet owns any Pod with a matching label, not only the ones it created itself. An outside Pod labeled app: web either takes the place of one of the replicas or gets deleted as an extra.

What kubectl shows you

kubectl get rs web-rs
NAME     DESIRED   CURRENT   READY   AGE
web-rs   3         3         3       12s

If the three numbers match, the ReplicaSet is meeting the plan. If READY is less than DESIRED, look for the cause with kubectl describe rs web-rs and kubectl get pod. Keep in mind the numbers count every Pod under the selector, including outside ones the controller adopted.

At the end of kubectl describe rs web-rs's output, the Events block shows what the controller has been doing:

Events:
  Type    Reason            Age   From                   Message
  ----    ------            ----  ----                   -------
  Normal  SuccessfulCreate  11s   replicaset-controller  Created pod: web-rs-rdhzf
  Normal  SuccessfulCreate  11s   replicaset-controller  Created pod: web-rs-6l4dr
  Normal  SuccessfulCreate  11s   replicaset-controller  Created pod: web-rs-jmtmd
  Normal  SuccessfulCreate  8s    replicaset-controller  Created pod: web-rs-87zss

What self-healing looks like

Delete one Pod by hand and watch the list right away:

kubectl get pod -l app=web
kubectl delete pod web-rs-6l4dr
kubectl get pod -l app=web -w

Use a name from your own output instead of web-rs-6l4dr. In a couple of seconds, a new Pod shows up with a different random suffix in its name. That's the ReplicaSet at work. -w keeps the output open, exit with Ctrl+C.

You can change the replica count on the fly, without editing YAML:

kubectl scale rs/web-rs --replicas=5

If you then apply the old file with replicas: 3, the count goes back to three: kubectl apply brings the object in line with what's written in the file.

ReplicaSet and Deployment

In practice, a ReplicaSet is rarely written into manifests directly. On top of it sits a Deployment: a controller that can update the image version without downtime (a rolling update) and roll it back. But a Deployment creates a ReplicaSet under the hood anyway, so you need to understand its fields.

Common mistakes

What you'll need for the tasks

The tasks are in the labs/ folder: each lab brings up its own kind cluster on your computer, instructions are in its README.md.

Homework

Practice the theory on your own cluster: 3 steps with a check. Set up the cluster with the Kubernetes locally track, the tasks live in the k8s-practice repository.

./run.sh start k8s-replicaset     # set up the starting state
./run.sh verify k8s-replicaset    # check the current step

In short

Done reading?

Mark the lesson as read to move your track progress forward.

Go through it with an AI agent

The agent explains anything unclear in plain words, gives you 2 labs for this lesson and drills you on interview questions. $49 one-time, lifetime access.

Get access for $49

Tell the agent: use MCP izi, give me lesson k8s-workloads/replicaset