Deployments — Managing Replicated Pods¶
Overview¶
A single Pod is ephemeral — delete it and it is gone. Production workloads need replicated, self-healing, and updatable application tiers. Deployments are the standard Kubernetes controller for stateless applications. They declare how many Pod replicas you want, continuously reconcile actual state to desired state, and orchestrate rolling updates and rollbacks without downtime.
When an SRE scales an API from 3 to 30 replicas during a traffic spike, or a DevOps engineer ships version 2.1.0 with a controlled rollout, they use Deployments. When a bad image tag takes down every replica, kubectl rollout undo restores the previous ReplicaSet in seconds.
This is Tutorial 6 in Module 2: Workloads of the REBASH Academy Kubernetes series. Complete Pods — The Atomic Unit and kubectl Essentials and Workflows first. This tutorial follows our documentation standards.
Prerequisites¶
- Completed Installing Kubernetes and kubectl — a working cluster (minikube, kind, k3s, or cloud)
- Completed kubectl Essentials and Workflows — apply, get, describe, logs, exec
- Completed Pods — The Atomic Unit — Pod spec fields, labels, selectors, restart policies
- Familiarity with From Docker to Kubernetes — Docker Compose services map to Deployments
- Optional: Linux Foundations for reading YAML and shell scripting
Learning Objectives¶
By the end of this tutorial, you will be able to:
- Explain the relationship between Deployments, ReplicaSets, and Pods
- Create and scale Deployments declaratively with YAML and imperatively with kubectl
- Perform rolling updates and understand
maxSurgeandmaxUnavailable - Roll back a failed deployment to a previous revision
- Inspect rollout history and diagnose stuck rollouts
- Apply production patterns: resource requests/limits, probes, and label selectors
- Know when to use Deployments vs StatefulSets, DaemonSets, or bare Pods
Architecture Diagram¶
Deployments own ReplicaSets; ReplicaSets own Pods. Each rollout creates a new ReplicaSet while phasing out the old one.
flowchart TB
subgraph Control["Control Plane"]
DEP["Deployment<br/>replicas: 3"]
RS1["ReplicaSet v1<br/>desired: 0"]
RS2["ReplicaSet v2<br/>desired: 3"]
end
subgraph Data["Worker Nodes"]
P1["Pod v2"]
P2["Pod v2"]
P3["Pod v2"]
end
DEP --> RS1
DEP --> RS2
RS2 --> P1
RS2 --> P2
RS2 --> P3
RS1 -.->|"scaled to 0<br/>after rollout"| DEP```
## Theory
### Why Deployments Exist
Bare Pods have no built-in replication or update strategy. If a node fails, Kubernetes does not recreate a standalone Pod unless a controller manages it. **ReplicaSets** ensure a specified number of Pod replicas exist and match label selectors. **Deployments** wrap ReplicaSets with declarative updates, revision history, and rollback.
| Object | Responsibility |
|--------|----------------|
| **Pod** | Run one or more containers; smallest schedulable unit |
| **ReplicaSet** | Maintain N identical Pods via label selector |
| **Deployment** | Manage ReplicaSets; rolling updates, rollbacks, scaling |
### Deployment Spec Essentials
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "64Mi"
limits:
cpu: "250m"
memory: "128Mi"
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 3
periodSeconds: 5 | Field | Purpose |
|---|---|
replicas | Desired Pod count |
selector.matchLabels | Must match Pod template labels — immutable after creation |
strategy.type | RollingUpdate (default) or Recreate |
maxSurge | Extra Pods above desired count during rollout |
maxUnavailable | Pods that can be unavailable during rollout |
template | Pod spec for every replica |
Selector immutability
You cannot change a Deployment's selector after creation. Plan labels (app, version, tier) before the first apply.
Rolling Update Mechanics¶
When you change the Pod template (typically the container image), the Deployment:
- Creates a new ReplicaSet with the updated template
- Scales up the new ReplicaSet according to
maxSurge - Scales down the old ReplicaSet according to
maxUnavailable - Repeats until all Pods run the new version
With replicas: 3, maxSurge: 1, and maxUnavailable: 0, Kubernetes never drops below 3 ready Pods during the rollout — ideal for production.
Recreate strategy kills all old Pods before starting new ones. Use only when the app cannot run two versions simultaneously (rare for stateless services).
Rollback and Revision History¶
Each template change creates a new ReplicaSet. Old ReplicaSets are retained (scaled to zero) for rollback:
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
kubectl rollout undo deployment/web --to-revision=2
By default, Kubernetes keeps 10 old ReplicaSets. Tune with revisionHistoryLimit.
Scaling¶
Horizontal scaling changes replicas:
kubectl scale deployment/web --replicas=5
kubectl autoscale deployment web --cpu-percent=70 --min=2 --max=10 # requires metrics-server
For production autoscaling, use a HorizontalPodAutoscaler (HPA) — covered in Production Patterns.
Deployment vs Other Controllers¶
| Controller | Use case |
|---|---|
| Deployment | Stateless apps, web APIs, workers |
| StatefulSet | Stable network identity, ordered rollout, persistent storage per Pod |
| DaemonSet | One Pod per node — log agents, CNI plugins |
| Job / CronJob | Run-to-completion batch work |
Labels and Selectors¶
Services, NetworkPolicies, and HPAs route traffic or apply rules using labels. Standard pattern:
The Deployment selector must be a subset of template labels. Avoid putting unique values (like Pod name) in the selector.
Hands-on Lab¶
These labs use a local cluster. Adjust namespace flags if your environment differs.
Step 1 – Create a Deployment¶
Command:
kubectl create namespace lab-deployments
kubectl create deployment web \
--image=nginx:1.25-alpine \
--replicas=3 \
-n lab-deployments
kubectl get deployment,rs,pods -n lab-deployments -o wide
Explanation: kubectl create deployment generates a Deployment with default rolling update strategy. Observe one ReplicaSet and three Pods.
Expected output:
NAME READY UP-TO-DATE AVAILABLE AGE
web 3/3 3 3 30s
NAME DESIRED CURRENT READY AGE
web-7d4b8c9f6d 3 3 3 30s
NAME READY STATUS RESTARTS AGE
web-7d4b8c9f6d-xxxxx 1/1 Running 0 25s
...
Step 2 – Apply a declarative manifest¶
Command:
Save as web-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: lab-deployments
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25-alpine
ports:
- containerPort: 80
readinessProbe:
httpGet:
path: /
port: 80
periodSeconds: 5
Explanation: Declarative manifests are the production standard — version-controlled, reviewable, and GitOps-ready.
Step 3 – Perform a rolling update¶
Command:
kubectl set image deployment/web nginx=nginx:1.26-alpine -n lab-deployments
kubectl rollout status deployment/web -n lab-deployments
kubectl get rs -n lab-deployments
Explanation: Image changes trigger a rolling update. Two ReplicaSets appear briefly — old scaling down, new scaling up.
Expected output:
deployment "web" successfully rolled out
NAME DESIRED CURRENT READY AGE
web-7d4b8c9f6d 0 0 0 5m
web-9a2f1e8b7c 3 3 3 1m
Step 4 – Roll back a bad release¶
Command:
# Simulate a bad image
kubectl set image deployment/web nginx=nginx:does-not-exist -n lab-deployments
kubectl rollout status deployment/web -n lab-deployments --timeout=60s || true
kubectl get pods -n lab-deployments
kubectl rollout undo deployment/web -n lab-deployments
kubectl rollout status deployment/web -n lab-deployments
Explanation: Failed rollouts leave Pods in ImagePullBackOff or CrashLoopBackOff. rollout undo reverts to the previous ReplicaSet.
Step 5 – Scale and inspect history¶
Command:
kubectl scale deployment/web --replicas=5 -n lab-deployments
kubectl rollout history deployment/web -n lab-deployments
kubectl rollout history deployment/web -n lab-deployments --revision=2
Explanation: History records each template change. Use --revision to inspect the manifest diff before undoing to a specific version.
Step 6 – Pause and resume rollouts¶
Command:
kubectl rollout pause deployment/web -n lab-deployments
kubectl set image deployment/web nginx=nginx:1.27-alpine -n lab-deployments
kubectl get rs -n lab-deployments
kubectl rollout resume deployment/web -n lab-deployments
kubectl rollout status deployment/web -n lab-deployments
Explanation: Pausing lets you apply multiple template changes before a single coordinated rollout — useful for complex updates.
Step 7 – Clean up¶
Command:
Commands & Code¶
| Command | Description | Example |
|---|---|---|
kubectl create deployment | Imperative Deployment creation | kubectl create deployment web --image=nginx --replicas=3 |
kubectl scale | Change replica count | kubectl scale deployment/web --replicas=5 |
kubectl set image | Update container image | kubectl set image deployment/web nginx=nginx:1.26 |
kubectl rollout status | Watch rollout progress | kubectl rollout status deployment/web |
kubectl rollout history | List revision history | kubectl rollout history deployment/web |
kubectl rollout undo | Roll back to previous revision | kubectl rollout undo deployment/web --to-revision=2 |
kubectl rollout pause/resume | Control rollout timing | kubectl rollout pause deployment/web |
Production-ready Deployment template¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
labels:
app: api
spec:
replicas: 3
revisionHistoryLimit: 5
selector:
matchLabels:
app: api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: registry.example.com/api:1.4.2
ports:
- containerPort: 8080
resources:
requests:
cpu: "200m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
Common Mistakes¶
Deploying bare Pods in production
Standalone Pods are not rescheduled with the same name, have no replication, and no rollout strategy. Always use a Deployment (or appropriate controller) for long-running stateless apps.
Changing selector labels after creation
Deployment selectors are immutable. Changing matchLabels requires deleting and recreating the Deployment — causing downtime. Plan label keys before first deploy.
Setting maxUnavailable too high on small replica counts
With replicas: 2 and maxUnavailable: 50%, one Pod can be down during rollout — cutting capacity in half. For low replica counts, set maxUnavailable: 0 and maxSurge: 1.
Skipping readiness probes during rollouts
Without readiness probes, Kubernetes marks Pods ready as soon as the container starts — sending traffic before the app accepts connections. Always define readiness for services fronting Deployments.
Best Practices¶
Use declarative YAML in Git
Store Deployment manifests in version control. Pair with CI/CD or GitOps (Argo CD, Flux) so every production change is auditable.
Pin image tags, not :latest
Use semantic version tags or digest pins (image@sha256:...). :latest makes rollbacks and debugging unpredictable.
Set resource requests and limits
Unbounded Pods starve neighbors and get OOMKilled unpredictably. Requests drive scheduling; limits cap burst usage.
Keep revisionHistoryLimit intentional
Retain enough revisions for rollback (5–10) without cluttering etcd with stale ReplicaSets.
Troubleshooting¶
| Issue | Cause | Solution |
|---|---|---|
| Rollout stuck | Image pull failure, crash loop, failed probe | kubectl describe rs, kubectl logs; fix image or probes |
ImagePullBackOff | Wrong tag, missing registry credentials | Verify image name; add imagePullSecrets |
| Old ReplicaSets accumulate | Normal behavior | Old RS scale to 0; tune revisionHistoryLimit |
| Selector mismatch error | Template labels don't match selector | Align spec.selector.matchLabels with template metadata labels |
| Rollout undo fails | Revision pruned | Check rollout history; redeploy known-good manifest from Git |
| Pods pending after scale-up | Insufficient cluster resources | kubectl describe pod; add nodes or reduce requests |
Summary¶
- Deployments manage stateless, replicated applications through ReplicaSets and Pods
- Rolling updates replace Pods incrementally using
maxSurgeandmaxUnavailable - Rollout history enables fast rollback when a release fails
- Labels and selectors connect Deployments to Services, HPAs, and NetworkPolicies
- Production Deployments need pinned images, resource limits, and readiness/liveness probes
- Next: expose Deployments with stable networking in Services and Cluster Networking
Interview Questions¶
- What is the relationship between a Deployment, ReplicaSet, and Pod?
- Explain rolling update strategy and the purpose of
maxSurgeandmaxUnavailable. - How do you roll back a Deployment to a previous version?
- Why are Deployment selectors immutable?
- When would you choose
RecreateoverRollingUpdate? - What happens to old ReplicaSets after a successful rollout?
- How does
kubectl scalediffer from a HorizontalPodAutoscaler? - Why should you avoid deploying bare Pods in production?
- What is the purpose of
revisionHistoryLimit? - How do readiness probes affect rolling updates?
Sample Answers (Questions 1, 2, and 3)
Q1 — Relationship: A Deployment declares desired state (replica count, Pod template, update strategy). It creates and manages ReplicaSets. Each ReplicaSet ensures a specified number of Pods matching its label selector are running. Pods are the actual workload instances running containers.
Q2 — Rolling update: RollingUpdate replaces Pods incrementally rather than all at once. maxSurge allows extra Pods above the desired count during the rollout (e.g., 4 Pods when desired is 3). maxUnavailable allows Pods to be temporarily unavailable (e.g., 2 ready when desired is 3). Together they control rollout speed and availability.
Q3 — Rollback: Run kubectl rollout undo deployment/<name> to revert to the previous ReplicaSet. Use kubectl rollout history to list revisions and --to-revision=N to target a specific version. Kubernetes scales up the old ReplicaSet and scales down the failed one.
Related Tutorials¶
- Kubernetes – Category Overview
- Pods — The Atomic Unit (previous in Module 2)
- kubectl Essentials and Workflows
- Services and Cluster Networking (next in series)
- From Docker to Kubernetes
- Learning Paths – DevOps Engineer