CKAD — Pod Design Hands-On Lab & Practice

5 minute read

Lab 1: Label Management

Practice adding, viewing, and selecting Pods by labels.

# Create 3 Pods with different labels
kubectl run app1 --image=nginx --labels="app=web,env=prod"
kubectl run app2 --image=nginx --labels="app=web,env=staging"
kubectl run app3 --image=nginx --labels="app=api,env=prod"

# Find all Pods in prod environment
kubectl get pods -l env=prod
# Expected: app1 and app3

# Find all web Pods
kubectl get pods -l app=web
# Expected: app1 and app2

# Find web Pods in prod
kubectl get pods -l app=web,env=prod
# Expected: app1 only

# Add a version label to all web Pods
kubectl label pods -l app=web version=v1

# Verify
kubectl get pods -l app=web --show-labels
# Expected: app1 and app2 both show version=v1

Verify Your Work

# Count Pods matching each selector
kubectl get pods -l env=prod --no-headers | wc -l
# Expected: 2

kubectl get pods -l app=web,env=prod --no-headers | wc -l
# Expected: 1

kubectl get pods -l 'env in (prod,staging)' --no-headers | wc -l
# Expected: 3

Lab 2: Annotations

Practice adding and managing annotations.

# Add annotations to a Pod
kubectl annotate pod app1 build="ci-build-123" team="platform"

# Verify annotations
kubectl describe pod app1 | grep -A 5 Annotations

# Update an annotation
kubectl annotate pod app1 build="ci-build-456" --overwrite

# Remove an annotation
kubectl annotate pod app1 team-

# Verify removal
kubectl get pod app1 -o jsonpath='{.metadata.annotations}' | python3 -m json.tool

Lab 3: Pod Lifecycle Debugging

Practice debugging Pods in different states.

# Create a Pod that will crash
kubectl run crasher --image=busybox:1.36 -- /bin/sh -c "exit 1"

# Check the status — it will show CrashLoopBackOff
kubectl get pod crasher -w

# Press Ctrl+C after seeing CrashLoopBackOff, then check events
kubectl describe pod crasher | tail -20

# Check the exit code
kubectl get pod crasher -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'
# Expected: 1

# Check restart count
kubectl get pod crasher -o jsonpath='{.status.containerStatuses[0].restartCount}'
# Expected: a number > 0

# Clean up
kubectl delete pod crasher

Create a Succeeding Pod

# Create a Pod that exits successfully
kubectl run success --image=busybox:1.36 --restart=Never -- /bin/sh -c "echo done"

# Check status
kubectl get pod success
# Expected: STATUS = Completed

# Check logs
kubectl logs success
# Expected: done

# Clean up
kubectl delete pod success

Lab 4: Ephemeral Volumes

Practice sharing data between containers using emptyDir.

# Create the Pod spec
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: vol-lab
spec:
  containers:
  - name: writer
    image: busybox:1.36
    command: ["/bin/sh", "-c"]
    args:
    - |
      for i in 1 2 3 4 5; do
        echo "Message \$i at \$(date)" >> /shared/messages.txt
        sleep 2
      done
      sleep 3600
    volumeMounts:
    - name: shared
      mountPath: /shared
  - name: reader
    image: busybox:1.36
    command: ["/bin/sh", "-c", "sleep 15 && cat /shared/messages.txt && sleep 3600"]
    volumeMounts:
    - name: shared
      mountPath: /shared
  volumes:
  - name: shared
    emptyDir: {}
EOF

# Wait for the Pod to be ready
kubectl wait --for=condition=Ready pod/vol-lab --timeout=30s

# Read the shared messages
kubectl exec vol-lab -c reader -- cat /shared/messages.txt
# Expected: 5 messages with timestamps

# Clean up
kubectl delete pod vol-lab

Lab 5: Node Selection

Practice scheduling Pods on specific nodes (requires a multi-node cluster).

# List your nodes
kubectl get nodes

# Label a node
kubectl label node <node-name> disktype=ssd

# Create a Pod that requires the label
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: ssd-pod
spec:
  nodeSelector:
    disktype: ssd
  containers:
  - name: app
    image: nginx:1.27
EOF

# Verify the Pod is on the labeled node
kubectl get pod ssd-pod -o wide
# Expected: NODE column shows the labeled node

# Clean up
kubectl delete pod ssd-pod
kubectl label node <node-name> disktype-

Practice Questions

Q1: How do you select all Pods that have the label env=prod but NOT app=cache?

Show Answer ```bash kubectl get pods -l 'env=prod,app!=cache' ``` Or using set-based syntax: ```bash kubectl get pods -l 'env=prod,app notin (cache)' ```

Q2: What is the difference between labels and annotations?

Show Answer **Labels** are used to identify and select objects. Kubernetes uses them for grouping, filtering, and service discovery. They have strict character limits (63 chars, alphanumeric + `-_.`). **Annotations** store non-identifying metadata. They are used by tools, humans, and controllers for additional information (build details, monitoring configuration, etc.). They can contain larger, unstructured data.

Q3: A Pod is stuck in Pending state. List 3 possible reasons and how to debug each.

Show Answer 1. **Insufficient resources** — No node has enough CPU/memory for the Pod's requests. - Debug: `kubectl describe pod ` → check Events for "Insufficient cpu" or "Insufficient memory" - Fix: Reduce resource requests or add nodes 2. **No matching nodeSelector** — The Pod requires a label that no node has. - Debug: `kubectl describe pod ` → check for "node(s) didn't match Pod's node affinity" - Fix: Label a node or remove the nodeSelector 3. **PVC not bound** — The Pod references a PersistentVolumeClaim that is not yet bound. - Debug: `kubectl get pvc` → check for Pending PVCs - Fix: Create a matching PersistentVolume or StorageClass </details> **Q4: Create a Pod named `labeled-pod` with image `nginx:1.27` and labels `app=frontend`, `tier=web`, `release=stable`. Then show only Pods with the `tier` label.**
Show Answer ```bash # Create the Pod kubectl run labeled-pod --image=nginx:1.27 --labels="app=frontend,tier=web,release=stable" # Show Pods that have the tier label (any value) kubectl get pods -l tier # Or show with label column kubectl get pods -L tier ```
**Q5: What happens to an `emptyDir` volume when the Pod is deleted?**
Show Answer The `emptyDir` volume and all its data are **permanently deleted** when the Pod is removed from a node. The data persists across container restarts within the same Pod, but not across Pod deletions or rescheduling.
**Q6: A Deployment spec contains `restartPolicy: OnFailure`. What happens when you apply it?**
Show Answer The Deployment will be **rejected** with a validation error. Deployments only support `restartPolicy: Always`. The `OnFailure` and `Never` policies are only valid for standalone Pods and Jobs/CronJobs.
**Q7: You need to schedule a Pod on nodes in availability zone `us-west-2a` but also allow it to run in `us-west-2b` if `us-west-2a` has no capacity. Write the affinity spec.**
Show Answer ```yaml spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: topology.kubernetes.io/zone operator: In values: - us-west-2a - weight: 50 preference: matchExpressions: - key: topology.kubernetes.io/zone operator: In values: - us-west-2b ``` Using `preferredDuringSchedulingIgnoredDuringExecution` makes this a soft constraint. The higher weight (100) for `us-west-2a` means the scheduler prefers it, but will fall back to `us-west-2b` (weight 50) if needed.
--- [← Back to Pod Design Overview](/k8s/ckad/pod-design/){: .btn .btn--inverse}

Updated: