Certifications · CKA · Mock Exam · Set 1

CKA Mock Exam · Set 1 — 16 tasks, one clock, 100 points

This is the closest a static page can get to actually sitting the CKA: 16 performance-based tasks, 100 points, weighted 25/15/20/10/30 to match the real CKA domains, against one unbroken 2-hour block on a cluster you build yourself. There's no question bank here and nothing to memorize — every task hands you a broken, missing, or half-finished piece of a real kubeadm lab (a node that never joined, a Pod stuck crash-looping, a PVC that won't bind) and grades only the state you leave it in, exactly like the real exam does. Each task below comes with a done-when check so you can grade yourself honestly, and a full worked solution — real commands, real YAML — folded behind a summary so you're never tempted to peek before you've actually tried. If the mechanics under any of the five domains still feel shaky, work the study plan and this course's Exam Blueprint pages first; this paper measures whether you can execute under a clock, not whether you've read about it.

☺ Explain it like I'm 10

Most tests hand you a worksheet full of questions about a building. This is more like being handed the keys to a real, slightly broken clubhouse and told: "You have two hours. Sixteen things in here are wrong or missing — a new member who can't get in, a light that won't turn off, a mailbox nobody can find, a door that's locked to everyone including the people who should have a key. Fix what you can, prove each fix actually works, and don't spend twenty minutes on the one stuck window while three easy doors sit unopened." Nobody is going to ask you to describe how a lock works. They're going to hand you the actual broken lock.

🐢🐰Your hosts for this topic: Timmy the Turtle & Remy the Rabbit — Timmy holds the stopwatch and won't credit a fix nobody re-verified, and Remy is the reminder that on this exam, knowing the answer and typing it fast enough are two completely different skills.

How this paper is built, and where it fits your prep

☺ Like you're 10: This is a full practice game, not sixteen separate practice drills — the same skills you've built one at a time, now back to back, on a clock that never pauses.

The real CKA doesn't publish which task is worth how many points, but it does publish the five domains' weights, and this paper takes that seriously: 25 points for Cluster Architecture, Installation & Configuration, 15 for Workloads & Scheduling, 20 for Services & Networking, 10 for Storage, and 30 for Troubleshooting — the same 25/15/20/10/30 split the study plan builds its nine weeks around. Below, that turns into 16 tasks (4 + 3 + 3 + 2 + 4), each with its own point value and its own done-when check. For teaching clarity this page groups tasks by domain, with the worked solution sitting right below each one — the real exam has no such courtesy, drawing tasks from several separate clusters in an order that ignores domain boundaries entirely. Once you've sat this paper once and read every solution, get a second, more realistic rep by writing the sixteen task numbers on paper, shuffling them, and re-attempting your own cluster in that random order with the solutions covered.

StageWhat you doWhat it tells you
1 · Build fluencyWork the five blueprint pages and drill the kubectl fluency baseline until none of the commands below are new to you.Whether you know the material at all — untimed.
2 · Sit Set 1This page. One 2-hour block, no pausing, the full 100-point paper.Your pacing and verification discipline under a clock — can you bank 66+ points, not just eventually get there.
3 · Fix gapsRe-drill only the domains where you dropped points, using CKA Practice Tasks and the matching capstone lab part or drill.Converts a diagnosis into targeted practice instead of a full re-read.
4 · Sit Sets 2 & 3Set 2 and Set 3 — fresh papers, spaced out closer to your real exam date.Your actual readiness on tasks you haven't already seen the answer to.
◆ Why the points sit where they do

A 30-point domain and a 10-point domain sitting side by side on the same paper is the whole lesson. Troubleshooting alone is worth as much as Storage and Workloads & Scheduling combined — so a candidate who nails every Storage task and freezes on Troubleshooting can still fail outright, while one who's shaky on Storage but fluent at triage has real room to spare. Score yourself by domain, not just by total, and you'll see exactly which shape your own knowledge has.

Before you start — build the lab cluster yourself

☺ Like you're 10: There's no download link for "one broken clubhouse" — you build the clubhouse yourself, from real materials, so the fixes you make are fixes you actually earned.

This page has no vendor-hosted environment behind it — it can't, being a static site. Build a throwaway 4-node kubeadm cluster the night before: one control-plane node and two already-joined workers running Kubernetes v1.35.0 on containerd with Calico as the CNI, plus a fourth machine — prepared with containerd, kubelet and kubeadm already installed, swap off, br_netfilter loaded — that is deliberately not yet joined. (No spare VMs available? A kind cluster with an extra unjoined node is harder to arrange; in that case skip task 1.1 only, and score it separately as "not attempted — no lab for this one" rather than guessing at it.) Install ingress-nginx, the metrics-server add-on, and a local-path-style dynamic provisioner, and confirm all of it is healthy before the timer starts:

# --- the night before ---------------------------------------------------
kubectl get nodes -o wide
# cka-mock-cp   Ready   control-plane   —   v1.35.0
# cka-mock-w1   Ready   <none>          —   v1.35.0
# cka-mock-w2   Ready   <none>          —   v1.35.0
# (cka-mock-w3 is provisioned but NOT joined yet — that's task 1.1)

