Mock Exam 2 — Tasks 11–20
SummaryTen exam tasks continuing the advanced mock exam:...
Ten exam tasks continuing the advanced mock exam:...
Ten exam tasks continuing the advanced mock exam: multi-container Pod with init container, DaemonSet with control-plane tolerations, TLS-terminated Ingress with manually created Secret, ephemeral volumes with resource limits and OOMKilled observation, ServiceAccount with Role binding, Deployment-to-Ingress exposure chain, diagnosing Pending Pods from nodeSelector mismatch, Job failure handling with backoffLimit, blue-green deployment switchover, and a comprehensive aggregate task building a full application stack.
Mock Exam 2 — Tasks 11–20
Continue from Section 1. Your timer should still be running. These tasks increase in complexity, with the final task requiring you to assemble a full application stack from scratch.
Pacing check: If you are past the 60-minute mark and have not finished Section 1, skip remaining Section 1 tasks and start here. Tasks 17 and 20 carry the same weight as Task 1 — do not let sunk time on a difficult earlier task prevent you from collecting points on tasks you can finish quickly. Return to skipped tasks if time permits.
Task 11 — Multi-Container Pod with Init Container (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: init-ns
Requirements
-
Create namespace
init-ns. -
Create a Pod named
app-with-initin namespaceinit-nswith the following specification:-
Init container:
- Name:
config-downloader - Image:
busybox:1.36 - Command:
sh -c "echo 'server.port=8080' > /shared/config.txt && echo 'Config downloaded'" - Volume mount: mount a shared volume at
/shared
- Name:
-
Main container:
- Name:
app - Image:
busybox:1.36 - Command:
sh -c "cat /config/config.txt && sleep 3600" - Volume mount: mount the same shared volume at
/config
- Name:
-
Volume:
- Name:
shared-data - Type:
emptyDir
- Name:
-
-
Wait for the Pod to reach
Runningstatus. The init container must complete before the main container starts. -
Verify the config file was passed from init container to main container:
kubectl logs app-with-init -c app -n init-nsExpected output must include
server.port=8080. -
Confirm the init container completed:
kubectl get pod app-with-init -n init-ns -o jsonpath='{.status.initContainerStatuses[0].state}'
Exam strategy: Init containers run to completion before any regular containers start. If the init container fails, the main container never launches — the Pod stays in Init:Error or Init:CrashLoopBackOff status. When debugging multi-container Pods, always check init container logs first: kubectl logs <pod> -c <init-container-name>.
The volume sharing pattern here is the foundation of the sidecar/init pattern: one container prepares data, another consumes it. The emptyDir volume is shared between all containers in the same Pod and is created when the Pod is assigned to a node.
Expected time: 4 minutes. Writing the YAML with both containers and the shared volume is the most time-consuming part. Use kubectl run --dry-run=client -o yaml to generate the Pod scaffold, then add the init container manually.
Task 12 — DaemonSet with Control-Plane Tolerations (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: daemon-ns
Requirements
-
Create namespace
daemon-ns. -
Create a DaemonSet named
log-agentin namespacedaemon-ns:- Image:
busybox:1.36 - Command:
sh -c "while true; do echo 'Collecting logs from $(hostname)'; sleep 60; done" - Labels on Pod template:
app: log-agent
- Image:
-
Add tolerations so the DaemonSet schedules on all nodes, including control-plane nodes:
tolerations: - key: "node-role.kubernetes.io/control-plane" operator: "Exists" effect: "NoSchedule" -
Verify the DaemonSet has Pods scheduled on every node:
kubectl get daemonset log-agent -n daemon-nsThe
DESIREDcount must equal the total number of nodes in the cluster, and theREADYcount must match. -
Check that at least one Pod is running on a control-plane node:
kubectl get pods -n daemon-ns -o wide
Exam strategy: There is no kubectl create daemonset imperative command. The fastest approach is to generate a Deployment YAML with kubectl create deployment --dry-run=client -o yaml, change the kind to DaemonSet, remove the replicas and strategy fields, and add the tolerations block. This conversion takes under 60 seconds once you have practiced it.
Tolerations must match the taint key, operator, and effect. The Exists operator matches any value for the given key, making it the safest choice when you do not know the exact taint value. The NoSchedule effect means the taint prevents scheduling but does not evict already-running Pods.
To verify that the DaemonSet is running on control-plane nodes, compare the NODE column in kubectl get pods -o wide against kubectl get nodes and identify which nodes have the control-plane role.
Expected time: 4 minutes.
Task 13 — Ingress with TLS (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: tls-ns
Requirements
-
Create namespace
tls-ns. -
Generate a self-signed TLS certificate (for exam simulation purposes):
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ -keyout tls.key -out tls.crt \ -subj "/CN=secure-app.example.com" -
Create a Kubernetes Secret of type
kubernetes.io/tlsnamedtls-secretin namespacetls-ns:kubectl create secret tls tls-secret --cert=tls.crt --key=tls.key -n tls-ns -
Create a Deployment named
secure-appin namespacetls-ns:- Image:
nginx:1.25 - Replicas: 2
- Labels:
app: secure-app
- Image:
-
Create a ClusterIP Service named
secure-app-svcin namespacetls-ns:- Selector:
app: secure-app - Port: 80, targetPort: 80
- Selector:
-
Create an Ingress named
secure-app-ingressin namespacetls-ns:- Host:
secure-app.example.com - TLS section referencing Secret
tls-secretfor hostsecure-app.example.com - Rule: route traffic for host
secure-app.example.com, path/, pathTypePrefix, to Servicesecure-app-svcon port 80 ingressClassName: nginx
- Host:
-
Verify the Ingress is created with TLS configured:
kubectl describe ingress secure-app-ingress -n tls-nsThe TLS section must show
secure-app.example.comwith Secrettls-secret.
Exam strategy: TLS Ingress tasks have four distinct steps: generate a certificate, create a TLS Secret, create the backend (Deployment + Service), and wire up the Ingress. The openssl command and kubectl create secret tls commands are both provided in the task — you do not need to memorize them, but you must type them accurately.
The Ingress manifest requires a tls section that lists the hosts and the Secret name. The tls[].hosts must exactly match the rules[].host — a mismatch means TLS termination does not apply to the hostname. Use kubectl explain ingress.spec.tls during the exam to confirm the field structure.
The Deployment and Service are standard — use imperative commands (kubectl create deployment, kubectl expose) to save time and focus your YAML writing on the Ingress, which lacks an imperative equivalent.
Expected time: 5 minutes.
Task 14 — Pod with Ephemeral Volume and OOMKilled Behavior (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: oom-ns
Requirements
-
Create namespace
oom-ns. -
Create a Pod named
memory-hogin namespaceoom-ns:- Image:
polinux/stress - Command:
stress --vm 1 --vm-bytes 128M --vm-hang 0 - Resource requests:
memory: 64Mi,cpu: 100m - Resource limits:
memory: 100Mi,cpu: 200m - Mount an
emptyDirvolume namedtmp-dataat/tmp/data
- Image:
-
Observe the Pod status. Because the stress command allocates 128Mi but the memory limit is 100Mi, the container must be OOMKilled.
-
Verify the OOMKilled status:
kubectl get pod memory-hog -n oom-ns -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}'Expected output:
OOMKilled -
Fix the Pod by creating a new Pod named
memory-okwith the same specification but increase the memory limit to256Miand the memory request to128Mi. This Pod must reach and stay inRunningstatus.
Exam strategy: The OOMKilled state is triggered by the Linux kernel’s Out-Of-Memory killer when a process exceeds the cgroup memory limit set by Kubernetes. The key is understanding that the memory limit is an enforced ceiling — the container is killed if it exceeds this value. The memory request is a scheduling guarantee — it ensures the node has that much memory available when placing the Pod.
To verify OOMKilled, check lastState.terminated.reason (not state, because the Pod may have restarted by the time you look). The status.containerStatuses[0].restartCount field shows how many times the container has been OOMKilled and restarted.
The fix is to create a new Pod (not edit the existing one) because Pods are immutable for resource limits. You cannot change limits on a running Pod — you must delete and recreate it (or create a new one with a different name as the task specifies).
Expected time: 4 minutes.
Task 15 — ServiceAccount with Role Binding (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: rbac-ns
Requirements
-
Create namespace
rbac-ns. -
Create a ServiceAccount named
pod-reader-sain namespacerbac-ns. -
Create a Role named
pod-reader-rolein namespacerbac-nswith the following permissions:- API group:
""(core) - Resources:
pods - Verbs:
get,list,watch
- API group:
-
Create a RoleBinding named
pod-reader-bindingin namespacerbac-ns:- Bind Role
pod-reader-roleto ServiceAccountpod-reader-sa
- Bind Role
-
Create a Pod named
reader-podin namespacerbac-ns:- Image:
bitnami/kubectl:latest - Command:
sh -c "kubectl get pods -n rbac-ns && sleep 3600" serviceAccountName: pod-reader-sa
- Image:
-
Verify the Pod can list Pods in namespace
rbac-ns:kubectl logs reader-pod -n rbac-nsThe output must list Pods (at minimum, the
reader-poditself). -
Verify the ServiceAccount cannot list Pods in other namespaces:
kubectl exec reader-pod -n rbac-ns -- kubectl get pods -n defaultThis command must return a
Forbiddenerror.
Exam strategy: RBAC tasks are fast when you use imperative commands: kubectl create sa, kubectl create role, kubectl create rolebinding — three commands in sequence, under 30 seconds total. The YAML equivalent is longer and more error-prone because RoleBinding subjects require the ServiceAccount’s namespace in the subjects[].namespace field.
The distinction between Role/RoleBinding (namespace-scoped) and ClusterRole/ClusterRoleBinding (cluster-scoped) is critical. This task requires namespace-scoped access only. Using a ClusterRoleBinding would grant cross-namespace access, which violates step 7.
The bitnami/kubectl image includes kubectl inside the container, making it ideal for testing RBAC from inside a Pod. The Pod must specify serviceAccountName to use the custom ServiceAccount instead of the default one.
Expected time: 5 minutes.
Task 16 — Deployment → Service → Ingress Chain (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: app-stack
Requirements
-
Create namespace
app-stack. -
Create a Deployment named
frontendin namespaceapp-stack:- Image:
nginx:1.25 - Replicas: 3
- Labels on Pod template:
app: frontend
- Image:
-
Expose the Deployment with a ClusterIP Service named
frontend-svc:- Port: 80, targetPort: 80
- Selector:
app: frontend
-
Verify the Service has 3 endpoints:
kubectl get endpoints frontend-svc -n app-stack -
Create an Ingress named
frontend-ingressin namespaceapp-stack:ingressClassName: nginx- Annotation:
nginx.ingress.kubernetes.io/rewrite-target: / - Rule for host
frontend.example.com:- Path:
/app - PathType:
Prefix - Backend: Service
frontend-svc, port 80
- Path:
-
Verify the Ingress:
kubectl describe ingress frontend-ingress -n app-stackThe rules section must show the path
/apppointing tofrontend-svc:80.
Exam strategy: The three-resource chain (Deployment → Service → Ingress) is a common exam pattern. Use imperative commands for the Deployment and Service, then write YAML only for the Ingress. The rewrite-target annotation is essential when the Ingress path (/app) differs from the backend path (/). Without it, nginx receives requests at /app and returns 404 because the backend application serves content at /.
The kubectl expose deployment command creates the Service with a selector that matches the Deployment’s Pod labels. Verify the generated selector matches your expectations with kubectl get service frontend-svc -n app-stack -o yaml.
Expected time: 4 minutes.
Task 17 — Fix: Pod Stuck Pending (nodeSelector Mismatch) (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: schedule-ns
Requirements
-
Create namespace
schedule-ns. -
Apply the following Pod manifest:
apiVersion: v1 kind: Pod metadata: name: picky-pod namespace: schedule-ns spec: containers: - name: app image: nginx:1.25 nodeSelector: disktype: ssd -
The Pod stays in
Pendingstatus. Diagnose the problem:kubectl describe pod picky-pod -n schedule-nsThe Events section shows the scheduler cannot find a node matching
disktype=ssd. -
Fix the issue by labeling a node with the required label:
kubectl get nodes --show-labelsPick any worker node and apply the label:
kubectl label node <node-name> disktype=ssd -
Wait for the Pod to be scheduled and reach
Runningstatus:kubectl get pod picky-pod -n schedule-ns -w -
Clean up the label after the Pod is running (optional but good practice):
kubectl label node <node-name> disktype-
Exam strategy: Pending Pod diagnosis is one of the fastest tasks on the exam if you know where to look. Run kubectl describe pod <name> and read the Events section. The scheduler event message tells you exactly what constraint failed. In this case, it reports that no nodes match the nodeSelector. The fix is to label a node, not to modify the Pod (which would require deletion and recreation because nodeSelector is immutable).
After labeling the node, the scheduler re-evaluates pending Pods automatically. You do not need to restart anything. Watch the Pod with kubectl get pod -w to see it transition from Pending to Running.
Node label cleanup (kubectl label node <name> disktype-) removes the label without affecting the running Pod. nodeSelector is evaluated only at scheduling time.
Expected time: 3 minutes.
Task 18 — Job with backoffLimit and Failure Handling (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: job-ns
Requirements
-
Create namespace
job-ns. -
Create a Job named
failing-jobin namespacejob-ns:apiVersion: batch/v1 kind: Job metadata: name: failing-job namespace: job-ns spec: backoffLimit: 3 template: spec: containers: - name: worker image: busybox:1.36 command: ["sh", "-c", "echo 'Attempting task...' && exit 1"] restartPolicy: Never -
Watch the Job create Pods that fail repeatedly:
kubectl get pods -n job-ns -wAfter 4 total attempts (1 initial + 3 retries), the Job must stop creating new Pods.
-
Check the Job status:
kubectl describe job failing-job -n job-nsThe Job condition must show
Failedwith reasonBackoffLimitExceeded. -
Delete the failed Job and create a fixed version named
success-jobin namespacejob-ns:- Same specification but change the command to:
sh -c "echo 'Task completed' && exit 0" backoffLimit: 3
- Same specification but change the command to:
-
Verify the
success-jobcompletes:kubectl get job success-job -n job-nsThe
COMPLETIONScolumn must show1/1.
Exam strategy: Job failure behavior is controlled by two fields: backoffLimit (how many retries before the Job is marked failed) and restartPolicy (whether to restart in-place or create new Pods). With restartPolicy: Never, each failure creates a new Pod. With restartPolicy: OnFailure, the same Pod restarts in-place.
backoffLimit: 3 means 3 retries after the initial attempt, for a total of 4 Pod creations. The naming is counterintuitive — it limits the number of retries, not the total attempts.
Watching the failure sequence with kubectl get pods -w teaches you the exponential back-off behavior: each retry waits longer than the previous one. The Job controller uses 10s, 20s, 40s, etc. delays between Pod creations.
Expected time: 4 minutes.
Task 19 — Blue-Green Deployment (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: bg-ns
Requirements
-
Create namespace
bg-ns. -
Create the blue Deployment named
app-bluein namespacebg-ns:- Image:
hashicorp/http-echo:0.2.3 - Args:
["-listen=:5678", "-text=blue"] - Replicas: 3
- Labels on Pod template:
app: webapp,version: blue
- Image:
-
Create a Service named
webapp-svcin namespacebg-ns:- Type:
ClusterIP - Selector:
app: webapp,version: blue - Port: 80, targetPort: 5678
- Type:
-
Verify the Service returns “blue”:
kubectl run test-blue --image=curlimages/curl --rm -it --restart=Never -n bg-ns -- curl http://webapp-svc.bg-ns.svc.cluster.localExpected output:
blue -
Create the green Deployment named
app-greenin namespacebg-ns:- Image:
hashicorp/http-echo:0.2.3 - Args:
["-listen=:5678", "-text=green"] - Replicas: 3
- Labels on Pod template:
app: webapp,version: green
- Image:
-
Wait for all green Pods to be
RunningandReady. -
Switch the Service to route traffic to the green Deployment by patching the selector:
kubectl patch service webapp-svc -n bg-ns -p '{"spec":{"selector":{"version":"green"}}}' -
Verify the Service now returns “green”:
kubectl run test-green --image=curlimages/curl --rm -it --restart=Never -n bg-ns -- curl http://webapp-svc.bg-ns.svc.cluster.localExpected output:
green -
Both Deployments must remain running. The blue Deployment is kept as a rollback target.
Exam strategy: Blue-green deployment is a zero-downtime release strategy. Unlike canary (which splits traffic), blue-green runs both versions simultaneously but routes all traffic to one version at a time. The switchover is instantaneous because it changes the Service selector — no Pod restarts needed.
The key difference from canary: in canary, the Service selector matches a common label shared by both Deployments (like app: web). In blue-green, the Service selector includes the version label (version: blue or version: green) so that traffic goes entirely to one Deployment. The switch is a single kubectl patch command.
Both Deployments must remain running after the switch. The blue Deployment serves as an instant rollback target — if the green version has issues, patch the Service selector back to version: blue.
Expected time: 5 minutes.
Task 20 — Full-Stack Aggregate Task (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: fullstack-ns
Requirements
Build a complete application stack from scratch. Every resource must be in namespace fullstack-ns.
-
Namespace and Quota:
- Create namespace
fullstack-ns - Create a ResourceQuota named
stack-quotawith: max 10 Pods,requests.cpu: 2,requests.memory: 2Gi
- Create namespace
-
ConfigMap:
- Name:
app-settings - Data:
APP_ENV=production LOG_LEVEL=warn MAX_CONNECTIONS=100
- Name:
-
Secret:
- Name:
app-secrets - Data:
API_KEY=super-secret-key-2026
- Name:
-
Deployment:
- Name:
fullstack-app - Image:
nginx:1.25 - Replicas: 3
- Resource requests per container:
cpu: 100m,memory: 128Mi - Resource limits per container:
cpu: 200m,memory: 256Mi - Inject all keys from ConfigMap
app-settingsas environment variables usingenvFromwithconfigMapRef - Inject the
API_KEYfrom Secretapp-secretsas environment variableAPI_KEYusingsecretKeyRef - Labels on Pod template:
app: fullstack,tier: frontend
- Name:
-
Service:
- Name:
fullstack-svc - Type:
ClusterIP - Selector:
app: fullstack - Port: 80, targetPort: 80
- Name:
-
Ingress:
- Name:
fullstack-ingress ingressClassName: nginx- Host:
fullstack.example.com - Path:
/, PathType:Prefix - Backend: Service
fullstack-svc, port 80
- Name:
-
Verification:
# All resources exist kubectl get quota,configmap,secret,deployment,service,ingress -n fullstack-ns # Deployment has 3 ready replicas kubectl get deployment fullstack-app -n fullstack-ns # Environment variables are set kubectl exec deploy/fullstack-app -n fullstack-ns -- env | grep -E "APP_ENV|LOG_LEVEL|API_KEY" # Service has 3 endpoints kubectl get endpoints fullstack-svc -n fullstack-nsAll resources must be present and correctly wired together.
Exam strategy: This is the hardest task on the exam because it combines 6 resource types into a single coherent stack. Work in layers, verifying each layer before moving to the next:
- Namespace + Quota — verify the quota is active before creating any workloads.
- ConfigMap + Secret — use imperative commands for speed.
- Deployment — this is the most complex resource because it must reference both the ConfigMap and Secret, set resource requests/limits (required by the quota), and use the correct labels.
- Service — one imperative command.
- Ingress — write YAML.
The ResourceQuota is the hidden trap in this task. When a quota specifies requests.cpu or requests.memory, every container in the namespace must declare those fields. If you deploy the Deployment without resource requests, the Pods are rejected by the admission controller. The error message mentions the quota, not the Deployment, which can be confusing.
Budget 8 minutes for this task. If you are running low on time, prioritize getting the Deployment running with correct ConfigMap/Secret injection and the Service exposing it. The Ingress and Quota are easier to verify and less likely to have cascading failures.
Expected time: 8 minutes.
End of Mock Exam 2. Stop your timer. Record your completion time and note which tasks you skipped or struggled with. Check your answers against the solutions in Chapter 29.