Kubernetes Deployments, ReplicaSets and Services: Rolling Updates
DevOpsLesson 2 of 44 min read
What you'll learn
- Explain how a Deployment, a ReplicaSet and Pods relate to each other
- Write a Deployment with a rolling update strategy and roll it forward and back
- Expose a Deployment with the right type of Service
- Diagnose a Service that has no endpoints
On this page
An invoice-parsing service has to be upgraded on a Friday afternoon, and the release goes out with a typo in the image tag. The old version is still serving traffic, and the new Pods are stuck. Nobody is paged and no customer notices. That calm outcome is not luck. It is the behaviour of a Deployment doing a rolling update with sensible limits, and this lesson shows how to configure it and how Services keep clients talking to healthy Pods during the change.
Three layers: Deployment, ReplicaSet, Pod
You write a Deployment. It creates a ReplicaSet, whose only job is to keep exactly N identical Pods running. The ReplicaSet creates the Pods. Why the extra layer? Because each version of your Pod template gets its own ReplicaSet. During an update, the Deployment scales the new ReplicaSet up and the old one down. The old ReplicaSet is kept (scaled to zero), which is what makes rollback quick: scale the old one back up.
Deployment invoice-parser
|
+-- ReplicaSet <hash-a> (old) replicas: 3 -> 0
| +-- Pod, Pod, Pod
|
+-- ReplicaSet <hash-b> (new) replicas: 0 -> 3
+-- Pod, Pod, PodA Deployment with explicit update rules
apiVersion: apps/v1
kind: Deployment
metadata:
name: invoice-parser
spec:
replicas: 3
selector:
matchLabels:
app: invoice-parser
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: invoice-parser
spec:
containers:
- name: parser
image: ghcr.io/example-org/invoice-parser:1.4.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5Line by line: selector.matchLabels tells the Deployment which Pods it owns, and template.metadata.labels stamps those labels on the Pods it creates, so the two must match. maxSurge: 1 lets the rollout create one extra Pod above the desired count. maxUnavailable: 0 forbids dropping below three ready Pods, so capacity never dips during the update. The readiness probe matters here: a new Pod only counts as available once /ready succeeds, so a version that cannot start never receives traffic and never replaces a healthy one. The image name is a placeholder registry path.
kubectl apply -f deployment.yaml
kubectl rollout status deployment/invoice-parser
# Ship a new version (here with a mistyped tag)
kubectl set image deployment/invoice-parser parser=ghcr.io/example-org/invoice-parser:1.4.1x
kubectl rollout status deployment/invoice-parser --timeout=60s
# Inspect and roll back
kubectl get replicasets -l app=invoice-parser
kubectl rollout history deployment/invoice-parser
kubectl rollout undo deployment/invoice-parserOutput
deployment.apps/invoice-parser created deployment "invoice-parser" successfully rolled out Waiting for deployment "invoice-parser" rollout to finish: 1 out of 3 new replicas have been updated... error: timed out waiting for the condition NAME DESIRED CURRENT READY AGE invoice-parser-<hash-new> 1 1 0 2m invoice-parser-<hash-old> 3 3 3 1h deployment.apps/invoice-parser rolled back
This transcript is illustrative: hash suffixes, ages and the exact wording of progress lines vary by version. The key observation is that the old ReplicaSet still holds three ready Pods while the new one has a single Pod that never becomes ready. Production traffic was never affected. rollout undo returns to the previous template.
Services: a stable address for changing Pods
Pods come and go, and each new one gets a new IP address. A Service gives a set of Pods one stable virtual IP and a DNS name (invoice-parser inside the same namespace, or invoice-parser.<namespace>.svc.cluster.local across namespaces). It selects Pods by label, and only Pods that are ready are included in its endpoints, which is why readiness probes and rolling updates cooperate so well.
apiVersion: v1
kind: Service
metadata:
name: invoice-parser
spec:
type: ClusterIP
selector:
app: invoice-parser
ports:
- name: http
port: 80
targetPort: 8080port is what clients call on the Service; targetPort is where the container actually listens. They do not have to match. Other Pods in the cluster can now call http://invoice-parser/, and Kubernetes spreads those requests across the ready Pods.
| Service type | Reachable from | Typical use |
|---|---|---|
| ClusterIP (default) | Inside the cluster only | Internal APIs, databases, anything behind another service |
| NodePort | Each node's IP on a port in 30000-32767 by default | Quick tests, or as a building block for something else |
| LoadBalancer | An external load balancer provisioned by your cloud | Exposing one service directly to the internet |
| Headless (clusterIP: None) | DNS returns Pod IPs directly | StatefulSets and clients that need to address individual Pods |
Common mistakes and the symptoms they cause
- Selector does not match template labels. Symptom: apply fails with an error that
selectordoes not match templatelabels. Fix: makematchLabelsa subset of the template labels. - Service selector typo. Symptom: requests hang or are refused, and
kubectl get endpoints invoice-parsershows<none>. Fix: comparekubectl get pods --show-labelswith the Service selector. - Confusing `port` and `targetPort`. Symptom: the Service has endpoints but connections are refused, because it forwards to a port nothing listens on.
- Changing a Deployment's selector after creation. Symptom:
field is immutableerror. Fix: delete and recreate the Deployment (plan the downtime), or keep the original selector. - No readiness probe. Symptom: traffic reaches a Pod that is still starting, so users see errors for the first seconds of every rollout.
- Using `latest` as the image tag. Symptom:
rollout undoreturns to a template that still pulls whateverlatestis today, so nothing actually rolls back. Fix: use immutable, specific tags.
How this shows up in real projects and interviews
Expect "explain the relationship between a Deployment, a ReplicaSet and a Pod" and "how does a rolling update work without downtime?" The strong answer mentions maxSurge, maxUnavailable and readiness. In practice, a large share of Kubernetes incidents on a normal team are one of the three failures above: a selector mismatch, a missing endpoint, or a rollout stuck on a bad image. If you can read rollout status, get replicasets and get endpoints, you can diagnose most of them in minutes. Strategies such as blue-green and canary build on these same objects; see the CI/CD course.
Try it yourself
Deploy three replicas of any HTTP image with maxUnavailable set to 0, then expose it with a ClusterIP Service. Update the image to a tag that does not exist and watch kubectl rollout status. Confirm the Service still answers via port-forward, then roll back and note which ReplicaSet is scaled up again.
Show a hint
Run kubectl get pods -w in a second terminal to watch the new Pod stay not-ready while the old ones keep serving.
Key takeaways
- A Deployment owns ReplicaSets, and each ReplicaSet owns identical Pods; old ReplicaSets make rollback fast.
- maxSurge, maxUnavailable and readiness probes determine whether a rollout causes downtime.
- A Service gives Pods a stable IP and DNS name, selecting them by label and including only ready Pods.
- An empty endpoints list almost always means a selector or readiness problem.
- Use specific immutable image tags so rollouts and rollbacks are meaningful.
Test yourself
A trick from this lesson's ride: Deployment = manager, ReplicaSet = shift supervisor per version, Pods = riders. Four more tricks like it wait along the route, each followed by a question.
Frequently asked questions
What is the difference between a Deployment and a ReplicaSet in Kubernetes?
A ReplicaSet keeps a set number of identical Pods running. A Deployment manages ReplicaSets and adds rollout logic: it creates a new ReplicaSet for each Pod template change, scales it up while scaling the old one down, and can roll back. You normally create Deployments, not ReplicaSets.
How do I do a rolling update in Kubernetes without downtime?
Use a Deployment with the RollingUpdate strategy, set maxUnavailable to 0 or a low number, define a readiness probe, and update the image with kubectl set image or by applying a new manifest. New Pods only receive traffic once ready, and old ones are removed gradually.
What are the Kubernetes Service types?
ClusterIP exposes a Service only inside the cluster and is the default. NodePort opens a port on every node, LoadBalancer provisions an external cloud load balancer, and a headless Service (clusterIP None) returns the Pod IPs directly through DNS.