Skip to main content
PProfileMatch

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

  1. Three layers: Deployment, ReplicaSet, Pod
  2. A Deployment with explicit update rules
  3. Services: a stable address for changing Pods
  4. Common mistakes and the symptoms they cause
  5. How this shows up in real projects and interviews

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.

Ownership chain during a rolling update
Deployment invoice-parser
 |
 +-- ReplicaSet <hash-a> (old)  replicas: 3 -> 0
 |     +-- Pod, Pod, Pod
 |
 +-- ReplicaSet <hash-b> (new)  replicas: 0 -> 3
       +-- Pod, Pod, Pod

A Deployment with explicit update rules

deployment.yaml
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: 5

Line 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.

Terminal
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-parser

Output

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.

service.yaml
apiVersion: v1
kind: Service
metadata:
  name: invoice-parser
spec:
  type: ClusterIP
  selector:
    app: invoice-parser
  ports:
    - name: http
      port: 80
      targetPort: 8080

port 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 typeReachable fromTypical use
ClusterIP (default)Inside the cluster onlyInternal APIs, databases, anything behind another service
NodePortEach node's IP on a port in 30000-32767 by defaultQuick tests, or as a building block for something else
LoadBalancerAn external load balancer provisioned by your cloudExposing one service directly to the internet
Headless (clusterIP: None)DNS returns Pod IPs directlyStatefulSets 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 selector does not match template labels. Fix: make matchLabels a subset of the template labels.
  • Service selector typo. Symptom: requests hang or are refused, and kubectl get endpoints invoice-parser shows <none>. Fix: compare kubectl get pods --show-labels with 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 immutable error. 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 undo returns to a template that still pulls whatever latest is 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.

Ride this lesson

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.