Skip to content

Module 2.1: Deployments Deep Dive

Hands-On Lab Available
K8s Cluster intermediate 30 min
Launch Lab ↗

Opens in Killercoda in a new tab

Complexity: [MEDIUM] - Core CKAD skill with multiple operations

Time to Complete: 45-55 minutes

Prerequisites: Part 1 completed, understanding of Pods and ReplicaSets


After completing this module, you will be able to:

  • Deploy applications using Deployments with correct replica counts and update strategies
  • Configure rolling update parameters including maxSurge and maxUnavailable
  • Diagnose stuck rollouts and perform rollbacks using kubectl rollout commands
  • Implement scaling operations both imperatively and declaratively

Deployments are how you run applications in production Kubernetes. They manage ReplicaSets, which manage Pods. Understanding Deployments means understanding rolling updates, rollbacks, scaling, and the entire lifecycle of your application.

The CKAD heavily tests Deployment operations:

  • Create and scale Deployments
  • Perform rolling updates
  • Rollback to previous versions
  • Pause and resume rollouts
  • Check rollout status and history

The Software Release Pipeline Analogy

A Deployment is like a release manager. When you want to ship a new version, the release manager (Deployment) creates a new production line (ReplicaSet) running the new code. It gradually moves traffic from the old line to the new one. If something goes wrong, it can quickly switch back to the old line. The workers (Pods) just follow instructions—the Deployment orchestrates everything.


Terminal window
# Imperative creation
k create deploy nginx --image=nginx:1.21 --replicas=3
# With port
k create deploy web --image=nginx --port=80
# Generate YAML
k create deploy api --image=httpd --replicas=2 --dry-run=client -o yaml > deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.21
ports:
- containerPort: 80
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
ComponentPurpose
replicasNumber of Pod copies to run
selector.matchLabelsHow Deployment finds its Pods
templatePod specification (must match selector labels)
strategyHow updates are performed

Terminal window
# Scale to 5 replicas
k scale deploy web-app --replicas=5
# Scale to zero (stop all pods)
k scale deploy web-app --replicas=0
# Scale multiple deployments
k scale deploy web-app api-server --replicas=3
Terminal window
# Watch pods scale
k get pods -l app=web -w
# Check deployment status
k get deploy web-app
# Detailed status
k describe deploy web-app | grep -A5 Replicas

Rolling updates replace Pods gradually, ensuring zero downtime.

graph TD
subgraph Deployment ["Deployment (Orchestrator)"]
D[web-app]
end
subgraph RS_New ["New ReplicaSet (v1.22)"]
RS2[ReplicaSet 2]
P4((Pod 4<br/>v1.22))
P5((Pod 5<br/>v1.22))
end
subgraph RS_Old ["Old ReplicaSet (v1.21)"]
RS1[ReplicaSet 1]
P1((Pod 1<br/>v1.21))
P2((Pod 2<br/>Terminating))
end
D -- "Scales up" --> RS2
D -- "Scales down" --> RS1
RS2 --> P4
RS2 --> P5
RS1 --> P1
RS1 -.-> P2
classDef deploy fill:#326ce5,stroke:#fff,stroke-width:2px,color:#fff;
classDef rs fill:#2b3a42,stroke:#fff,stroke-width:2px,color:#fff;
classDef pod fill:#68a063,stroke:#fff,stroke-width:2px,color:#fff;
classDef terminating fill:#e53935,stroke:#fff,stroke-width:2px,color:#fff,stroke-dasharray: 5 5;
class D deploy;
class RS1,RS2 rs;
class P1,P4,P5 pod;
class P2 terminating;
Terminal window
# Update container image
k set image deploy/web-app nginx=nginx:1.22
# Update with record (deprecated but still works)
k set image deploy/web-app nginx=nginx:1.22 --record
# Update using patch
k patch deploy web-app -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"nginx:1.22"}]}}}}'
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # Max pods over desired count during update
maxUnavailable: 0 # Max pods unavailable during update
SettingDescriptionExample
maxSurgeExtra pods allowed during update1 or 25%
maxUnavailablePods that can be down during update0 or 25%

StatefulSet MaxUnavailable