kubectl get pods -A | grep -vE 'Running|Completed'   # anything unhealthy here is a task you'll skip, not fix
kubectl get storageclass                              # a default, dynamic-provisioning class should already exist
kubectl top nodes                                      # confirms metrics-server is actually working

# --- ninety seconds before the clock starts ------------------------------
kubectl config use-context cka-mock1
kubectl config set-context --current --namespace=default
kubectl config view --minify -o jsonpath='{..namespace}{"\n"}'   # say it out loud

alias k=kubectl
export do="--dry-run=client -o yaml"
export now="--grace-period=0 --force"
⚠ Confirm your CNI actually enforces NetworkPolicy

Task 3.2 is the highest-value single task on this paper, and it is unverifiable on a CNI that doesn't implement NetworkPolicy at all — historically true of kind's and minikube's default CNI unless you deliberately install a policy-capable one. Confirm yours enforces it (Calico and Cilium both do) before the clock starts, not mid-task.

Exam conditions for this sitting

☺ Like you're 10: Same rules as the real day — one timer that never pauses, and no peeking at the answer until you've either solved it or spent its full budget.

One timer, started once. 120 minutes. You don't pause it to fix a typo'd YAML file, wait out an apt-get mirror, or answer the door — the real exam won't refund that time either, so losing eight minutes to your own laptop is data, not an excuse. Nothing open except kubernetes.io/docs and kubernetes.io/blog. That is the entire allowlist the real exam has historically published — not this site, not your notes, not a search engine, not an AI assistant. Close every other tab before you start. No peeking at a worked solution before you've either passed the done-when check or burned the task's full budget — reading the answer first turns a diagnostic into a comprehension exercise, and you'll repeat the same stall on the real day. Skim all sixteen task briefs first — five minutes spent scanning the whole paper is the highest-return five minutes of the sitting, because it tells you which three tasks you can close in under five minutes and which one is going to eat twelve.

🐢 Timmy's flag-and-move rule

Each task below carries its own budget, but the mechanical rule is identical everywhere: once a task passes its listed budget with no passing done-when check, stop. Write one line noting where you stalled, leave whatever you've already applied in place — partial credit is real, and a half-finished fix beats a reverted one — and move to the next task. You are not paid to finish tasks in the order they're printed; you are paid to bank points before the clock hits zero.

Your time budget across the two hours

☺ Like you're 10: Give every part of the clubhouse its own fair slice of the two hours, and save a few extra minutes at the end just for double-checking your work.

120 minutes splits into 5 to skim, 106 of task work, and a 9-minute verification sweep at the end — and the 106 don't divide evenly across domains, because the domains themselves aren't equal. Troubleshooting alone claims almost a third of the clock; Storage, the smallest domain, claims well under a tenth.

BlockTasksBudgetRunning totalNote
Skim the whole paper5 min5Mark each task cheap / medium / expensive before you touch a terminal.
Domain 1 — Cluster Architecture1.1–1.46+6+8+7 = 27 min32The etcd restore (1.3) is the one task here that genuinely needs its full budget.
Domain 2 — Workloads & Scheduling2.1–2.35+5+5 = 15 min47Three even five-minute tasks — bank all three before touching Domain 3.
Domain 3 — Services & Networking3.1–3.37+8+6 = 21 min683.2's NetworkPolicy is the paper's highest single value at 7 points — don't let it run past budget on a DNS-egress typo.
Domain 4 — Storage4.1–4.25+6 = 11 min79Smallest domain, but 4.2's diagnosis is easy to rush past the actual mismatch.
Domain 5 — Troubleshooting5.1–5.48+8+9+7 = 32 min111Almost a third of the whole clock. The four tasks here are worth 30 of your 100 points.
Verify & sweep flags9 min120Re-run every done-when check. This block earns more points per minute than anything else on the paper.
120 minutes · 16 tasks · 100 points skim 5m D1 · Cluster Arch 27m 25 pts D2 · Workloads 15m 15 pts D3 · Networking 21m 20 pts D4 11m 10 pts D5 · Troubleshooting 32m 30 pts verify 9m Flag-and-move rule Past a task's listed budget with no passing done-when check: one note, leave the partial work, move on. On the real exam these domains are not grouped like this — tasks are shuffled across several separate clusters.

Domain 1 — Cluster Architecture, Installation & Configuration (25 points)

☺ Like you're 10: This domain is building and keeping the clubhouse itself standing — welcoming a new member, writing down who's allowed to touch what, keeping a spare key, and changing the locks without accidentally locking out everyone who's already inside.

Scenario. Your cka-mock1 cluster: control-plane node cka-mock-cp and workers cka-mock-w1/cka-mock-w2, all on v1.35.0. A fourth machine, cka-mock-w3, is provisioned but not yet part of the cluster. Namespace payments already exists and is empty.

#TaskDone whenPointsBudget
1.1Join cka-mock-w3 to cka-mock1 as a worker node.kubectl get nodes lists cka-mock-w3 as Ready.66 min
1.2In namespace payments, create ServiceAccount billing-bot that can get/list/watch Pods and Pod logs there — nothing else, nowhere else.kubectl auth can-i get pods as billing-bot is yes; delete pods and get secrets are both no.66 min
1.3Take an etcd snapshot, verify it, then restore it into a fresh data directory and repoint the static Pod at it.The etcd static Pod is Running against the restored data directory and kubectl get nodes still returns data.78 min
1.4Upgrade cka-mock-cp from v1.35.0 to v1.35.1, in the correct order.kubectl get nodes shows cka-mock-cp at v1.35.1, Ready, with all static Pods healthy.67 min
Worked solution — Domain 1

1.1 — Join the worker. Bootstrap tokens expire after 24 hours by default, so generate a fresh one rather than reusing whatever the cluster was built with:

# on cka-mock-cp
kubeadm token create --print-join-command
# on cka-mock-w3, as root
kubeadm join 10.0.0.10:6443 --token 9a08jv.c0izixklcxtmnze7 \
  --discovery-token-ca-cert-hash sha256:1234...cafe
kubectl get nodes -o wide
# cka-mock-w3   Ready   <none>   41s   v1.35.0

If the join hangs on the TLS bootstrap step instead of failing fast, that's almost always swap or the bridge sysctls, not the token — check swapon --show (should print nothing) and lsmod | grep br_netfilter on cka-mock-w3 before assuming the token itself is bad.

1.2 — RBAC. Default-deny means the ServiceAccount starts with nothing; a Role plus a RoleBinding grants exactly the two verbs on exactly the two resources asked for, no more:

apiVersion: v1
kind: ServiceAccount
metadata: { name: billing-bot, namespace: payments }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: pod-log-reader, namespace: payments }
rules:
  - apiGroups: [""]
    resources: ["pods", "pods/log"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: billing-bot-reads-pods, namespace: payments }
subjects:
  - kind: ServiceAccount
    name: billing-bot
    namespace: payments
roleRef: { kind: Role, name: pod-log-reader, apiGroup: rbac.authorization.k8s.io }
kubectl auth can-i get pods -n payments --as=system:serviceaccount:payments:billing-bot        # yes
kubectl auth can-i delete pods -n payments --as=system:serviceaccount:payments:billing-bot     # no
kubectl auth can-i get secrets -n payments --as=system:serviceaccount:payments:billing-bot      # no

1.3 — etcd backup and restore. etcdctl takes the snapshot against a live cluster; since etcd 3.6, restoring is etcdutl's job, not etcdctl's, and it never talks to a running API server at all:

# on cka-mock-cp
ETCDCTL_API=3 etcdctl snapshot save /opt/backups/etcd-$(date +%s).db \
  --endpoints=https://127.0.0.1:2379 \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key

etcdutl snapshot status /opt/backups/etcd-1755000000.db --write-out=table
etcdutl snapshot restore /opt/backups/etcd-1755000000.db \
  --data-dir=/var/lib/etcd-restored
# then edit /etc/kubernetes/manifests/etcd.yaml — change hostPath from
# /var/lib/etcd to /var/lib/etcd-restored (both the volume and volumeMount)
# the kubelet watches that manifest directory and restarts the static Pod automatically
crictl ps -a --name etcd
kubectl get nodes    # a real answer here proves the API server can talk to the restored etcd

1.4 — Upgrade, in order. Nothing is ever upgraded ahead of kube-apiserver: kubeadm itself first, then kubeadm upgrade apply on the first control-plane node only, then drain, upgrade kubelet/kubectl, restart, uncordon:

apt-mark unhold kubeadm && apt-get update && apt-get install -y kubeadm=1.35.1-1.1 && apt-mark hold kubeadm
kubeadm upgrade plan
kubeadm upgrade apply v1.35.1

kubectl drain cka-mock-cp --ignore-daemonsets --delete-emptydir-data
apt-mark unhold kubelet kubectl
apt-get install -y kubelet=1.35.1-1.1 kubectl=1.35.1-1.1
apt-mark hold kubelet kubectl
systemctl daemon-reload && systemctl restart kubelet
kubectl uncordon cka-mock-cp
kubectl get nodes
# cka-mock-cp   Ready   control-plane   —   v1.35.1
# cka-mock-w1   Ready   <none>          —   v1.35.0   (fine — kubelet may trail apiserver by up to 3 minors)
# cka-mock-w2   Ready   <none>          —   v1.35.0

Leaving the workers on v1.35.0 while cka-mock-cp runs v1.35.1 is not a mistake to fix here — the version skew policy allows kubelet to trail kube-apiserver by up to three minor versions, and this task only asked you to upgrade the control-plane node.

More depth: Domain 1 blueprint, the kubeadm tool guide, control-plane internals, hands-on in Capstone Part 1 and the failed-upgrade drill.

Domain 2 — Workloads & Scheduling (15 points)

☺ Like you're 10: This domain is which chores get done, in what order, by which member, and how many extra helpers show up automatically once the chore pile grows too big for the crew on duty.

Scenario. Namespace web holds Deployment storefront, three Ready replicas of nginx:1.25. Node cka-mock-w2 already carries the label disktype=ssd and the taint dedicated=gpu:NoSchedule. metrics-server is installed and healthy.

#TaskDone whenPointsBudget
2.1Roll storefront to nginx:1.27-doesnotexist, notice the rollout never completes, and roll it back.kubectl rollout undo has run and all three Pods are back on nginx:1.25 and Running.55 min
2.2Schedule a Pod named batch-job so it lands on cka-mock-w2 specifically — not just any node that tolerates its taint.kubectl get pod batch-job -o wide shows NODE = cka-mock-w2.55 min
2.3Create an HPA for storefront: target 50% CPU, min 2, max 6 replicas.kubectl get hpa shows a real TARGETS percentage (not <unknown>) against the correct min/max.55 min
Worked solution — Domain 2

2.1 — Bad rollout, then rollback. A rollout to a tag that doesn't exist doesn't error immediately — it stalls in ImagePullBackOff, which is exactly the shape the real exam likes to hand you:

kubectl set image deployment/storefront nginx=nginx:1.27-doesnotexist -n web
kubectl rollout status deployment/storefront -n web --timeout=30s
# error: timed out waiting for the condition
kubectl rollout undo deployment/storefront -n web
kubectl rollout status deployment/storefront -n web
kubectl get pods -n web -l app=storefront -o jsonpath='{.items[*].spec.containers[0].image}'
# nginx:1.25 nginx:1.25 nginx:1.25

2.2 — Taint plus affinity, together. A toleration alone only makes cka-mock-w2 eligible — the scheduler could still put the Pod anywhere untainted. Pinning it specifically needs a required node affinity on top:

apiVersion: v1
kind: Pod
metadata: { name: batch-job }
spec:
  tolerations:
    - key: dedicated
      operator: Equal
      value: gpu
      effect: NoSchedule
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: disktype
                operator: In
                values: ["ssd"]
  containers:
    - name: worker
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
kubectl apply -f batch-job.yaml
kubectl get pod batch-job -o wide
# batch-job   1/1   Running   cka-mock-w2

2.3 — HPA on CPU. The HPA is only as good as the metric feeding it — without metrics-server, TARGETS shows <unknown> forever and the controller can never decide anything:

kubectl autoscale deployment storefront -n web --cpu-percent=50 --min=2 --max=6
kubectl get hpa storefront -n web
# NAME         REFERENCE               TARGETS   MINPODS   MAXPODS   REPLICAS
# storefront   Deployment/storefront   14%/50%   2         6         3

A real percentage under TARGETS — even a low, resting one like 14%/50% — is the actual done-when here, because it's the proof metrics-server is wired up and the HPA controller is polling it; you don't need to generate load and watch it scale to pass this task, only to know your controller could if load arrived.

More depth: Domain 2 blueprint, scheduling & resource management, autoscaling, hands-on in Capstone Part 2 and the scale-under-load drill.

Domain 3 — Services & Networking (20 points)

☺ Like you're 10: This domain is the clubhouse's phone system and front door — dialing between rooms, deciding exactly who's allowed to call which room, and letting outside visitors in through one clearly marked entrance.

Scenario. Namespace web: Deployment storefront (label app=storefront, listening on port 80) and Deployment checkout (label app=checkout, container port 8080), both Running. ingress-nginx is already installed cluster-wide.

#TaskDone whenPointsBudget
3.1Expose storefront as ClusterIP Service storefront-svc, then route shop.cka-mock.local to it via an nginx Ingress.A curl -H "Host: shop.cka-mock.local" against the ingress controller returns the nginx welcome page.77 min
3.2In namespace web, default-deny all ingress and egress, then allow only Pods labeled role=frontend in namespace edge to reach storefront Pods on port 80 — without breaking DNS for storefront itself.A test Pod labeled role=frontend in edge can curl a storefront Pod; an unlabeled test Pod cannot; storefront Pods can still resolve DNS.78 min
3.3Service checkout-svc resolves by DNS but every request is refused. Find and fix why.kubectl get endpoints checkout-svc lists real Pod IPs, and a curl from another Pod succeeds.66 min
Worked solution — Domain 3

3.1 — Service and Ingress.

kubectl expose deployment storefront -n web --name=storefront-svc --port=80 --target-port=80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: storefront-ingress
  namespace: web
spec:
  ingressClassName: nginx
  rules:
    - host: shop.cka-mock.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: storefront-svc
                port: { number: 80 }
curl -H "Host: shop.cka-mock.local" http://<ingress-controller-ip>/
# <title>Welcome to nginx!</title>

