CKA — Workloads & Scaling Lab
Lab Overview
This hands-on lab brings together everything from Chapter 7 — Workloads & Scaling. You’ll create Deployments, perform rolling updates, rollback, configure applications with ConfigMaps and Secrets, scale workloads, and set resource boundaries.
What you’ll practice:
| Task | Skills Tested |
|---|---|
| Task 1 | Create a Deployment |
| Task 2 | Perform a rolling update |
| Task 3 | Rollback to a previous version |
| Task 4 | ConfigMaps and Secrets |
| Task 5 | Manual scaling and HPA |
| Task 6 | Resource limits, LimitRange, ResourceQuota |
Prerequisites:
- A working Kubernetes cluster (kubeadm, minikube, or kind)
kubectlconfigured and connected- Metrics Server installed (for Task 5 HPA section)
Task 1: Create a Deployment
Objective: Create a Deployment named webapp with 3 replicas running nginx:1.26.
Steps
# Create the Deployment
kubectl create deployment webapp --image=nginx:1.26 --replicas=3
# Verify the Deployment
kubectl get deployment webapp
Expected output:
NAME READY UP-TO-DATE AVAILABLE AGE
webapp 3/3 3 3 30s
# Verify Pods are running
kubectl get pods -l app=webapp
Expected output:
NAME READY STATUS RESTARTS AGE
webapp-5d9b7f6c4b-abc12 1/1 Running 0 45s
webapp-5d9b7f6c4b-def34 1/1 Running 0 45s
webapp-5d9b7f6c4b-ghi56 1/1 Running 0 45s
# Check the ReplicaSet
kubectl get rs -l app=webapp
Expected output:
NAME DESIRED CURRENT READY AGE
webapp-5d9b7f6c4b 3 3 3 1m
# Verify the image
kubectl describe deployment webapp | grep Image
Expected output:
Image: nginx:1.26
Task 2: Perform a Rolling Update
Objective: Update the webapp Deployment to use nginx:1.27-alpine and watch the rollout.
Steps
# Trigger the rolling update
kubectl set image deployment/webapp webapp=nginx:1.27-alpine
# Watch the rollout in real time
kubectl rollout status deployment/webapp
Expected output:
Waiting for deployment "webapp" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "webapp" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "webapp" rollout to finish: 2 of 3 updated replicas are available...
deployment "webapp" successfully rolled out
# Verify the new image
kubectl describe deployment webapp | grep Image
Expected output:
Image: nginx:1.27-alpine
# Check ReplicaSets — you should see two (old and new)
kubectl get rs -l app=webapp
Expected output:
NAME DESIRED CURRENT READY AGE
webapp-5d9b7f6c4b 0 0 0 5m # old
webapp-7f8c9d2e1a 3 3 3 1m # new
# View rollout history
kubectl rollout history deployment/webapp
Expected output:
REVISION CHANGE-CAUSE
1 <none>
2 <none>
Task 3: Rollback
Objective: Rollback the webapp Deployment to the previous version (nginx:1.26).
Steps
# Rollback to previous revision
kubectl rollout undo deployment/webapp
# Check rollout status
kubectl rollout status deployment/webapp
Expected output:
deployment "webapp" successfully rolled out
# Verify the image is back to nginx:1.26
kubectl describe deployment webapp | grep Image
Expected output:
Image: nginx:1.26
# View updated history
kubectl rollout history deployment/webapp
Expected output:
REVISION CHANGE-CAUSE
2 <none>
3 <none>
Note: Revision 1 becomes revision 3 after the rollback. The rollback is itself a new revision.
# Verify Pods
kubectl get pods -l app=webapp
All Pods should be Running with the original image.
Task 4: ConfigMaps and Secrets
Objective: Create a ConfigMap and a Secret, then create a Pod that consumes both.
Step 1: Create a ConfigMap
kubectl create configmap app-settings \
--from-literal=APP_ENV=production \
--from-literal=APP_PORT=8080 \
--from-literal=LOG_LEVEL=info
Verify:
kubectl get configmap app-settings -o yaml
Expected output (data section):
data:
APP_ENV: production
APP_PORT: "8080"
LOG_LEVEL: info
Step 2: Create a Secret
kubectl create secret generic app-secrets \
--from-literal=DB_USER=admin \
--from-literal=DB_PASS=supersecret123
Verify:
kubectl get secret app-secrets -o yaml
The values will be base64-encoded:
echo "YWRtaW4=" | base64 -d # admin
echo "c3VwZXJzZWNyZXQxMjM=" | base64 -d # supersecret123
Step 3: Create a Pod consuming both
Create a file named config-pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: config-pod
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
envFrom:
- configMapRef:
name: app-settings
- secretRef:
name: app-secrets
volumeMounts:
- name: config-vol
mountPath: /etc/config
readOnly: true
- name: secret-vol
mountPath: /etc/secrets
readOnly: true
volumes:
- name: config-vol
configMap:
name: app-settings
- name: secret-vol
secret:
secretName: app-secrets
kubectl apply -f config-pod.yaml
Step 4: Verify
# Check environment variables
kubectl exec config-pod -- env | grep -E "APP_|DB_|LOG_"
Expected output:
APP_ENV=production
APP_PORT=8080
LOG_LEVEL=info
DB_USER=admin
DB_PASS=supersecret123
# Check mounted files
kubectl exec config-pod -- ls /etc/config/
Expected output:
APP_ENV
APP_PORT
LOG_LEVEL
kubectl exec config-pod -- cat /etc/config/APP_ENV
Expected output:
production
kubectl exec config-pod -- ls /etc/secrets/
Expected output:
DB_PASS
DB_USER
Task 5: Scaling
Objective: Manually scale the webapp Deployment and create an HPA.
Step 1: Manual scaling
# Scale up to 5 replicas
kubectl scale deployment webapp --replicas=5
# Verify
kubectl get deployment webapp
Expected output:
NAME READY UP-TO-DATE AVAILABLE AGE
webapp 5/5 5 5 15m
# Scale back to 3
kubectl scale deployment webapp --replicas=3
kubectl get deployment webapp
Step 2: Add resource requests (required for HPA)
kubectl set resources deployment webapp -c webapp \
--requests=cpu=100m,memory=128Mi \
--limits=cpu=250m,memory=256Mi
Verify:
kubectl describe deployment webapp | grep -A6 "Limits\|Requests"
Step 3: Create HPA (requires Metrics Server)
kubectl autoscale deployment webapp --min=2 --max=8 --cpu-percent=50
Verify:
kubectl get hpa
Expected output:
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
webapp Deployment/webapp 10%/50% 2 8 3 30s
Note: If you see
<unknown>/50%in TARGETS, wait a minute for the Metrics Server to collect data, or ensure Metrics Server is installed.
# Describe for detailed info
kubectl describe hpa webapp
Task 6: Resource Limits
Objective: Create a namespace with LimitRange and ResourceQuota, then deploy a Pod.
Step 1: Create namespace
kubectl create namespace workloads-lab
Step 2: Create a LimitRange
Create limitrange.yaml:
apiVersion: v1
kind: LimitRange
metadata:
name: lab-limits
namespace: workloads-lab
spec:
limits:
- type: Container
default:
cpu: 300m
memory: 256Mi
defaultRequest:
cpu: 100m
memory: 128Mi
min:
cpu: 50m
memory: 64Mi
max:
cpu: "1"
memory: 512Mi
kubectl apply -f limitrange.yaml
kubectl describe limitrange lab-limits -n workloads-lab
Expected output:
Type Resource Min Max Default Request Default Limit
---- -------- --- --- --------------- -------------
Container cpu 50m 1 100m 300m
Container memory 64Mi 512Mi 128Mi 256Mi
Step 3: Create a ResourceQuota
Create quota.yaml:
apiVersion: v1
kind: ResourceQuota
metadata:
name: lab-quota
namespace: workloads-lab
spec:
hard:
pods: "10"
requests.cpu: "2"
requests.memory: 2Gi
limits.cpu: "4"
limits.memory: 4Gi
kubectl apply -f quota.yaml
kubectl describe resourcequota lab-quota -n workloads-lab
Expected output:
Resource Used Hard
-------- ---- ----
limits.cpu 0 4
limits.memory 0 4Gi
pods 0 10
requests.cpu 0 2
requests.memory 0 2Gi
Step 4: Deploy a Pod (gets default limits from LimitRange)
kubectl run test-pod --image=nginx:1.27 -n workloads-lab
Verify that defaults were applied:
kubectl describe pod test-pod -n workloads-lab | grep -A6 "Limits\|Requests"
Expected output:
Limits:
cpu: 300m
memory: 256Mi
Requests:
cpu: 100m
memory: 128Mi
Check quota usage:
kubectl describe resourcequota lab-quota -n workloads-lab
Expected output shows resources now in use:
Resource Used Hard
-------- ---- ----
limits.cpu 300m 4
limits.memory 256Mi 4Gi
pods 1 10
requests.cpu 100m 2
requests.memory 128Mi 2Gi
Step 5: Try exceeding limits
# Try creating a Pod that exceeds the LimitRange max
kubectl run big-pod --image=nginx:1.27 -n workloads-lab \
--overrides='{"spec":{"containers":[{"name":"big-pod","image":"nginx:1.27","resources":{"requests":{"cpu":"2","memory":"1Gi"},"limits":{"cpu":"2","memory":"1Gi"}}}]}}'
Expected: The Pod will be rejected because CPU limit 2 exceeds the LimitRange max of 1.
Cleanup
Remove all resources created during this lab:
# Delete Deployment and HPA
kubectl delete deployment webapp
kubectl delete hpa webapp
# Delete Pod and config resources
kubectl delete pod config-pod
kubectl delete configmap app-settings
kubectl delete secret app-secrets
# Delete namespace (also removes everything inside it)
kubectl delete namespace workloads-lab
# Remove generated files
rm -f config-pod.yaml limitrange.yaml quota.yaml
Verify cleanup:
kubectl get deployments
kubectl get pods
kubectl get hpa
kubectl get namespace workloads-lab
Congratulations! You’ve completed the Workloads & Scaling lab. You’ve practiced creating Deployments, performing rolling updates and rollbacks, managing configuration with ConfigMaps and Secrets, scaling workloads manually and with HPA, and enforcing resource constraints with LimitRange and ResourceQuota.