What You'll Build
You will deploy the CricketPulse API as a production-grade Kubernetes Deployment with 3 replicas, all probes configured, and proper resource requests and limits. You will perform a rolling update from v1 to v2 (using different nginx configurations to simulate versions), use `kubectl rollout pause` to perform a manual canary check, complete the rollout, and then simulate a bad deployment by updating to a broken image. You will use `kubectl rollout status` to detect the failure and `kubectl rollout undo` to recover. Finally, you will use HPA prerequisites to scale the Deployment manually and verify that the correct number of Pods are running at all times.
Prerequisites
- A running Kubernetes cluster — the kind cluster from Lesson 4 works; re-create it if needed with `kind create cluster --name cricketpulse-cluster`.
- kubectl configured and pointing to the cluster — verify with `kubectl cluster-info`.
- Understanding of Deployment spec structure, probe configuration, and rollout commands from Lessons 5–7.
- Docker installed for image operations (optional — this exercise uses public nginx images).
- ~30 minutes of focused time — you'll be watching rollout status in real time.
Setup & Project Structure
Create the manifest directory structure and a ConfigMap that simulates different CricketPulse API versions by serving different responses from nginx. Using ConfigMaps with nginx lets you test multi-version deployments without building custom Docker images.
mkdir -p cricketpulse-k8s/workloads
cd cricketpulse-k8s
# ConfigMap simulating CricketPulse API v1 response
cat > workloads/configmap-v1.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: cricketpulse-config
namespace: default
data:
index.html: |
{"version": "1.0", "service": "CricketPulse API",
"status": "running", "features": ["live-scores"]}
EOF
# ConfigMap for v2
cat > workloads/configmap-v2.yaml << 'EOF'
apiVersion: v1
kind: ConfigMap
metadata:
name: cricketpulse-config-v2
namespace: default
data:
index.html: |
{"version": "2.0", "service": "CricketPulse API",
"status": "running", "features": ["live-scores", "player-stats", "ipl-2024"]}
EOF
kubectl apply -f workloads/configmap-v1.yaml
kubectl apply -f workloads/configmap-v2.yaml
echo 'ConfigMaps created'Step 1 — Foundation
Deploy CricketPulse v1 as a 3-replica Deployment with all lifecycle features configured. This is the baseline production deployment that all subsequent update operations will modify.
cat > workloads/deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: cricketpulse-api
namespace: default
annotations:
kubernetes.io/change-cause: 'initial deployment v1.0'
spec:
replicas: 3
progressDeadlineSeconds: 120
revisionHistoryLimit: 5
selector:
matchLabels:
app: cricketpulse
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: cricketpulse
version: v1
spec:
containers:
- name: api-server
image: nginx:alpine
ports:
- containerPort: 80
resources:
requests: { cpu: 50m, memory: 64Mi }
limits: { cpu: 200m, memory: 128Mi }
readinessProbe:
httpGet: { path: /, port: 80 }
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet: { path: /, port: 80 }
periodSeconds: 30
failureThreshold: 3
volumeMounts:
- name: api-content
mountPath: /usr/share/nginx/html
volumes:
- name: api-content
configMap:
name: cricketpulse-config
EOF
kubectl apply -f workloads/deployment.yaml
kubectl rollout status deployment/cricketpulse-api
# Verify 3 replicas running on worker nodes
kubectl get pods -l app=cricketpulse -o wide
kubectl get deployment cricketpulse-apiStep 2 — Core Logic
Perform a rolling update from v1 to v2 with a manual canary check using `kubectl rollout pause`. Update the Deployment to use the v2 ConfigMap, immediately pause the rollout after one Pod updates, verify the new version, then resume.
# Update the Deployment to use v2 ConfigMap and new version label
kubectl annotate deployment cricketpulse-api \
kubernetes.io/change-cause='v2.0: add player stats and IPL 2024 features'
# Patch the deployment to use v2 config
kubectl patch deployment cricketpulse-api \
--type='json' \
-p='[{"op": "replace", "path": "/spec/template/spec/volumes/0/configMap/name", "value": "cricketpulse-config-v2"}, {"op": "replace", "path": "/spec/template/metadata/labels/version", "value": "v2"}]'
# IMMEDIATELY pause the rollout after 1 Pod starts updating
kubectl rollout pause deployment/cricketpulse-api
# Check the current state
kubectl get pods -l app=cricketpulse
# Should show: 2x version=v1, 1x version=v2 (the canary)
# Verify the canary Pod (v2) is serving the new content
CANARY_POD=$(kubectl get pods -l app=cricketpulse,version=v2 -o jsonpath='{.items[0].metadata.name}')
kubectl port-forward pod/$CANARY_POD 9090:80 &
sleep 2
curl localhost:9090 # Should return v2 JSON with new features
kill %1
# Canary looks good — resume the rollout
kubectl rollout resume deployment/cricketpulse-api
kubectl rollout status deployment/cricketpulse-api
# Verify all Pods are now v2
kubectl get pods -l app=cricketpulse --show-labelsStep 3 — Integration & Enhancement
Simulate a bad deployment by updating to a non-existent image, observe the rollout stall, detect it via rollout status, and execute an automated rollback. This is the recovery scenario every on-call engineer must know.
# Simulate a bad deployment with a non-existent image
kubectl annotate deployment cricketpulse-api \
kubernetes.io/change-cause='v3.0: BROKEN — image does not exist'
kubectl set image deployment/cricketpulse-api \
api-server=ghcr.io/srihayavadhana/cricketpulse:v99-DOES-NOT-EXIST
# Watch the rollout stall (new Pod will be in ImagePullBackOff)
kubectl get pods -l app=cricketpulse -w &
WATCH_PID=$!
sleep 10
kill $WATCH_PID
# Try rollout status (will block/fail)
timeout 30 kubectl rollout status deployment/cricketpulse-api || echo 'Rollout is stalled!'
# Diagnose: what's wrong with the new Pod?
NEW_POD=$(kubectl get pods -l app=cricketpulse -o jsonpath='{.items[?(@.status.phase=="Pending")].metadata.name}' | head -1)
[ -z "$NEW_POD" ] && NEW_POD=$(kubectl get pods -l app=cricketpulse --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1].metadata.name}')
kubectl describe pod $NEW_POD | tail -20
# Events will show: Failed to pull image — ImagePullBackOff
# ROLLBACK — return to last known good version
kubectl rollout undo deployment/cricketpulse-api
kubectl rollout status deployment/cricketpulse-api
# Verify we're back to v2
kubectl rollout history deployment/cricketpulse-api
kubectl get pods -l app=cricketpulse --show-labelsStep 4 — Testing & Verification
Practice manual scaling and verify the Deployment maintains desired replica counts even when Pods are manually deleted.
# Scale up
kubectl scale deployment cricketpulse-api --replicas=5
kubectl rollout status deployment/cricketpulse-api
kubectl get pods -l app=cricketpulse # Should show 5 pods
# Delete one Pod manually — Deployment should recreate it
POD_TO_DELETE=$(kubectl get pods -l app=cricketpulse -o jsonpath='{.items[0].metadata.name}')
kubectl delete pod $POD_TO_DELETE
kubectl get pods -l app=cricketpulse -w &
WATCH_PID=$!
sleep 20
kill $WATCH_PID
# Should show: deleted pod disappears, new pod appears, total stays at 5
# Scale back down
kubectl scale deployment cricketpulse-api --replicas=3
# Final: view rollout history with all changes
kubectl rollout history deployment/cricketpulse-api
# REVISION CHANGE-CAUSE
# 1 initial deployment v1.0
# 2 v2.0: add player stats and IPL 2024 features
# 3 v3.0: BROKEN — image does not exist
# 4 ← rollback to revision 2
# Cleanup
kubectl delete deployment cricketpulse-api
kubectl delete configmap cricketpulse-config cricketpulse-config-v2
echo 'Exercise complete!'Warning: If `kubectl rollout pause` doesn't pause quickly enough before the rollout completes (all 3 Pods update before you run the pause command), that's fine — the rollout completed successfully. Simply proceed to Step 3 (the bad deployment test). In a real environment with more replicas and slower image pulls, pause gives you a longer window. The important skill is knowing the pause/resume/undo commands and when to use them.
Extension Challenge: Implement a proper canary deployment pipeline using two separate Deployments: `cricketpulse-api-stable` (9 replicas, v2 image) and `cricketpulse-api-canary` (1 replica, v3 image). Create a Service that selects Pods from both Deployments using only the `app: cricketpulse` label — this routes approximately 10% of traffic to the canary. Monitor both Deployments separately, and when satisfied with the canary's metrics, update `cricketpulse-api-stable` to v3 and delete `cricketpulse-api-canary`. This two-Deployment canary pattern works with standard Kubernetes Services without requiring a service mesh.
- Always follow `kubectl apply` with `kubectl rollout status` to confirm the rollout completed successfully — the apply command returns before any Pods are updated.
- Use `kubectl rollout pause` immediately after triggering an update for high-risk changes — this creates a native Kubernetes canary: one Pod updates, old Pods continue serving, you observe before proceeding.
- A bad deployment with `maxUnavailable: 0` stalls safely — old Pods keep running; only the surge Pod (the new, broken one) cycles through CrashLoopBackOff while the service remains available.
- `kubectl rollout undo` reverts to the previous ReplicaSet — it's a full rolling update in reverse, zero-downtime, and completes in seconds if the old image is already cached on the nodes.
- Manual Pod deletion tests the ReplicaSet controller's self-healing: the Deployment immediately creates a replacement to maintain the desired replica count.
- Rollout history provides an audit trail — but only if you annotate deployments with `kubernetes.io/change-cause` before applying; empty CHANGE-CAUSE makes incident correlation impossible.