3.2 — Default-deny, then one scoped path, with DNS still open. The standard baseline is a five-line manifest with empty ingress/egress blocks — apply it first, confirm everything breaks, then reopen exactly what's needed:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny-all, namespace: web }
spec:
  podSelector: {}
  policyTypes: [Ingress, Egress]
  # no rules at all = nothing allowed, in either direction
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: storefront-allow-edge, namespace: web }
spec:
  podSelector:
    matchLabels: { app: storefront }
  policyTypes: [Ingress, Egress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: edge }
          podSelector:
            matchLabels: { role: frontend }
      ports:
        - { protocol: TCP, port: 80 }
  egress:
    - to:                                    # DNS — the rule everyone forgets
        - namespaceSelector:
            matchLabels: { kubernetes.io/metadata.name: kube-system }
          podSelector:
            matchLabels: { k8s-app: kube-dns }
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }
kubectl run allowed --image=busybox:1.36 -n edge -l role=frontend --rm -it --restart=Never \
  -- wget -qO- --timeout=3 storefront-svc.web.svc.cluster.local
# <title>Welcome to nginx!</title>

kubectl run blocked --image=busybox:1.36 -n edge --rm -it --restart=Never \
  -- wget -qO- --timeout=3 storefront-svc.web.svc.cluster.local
# wget: download timed out    (no role=frontend label — correctly blocked)

3.3 — Service resolves but never answers. A DNS answer only proves CoreDNS knows the Service's ClusterIP exists — it says nothing about whether any Pod is actually behind it:

kubectl get endpoints checkout-svc -n web
# NAME            ENDPOINTS
# checkout-svc    <none>

kubectl get svc checkout-svc -n web -o yaml | grep -A2 selector
#   selector:
#     app: checkout-service        # doesn't match any Pod's real label

kubectl get pods -n web -l app=checkout --show-labels
# checkout-7f4...   1/1   Running   app=checkout          <-- actual label
kubectl patch svc checkout-svc -n web -p '{"spec":{"selector":{"app":"checkout"}}}'
kubectl get endpoints checkout-svc -n web
# checkout-svc   10.244.1.14:8080,10.244.2.9:8080

More depth: Domain 3 blueprint, networking & the CNI, Calico, ingress-nginx, hands-on in Capstone Part 3 and the networking-failure drill.

Domain 4 — Storage (10 points)

☺ Like you're 10: This domain is the storage room downstairs — ordering a fresh box the instant someone actually needs one, instead of always keeping a pile of empty boxes around, and working out why a request for a box sometimes never gets answered.

Scenario. A dynamic-provisioning StorageClass isn't yet defined for this namespace's use case. Separately, in namespace ops, PersistentVolume archive-pv already exists and PersistentVolumeClaim archive-data has been Pending for ten minutes.

#TaskDone whenPointsBudget
4.1Create StorageClass fast-ssd with volumeBindingMode: WaitForFirstConsumer, then a 5Gi PVC using it, then a Pod that mounts it.The PVC is Pending with no Pod referencing it, and Bound only after the Pod is created.55 min
4.2Diagnose and fix why archive-data won't bind to archive-pv.kubectl get pvc archive-data -n ops shows Bound.56 min
Worked solution — Domain 4

4.1 — WaitForFirstConsumer, proven, not just declared. The point of this binding mode is that provisioning is delayed until a Pod exists — proving it means watching the PVC sit Pending on purpose before creating that Pod:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: { name: fast-ssd }
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
allowVolumeExpansion: true
apiVersion: v1
kind: PersistentVolumeClaim
metadata: { name: reports-data }
spec:
  accessModes: [ReadWriteOnce]
  storageClassName: fast-ssd
  resources: { requests: { storage: 5Gi } }
kubectl apply -f storageclass.yaml -f pvc.yaml
kubectl get pvc reports-data
# reports-data   Pending   —   —   fast-ssd     <-- correct: no Pod has claimed it yet
kubectl run reports --image=busybox:1.36 --restart=Never \
  --overrides='{"spec":{"containers":[{"name":"reports","image":"busybox:1.36","command":["sleep","3600"],
  "volumeMounts":[{"name":"data","mountPath":"/data"}]}],"volumes":[{"name":"data",
  "persistentVolumeClaim":{"claimName":"reports-data"}}]}}'
kubectl get pvc reports-data
# reports-data   Bound   pvc-8a1...   5Gi   RWO   fast-ssd

4.2 — Diagnose the stuck PVC. describe on both objects, side by side, is faster than guessing:

kubectl describe pvc archive-data -n ops | grep -A2 "Events\|StorageClass\|Access Modes"
# StorageClassName: fast-archive
# Access Modes: RWO
kubectl describe pv archive-pv | grep -A2 "StorageClass\|Access Modes\|Capacity"
# StorageClass: manual
# Access Modes: RWO
# Capacity: 10Gi

Capacity and access mode both satisfy the claim — the binder refuses purely because archive-pv's storageClassName (manual) doesn't match what archive-data is asking for (fast-archive). PersistentVolumes are effectively immutable once created, so the fix edits the PV's class to match, not the other way round:

kubectl patch pv archive-pv -p '{"spec":{"storageClassName":"fast-archive"}}'
kubectl get pvc archive-data -n ops
# archive-data   Bound   archive-pv   10Gi   RWO   fast-archive

More depth: Domain 4 blueprint, storage & the CSI, hands-on in Capstone Part 4 and the storage-incident drill.

