Sign in
← Kubernetes: workloads
6 min · easy · Lesson 2 of 11

Deployment: a controller on top of ReplicaSet

How Deployment manages a ReplicaSet, what the READY and UP-TO-DATE columns mean, and which fields real manifests set.

How Deployment manages a ReplicaSet, what the READY, UP-TO-DATE, and AVAILABLE columns mean, and which fields show up in real manifests.

Layer on top of layer

Deployment strategy and revisions creates ReplicaSet web-7d8c9f4b5 creates Pod ×3 image, ports image change ReplicaSet web-59c8d6f7a Pod ×3 new image
Each layer creates the next one. An image change adds a second ReplicaSet next to the old one.

In Kubernetes, controllers nest like matryoshka dolls. A Pod is one copy of an app. A ReplicaSet keeps N such copies running. A Deployment manages a ReplicaSet and adds one more thing on top: updating versions without downtime.

When you run kubectl apply -f deploy.yaml, here's what happens:

  1. A Deployment object gets created.
  2. The Deployment controller creates a ReplicaSet with the needed template.
  3. The ReplicaSet controller creates Pods from the template.

The image in the Deployment changes, and the controller creates a new ReplicaSet while gradually winding down the old one. The old one's replica count drifts to 0, the new one's grows to N. This is a rolling update, and it gets its own lesson.

Minimal manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
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

Compared to a ReplicaSet, the top-level fields are almost identical. The same apps/v1, the same selector.matchLabels, the same template. The difference is kind: Deployment and a few optional fields that a ReplicaSet doesn't have.

The rules for the selector are the same as for a ReplicaSet. The template's labels must match the selector, otherwise apply fails:

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

You can't change a created Deployment's selector, the API answers with field is immutable. You need a different selector, which means you need a new Deployment.

spec.strategy

How to roll out a new version. The default is RollingUpdate (gradual), there's also Recreate (kill everything, then bring it all back up). For RollingUpdate you tune two parameters:

At the start you don't need to write these fields, the defaults are reasonable.

spec.revisionHistoryLimit

How many old ReplicaSets to keep for rollback. 10 by default. Each old ReplicaSet hangs around with replicas: 0, but it remembers the template from that release. kubectl rollout undo uses exactly these: on rollback, Deployment doesn't create a new ReplicaSet, it spins the old one back up.

spec.progressDeadlineSeconds

How many seconds to wait for an update to make progress before declaring it stuck. 600 by default, that is, 10 minutes. More on this in the "Conditions" section.

What kubectl shows

kubectl get deploy web
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/3     3            3           6s
ColumnWhat it countsLess than replicas when
READYPods that passed readinessPods are still starting or can't start
UP-TO-DATEPods on the current templatea rolling update is in progress or stuck
AVAILABLEReady longer than minReadySecondssame as READY, plus Pods ready for less than minReadySeconds

Next, look at the ReplicaSet the Deployment created:

kubectl get rs -l app=web
NAME             DESIRED   CURRENT   READY   AGE
web-854cf6f87f   3         3         3       6s

The ReplicaSet's name is built like this: <Deployment name>-<template hash>. The controller puts the same hash on the ReplicaSet and its Pods as the pod-template-hash label, and the Pod's name also gets a random suffix: web-854cf6f87f-8j74h. The hash changes every time the template changes (image, env, anything). This is how Deployment knows which ReplicaSet is the new one and which is old.

You don't scale a ReplicaSet that a Deployment owns by hand. If you run kubectl scale rs web-854cf6f87f --replicas=1, Deployment almost immediately returns it to the previous number: it reconciles the ReplicaSet against its own spec.replicas. Delete the Deployment, and its ReplicaSet and Pods get deleted with it.

Conditions: what Deployment itself says

A Deployment's status has a conditions array. The two main conditions:

The easiest way to see them is kubectl describe:

kubectl describe deploy web | grep -A4 Conditions
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable

If the new ReplicaSet gets stuck (for example, the image can't be pulled), after progressDeadlineSeconds the Progressing condition flips to False with the reason ProgressDeadlineExceeded. The old Pods keep working meanwhile, so Available stays True. Here's what a Deployment with 5 replicas looks like after being given an image that doesn't exist:

NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    4/5     3            4           108s
  Available      True    MinimumReplicasAvailable
  Progressing    False   ProgressDeadlineExceeded

Three new Pods sit in ErrImagePull or ImagePullBackOff, four old ones serve traffic. Kubernetes itself doesn't roll back an update like this, it only reports the problem. kubectl rollout status exits with an error in this case:

error: deployment "web" exceeded its progress deadline

For the update to move forward, the Deployment's template needs to change again: fix the error or roll back to a previous revision.

kubectl scale and kubectl rollout

The most common actions on a Deployment:

kubectl scale deploy web --replicas=5

Changes the replica count on the fly.

kubectl rollout status deploy web

Waits for the current update to finish and shows how it's going.

kubectl rollout restart deploy web

Restarts all the Pods. The command puts the kubectl.kubernetes.io/restartedAt annotation with the current time into the template. The template changed, so a new ReplicaSet appears, and the Pods get replaced following the rolling update rules.

kubectl rollout undo deploy web

Rolls back to the previous revision. kubectl rollout history deploy web shows the list of revisions.

kubectl scale changes the Deployment's spec.replicas. The Deployment passes the number to the current ReplicaSet, which adds or removes Pods. The template doesn't change here, so no new ReplicaSet appears. If you later apply an old file with replicas: 3, the number goes back to three.

What a real manifest sets at minimum

Real manifests almost always have these fields on a Deployment:

A ReplicaSet almost never gets written directly. You write a Deployment.

Common mistakes

What you'll need for the tasks

The tasks live in the labs/ folder: each lab brings up its own kind cluster on your machine, 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-deployment-basics     # set up the starting state
./run.sh verify k8s-deployment-basics    # 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/deployment