StatefulSets support maxUnavailable in their updateStrategy (GA since K8s 1.27), enabling parallel pod updates instead of sequential one-at-a-time. This can make StatefulSet updates up to 60% faster — critical for database clusters and stateful workloads:

updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 2 # Update 2 pods at a time instead of 1

Pause and predict: With maxSurge: 1 and maxUnavailable: 0 on a 4-replica Deployment, what’s the maximum number of pods that can exist during an update? What about the minimum number of available pods? Work it out before checking the strategy types below.

# RollingUpdate (default) - gradual replacement
strategy:
type: RollingUpdate
# Recreate - kill all, then create new
strategy:
type: Recreate

Use Recreate when:

  • Application can’t run multiple versions simultaneously
  • Database migrations require single version
  • Downtime is acceptable

Terminal window
# Watch rollout progress
k rollout status deploy/web-app
# Check if rollout completed
k rollout status deploy/web-app --timeout=60s
Terminal window
# List revision history
k rollout history deploy/web-app
# See specific revision details
k rollout history deploy/web-app --revision=2
# Check current revision
k describe deploy web-app | grep -i revision

Stop and think: You need to change both the image AND the resource limits of a Deployment simultaneously. If you run two separate kubectl set commands, Kubernetes triggers two separate rollouts. How can you batch these into a single rollout?

Terminal window
# Pause rollout (for batched changes)
k rollout pause deploy/web-app
# Make multiple changes while paused
k set image deploy/web-app nginx=nginx:1.23
k set resources deploy/web-app -c nginx --limits=memory=256Mi
# Resume rollout
k rollout resume deploy/web-app

Terminal window
# Rollback to previous revision
k rollout undo deploy/web-app
# Rollback to specific revision
k rollout undo deploy/web-app --to-revision=2
# Check rollback status
k rollout status deploy/web-app
Terminal window
# Each change creates a new ReplicaSet
k get rs -l app=web
# Output:
# NAME DESIRED CURRENT READY AGE
# web-app-6d8f9b6b4f 3 3 3 5m (current)
# web-app-7b8c9d4e3a 0 0 0 10m (previous)

Old ReplicaSets are kept (scaled to 0) for rollback capability.

spec:
revisionHistoryLimit: 5 # Keep only 5 old ReplicaSets

Terminal window
# Get conditions
k get deploy web-app -o jsonpath='{.status.conditions[*].type}'
# Detailed conditions
k describe deploy web-app | grep -A10 Conditions
ConditionMeaning
AvailableMinimum replicas are available
ProgressingDeployment is updating
ReplicaFailureCouldn’t create pods

What would happen if: You run kubectl set image deploy/web-app nginx=nginx:nonexistent-tag. The rollout starts but new pods can’t pull the image. Does Kubernetes automatically roll back, or does it just stall? What’s the safest recovery action?

spec:
progressDeadlineSeconds: 600 # Fail if no progress in 10 min

If a rollout stalls (e.g., image pull fails), it’s marked as failed after this deadline.


The selector.matchLabels MUST match template.metadata.labels:

spec:
selector:
matchLabels:
app: web # Must match below
tier: frontend
template:
metadata:
labels:
app: web # Must match above
tier: frontend
version: v1 # Can have extra labels
Terminal window
# Add label to deployment (metadata only)
k label deploy web-app environment=production
# Add label to pods via template (triggers rollout)
k patch deploy web-app -p '{"spec":{"template":{"metadata":{"labels":{"version":"v2"}}}}}'

Terminal window
# Create
k create deploy NAME --image=IMAGE --replicas=N
# Scale
k scale deploy NAME --replicas=N
# Update image
k set image deploy/NAME CONTAINER=IMAGE
# Update resources
k set resources deploy NAME -c CONTAINER --limits=cpu=200m,memory=512Mi
# Rollout status
k rollout status deploy/NAME
# Rollout history
k rollout history deploy/NAME
# Rollback
k rollout undo deploy/NAME
# Pause/Resume
k rollout pause deploy/NAME
k rollout resume deploy/NAME
# Restart all pods (rolling)
k rollout restart deploy/NAME

  • kubectl rollout restart triggers a rolling restart without changing the image. It adds an annotation with the current timestamp, causing pods to recreate. Great for picking up ConfigMap changes.

  • Deployments don’t delete old ReplicaSets immediately. They keep them (scaled to 0) for rollback capability. Control this with revisionHistoryLimit.

  • The --record flag is deprecated but still works. Kubernetes 1.22+ recommends using annotations instead to track change causes.