Domain 5 — Troubleshooting (30 points)

☺ Like you're 10: This domain is a firefighter's shift — four separate alarms go off in four separate rooms, and a full third of your score depends only on how fast and correctly you find and put out each one.

Scenario. Four independent incidents, each in namespace ops unless noted. Work them in any order — nothing here depends on anything else on this page.

#TaskDone whenPointsBudget
5.1Pod inventory-worker is stuck CrashLoopBackOff. Fix it in place — no delete-and-recreate.kubectl get pod inventory-worker shows 1/1 Running with restarts no longer climbing.88 min
5.2Node cka-mock-w1 shows NotReady. SSH in, diagnose, and fix.kubectl get nodes shows cka-mock-w1 Ready again.78 min
5.3Every kubectl command against cka-mock1 hangs or refuses. SSH to cka-mock-cp and fix it.kubectl get nodes from your own workstation succeeds again.89 min
5.4Deployment web-app's ServiceAccount can't read a ConfigMap it needs at startup — Pod logs show a 403. Diagnose and fix the RBAC gap.kubectl auth can-i get configmaps as the app's ServiceAccount returns yes, and a fresh Pod starts cleanly.77 min
Worked solution — Domain 5

5.1 — CrashLoopBackOff. describe's Events tell you why something restarted; logs alone often doesn't, because a killed container may never log the reason for its own death:

kubectl describe pod inventory-worker -n ops | tail -15
# Warning  Unhealthy  kubelet  Liveness probe failed: Get "http://10.244.1.9:9090/healthz":
#          dial tcp 10.244.1.9:9090: connect: connection refused
# Normal   Killing    kubelet  Container inventory-worker failed liveness probe, will be restarted
kubectl logs inventory-worker -n ops --previous | grep -i listen
# level=info msg="listening" addr=":8080"    <-- the app actually listens on 8080, not 9090

The liveness probe is checking a port the container was never going to answer on, so kubelet keeps killing a perfectly healthy process. Fix the probe, not the app:

kubectl edit deployment inventory-worker -n ops
#   livenessProbe:
#     httpGet:
#       path: /healthz
#-      port: 9090
#+      port: 8080
kubectl get pod inventory-worker -n ops -w
# inventory-worker   1/1   Running   0   45s   <-- restarts stopped climbing

5.2 — NotReady node. kubectl can still describe the node from outside (the API server has its last-known status), but the real evidence lives on the node itself:

kubectl describe node cka-mock-w1 | grep -A3 Conditions
# Ready   False   KubeletNotReady   PLEG is not healthy: ...

ssh cka-mock-w1
sudo systemctl status kubelet
# ● kubelet.service - active (running) but flapping
sudo journalctl -u kubelet -n 60 --no-pager -p err
# "container runtime network not ready" / cgroup driver mismatch reported by containerd
cat /etc/containerd/config.toml | grep -A1 SystemdCgroup
# SystemdCgroup = false        <-- kubelet is configured to expect systemd; this is out of step
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
sudo systemctl restart containerd
sudo systemctl restart kubelet
kubectl get nodes
# cka-mock-w1   Ready   <none>   —   v1.35.0

5.3 — API server unreachable. When kubectl itself can't talk to anything, kubectl is useless for diagnosing the problem — SSH to the node and go one layer below the API, to the kubelet driving the static Pod that is the API server:

ssh cka-mock-cp
sudo journalctl -u kubelet -n 60 --no-pager -p err
# "Error: unknown flag: --secure-port=644 3" — kubelet can't run the manifest, no API server to report it anywhere else

sudo crictl ps -a --name kube-apiserver
# CONTAINER   STATE      exited (1) 4 minutes ago

sudo cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep secure-port
# - --secure-port=644 3        <-- a stray space, turning one flag into a malformed value
sudo sed -i 's/--secure-port=644 3/--secure-port=6443/' /etc/kubernetes/manifests/kube-apiserver.yaml
# the kubelet watches this directory and restarts the static Pod on its own — no apply needed
sudo crictl ps -a --name kube-apiserver
# CONTAINER   STATE      Running

# from your own workstation
kubectl get nodes
# succeeds again

A malformed static-Pod manifest never produces a Kubernetes-level error anywhere kubectl can show you — there's no API server yet to report one. The kubelet's own journal is the only place this failure is visible.

5.4 — RBAC-caused startup failure. can-i confirms the theory before you go editing anything, and it's the fastest way to find which of the four RBAC objects is actually wrong:

kubectl logs deployment/web-app -n ops --tail=5
# Error: configmaps "web-app-config" is forbidden: User "system:serviceaccount:ops:web-app"
#        cannot get resource "configmaps" in API group "" in the namespace "ops"

kubectl auth can-i get configmaps -n ops --as=system:serviceaccount:ops:web-app
# no

kubectl get role web-app-role -n ops -o yaml | grep -A4 resources
#   resources: ["pods"]        <-- Role never granted configmaps at all
kubectl patch role web-app-role -n ops --type=json \
  -p='[{"op":"add","path":"/rules/0/resources/-","value":"configmaps"}]'

kubectl auth can-i get configmaps -n ops --as=system:serviceaccount:ops:web-app
# yes

kubectl rollout restart deployment web-app -n ops
kubectl logs deployment/web-app -n ops --tail=5 --follow
# no more Forbidden errors

More depth: Domain 5 blueprint, a troubleshooting methodology, control-plane internals, hands-on in the broken-cluster, stuck-pod, and RBAC-hardening drills.

Score yourself

☺ Like you're 10: Add up your points, compare to the pass line, then look at which questions cost you — that second part is the part that actually helps next time.

Mark after a short break, not immediately — adrenaline makes for generous self-grading. Award full points only when the done-when check genuinely passed on your own cluster, half when the object exists and is broadly right but the check didn't pass clean, and zero for anything unattempted. "I knew how to do that one" earns nothing a passing check doesn't already prove.

#TaskPointsYour score
1.1D1 — join the worker node6
1.2D1 — scoped RBAC, verified6
1.3D1 — etcd backup and restore7
1.4D1 — control-plane upgrade, in order6
2.1D2 — bad rollout, then rollback5
2.2D2 — taint + affinity, pinned to one node5
2.3D2 — HPA on CPU5
3.1D3 — ClusterIP + Ingress7
3.2D3 — default-deny + one scoped path + DNS7
3.3D3 — Service selector mismatch, fixed6
4.1D4 — WaitForFirstConsumer, proven5
4.2D4 — stuck PVC, diagnosed and bound5
5.1D5 — CrashLoopBackOff, fixed in place8
5.2D5 — NotReady node, fixed7
5.3D5 — broken static Pod manifest, fixed8
5.4D5 — RBAC startup failure, fixed7
TotalAll 5 domains100

Computing your result. The points total 100, so your raw score is your percentage, and the CKA's published pass mark has generally sat at 66% — confirm the current figure before treating it as gospel. That leaves roughly 34 points spendable: you could drop the entirety of Domain 4 and most of a second domain and still clear the bar, which is worth internalizing before panic sets in over any single missed task. Then do the more useful arithmetic and total each domain separately, because two candidates can both score 70 and be in completely different shape — one with a shallow, even gap across all five domains, the other with one domain at zero and the rest close to perfect. The second candidate has a specific, fixable hole; the first has a longer list of small ones.

🦊Domain 5 — Troubleshooting
30 pts
🦉Domain 1 — Cluster Architecture, Installation & Configuration
25 pts
🐦Domain 3 — Services & Networking
20 pts
🦫Domain 2 — Workloads & Scheduling
15 pts
🐘Domain 4 — Storage
10 pts
DomainAvailableYoursIf you scored under two-thirds, go here
Cluster Architecture, Installation & Configuration25Domain 1 blueprint, then Practice Tasks and Capstone Part 1.
Workloads & Scheduling15Domain 2 blueprint, then Capstone Part 2.
Services & Networking20Domain 3 blueprint, then the networking-failure drill.
Storage10Domain 4 blueprint, then the storage-incident drill.
Troubleshooting30Domain 5 blueprint, then the broken-cluster drill and the stuck-pod drill.

What each miss means

☺ Like you're 10: There are three different reasons to lose points, and each one needs a different fix — guessing the wrong one wastes a week of studying the wrong thing.

Sort your zeros and halves into three piles before deciding what to re-study, because they call for genuinely different remedies. A task you didn't recognize the shape of at all is a real knowledge gap — go read the linked domain page, then redo that exact task cold in two or three days, not immediately while the answer is still fresh in short-term memory. A task where you knew roughly what to do but burned the clock hunting for a flag or a field name is a speed problem, not a knowledge one, and the fix is the kubectl fluency baseline and repetition, not another read of the blueprint. A task that broke in a way you couldn't diagnose — a command that errored strangely, evidence that didn't point anywhere — is a triage gap, and the fix is a troubleshooting methodology worked deliberately, evidence before guesses. One pattern sits outside all three: count how many tasks you left completely untouched. More than two or three, and the honest diagnosis is pacing, not knowledge — points you never got the chance to contest.

⚠ A high score here proves less than it feels like it does

You've now read every solution on this paper. A strong score tells you that you can execute this specific material under a clock — genuinely useful, but not the same claim as "I'm ready for the real exam." Set 2 and Set 3 are unseen for exactly this reason; save them, spaced out, for closer to your real exam date rather than sitting all three this week.

🎬 At the Pod Squad
🦊

Foxy: 71. Missed the pass line by, apparently, not missing it — but I burned nineteen minutes on task 5.3 alone.

🐢

Timmy: Nineteen minutes against a nine-minute budget. What did you do at minute ten?

🦊

Foxy: ...kept re-reading the manifest looking for the typo. I never actually opened the kubelet's own journal.

👺

Gizmo: Rookie move. Next time just delete the whole static Pod manifest and let kubeadm regenerate one. Way faster! 🤑

🐢

