Mock Exam 2 — Tasks 1–10
SummaryTen exam tasks at elevated difficulty: namespace ResourceQuotas,...
Ten exam tasks at elevated difficulty: namespace ResourceQuotas,...
Ten exam tasks at elevated difficulty: namespace ResourceQuotas, Pod SecurityContext lockdown, debugging ImagePullBackOff and probe failures, canary deployment patterns, CronJob-to-Job extraction, combined Secret and ConfigMap consumption, deny-all NetworkPolicy with namespace-scoped exceptions, StatefulSet with headless Service and volumeClaimTemplates, Service port debugging, and Helm install-upgrade-rollback workflow.
Mock Exam 2 — Tasks 1–10
Start your timer. You have 120 minutes to complete all 20 tasks across both sections. Each task specifies the cluster context and namespace. Switch contexts when instructed and create namespaces before deploying resources.
Before you begin:
- Open the Kubernetes documentation (kubernetes.io/docs) in a browser tab. Bookmark the reference pages for Pod, Deployment, Service, Ingress, NetworkPolicy, StatefulSet, Job, CronJob, and ResourceQuota. Having these pages ready saves 30–60 seconds per task compared to searching from scratch.
- Set your default editor to something fast. In the exam environment,
viornanoare available. Runexport KUBE_EDITOR=nanoif you prefer a simpler editor, or keepviif you are comfortable with modal editing. - Use
kubectl explain <resource>.<field>as your primary reference tool. It is faster than switching to a browser for field names and nesting structure. For example,kubectl explain pod.spec.securityContextgives you every available field with descriptions. - Set up shell aliases if the exam environment allows it:
alias k=kubectl,alias kgp='kubectl get pods',alias kns='kubectl config set-context --current --namespace'. These compound aliases save significant keystrokes across 20 tasks.
Task 1 — Namespace with ResourceQuota (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: Create namespace quota-lab
Requirements
-
Create a namespace called
quota-lab. -
Create a ResourceQuota named
pod-memory-quotain namespacequota-labwith the following constraints:- Maximum number of Pods: 2
- Maximum total memory requests across all Pods: 1Gi
- Maximum total memory limits across all Pods: 1Gi
-
Verify the quota is active by running:
kubectl describe resourcequota pod-memory-quota -n quota-labThe output must show the hard limits for
pods,requests.memory, andlimits.memory. -
Test the quota by creating three Pods with
memory: 256Mirequest and limit each. The third Pod must be rejected.
Exam strategy: This task combines namespace creation with a ResourceQuota, which has no imperative shortcut — you must write YAML. Use kubectl explain resourcequota.spec.hard to find the exact field names. The verification step (creating three Pods) is critical — do not skip it, as the grading environment may check whether quota enforcement is active.
Expected time: 4 minutes. Spend no more than 2 minutes writing the ResourceQuota YAML, 1 minute applying and verifying, and 1 minute on the test Pods.
Documentation reference: Search for “Resource Quotas” on kubernetes.io/docs. The Concepts → Policy → ResourceQuotas page lists all supported resource names you can constrain.
Task 2 — Pod with Strict SecurityContext (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: secure-app
Requirements
-
Create namespace
secure-app. -
Create a Pod named
locked-podin namespacesecure-appwith these specifications:- Image:
nginx:1.25 - The Pod-level security context must set
runAsNonRoot: true - The container-level security context must set:
readOnlyRootFilesystem: trueallowPrivilegeEscalation: false- Drop ALL capabilities
runAsUser: 1000runAsGroup: 3000
- Image:
-
The Pod will crash because nginx needs to write to certain directories. Add two
emptyDirvolume mounts to make it work:- Mount an emptyDir at
/var/cache/nginx - Mount an emptyDir at
/var/run
- Mount an emptyDir at
-
The Pod must reach
Runningstatus.
Exam strategy: Security context tasks require you to know where each field goes — Pod-level vs container-level. The most reliable approach is to draft the YAML first with all security fields, apply it, watch it crash, then add the emptyDir volumes to fix the crash. Do not try to get it right in one shot; applying, observing failure logs (kubectl logs locked-pod -n secure-app), and iterating is faster than trying to memorize every nginx directory that needs write access.
Expected time: 5 minutes. The security context YAML takes 2–3 minutes to write correctly. Debugging the volume mounts takes 2 minutes.
Key fields to remember:
spec.securityContext(Pod-level):runAsNonRoot,runAsUser,runAsGroupspec.containers[].securityContext(container-level):readOnlyRootFilesystem,allowPrivilegeEscalation,capabilities.drop
Use kubectl explain pod.spec.securityContext and kubectl explain pod.spec.containers.securityContext to confirm the field placement during the exam.
Task 3 — Fix Broken Deployment (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: debug-deploy
Requirements
-
Create namespace
debug-deploy. -
Apply the following Deployment manifest:
apiVersion: apps/v1 kind: Deployment metadata: name: broken-web namespace: debug-deploy spec: replicas: 2 selector: matchLabels: app: broken-web template: metadata: labels: app: broken-web spec: containers: - name: web image: ngnix:1.25 ports: - containerPort: 80 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 5 -
The Deployment has two errors. Identify and fix both:
- Error 1: The image name is misspelled, causing
ImagePullBackOff. - Error 2: The readiness probe targets port
8080, but nginx listens on port80. Additionally, nginx does not serve a/healthzendpoint — change the path to/.
- Error 1: The image name is misspelled, causing
-
After applying fixes, all 2 replicas must be
RunningandReady.
Exam strategy: Debugging tasks require a systematic approach. Follow this sequence every time:
kubectl get pods -n <namespace>— check the STATUS column for clues (ImagePullBackOff,CrashLoopBackOff,Runningbut notReady).kubectl describe pod <pod-name> -n <namespace>— read the Events section at the bottom for scheduling errors, pull failures, or probe failures.kubectl logs <pod-name> -n <namespace>— check container stdout/stderr for application-level errors.
Applying fixes in order of severity (image pull first, then probe configuration) avoids masking one error with another.
Expected time: 4 minutes. Diagnosis takes 1–2 minutes, and applying fixes (image name correction + probe adjustment) takes 2 minutes. Use kubectl set image for the image fix — it is the fastest imperative option.
Watch out for: The readiness probe path /healthz is a common Kubernetes convention, but nginx does not have this endpoint by default. The correct path for a basic nginx readiness check is / (which returns 200 on the default welcome page). Some candidates change the path to /index.html, which also works but is less idiomatic.
Task 4 — Canary Deployment (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: canary-ns
Requirements
-
Create namespace
canary-ns. -
Create a Deployment named
web-stablein namespacecanary-ns:- Image:
nginx:1.24 - Replicas: 4
- Labels on the Pod template:
app: web,track: stable
- Image:
-
Create a Service named
web-svcin namespacecanary-ns:- Type:
ClusterIP - Selector:
app: web(note: only theapplabel, nottrack) - Port: 80, targetPort: 80
- Type:
-
Create a second Deployment named
web-canaryin the same namespace:- Image:
nginx:1.25 - Replicas: 1
- Labels on the Pod template:
app: web,track: canary
- Image:
-
Verify the Service endpoints include all 5 Pods (4 stable + 1 canary):
kubectl get endpoints web-svc -n canary-nsThe endpoint list must contain 5 IP addresses.
Exam strategy: The canary pattern is a deployment strategy question. The core concept: a Service that selects on a shared label (app: web) routes traffic to all matching Pods across multiple Deployments. The track label differentiates stable from canary but is not in the Service selector. If you include track in the Service selector, only one set of Pods receives traffic — defeating the entire purpose of a canary deployment.
The traffic split ratio is determined by the replica counts: 4 stable + 1 canary = 80% stable, 20% canary. This is a coarse-grained approach compared to service mesh-based canary deployments, but it is the method testable within the CKAD exam scope.
Expected time: 5 minutes. Create both Deployments (2 minutes), create the Service with careful selector choice (1 minute), verify endpoints (1 minute), and review labels (1 minute).
Documentation reference: The Kubernetes documentation does not have a dedicated “canary deployment” page. The relevant concept is covered under “Managing Deployments” and “Labels and Selectors”. Practice this pattern from memory.
Task 5 — CronJob and Job Extraction (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: batch-ns
Requirements
-
Create namespace
batch-ns. -
Create a CronJob named
log-timestampin namespacebatch-ns:- Schedule:
*/5 * * * *(every 5 minutes) - Image:
busybox:1.36 - Command:
sh -c "echo Timestamp: $(date) && sleep 5" restartPolicy: NeversuccessfulJobsHistoryLimit: 3failedJobsHistoryLimit: 1
- Schedule:
-
Without waiting for the CronJob to trigger, manually create a Job from it:
kubectl create job log-now --from=cronjob/log-timestamp -n batch-ns -
Wait for the Job
log-nowto complete. Verify its logs contain the timestamp output:kubectl logs job/log-now -n batch-ns -
Confirm the CronJob still exists and is scheduled:
kubectl get cronjob log-timestamp -n batch-ns
Exam strategy: The kubectl create job --from=cronjob/<name> command is a powerful shortcut that many candidates do not know exists. It creates a one-off Job using the CronJob’s exact template — identical container spec, volumes, restart policy — without waiting for the scheduled trigger. Memorize this command: it appears frequently in CKAD exams and takes only seconds to execute.
For the CronJob creation itself, the imperative command kubectl create cronjob supports --schedule, --image, and the container command. However, it does not support successfulJobsHistoryLimit or failedJobsHistoryLimit — those require a YAML manifest or a post-creation kubectl patch.
Expected time: 4 minutes. CronJob YAML (2 minutes), create job --from (30 seconds), verification (1.5 minutes).
Cron schedule syntax refresher: */5 * * * * means “every 5 minutes”. The five fields are: minute, hour, day-of-month, month, day-of-week. Use kubectl explain cronjob.spec.schedule to confirm this during the exam.
Task 6 — Pod with Secret (env) and ConfigMap (volume) (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: config-ns
Requirements
-
Create namespace
config-ns. -
Create a Secret named
db-credentialsin namespaceconfig-nswith the following key-value pairs:DB_USER=adminDB_PASS=exam-secret-2026
-
Create a ConfigMap named
app-configin namespaceconfig-nswith a file-like key:- Key:
app.properties - Value:
log.level=INFO cache.ttl=300 feature.flag=true
- Key:
-
Create a Pod named
config-consumerin namespaceconfig-ns:- Image:
busybox:1.36 - Command:
sh -c "echo DB_USER=$DB_USER DB_PASS=$DB_PASS && cat /config/app.properties && sleep 3600" - Environment variables
DB_USERandDB_PASSinjected from Secretdb-credentialsusingsecretKeyRef - The ConfigMap
app-configmounted as a volume at/config
- Image:
-
Verify the Pod is running and the environment variables and mounted file are correct:
kubectl exec config-consumer -n config-ns -- env | grep DB_ kubectl exec config-consumer -n config-ns -- cat /config/app.properties
Exam strategy: This task tests two separate injection mechanisms in a single Pod. The critical distinction is:
- Secret → environment variable via
valueFrom.secretKeyRefon individual keys. This injects specific key-value pairs as env vars. - ConfigMap → volume via
volumes[].configMapandvolumeMounts. This mounts all keys in the ConfigMap as files inside the container.
The common mistake is to use envFrom for both (which works but does not match the requirement) or to mount the Secret as a volume (which also works but is not what was asked). Read the task carefully: it specifies secretKeyRef for the Secret and volume mount for the ConfigMap.
Expected time: 5 minutes. Create the Secret imperatively (30 seconds), create the ConfigMap using --from-file or --from-literal (1 minute), write the Pod YAML with both env and volume blocks (2.5 minutes), verify (1 minute).
Speed optimization: Create the Secret and ConfigMap imperatively before writing any YAML. Then write the Pod manifest with references to both. This is faster than writing a single large YAML that includes all three resources.
Task 7 — NetworkPolicy: Deny All Then Allow by Namespace (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: netpol-ns
Requirements
-
Create two namespaces:
netpol-nstrusted-ns
-
Label the
trusted-nsnamespace:kubectl label namespace trusted-ns purpose=trusted -
Create a Pod named
web-serverin namespacenetpol-ns:- Image:
nginx:1.25 - Labels:
app: web-server
- Image:
-
Create a deny-all ingress NetworkPolicy named
deny-all-ingressin namespacenetpol-ns:- Apply to all Pods in the namespace (use
podSelector: {}) - Define
policyTypes: ["Ingress"] - Provide an empty
ingress: []list (no rules = deny all)
- Apply to all Pods in the namespace (use
-
Create a second NetworkPolicy named
allow-from-trustedin namespacenetpol-ns:- Apply to Pods with label
app: web-server - Allow ingress traffic from Pods in namespaces labeled
purpose: trusted - Allow traffic on port 80 (TCP)
- Apply to Pods with label
-
To verify (conceptually — actual traffic tests depend on CNI support):
- A Pod in
trusted-nsshould reachweb-serveron port 80 - A Pod in any other namespace should be blocked
If your cluster CNI supports NetworkPolicy enforcement (Calico, Cilium, Weave Net — but not Flannel by default), you can run an actual traffic test:
# From trusted-ns — should succeed kubectl run test-trusted --image=curlimages/curl --rm -it --restart=Never -n trusted-ns -- curl --max-time 5 http://<web-server-pod-ip>:80 # From default namespace — should time out kubectl run test-default --image=curlimages/curl --rm -it --restart=Never -- curl --max-time 5 http://<web-server-pod-ip>:80 - A Pod in
Exam strategy: NetworkPolicy tasks have two traps: understanding the deny-all baseline and the additive allow rule. NetworkPolicies are additive — if any policy allows traffic, it passes. The deny-all policy works by selecting all Pods (podSelector: {}) and providing zero ingress rules. The allow-from-trusted policy then adds an exception for a specific namespace.
The namespaceSelector field in the allow rule matches namespaces by their labels — not by name. You must label the source namespace first, then reference that label in the NetworkPolicy. Forgetting to label the namespace is the most common failure mode.
Expected time: 5 minutes. Namespace creation and labeling (1 minute), Pod creation (30 seconds), both NetworkPolicies (2.5 minutes), verification (1 minute).
Documentation reference: Search for “Network Policies” on kubernetes.io/docs. The Concepts → Services, Load Balancing, and Networking → Network Policies page has example deny-all and allow-by-namespace policies you can adapt.
Task 8 — StatefulSet with Headless Service and volumeClaimTemplates (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: stateful-ns
Requirements
-
Create namespace
stateful-ns. -
Create a headless Service named
db-headlessin namespacestateful-ns:clusterIP: None- Selector:
app: db - Port: 5432, targetPort: 5432
-
Create a StatefulSet named
dbin namespacestateful-ns:- Replicas: 3
serviceName: db-headless- Selector and Pod template labels:
app: db - Container:
- Name:
postgres - Image:
postgres:16 - Port: 5432
- Environment variable:
POSTGRES_PASSWORD=exam-pass
- Name:
volumeClaimTemplates:- Name:
data accessModes: ["ReadWriteOnce"]- Storage request:
1Gi - Mount path:
/var/lib/postgresql/data
- Name:
-
Wait for all 3 Pods to reach
Runningstatus. They should be nameddb-0,db-1,db-2. -
Verify the headless DNS entries exist:
kubectl exec db-0 -n stateful-ns -- nslookup db-headless.stateful-ns.svc.cluster.local -
Verify PVCs were created:
kubectl get pvc -n stateful-nsThree PVCs named
data-db-0,data-db-1,data-db-2must exist.
Exam strategy: StatefulSet tasks have the most YAML of any resource type because they combine a Deployment-like workload spec with volumeClaimTemplates and require a headless Service to function correctly. Write the headless Service first — it must exist before the StatefulSet is applied, otherwise DNS resolution for individual Pods does not work.
Key differences from a Deployment:
serviceNamefield is required and must match the headless Service name.- Pods are created sequentially (db-0, then db-1, then db-2). Each Pod must reach Running before the next one starts.
- PVCs are named
<volumeClaimTemplate-name>-<statefulset-name>-<ordinal>— in this case,data-db-0,data-db-1,data-db-2. - Deleting a StatefulSet does not delete its PVCs. This is by design to prevent data loss.
The headless Service must have clusterIP: None. Any other value creates a regular ClusterIP Service, which breaks the StatefulSet’s per-Pod DNS records (db-0.db-headless.stateful-ns.svc.cluster.local).
Expected time: 6 minutes. Headless Service YAML (1 minute), StatefulSet YAML (3 minutes), waiting for Pods to start (1 minute), verification of PVCs and DNS (1 minute).
Watch out for: The postgres image requires the POSTGRES_PASSWORD environment variable. Without it, the container exits immediately with an error. This is not a Kubernetes issue — it is a postgres Docker image requirement.
Task 9 — Debug: Pod Returns 503 (5%)
Context: kubectl config use-context ckad-cluster1
Namespace: debug-svc
Requirements
-
Create namespace
debug-svc. -
Apply the following resources:
apiVersion: v1 kind: Pod metadata: name: api-server namespace: debug-svc labels: app: api-server spec: containers: - name: api image: hashicorp/http-echo:0.2.3 args: - "-listen=:5678" - "-text=healthy" ports: - containerPort: 5678 readinessProbe: httpGet: path: /ready port: 5678 initialDelaySeconds: 2 periodSeconds: 3 --- apiVersion: v1 kind: Service metadata: name: api-svc namespace: debug-svc spec: selector: app: api-server ports: - port: 80 targetPort: 8080 -
The Pod runs but the Service returns no response. There are two problems:
-
Problem 1: The readiness probe hits
/ready, buthttp-echoresponds with 200 on any path. However, the probe is fine — the real issue is the Pod never becomes an endpoint. Check the ServicetargetPort: it points to8080, but the container listens on5678. Fix the ServicetargetPortto5678. -
Problem 2: Verify the fix by confirming the Pod appears in the Service endpoints:
kubectl get endpoints api-svc -n debug-svc
-
-
Test the Service:
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -n debug-svc -- curl http://api-svc.debug-svc.svc.cluster.localExpected output:
healthy
Exam strategy: Debugging Service connectivity is a three-step process:
- Check the Pod status. Is it Running and Ready? If not, fix the Pod first.
- Check the Service endpoints. Run
kubectl get endpoints <service-name> -n <namespace>. If the endpoint list is empty, the Service selector does not match any Ready Pods, or thetargetPortis wrong. - Check the port mapping. The Service
targetPortmust match the port the container is actually listening on. In this task, the container listens on5678but the Service targets8080. This mismatch means the Service forwards traffic to a port that nothing is listening on — resulting in connection refused or timeout errors.
The http-echo image is particularly useful for debugging because it responds to any HTTP path with a 200 status code and the configured text. This means the readiness probe at /ready works fine (it returns 200). The problem is purely a Service port mismatch, not a Pod readiness issue.
Expected time: 4 minutes. Diagnosis (2 minutes), fix (1 minute), verification (1 minute).
Task 10 — Helm: Install, Upgrade, Rollback (5%)
Context: kubectl config use-context ckad-cluster2
Namespace: helm-ns
Requirements
-
Create namespace
helm-ns. -
Add the Bitnami Helm repository (if not already added):
helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update -
Install a Helm release named
my-nginxin namespacehelm-nsusing thebitnami/nginxchart:helm install my-nginx bitnami/nginx -n helm-ns --set replicaCount=1 -
Verify the release is deployed:
helm list -n helm-nsStatus must show
deployed. -
Upgrade the release to set
replicaCount=3:helm upgrade my-nginx bitnami/nginx -n helm-ns --set replicaCount=3 -
Verify the upgrade by checking that the Deployment now has 3 replicas:
kubectl get deployment -n helm-ns -
View the release history:
helm history my-nginx -n helm-nsTwo revisions must appear.
-
Roll back to revision 1:
helm rollback my-nginx 1 -n helm-ns -
Confirm rollback: the Deployment must return to 1 replica, and
helm historymust show 3 revisions (original, upgrade, rollback).
Exam strategy: Helm tasks follow a predictable pattern: add repo, install, upgrade, rollback. The commands are short and imperative — no YAML needed. The main risk is forgetting the -n <namespace> flag, which causes Helm to operate in the default namespace.
Key commands to memorize:
helm repo add <name> <url>— adds a chart repositoryhelm repo update— refreshes the local index of all repositorieshelm install <release> <chart> -n <ns>— installs a releasehelm upgrade <release> <chart> -n <ns> --set key=value— upgrades with new valueshelm rollback <release> <revision> -n <ns>— rolls back to a specific revisionhelm history <release> -n <ns>— shows revision historyhelm list -n <ns>— shows installed releases in a namespace
A rollback creates a new revision, not a revert. After installing (rev 1), upgrading (rev 2), and rolling back (rev 3), helm history shows three entries. The current state matches revision 1’s configuration but carries revision number 3.
Expected time: 5 minutes. Repo setup (1 minute), install and verify (1 minute), upgrade and verify (1 minute), rollback and verify (2 minutes).
Documentation reference: The Helm documentation at helm.sh/docs covers all commands. In the exam, this site is an allowed reference. Bookmark the “Helm Commands” quickstart page.
End of Section 1. Proceed to Section 2 (Tasks 11–20). Do not stop your timer.