MistakeWhy It HurtsSolution
Selector doesn’t match template labelsDeployment can’t find its podsEnsure labels match exactly
Using Recreate in productionCauses downtimeUse RollingUpdate with proper settings
maxUnavailable: 100%All pods killed at onceSet reasonable percentages
Forgetting to check rollout statusDon’t know if update succeededAlways run rollout status
Not setting resource limitsPods can consume all node resourcesAlways set requests and limits

  1. After a deployment update, your application is throwing errors. You check kubectl rollout history deploy/api-server and see revisions 1 through 4. Revision 2 was the last known-good version. How do you roll back specifically to revision 2, and what happens to the revision numbering afterward?

    Answer Run `kubectl rollout undo deploy/api-server --to-revision=2`. This doesn't "go back in time" -- it creates a NEW revision (revision 5) that uses the same pod template as revision 2. The old revision 2 disappears from history (since its ReplicaSet is now the active one again). This is important to understand: rollbacks don't rewrite history, they create new revisions. Always run `kubectl rollout status deploy/api-server` afterward to confirm the rollback completed successfully.
  2. Your team runs a legacy application that writes to a local SQLite database file. Two versions of the app can’t access the database simultaneously without corruption. Which deployment strategy should you use, and what’s the trade-off?

    Answer Use `strategy: type: Recreate`. This kills all existing pods before creating new ones, ensuring only one version accesses the database at a time. The trade-off is downtime -- there's a gap between old pods terminating and new pods becoming ready. `RollingUpdate` would cause data corruption since both old and new pods would access SQLite concurrently. For production, consider migrating to a proper database (PostgreSQL, MySQL) that handles concurrent access, which lets you use `RollingUpdate` for zero-downtime deployments.
  3. You updated a ConfigMap that your Deployment’s pods consume as environment variables. Running kubectl get pods shows all pods are still running the old config. kubectl rollout restart isn’t working because your cluster RBAC restricts that command. What alternative approach triggers a pod recreation?

    Answer Use `kubectl set env deploy/NAME RESTART_TRIGGER=$(date +%s)` to add a dummy environment variable with a timestamp. Any change to the pod template triggers a rolling update, which recreates all pods with the fresh ConfigMap values. Alternatively, you could patch the deployment to add an annotation: `kubectl patch deploy NAME -p '{"spec":{"template":{"metadata":{"annotations":{"restart":"'$(date +%s)'"}}}}}'`. Both approaches force pod recreation without the `rollout restart` command.
  4. You create a Deployment with replicas: 5, maxSurge: 2, and maxUnavailable: 0. During a rolling update, you notice the rollout stalls — new pods are stuck in Pending because the cluster has no capacity for extra pods. What went wrong with your strategy configuration?

    Answer With `maxSurge: 2` and `maxUnavailable: 0`, Kubernetes must create 2 extra pods (total 7) before it can terminate any old ones. If the cluster can't schedule 7 pods, the rollout deadlocks. The fix is either: (1) set `maxUnavailable: 1` so Kubernetes can remove an old pod first to make room; (2) reduce `maxSurge` to 1; or (3) ensure the cluster has capacity for `replicas + maxSurge` pods. The `maxUnavailable: 0` + insufficient capacity combination is a common rollout trap. Setting `progressDeadlineSeconds` helps detect this automatically.

Task: Practice the full Deployment lifecycle.

Part 1: Create and Scale

Terminal window
# Create deployment
k create deploy webapp --image=nginx:1.20 --replicas=2
# Verify
k get deploy webapp
k get pods -l app=webapp
# Scale up
k scale deploy webapp --replicas=5
# Verify scaling
k get pods -l app=webapp -w

Part 2: Rolling Update

Terminal window
# Update image
k set image deploy/webapp nginx=nginx:1.21
# Watch rollout
k rollout status deploy/webapp
# Check history
k rollout history deploy/webapp
# Update again
k set image deploy/webapp nginx=nginx:1.22

Part 3: Rollback

Terminal window
# Rollback to previous
k rollout undo deploy/webapp
# Verify image reverted
k describe deploy webapp | grep Image
# Rollback to specific revision
k rollout history deploy/webapp
k rollout undo deploy/webapp --to-revision=1

Part 4: Pause and Batch Changes

Terminal window
# Pause
k rollout pause deploy/webapp
# Make multiple changes
k set image deploy/webapp nginx=nginx:1.23
k set resources deploy/webapp -c nginx --limits=memory=128Mi
# Resume
k rollout resume deploy/webapp
# Verify single rollout
k rollout status deploy/webapp

Cleanup:

Terminal window
k delete deploy webapp

Drill 1: Basic Deployment (Target: 2 minutes)

Section titled “Drill 1: Basic Deployment (Target: 2 minutes)”
Terminal window
# Create deployment with 3 replicas
k create deploy drill1 --image=nginx --replicas=3
# Verify all pods running
k get pods -l app=drill1
# Scale to 5
k scale deploy drill1 --replicas=5
# Verify
k get deploy drill1
# Cleanup
k delete deploy drill1
Terminal window
# Create deployment
k create deploy drill2 --image=nginx:1.20
# Update image
k set image deploy/drill2 nginx=nginx:1.21
# Check rollout status
k rollout status deploy/drill2
# Verify new image
k describe deploy drill2 | grep Image
# Cleanup
k delete deploy drill2
Terminal window
# Create and update multiple times
k create deploy drill3 --image=nginx:1.19
k set image deploy/drill3 nginx=nginx:1.20
k set image deploy/drill3 nginx=nginx:1.21
# Check history
k rollout history deploy/drill3
# Rollback to revision 1
k rollout undo deploy/drill3 --to-revision=1
# Verify image is 1.19
k describe deploy drill3 | grep Image
# Cleanup
k delete deploy drill3

Drill 4: Rolling Update Settings (Target: 4 minutes)

Section titled “Drill 4: Rolling Update Settings (Target: 4 minutes)”
Terminal window
# Create deployment with custom strategy
cat << 'EOF' | k apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: drill4
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: drill4
template:
metadata:
labels:
app: drill4
spec:
containers:
- name: nginx
image: nginx:1.20
EOF
# Update and watch (should see 5 pods max, 4 always ready)
k set image deploy/drill4 nginx=nginx:1.21
k get pods -l app=drill4 -w
# Cleanup
k delete deploy drill4

Drill 5: Pause and Resume (Target: 3 minutes)

Section titled “Drill 5: Pause and Resume (Target: 3 minutes)”
Terminal window
# Create deployment
k create deploy drill5 --image=nginx:1.20
# Pause
k rollout pause deploy/drill5
# Make changes (no rollout yet)
k set image deploy/drill5 nginx=nginx:1.21
k set resources deploy/drill5 -c nginx --requests=cpu=100m
# Verify paused
k rollout status deploy/drill5
# Resume
k rollout resume deploy/drill5
# Check single rollout applied both changes
k rollout status deploy/drill5
# Cleanup
k delete deploy drill5

Drill 6: Complete Deployment Scenario (Target: 6 minutes)

Section titled “Drill 6: Complete Deployment Scenario (Target: 6 minutes)”

Scenario: Deploy an application, update it, encounter an issue, and rollback.

Terminal window
# 1. Create initial deployment
k create deploy production --image=nginx:1.20 --replicas=3
# 2. Expose as service
k expose deploy production --port=80
# 3. Verify working
k rollout status deploy/production
k get pods -l app=production
# 4. Update to "broken" image (simulate bad release)
k set image deploy/production nginx=nginx:broken-tag
# 5. Check rollout stalled
k rollout status deploy/production --timeout=30s
# 6. See problem pods
k get pods -l app=production
# 7. Rollback quickly
k rollout undo deploy/production
# 8. Verify recovered
k rollout status deploy/production
k get pods -l app=production
# 9. Cleanup
k delete deploy production
k delete svc production

Module 2.2: Helm Package Manager - Deploy and manage applications with Helm charts.