Timmy: Kubeadm doesn't regenerate static Pod manifests on the fly, Gizmo — delete that file and the API server never comes back at all. That "faster" fix would've scored zero and made the actual problem unrecoverable.

🐰

Remy: And the nine-minute budget on 5.3 exists exactly so you catch yourself at minute nine, flag it, and go bank the seven points sitting on task 5.4 instead.

🦉

Professor Owl: Sit it again next week, Foxy, but time only the four Domain 5 tasks in isolation. If your 5.3 number drops under nine minutes without you learning anything new, the fix was never knowledge — it was knowing to check the kubelet's journal first, not last.

After this sitting

☺ Like you're 10: Only re-practice the parts you actually got wrong, and save the next full sitting for a little closer to the real day.

Resist re-drilling everything — the entire value of a scored, weighted paper is telling you where not to spend your next week. Take your one or two weakest domains, work only those sections of CKA Practice Tasks and the matching capstone part or drill, and leave the domains you already cleared alone. When you're ready for a genuinely cold rep, move to Set 2; keep Set 3 sealed until closer to your real exam date. For the logistics of the actual sitting — registration, the proctoring environment, and what candidates report about the day itself — this course's exam-day page covers it, and the study plan's resource table has second opinions on the same exam from Platform Engineering, SRE, and DevSecOps. Once CKA, CKAD, and CKS are all behind you, the wider Kubestronaut and Golden Kubestronaut ladder — the other nine CNCF certifications plus the LFCS — lives in the sibling Golden Astronaut course.

⚠ Verify officially before you book

This page is an independent, unofficial study resource — not affiliated with the CNCF or the Linux Foundation. The 66% pass mark, the 2-hour duration, the domain weights, the permitted-documentation allowlist, and the curriculum version all change over time. Confirm current details on the official Linux Foundation CKA page and the CNCF certification page before you pay for anything, and read the Candidate Handbook in your LF portal. The curriculum itself is published openly at github.com/cncf/curriculum.

🐢 Timmy's checkpoint

1. Why does this page group tasks by domain when the real exam deliberately doesn't, and what's the recommended way to get a more realistic second rep out of the same sixteen tasks? 2. Name the two documentation sites on the permitted allowlist, and what happens to the flag-and-move budget if a task like 5.3 runs past it? 3. In task 1.4, why is it correct to leave the worker nodes on v1.35.0 after upgrading only cka-mock-cp to v1.35.1? 4. In task 3.2, why does the NetworkPolicy need an explicit egress rule for port 53, and what breaks if it's left out? 5. You score 70, but all 30 missed points came from a single domain — what does that tell you that a 70 spread evenly across all five wouldn't? 6. What's the difference between a task you "knew but ran out of time on" and one you "genuinely didn't recognize," and why do they need different remedies?

Check your answers
  1. Grouping by domain keeps each worked solution next to its task for teaching clarity; the real exam draws tasks from several separate clusters in an order that ignores domain boundaries. For a more realistic second rep, write the sixteen task numbers on paper, shuffle them, and re-attempt in that random order with the solutions covered.
  2. kubernetes.io/docs and kubernetes.io/blog — nothing else, not even this site. Once a task passes its listed budget with no passing done-when check, the flag-and-move rule applies regardless of which allowlist site you were reading: note where you stalled, leave the partial work in place, and move to the next task.
  3. Because Kubernetes' version skew policy allows kubelet to trail kube-apiserver by up to three minor versions — a one-minor-version gap between a freshly upgraded control-plane node and its still-v1.35.0 workers is well inside that policy, and the task only asked for the control-plane node to be upgraded.
  4. Because a default-deny-egress policy blocks a Pod's outbound DNS queries along with everything else unless a rule explicitly reopens port 53 to CoreDNS. Without it, the Pod stops being able to resolve any Service name at all — traffic that was allowed by the ingress rule can still arrive, but the Pod itself can no longer look up where to send anything.
  5. That you have one concentrated blind spot rather than a shallow, even gap. Two candidates can post the identical 70, but the one who lost every point in a single domain has a specific, fixable hole — and on the real exam, a task draw that leans even slightly harder into that same domain is genuinely riskier for them than for a candidate with the identical score spread evenly.
  6. A time-burned task is a speed problem — the fix is the kubectl fluency baseline and repetition. A genuinely unrecognized task is a knowledge gap — the fix is reading the domain page and redoing the task cold a few days later. Applying the wrong remedy (drilling speed on something you never actually understood, or re-reading theory on something you already knew but typed too slowly) wastes real study time without closing the actual gap.

That's the paper. Build the lab cluster yourself, start one timer, and let the sixteen tasks above take you through two hours that should feel noticeably less comfortable than working through the blueprint pages untimed. Score it honestly, note your weakest domain, and spend your next study block exactly there — then come back for Set 2 when you're ready, or head to Kubernetes Certifications to plan the rest of the ladder.

⏱ The three papers

Set 1 (you are here) · Set 2 · Set 3. All three are scored out of 100 against the same 25/15/20/10/30 domain weighting; see the study plan for when to sit each one, and CKA Practice Tasks for untimed reps in between.