| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | kubectl 1.36.3 |
| Applies to | Any host with kubectl configured; any Kubernetes cluster |
| Cert prep | CKA · CKAD |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user (no sudo required on the workstation) |
| Scope | ReplicaSet YAML, selector and template matching, verification, self-healing, scaling, Pod selection, comparison with Deployment and ReplicationController, Deployment ownership, deletion, and common problems. Does not cover rolling-update tuning, HPA, StatefulSet, DaemonSet, Job, probes, or scheduling depth. |
| Related guides | Choose a Kubernetes workload resource Kubernetes API resources |
This walkthrough uses one ReplicaSet named web in the rs-demo namespace. You will create three nginx Pods, delete one to watch the controller replace it, scale the replica count, and see how label selectors connect the ReplicaSet to its Pods.
What Is a Kubernetes ReplicaSet?
A ReplicaSet maintains a specified number of Pods whose labels match its selector. When a matching Pod is deleted or fails, the controller creates a replacement from spec.template until the desired count is restored.
- A ReplicaSet identifies potential managed Pods through
spec.selector - Deployments normally create and manage ReplicaSets for you
- Create a ReplicaSet directly mainly for learning or specialised cases
For normal stateless applications, apply a Deployment instead. It owns ReplicaSets and adds rollout and rollback. The resource flow looks like this:
Deployment → ReplicaSet → Pods
Create and Understand a ReplicaSet
ReplicaSet manifest
Save this manifest as web-replicaset.yaml:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: web
namespace: rs-demo
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27-alpineCreate the namespace first:
kubectl create namespace rs-demoSample output:
namespace/rs-demo createdValidate the manifest against the API server:
kubectl apply --dry-run=server -f web-replicaset.yamlSample output:
replicaset.apps/web created (server dry run)Apply the ReplicaSet:
kubectl apply -f web-replicaset.yamlSample output:
replicaset.apps/web createdImportant YAML fields
| Field | Purpose |
|---|---|
spec.replicas |
Desired Pod count; defaults to one if omitted |
spec.selector |
Required, immutable selector for potential managed Pods |
spec.template |
Pod template used whenever new replicas are needed |
spec.template.spec.restartPolicy |
Must be Always or omitted, which defaults to Always |
A ReplicaSet Pod template cannot use OnFailure or Never. The lab manifest is valid because it omits the field.
Set spec.replicas to 0 to remove all managed Pods while keeping the ReplicaSet object.
Selector and template matching
These rules are strict:
spec.selector.matchLabelsmust match labels underspec.template.metadata.labels- Additional labels on the Pod template are allowed
- A mismatch causes the API server to reject the manifest
A ReplicaSet selector is immutable after creation. To use a different selector, create a new ReplicaSet. ReplicaSet selectors are required, must match the Pod-template labels, and are immutable in apps/v1.
Copy the working manifest and change only the template label to test a mismatch:
cp web-replicaset.yaml bad-replicaset.yamlEdit bad-replicaset.yaml: set metadata.name to bad-rs and change the Pod template labels:
template:
metadata:
labels:
app: frontendA server dry-run surfaces the error before you apply:
kubectl apply --dry-run=server -f bad-replicaset.yamlSample output:
The ReplicaSet "bad-rs" is invalid: spec.template.metadata.labels: Invalid value: {"app":"frontend"}: `selector` does not match template `labels`ReplicaSet also supports matchExpressions for set-based selectors. See Kubernetes labels and selectors for expression syntax.
Verify the ReplicaSet and Its Pods
Wait until three Pods are Ready before you trust the status columns:
kubectl wait --for=jsonpath='{.status.readyReplicas}'=3 replicaset/web -n rs-demo --timeout=60sSample output:
replicaset.apps/web condition metList ReplicaSets. The short resource name is rs:
kubectl get rs -n rs-demoSample output:
NAME DESIRED CURRENT READY AGE
web 3 3 3 6s| Column | Meaning |
|---|---|
DESIRED |
Desired number from spec.replicas |
CURRENT |
Most recently observed number of non-terminating Pods controlled by the ReplicaSet |
READY |
Non-terminating controlled Pods whose Ready condition is true |
AGE |
Time since the ReplicaSet object was created |
CURRENT is not simply every Pod that happens to match the selector. Ownership also matters, and Pods already controlled by another controller are not acquired.
During deletion or scale-down, terminating Pods can briefly coexist with replacements, so the total number of Pod objects may exceed spec.replicas. Kubernetes 1.36 can report these separately through .status.terminatingReplicas. That field is beta and enabled by default in current Kubernetes.
List the Pods the selector matched:
kubectl get pods -l app=web -n rs-demoSample output:
NAME READY STATUS RESTARTS AGE
web-5s9n8 1/1 Running 0 6s
web-g84pf 1/1 Running 0 6s
web-smwgk 1/1 Running 0 6skubectl describe shows the selector, template, and controller events:
kubectl describe replicaset web -n rs-demoSample Events excerpt:
Normal SuccessfulCreate 7s replicaset-controller Created pod: web-smwgk
Normal SuccessfulCreate 7s replicaset-controller Created pod: web-g84pf
Normal SuccessfulCreate 7s replicaset-controller Created pod: web-5s9n8Each Pod stores an owner reference back to the ReplicaSet:
kubectl get pods -l app=web -n rs-demo -o custom-columns='POD:.metadata.name,OWNER:.metadata.ownerReferences[0].kind,OWNER-NAME:.metadata.ownerReferences[0].name'Sample output:
POD OWNER OWNER-NAME
web-5s9n8 ReplicaSet web
web-g84pf ReplicaSet web
web-smwgk ReplicaSet webTest ReplicaSet Reconciliation and Scaling
Delete a managed Pod
Delete one managed Pod by name without hard-coding the generated suffix:
POD_NAME=$(kubectl get pod -n rs-demo -l app=web -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod "$POD_NAME" -n rs-demoSample output:
pod "web-5s9n8" deletedWatch until a new Pod with a different generated suffix reaches Running, then press Ctrl+C:
kubectl get pods -l app=web -n rs-demo --watchThe ReplicaSet creates and deletes Pods to restore its desired replica count. Generated names and suffixes vary between clusters.
Scale up and down
Scale up imperatively:
kubectl scale replicaset web --replicas=5 -n rs-demoSample output:
replicaset.apps/web scaledWait for five Ready Pods before you read 5/5:
kubectl wait --for=jsonpath='{.status.readyReplicas}'=5 replicaset/web -n rs-demo --timeout=60skubectl get rs web -n rs-demoSample output:
NAME DESIRED CURRENT READY AGE
web 5 5 5 16sYou can also change spec.replicas in web-replicaset.yaml and run kubectl apply -f web-replicaset.yaml for a declarative update.
kubectl scale changes the live ReplicaSet but does not update web-replicaset.yaml. The file still declares replicas: 3; applying it later restores that declared count. kubectl scale supports ReplicaSets and modifies their scale subresource.
Scale to zero
Set replicas to zero when you want to stop all managed Pods but keep the ReplicaSet object:
kubectl scale rs web --replicas=0 -n rs-demoSample output:
replicaset.apps/web scaledThe ReplicaSet remains; Pods terminate:
kubectl get rs web -n rs-demoSample output:
NAME DESIRED CURRENT READY AGE
web 0 0 0 18sScale back up before the next sections:
kubectl scale rs web --replicas=3 -n rs-demoSample output:
replicaset.apps/web scaledkubectl wait --for=jsonpath='{.status.readyReplicas}'=3 replicaset/web -n rs-demo --timeout=60skubectl get pods -l app=web -n rs-demoSample output:
NAME READY STATUS RESTARTS AGE
web-8bv92 1/1 Running 0 11s
web-bn59v 1/1 Running 0 11s
web-snqsd 1/1 Running 0 11sHow ReplicaSet Selection and Adoption Work
A ReplicaSet uses its selector to find potential Pods in the same namespace. It controls Pods that already reference it as controller and can adopt matching Pods that do not have another controlling owner. It does not take Pods away from another controller.
Once a matching Pod is controlled by the ReplicaSet, it is counted toward the ReplicaSet's observed replica status. ReplicaSets can acquire matching Pods without a controller owner reference, while owner references prevent controllers from interfering with dependents they do not control.
Changing a managed Pod's labels so it no longer matches the selector causes the ReplicaSet to release that Pod and create a replacement to restore the desired count. The isolated Pod continues running without that ReplicaSet as its controller until you delete it or another matching controller adopts it. This is important because isolating a Pod can temporarily leave the original Pod plus the replacement running.
Avoid overlapping selectors. Controllers do not steal Pods already owned by another controller, but matching unowned Pods can be adopted and overlapping controller instructions can behave unpredictably.
ReplicaSet and Deployment
ReplicaSet vs Deployment
| ReplicaSet | Deployment |
|---|---|
| Maintains matching Pod replicas | Manages ReplicaSets and Pods |
| Replaces deleted Pods | Replaces Pods through ReplicaSets |
| No rollout history | Supports rollout history |
| No built-in rollback workflow | Supports rollback |
| Usually managed indirectly | Preferred for stateless applications |
Changing a standalone ReplicaSet's spec.template does not replace existing Pods. Those Pods keep their original specification. Any Pod created later because of deletion or scale-up uses the new template, so one ReplicaSet can temporarily contain Pods created from different template versions. Use a Deployment for controlled replacement.
ReplicaSets maintain replica count but do not perform rolling updates directly.
For Pod versus Deployment trade-offs at the workload level, see Pod versus Deployment. For the full Deployment apply and rollout path, see Deployments and rolling updates.
How Deployments use ReplicaSets
When you apply a Deployment, Kubernetes follows this pattern:
- The Deployment creates a ReplicaSet from the Pod template
- The ReplicaSet creates the requested Pods
- A template change creates a new ReplicaSet with a new
pod-template-hash - With the default
RollingUpdatestrategy, the new ReplicaSet scales up while the previous ReplicaSet scales down - A
RecreateDeployment instead terminates the old Pods before creating Pods from the new template
Deployments support both RollingUpdate and Recreate. Older ReplicaSets may remain for rollout history.
Do not manually scale or edit a ReplicaSet owned by a Deployment. Change the Deployment instead; the Deployment controller is responsible for reconciling its ReplicaSets. A Deployment stores its revisions through the ReplicaSets it controls and uses them for rollout history and rollback.
The output below comes from a two-replica Deployment in a separate rs-demo2 namespace. Create it to compare Deployment-owned ReplicaSet names with a standalone ReplicaSet:
kubectl create namespace rs-demo2Save this manifest as web-deploy.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: rs-demo2
labels:
app: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27-alpineApply the Deployment and wait for rollout:
Label selectors used with kubectl get -l filter the metadata labels of each requested object. Pod-template labels do not automatically become Deployment metadata labels.
kubectl apply -f web-deploy.yamlSample output:
deployment.apps/web createdkubectl rollout status deployment/web -n rs-demo2Sample output:
deployment "web" successfully rolled outList what the Deployment created:
kubectl get deploy,rs,pod -n rs-demo2 -l app=webSample output:
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/web 2/2 2 2 8s
NAME DESIRED CURRENT READY AGE
replicaset.apps/web-7b8c57c6d6 2 2 2 8s
NAME READY STATUS RESTARTS AGE
pod/web-7b8c57c6d6-nnjcb 1/1 Running 0 8s
pod/web-7b8c57c6d6-rtplr 1/1 Running 0 8sGenerated hashes and suffixes vary. You edit the Deployment object; it reconciles ReplicaSets beneath it. Rollout pause, resume, and rollback live in the rolling updates guide.
ReplicaSet vs ReplicationController
| Feature | ReplicaSet | ReplicationController |
|---|---|---|
| API | apps/v1 |
Legacy core v1 |
| Equality selectors | Supported | Supported |
| Set-based selectors | Supported | Not supported |
| Recommended for new use | Yes, normally through Deployment | No |
Mention ReplicationController only when you maintain older manifests. New workloads should use ReplicaSet, almost always through a Deployment.
Delete a ReplicaSet
Delete the ReplicaSet and Pods
Foreground deletion removes managed Pods before the ReplicaSet object disappears:
kubectl delete replicaset web -n rs-demo --cascade=foregroundSample output:
replicaset.apps "web" deletedVerify both the controller and its Pods are gone:
kubectl get rs,pod -n rs-demo -l app=webSample output:
No resources found in rs-demo namespace.Recreate the ReplicaSet when you need it again:
kubectl apply -f web-replicaset.yamlkubectl wait --for=jsonpath='{.status.readyReplicas}'=3 replicaset/web -n rs-demo --timeout=60sOrphan the Pods
Before orphan deletion, wait until all three recreated Pods are Ready.
Orphan deletion removes the ReplicaSet object while leaving its Pods running:
kubectl delete replicaset web -n rs-demo --cascade=orphanSample output:
replicaset.apps "web" deletedThe Pods keep running without a controller:
kubectl get rs -n rs-demoSample output:
No resources found in rs-demo namespace.kubectl get pods -l app=web -n rs-demoSample output:
NAME READY STATUS RESTARTS AGE
web-5m494 1/1 Running 0 7s
web-tlcwr 1/1 Running 0 8s
web-wmk9g 1/1 Running 0 7sOrphan deletion leaves dependent Pods running without their ReplicaSet controller. A new ReplicaSet with the same selector can later adopt them. Use orphan deletion only when you understand that consequence.
Troubleshoot Common ReplicaSet Problems
Start with describe and Pod status:
kubectl describe replicaset <name> -n <namespace>
kubectl get pods -l app=web -n <namespace>| Symptom | Likely cause | First check |
|---|---|---|
| Apply is rejected | Selector and template labels differ, or another manifest field is invalid | Server-side dry run |
CURRENT below DESIRED |
Pod creation blocked by quota, admission, permissions, or API errors | ReplicaSet Events |
CURRENT equals DESIRED, but READY is lower |
Pods Pending, pulling images, crashing, or failing readiness | Pod status, Events, and logs |
| Extra or unexpected Pods counted | Broad selector or adoption of matching unowned Pods | Labels and owner references |
| New image appears only on replacement Pods | ReplicaSet template changed without a Deployment rollout | Compare images and creation times |
For deeper Pod phase and image-pull diagnosis, see Kubernetes Pods and Pod Lifecycle.
When to Use a ReplicaSet Directly
Create a ReplicaSet directly mainly when:
- you are learning how controllers maintain Pod replicas
- you need to understand the Deployment → ReplicaSet → Pod chain
- a rare workflow deliberately skips Deployment rollout features
For normal long-running stateless applications, apply a Deployment and let it manage ReplicaSets for you.
What's Next
- Kubernetes StatefulSet with Examples
- Differences and When to Use Each
- Kubernetes DaemonSet with Examples
References
Summary
A Kubernetes ReplicaSet keeps a desired number of Pods running whose labels match spec.selector. You declare spec.replicas, a required immutable selector, and a Pod template in one apps/v1 manifest, then verify DESIRED, CURRENT, and READY with kubectl get rs after Pods become Ready. Deleting a managed Pod triggers the controller to create a replacement with a new name and UID.
The selector must match spec.template.metadata.labels; a server dry-run catches mismatches before they reach the cluster. kubectl scale changes the live replica count but does not update your manifest file. Changing spec.template on a standalone ReplicaSet does not roll out existing Pods; use a Deployment for controlled replacement. Deployments normally own ReplicaSets and add rollout history and rollback on top of replica reconciliation.

