# CKA Mock Exam Pack v2 — 120 min, harder cut > 17 weighted tasks, 120 minutes, pass = 66. Range: kind `drills` (+ Cilium, Gateway API CRDs, metrics-server, helm). > **Difficulty delta vs v1:** control-plane components are broken and *tasks interact* — damage you don't fix early will block tasks you attempt later. Triage is part of the score. > Kind-isms: `ssh nodeX` → `docker exec -it drills- bash`. One kubectl context. ## PROTOCOL 1. Save MASTER SETUP to `/tmp/setup2.sh`, run blind: `bash /tmp/setup2.sh > /tmp/setup2.log 2>&1`. **Do not read it — spoilers.** 2. `mkdir -p /tmp/exam2`. Hard 120:00 timer. No pauses, no Claude, no solutions file. 3. Any order. Flag >8 min. Last 10 min = verification lap. 4. After the timer: run VALIDATE blocks, fill the score sheet, then open the solutions doc for the autopsy. ## MASTER SETUP (paste blind) ```bash #!/usr/bin/env bash set +e for ns in ex2-neptune ex2-web ex2-canary ex2-data ex2-guard ex2-front ex2-batch ex2-side ex2-store; do kubectl create ns $ns; done # workloads FIRST (scheduler dies later) kubectl -n ex2-front create deploy shop-ui --image=nginx --replicas=2 kubectl -n ex2-front patch deploy shop-ui --type=json -p='[{"op":"add","path":"/spec/template/spec/containers/0/readinessProbe","value":{"httpGet":{"path":"/","port":8080},"periodSeconds":5}}]' kubectl -n ex2-data run db --image=nginx --labels=role=db --port=80 kubectl -n ex2-data run app --image=nginx --labels=role=app --port=80 kubectl -n ex2-data run rogue --image=nginx --labels=role=rogue --port=80 kubectl -n ex2-web create deploy web-v1 --image=hashicorp/http-echo -- /http-echo -text=v1 -listen=:5678 kubectl -n ex2-web expose deploy web-v1 --port=5678 kubectl -n ex2-canary create deploy web-v2 --image=hashicorp/http-echo -- /http-echo -text=v2 -listen=:5678 kubectl -n ex2-canary expose deploy web-v2 --port=5678 kubectl apply -f - <<'EOF' apiVersion: gateway.networking.k8s.io/v1 kind: GatewayClass metadata: {name: exam2-class} spec: {controllerName: example.com/exam2} --- apiVersion: gateway.networking.k8s.io/v1 kind: Gateway metadata: {name: shop-gate, namespace: ex2-web} spec: gatewayClassName: exam2-class listeners: [{name: http, port: 80, protocol: HTTP}] EOF kubectl -n ex2-neptune create deploy pinned --image=nginx --replicas=2 kubectl -n ex2-neptune patch deploy pinned --type=json -p='[{"op":"add","path":"/spec/template/spec/nodeSelector","value":{"kubernetes.io/hostname":"drills-worker2"}}]' kubectl apply -f - <<'EOF' apiVersion: policy/v1 kind: PodDisruptionBudget metadata: {name: pinned-pdb, namespace: ex2-neptune} spec: maxUnavailable: 0 selector: {matchLabels: {app: pinned}} EOF kubectl label ns ex2-guard env=guarded # storage scenarios kubectl apply -f - <<'EOF' apiVersion: v1 kind: PersistentVolume metadata: {name: keeper-pv} spec: capacity: {storage: 1Gi} accessModes: [ReadWriteOnce] persistentVolumeReclaimPolicy: Retain storageClassName: keeper hostPath: {path: /opt/keeper} --- apiVersion: v1 kind: PersistentVolumeClaim metadata: {name: old-claim, namespace: ex2-store} spec: accessModes: [ReadWriteOnce] storageClassName: keeper resources: {requests: {storage: 1Gi}} EOF sleep 5; kubectl -n ex2-store delete pvc old-claim # -> PV Released kubectl apply -f - <<'EOF' apiVersion: v1 kind: PersistentVolume metadata: {name: narrow-pv} spec: capacity: {storage: 1Gi} accessModes: [ReadWriteOnce] storageClassName: narrow hostPath: {path: /opt/narrow} --- apiVersion: v1 kind: PersistentVolumeClaim metadata: {name: wide-claim, namespace: ex2-store} spec: accessModes: [ReadWriteMany] storageClassName: narrow resources: {requests: {storage: 1Gi}} EOF # broken helm chart mkdir -p /tmp/exam2 && cd /tmp/exam2 && helm create shipper >/dev/null 2>&1 sed -i 's/^apiVersion: v2/apiVersion: v3/' /tmp/exam2/shipper/Chart.yaml sed -i 's/{{ .Values.replicaCount }}/{{ .Values.replicaCount }/' /tmp/exam2/shipper/templates/deployment.yaml # broken kubeconfig for developer kubectl config view --raw --minify > /tmp/exam2/developer.kubeconfig SRV=$(grep server /tmp/exam2/developer.kubeconfig | awk '{print $2}') PORT=${SRV##*:}; NEW=$((PORT+1)) sed -i "s|$SRV|${SRV%:*}:$NEW|" /tmp/exam2/developer.kubeconfig kubectl -n ex2-front wait --for=condition=Available deploy --all --timeout=90s kubectl -n ex2-data wait --for=condition=Ready pod --all --timeout=90s # BREAK coredns kubectl -n kube-system get cm coredns -o yaml > /tmp/exam2/.coredns-backup.yaml kubectl -n kube-system patch cm coredns --type merge -p '{"data":{"Corefile":".:53 {\n errors\n forward . /etc/resolv.conf\n bogusplugin\n}\n"}}' kubectl -n kube-system rollout restart deploy coredns # BREAK scheduler (last) docker exec drills-control-plane sed -i 's|kube-scheduler|kube-schedulerr|' /etc/kubernetes/manifests/kube-scheduler.yaml echo "SETUP2 DONE" ``` --- ## THE EXAM ### Q1 — 8% — Troubleshooting New Pods across the cluster stay `Pending` with no scheduling events. A control-plane component is at fault. Find it, fix it on the node, prove new pods schedule again. Component name → `/tmp/exam2/q1.txt`. ### Q2 — 7% — Troubleshooting Cluster DNS is down (CoreDNS crash-looping). Diagnose and repair so that a busybox:1.28 pod can `nslookup kubernetes.default`. Root cause, one line → `/tmp/exam2/q2.txt`. ### Q3 — 6% — Troubleshooting The kubeconfig at `/tmp/exam2/developer.kubeconfig` is broken — `kubectl --kubeconfig /tmp/exam2/developer.kubeconfig get nodes` fails. Fix **the file** (not your admin config) so the command succeeds. Fault, one line → `/tmp/exam2/q3.txt`. ### Q4 — 5% — Troubleshooting Drain node `drills-worker2` (ignore DaemonSets). Something will block it. Resolve the blocker with the **minimal** change that still keeps ≥1 replica of the affected app available at all times, complete the drain, then uncordon. Blocker → `/tmp/exam2/q4.txt`. ### Q5 — 4% — Troubleshooting Deployment `shop-ui` in `ex2-front` shows 0/2 READY though containers run. Find why and fix the deployment (the container itself is fine). ### Q6 — 8% — Cluster Architecture Take an etcd snapshot to `/root/etcd-v2.db` on the control-plane node. Then create ConfigMap `marker` (`kubectl create cm marker -n default --from-literal=state=after-backup`), and **restore the snapshot** so that `marker` no longer exists. Prove it. ### Q7 — 6% — Cluster Architecture The Helm chart at `/tmp/exam2/shipper` fails to install. Find and fix **all** faults (start with `helm lint`), then install it as release `shipper` in namespace `ex2-batch` with 2 replicas set via CLI. ### Q8 — 6% — Cluster Architecture Namespace `ex2-guard` is labeled `env=guarded`. Create a ValidatingAdmissionPolicy `require-owner` + binding: every Deployment created in namespaces labeled `env=guarded` must carry a label `owner` (any value); violations are **denied** with a message mentioning "owner". Prove: one denied create, one accepted. ### Q9 — 5% — Cluster Architecture Under `/tmp/exam2/kz`, build a kustomization (no base reuse — from scratch): deploys `nginx:1.27` deployment `board`, namespace `ex2-batch`, replicas patched to 3 via a **JSON6902 patch**, plus a generated ConfigMap `board-cfg` with `MODE=exam`. Apply with `-k`. The deployment must reference the generated ConfigMap via `envFrom` (hash suffix handled by kustomize). ### Q10 — 8% — Services & Networking Create HTTPRoute `split-route` in `ex2-web` on Gateway `shop-gate`: path prefix `/shop`, traffic split **90%** to `web-v1:5678` (same ns) and **10%** to `web-v2:5678` — which lives in namespace `ex2-canary`. Make the cross-namespace backend reference legal. Spec-level correctness counts. ### Q11 — 7% — Services & Networking In `ex2-data`: Pods `role=app` may send egress **only** to Pods `role=db` on TCP 80, plus DNS (TCP+UDP 53) anywhere. Everything else outbound denied. Policy name `app-egress`. Prove: app→db works, app→rogue times out. ### Q12 — 5% — Services & Networking Expose deployment `web-v1` in `ex2-web` via a NodePort service `web-np` on port 5678, nodePort **30080**. Prove reachability with curl **from a node** (docker exec). ### Q13 — 6% — Workloads & Scheduling Create PriorityClass `exam-critical` (value 100000, not default). In `ex2-batch` create deployment `spread-app` (nginx, 4 replicas, priorityClassName `exam-critical`) with a topologySpreadConstraint: maxSkew 1 over `kubernetes.io/hostname`, `DoNotSchedule`. End state: 2 pods per worker. ### Q14 — 5% — Workloads & Scheduling Create DaemonSet `node-agent` in `ex2-batch` (busybox, `sleep 3600`) that runs on **all three nodes including the control-plane**. Prove 3/3. ### Q15 — 4% — Workloads & Scheduling In `ex2-side`: deployment `audit-app` (1 replica): main container `app` (busybox) writes the date to `/var/log/audit/audit.log` every 5s; **native sidecar** `shipper` (busybox) tails it. Shared emptyDir. Prove heartbeats via `kubectl logs ... -c shipper`. ### Q16 — 6% — Storage PV `keeper-pv` is `Released` and must be reused **without deleting the PV or its data**. Make it bindable again and bind a new PVC `new-claim` (1Gi, RWO, class `keeper`) in `ex2-store`. Both must reach `Bound`. What field did you touch → `/tmp/exam2/q16.txt`. ### Q17 — 4% — Storage PVC `wide-claim` in `ex2-store` is `Pending` and will never bind. Diagnose; fix by changing **the claim** so it binds to PV `narrow-pv`. Root cause → `/tmp/exam2/q17.txt`. --- ## VALIDATE (after the timer) ```bash # Q1 kubectl -n default run probe --image=nginx --restart=Never && sleep 5 && kubectl get pod probe # Running cat /tmp/exam2/q1.txt # kube-scheduler # Q2 kubectl run dnstest --image=busybox:1.28 --restart=Never --rm -it -- nslookup kubernetes.default # resolves # Q3 kubectl --kubeconfig /tmp/exam2/developer.kubeconfig get nodes # 3 nodes # Q4 kubectl get node drills-worker2 # Ready, SchedulingDisabled absent kubectl -n ex2-neptune get pdb pinned-pdb -o jsonpath='{.spec}' # loosened but still protective kubectl -n ex2-neptune get deploy pinned # 2/2 (back after uncordon or rescheduled) # Q5 kubectl -n ex2-front get deploy shop-ui # 2/2 # Q6 kubectl get cm marker -n default # NotFound (proof of restore) docker exec drills-control-plane ls -la /root/etcd-v2.db # exists # Q7 helm list -n ex2-batch | grep shipper # deployed kubectl -n ex2-batch get deploy -l app.kubernetes.io/name=shipper -o jsonpath='{.items[0].spec.replicas}' # 2 # Q8 kubectl -n ex2-guard create deploy bad --image=nginx 2>&1 | grep -i owner # denied, message mentions owner kubectl -n ex2-guard create deploy good --image=nginx && kubectl -n ex2-guard label deploy good owner=me # wrong order — see solutions kubectl -n ex2-guard delete deploy good 2>/dev/null # Q9 kubectl -n ex2-batch get deploy board -o jsonpath='{.spec.replicas}' # 3 kubectl -n ex2-batch get cm | grep board-cfg # board-cfg- # Q10 kubectl -n ex2-web get httproute split-route -o yaml # weights 90/10, ns on the v2 backendRef kubectl -n ex2-canary get referencegrant -o yaml # allows HTTPRoute/ex2-web -> Service # Q11 DBIP=$(kubectl -n ex2-data get pod db -o jsonpath='{.status.podIP}') RGIP=$(kubectl -n ex2-data get pod rogue -o jsonpath='{.status.podIP}') kubectl -n ex2-data exec app -- curl -s -m 2 $DBIP >/dev/null && echo DB-OK kubectl -n ex2-data exec app -- curl -s -m 2 $RGIP; echo EXIT=$? # EXIT=28 # Q12 docker exec drills-worker curl -s -m 2 localhost:30080 # v1 # Q13 kubectl -n ex2-batch get pods -l app=spread-app -o wide # 2 + 2 across workers # Q14 kubectl -n ex2-batch get ds node-agent # DESIRED 3, READY 3 # Q15 kubectl -n ex2-side logs deploy/audit-app -c shipper | tail -3 # dated lines kubectl -n ex2-side get pod -o jsonpath='{.items[0].spec.initContainers[0].restartPolicy}' # Always # Q16 kubectl get pv keeper-pv # Bound to ex2-store/new-claim cat /tmp/exam2/q16.txt # claimRef # Q17 kubectl -n ex2-store get pvc wide-claim # Bound cat /tmp/exam2/q17.txt # accessModes mismatch ``` ## SCORE SHEET | Q | W | Dom | Pass | Time | | Q | W | Dom | Pass | Time | |---|---|---|---|---|---|---|---|---|---|---| | 1 | 8 | TS | | | | 10 | 8 | SN | | | | 2 | 7 | TS | | | | 11 | 7 | SN | | | | 3 | 6 | TS | | | | 12 | 5 | SN | | | | 4 | 5 | TS | | | | 13 | 6 | WS | | | | 5 | 4 | TS | | | | 14 | 5 | WS | | | | 6 | 8 | CA | | | | 15 | 4 | WS | | | | 7 | 6 | CA | | | | 16 | 6 | ST | | | | 8 | 6 | CA | | | | 17 | 4 | ST | | | | 9 | 5 | CA | | | | | | | | | **Pass: 66.** Rebuild the range between attempts (`kind delete cluster --name drills` + range-up) — v2's damage is deep enough that RESET scripts lie.