Every workload contains a pod template: the spec of the pods it creates. A ReplicaSet (apps/v1) makes sure exactly replicas pods matching its selector exist.
apiVersion: apps/v1
kind: ReplicaSet
metadata: { name: training }
spec:
replicas: 2
selector:
matchLabels: { app: training }
template:
metadata:
labels: { app: training } # must match the selector
spec:
containers:
- name: training
image: nginx:1.27
flowchart LR RS["ReplicaSet training<br/>replicas: 2"] --> P1["pod training-x7k2"] RS --> P2["pod training-9qzl"] P2 -. "deleted" .-> N["RS creates a new pod"]
- Delete a pod → the ReplicaSet creates a new one to stay at 2.
- A ReplicaSet manages every pod matching its selector, even pods it did not create: hand-creating a pod with
app: trainingmakes the RS delete one. - Template labels that do not match the selector → the apiserver rejects it with
selector does not match template labels. - ReplicationController (
v1) is the old version with equality selectors only; know it only to read old docs. - You rarely create ReplicaSets directly: a Deployment creates and manages them for you and adds rolling updates and rollbacks.
| Command | Meaning |
|---|---|
kubectl get rs |
DESIRED / CURRENT / READY of each ReplicaSet |
kubectl describe rs training |
Selector, template, pod create/delete events |
kubectl delete rs training |
Delete the RS and its pods |
The control loop in action
A ReplicaSet controller runs a simple loop: count pods matching the selector, compare with replicas, then create or delete pods to close the gap. You can watch it react:
kubectl get pods -l app=training -w # in one terminal
kubectl delete pod <one-pod-name> # in another
A new pod appears within seconds. The same loop explains why scaling is just changing a number: kubectl scale rs training --replicas=4.
Ownership and adoption
Each pod a ReplicaSet manages carries an ownerReferences entry pointing to it. kubectl delete rs training --cascade=orphan deletes the ReplicaSet but leaves its pods running, and a new ReplicaSet with the same selector will adopt them. This is handy for migrations but confusing when labels overlap by accident, so give each workload unique labels.
Which pods are removed on scale-down
When reducing replicas, the controller prefers to delete pods that are not yet running or ready, then pods on nodes with more replicas, then the youngest pods. You can influence the choice with the controller.kubernetes.io/pod-deletion-cost annotation on a pod.
Set-based selectors
ReplicaSets support matchExpressions as well as matchLabels:
selector:
matchExpressions:
- { key: app, operator: In, values: [training, training-canary] }
- { key: tier, operator: NotIn, values: [debug] }
Operators include In, NotIn, Exists, and DoesNotExist.
Why Deployments wrap ReplicaSets
Changing a ReplicaSet's pod template does not update existing pods; only new pods use the new template. To roll out a new image you would have to delete pods by hand. A Deployment solves this by creating a new ReplicaSet for each template change and shifting replicas between old and new, which is why you work with Deployments day to day and read ReplicaSets mainly while debugging.