CKA — Workloads & Scaling Lab

6 minute read

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)
  • kubectl configured 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.

Back to Chapter Overview →

Updated: