Most CKA practice questions you will find stop at an answer line. The CKA exam itself hands you a broken cluster and two hours, so an answer without the typing does not get you far. We wrote this set of CKA practice questions around that gap: every task here was run on a real cluster in November 2026, and each one ends with the output that proves it worked.
The cluster is a two VM kubeadm setup on our Proxmox lab, running Kubernetes 1.36.6 with Calico as the CNI. We break it, back it up, upgrade it and restore it the way CKA exam tasks ask you to. Each section states the task the way the CKA exam would phrase it, walks through the commands, and shows the captured output. Keep our kubectl cheat sheet open in another tab while you work through them.
How the CKA exam grades you
Most theory exams grade what you remember. The Certified Kubernetes Administrator does the opposite: you get a live cluster and a set of tasks in an exam terminal, and the grader checks the state of that cluster after you finish, not what you clicked along the way. There are no multiple choice questions to fall back on, which is why hands-on practice matters more for this cert than for any exam you have sat.
A few details matter before you book a sitting.
- Two hours of tasks in a live Kubernetes cluster
- A pass mark of 66 percent, graded on the cluster state you leave behind
- kubectl and the kubernetes.io docs stay available, so recall matters less than speed
- One browser tab for the docs, one terminal, and a proctor watching the whole time
That last point shapes how you should practise. Because the docs are allowed, memorising flags is wasted effort. What the CKA exam really tests is whether you can read a task, decide which object it is about, and produce that object quickly. Still deciding between the CKA and the entry level KCNA? Our KCNA vs KCSA vs CKA comparison walks through which of the three fits where you are right now.
What we built the practice cluster on
We ran everything on two Ubuntu 24.04 VMs on our Proxmox box, sized the way a homelab practice cluster usually is. Each VM got its own job on the cluster.
- cka-cp, 3.8 GB of RAM, kubeadm 1.36.6, control plane
- cka-node1, 3.8 GB of RAM, single worker, joined by token
- Ubuntu 24.04.4, containerd 2.2.1, both nodes held on 1.36 with apt-mark
We chose 1.36 on purpose: it sits one minor version behind the current release, which is exactly the position the upgrade step wants to start from. One networking decision matters more than the rest. Calico 3.30.4 is the CNI, and we set its pod CIDR to 10.244.0.0/16 so it matches the CIDR we passed to kubeadm init. Calico is a good pick for practice because it actually enforces NetworkPolicy, which is the whole point of step 2. The whole cluster fits on a laptop class of hardware.
Here is how the pieces sit on the two VMs, with each component drawn where it does its defining job. The kubelet and Calico actually run on both nodes, since a control plane node is itself a worker with extra control plane pods.

Building the cluster follows the standard kubeadm path, with two small catches we hit along the way. First, the pkgs.k8s.io repo you point at decides your version, so we pinned it to the v1.36 channel before installing kubelet, kubeadm and kubectl, then held the packages with apt-mark hold. Second, the Calico manifest ships with a default pod CIDR of 192.168.0.0/16, which collides with our node subnet, so the CALICO_IPV4POOL_CIDR line has to be set before you apply it.
kubeadm init --pod-network-cidr=10.244.0.0/16
kubectl apply -f calico.yaml
Once Calico is up and the worker has joined, a clean get nodes output is the checkpoint you are after.
kubectl get nodes -o wide
Both nodes answer Ready with the versions and runtimes we expect.
NAME STATUS ROLES AGE VERSION OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
cka-cp Ready control-plane 12m v1.36.6 Ubuntu 24.04.4 LTS 6.14.0-33-generic containerd://2.2.1
cka-node1 Ready <none> 10m v1.36.6 Ubuntu 24.04.4 LTS 6.14.0-33-generic containerd://2.2.1
From here the practice tasks start. Each one below is a common CKA task, phrased the way the real thing phrases them, and each was solved live on this cluster. Try each task on your own cluster before you read our solution.
Step 1: Create a Role and verify it
Exam phrasing for this one looks like: in the rbac-lab namespace, create a ServiceAccount named api-reader, then a Role named pod-reader that allows get, watch and list on pods, bind the Role to that account, and verify the permissions. RBAC tasks are cheap marks on the CKA exam because the objects are small, but the verification step is where most people lose time. How do you prove a binding does what you think it does? One line of kubectl answers it, and you should end every RBAC task with it.
We created the namespace and the account first, because the manifests do nothing on a cluster that lacks them. A fresh kubeadm cluster only ships the four default namespaces, so both objects start with a create command.
kubectl create namespace rbac-lab
kubectl -n rbac-lab create serviceaccount api-reader
The Role and the RoleBinding are then two small manifests.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: rbac-lab
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "watch", "list"]
Our RoleBinding attaches that Role to the api-reader ServiceAccount in the same namespace. Note that a RoleBinding grants nothing outside its own namespace, which is why the task tells you the namespace in the first sentence.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: api-reader-binding
namespace: rbac-lab
subjects:
- kind: ServiceAccount
name: api-reader
namespace: rbac-lab
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
With both applied as the cluster admin, we ran the checks without switching contexts at all. The answer comes back for any identity you name, and the –as flag takes the full service account form system:serviceaccount:namespace:name.
kubectl -n rbac-lab auth can-i list pods --as=system:serviceaccount:rbac-lab:api-reader
kubectl -n rbac-lab auth can-i delete pods --as=system:serviceaccount:rbac-lab:api-reader
kubectl -n rbac-lab auth can-i list secrets --as=system:serviceaccount:rbac-lab:api-reader
The pattern to notice in the checks is that each question returns yes or no, and the ones you did not grant return no even for the namespace the Role lives in.

Our run confirmed exactly that shape: api-reader can list pods in rbac-lab, cannot delete pods there, and cannot list secrets at all. One habit worth taking into the CKA exam is to always end an RBAC task with these checks rather than assuming the yaml was right. If a question says the account should also manage deployments, add a second rule to the same Role rather than writing a new one.
Step 2: Deny all traffic, allow one client
The CKA exam phrases this task like so: in the backend namespace, deny all incoming traffic by default, then allow only pods labelled app=frontend in the frontend namespace to reach the web pod. This task fails silently on the wrong CNI, so before anything else check that your cluster runs a policy enforcing plugin. Flannel, for example, accepts NetworkPolicy objects and enforces none of them, and we have seen that confuse a lot of practice clusters.
The namespaces come first, since neither exists on a fresh cluster.
kubectl create namespace backend
kubectl create namespace frontend
Three test pods give the policy something to police, and two of them carry the labels the rule reads. The web pod serves the page, the client pod carries app=frontend, and the attacker pod runs with no label at all.
kubectl -n backend run web --image=nginx:1.29-alpine -l app=web
kubectl -n frontend run client --image=busybox:1.36 -l app=frontend -- sleep 3600
kubectl -n frontend run attacker --image=busybox:1.36 -- sleep 3600
Our default deny is one empty policy in the backend namespace. Its podSelector with no matchLabels selects every pod, and the empty ingress list blocks everything.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: backend
spec:
podSelector: {}
policyTypes:
- Ingress
ingress: []
Our allow policy is where the trap sits. Writing podSelector under from alone selects pods in the same namespace as the policy, so our first attempt locked the frontend client out too. To allow a pod in another namespace, the from entry needs both a podSelector and a namespaceSelector at the same level. The official NetworkPolicy concept page documents this and-ing of selectors, and it is worth reading once before you sit.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-from-frontend
namespace: backend
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: frontend
With both policies in place, the test is one wget from each frontend pod against the web pod’s IP, which you read straight off the pod.
WEBIP=$(kubectl -n backend get pod web -o jsonpath='{.status.podIP}')
kubectl exec -n frontend client -- wget -q -O- --timeout=5 $WEBIP
kubectl exec -n frontend attacker -- wget -q -O- --timeout=5 $WEBIP
The labelled client fetched the nginx welcome page and exited clean, and the unlabelled attacker sat on the request until wget reported download timed out. That pair of outcomes is what the grader will run, and the capture below shows both of them.

That pair of outcomes is what the grader will run, so make it your own last step. A wget that hangs instead of failing fast is your clue that the policy denied rather than that the pod was broken, since a dropped packet just waits for the timeout.
Step 3: Back up etcd
The CKA exam asks this one almost word for word: take a snapshot of the etcd data, save it at a given path, then restore it later. etcd holds every object in the cluster, which makes this the highest stakes task of the sitting. We worked from the official etcd backup and restore page, and the command needs the etcd certificates because you are talking to etcd directly, not through the API server.
On a kubeadm cluster those certificates live under /etc/kubernetes/pki/etcd, and etcd itself answers on localhost port 2379 from the control plane node. We snapshotted to /tmp, which is fine for a task, though a real backup lives on storage the control plane node does not depend on.
ETCDCTL_API=3 etcdctl \
--endpoints=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 \
snapshot save /tmp/cka-practice-snapshot.db
Our save finished in about half a second on this small cluster. Then we ran the check the grader expects you to know: snapshot status against the file.
ETCDCTL_API=3 etcdctl \
--endpoints=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 \
snapshot status /tmp/cka-practice-snapshot.db -w table
The table that comes back is the proof the grader wants to see.

That table is your proof of a real backup: hash a01eb5b9, revision 1819, 1309 keys and 6.8 MB. If your status command prints zeros, the file is not a snapshot and the restore later will fail. Copy the file off the node while you are practising, because that is the part of the task that maps to real life, where a control plane node that loses its disk takes its only backup with it.
Step 4: Fix a NotReady node
The task statement reads: a node in your cluster is NotReady, fix it. On the practice cluster we made the fault ourselves by stopping the kubelet on cka-node1, which is the cause behind most NotReady nodes you will meet. We then waited for the cluster to notice. Why does the node sit in NotReady for minutes instead of flipping right away? Because node status is a report the kubelet keeps sending, not a state the API server discovers, and the server only marks the node down once the reports stop arriving past its grace period.
That grace period ran several minutes on our cluster before get nodes told the story, which is longer than most people expect the first time they watch it happen.

Describing the node confirms the reason: the Ready condition has flipped to Unknown, carrying the message Kubelet stopped posting node status. That message is the API server saying it has heard nothing from the kubelet past its grace period, so the problem lies on the node, not in the API.
SSH to the node and look at the kubelet journal before you restart anything. Our journal showed the unit winding down cleanly, with the controller shutdown lines as the last output before systemd marked kubelet.service stopped.

Our case was a stopped unit, so the fix was one line on the node.
sudo systemctl start kubelet
Our kubelet posted its first node status within seconds of starting, and cka-node1 was Ready again.

The node flips to Ready as soon as the kubelet posts its first status again. In the CKA exam, if the journal shows the kubelet running but crash looping, the same two commands apply with restart, and then journalctl -u kubelet -f becomes your window into whether the config file at /var/lib/kubelet/config.yaml is the problem. If the node reports Ready but pods sit pending, that is a scheduler problem, and kubectl get events will point you there.
Step 5: Upgrade one minor version
The phrasing to expect: upgrade the cluster from 1.36 to 1.37, control plane first, then the worker, keeping everything running where you can. This is the longest task on the CKA exam and the one where reading the kubeadm upgrade docs pays off most. We rehearsed it by moving our own cluster from 1.36.6 to 1.37.2, and the order matters on every line.
- Install the 1.37 tools on the control plane
- Run kubeadm upgrade apply there
- Drain the worker, install 1.37, run kubeadm upgrade node
- Uncordon the worker and check both versions
Because we install Kubernetes from the pkgs.k8s.io repos, the first step on the control plane is to point the repo at the v1.37 channel and install the matching tool versions. Our packages are apt-mark hold from the build, which is a habit we recommend, and apt will refuse to move held packages unless you pass the flag that says you mean it.
sudo sed -i 's|stable:/v1.36|stable:/v1.37|' /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y --allow-change-held-packages kubelet kubeadm kubectl
sudo systemctl daemon-reload
sudo systemctl restart kubelet
The sed line is easy to get wrong: the repo URL contains slashes around the channel, so the substitution needs a different delimiter. After the install, a daemon-reload and a kubelet restart pick up the new binary, which the official kubeadm upgrade guide lists as its own step for exactly this reason. Our kubelet stayed on 1.36 until we restarted it, which is the detail that keeps get nodes honest later.
With kubeadm now at 1.37.2, run the plan. It reads the current cluster and prints a table of every component that will move. That table is the output to check before you commit.

Our plan also printed the exact apply command, and it names the kubelets as components you upgrade manually after the control plane. We applied with the yes flag because the interactive prompt will otherwise stop the run halfway.
kubeadm upgrade apply v1.37.2 --yes
The apply renews the control plane certificates, rewrites the static pod manifests, and restarts the API server, which makes kubectl hiccup for a few seconds. The docs also have you drain the control plane node as part of the kubelet upgrade step after the apply; with a single control plane that step empties the whole cluster, so on our one-of-everything lab we drained only the worker and accepted the brief overlap instead.
Then the kubelet on cka-cp needs to move to the matching version, and only then is the control plane done. Our worker came next. Drain it first, which cordons and evicts in one step, and note the flag we had to add: our cluster ran bare pods from earlier tasks, and drain refuses to evict pods not owned by a controller unless you insist.
kubectl drain cka-node1 --ignore-daemonsets --force
On cka-node1 itself, switch the repo the same way, install the 1.37 packages, then restart the kubelet after a daemon-reload. Run kubeadm upgrade node, which pulls the new kubelet config from the control plane. The official guide installs the kubeadm and kubectl packages first, runs kubeadm upgrade node, and only then installs kubelet; our order moved the config pull after the restart and still landed on 1.37.2 on both nodes, so either sequence works on a simple two node cluster. Back on the control plane, uncordon, and the cluster is whole again.

Both nodes Ready on v1.37.2 is the pass condition. The certificates that kubeadm renewed during this step are the same ones that expire after a year, and our guide to fixing expired Kubernetes certificates on kubeadm covers what to do when a cluster is already past that date.
Step 6: Restore etcd from the snapshot
The CKA task says: restore the cluster from the snapshot at the given path. This is the second half of step 3, and it is the task people fear most. The shape of the job is always the same: stop etcd, rebuild its data directory from the snapshot, start etcd again. On a kubeadm cluster etcd runs as a static pod, so you stop it by moving its manifest out of /etc/kubernetes/manifests and the kubelet shuts the container down within seconds.
Our first restore attempt failed in a way worth showing, because the error message does not point at the flag you missed. We ran snapshot restore with only the data dir set, and etcd rebuilt the directory with a peer URL of localhost:2380. When etcd came back up it looked for the cluster member it knew and panicked with failed to find remote peer in cluster, crash looping until we intervened. A correct restore needs three things besides the snapshot file.
- The etcd member name, which on kubeadm is the node hostname
- The peer URL the member advertises, port 2380 on the control plane
- An –initial-cluster line naming that same member
You fix that by telling restore who the member is, with the name and peer URL this etcd answers on. One step comes before the command, and skipping it fails immediately: snapshot restore refuses to write into a directory that already exists, so you remove the old data directory while etcd is stopped. Understand what that deletes. Everything etcd currently holds is gone at that moment, and the only copy left is the snapshot file, which is why you copied it off the node in step 3.
sudo rm -rf /var/lib/etcd
ETCDCTL_API=3 etcdctl snapshot restore /tmp/cka-practice-snapshot.db \
--data-dir=/var/lib/etcd \
--name=cka-cp \
--initial-cluster=cka-cp=https://192.168.1.50:2380 \
--initial-advertise-peer-urls=https://192.168.1.50:2380
Newer etcd also ships the etcdutl tool, whose snapshot restore does the same job without talking to a server, and the etcd docs steer new scripts toward it, having marked etcdctl restore deprecated even though it still works. On the 3.6.8 etcd our 1.36 cluster ran, the etcdctl form behaved exactly as the CKA exam expects, so we kept it there, where the command you know under pressure is the one that counts.
We moved the etcd manifest back, let the kubelet start the pod, and gave the API server a minute to reconnect. Then verify the recovery the way the grader will: the endpoint health command, and the objects you know existed before the backup.

Everything we had created before the snapshot came back with it: the rbac-lab Role and RoleBinding, the NetworkPolicies, the namespaces. One detail our run surfaced that no practice question had prepared us for. A restore rewinds all of etcd, so the node objects carry the kubelet versions from backup time, and get nodes showed 1.36.6 for a while even though the binaries were 1.37.2. The kubelets re-report their version on their next heartbeat, and the display corrects itself. Do not start a second restore because of it.
Try the CKA practice question bank
The hands-on tasks above cover the parts of the CKA exam where typing speed decides the mark. Our question bank covers the other part: the concepts behind the tasks, in the same domains the certification weights. A mixed set is the honest place to start, because the draw below pulls from all 27 of our CKA banks, 185 questions in total. Any question you miss tells you which task in this guide to run again.
Our bank mirrors the CKA exam domain weights closely, so a study session that only drills one domain leaves the others thin. Here is how the 185 questions break down today.
| Exam domain | Question banks | Questions |
|---|---|---|
| Cluster Architecture, Installation and Configuration | 8 | 46 |
| Workloads and Scheduling | 5 | 34 |
| Services and Networking | 6 | 35 |
| Storage | 3 | 18 |
| Troubleshooting | 5 | 52 |
Want the lighter version of this path first? Our KCNA study guide covers the concepts without the two hour clock, and a lot of readers take that before the CKA. When you finish a set here, go back to the cluster and run the matching task again until the commands come without the page.
Why there is no CKA exam dump worth your money
Search around the CKA and you will find sites selling question and answer files that claim to be the real exam. For this certification that market does not work the way it does for multiple choice exams, and the reason is structural rather than moral. Our exam grades the state of a cluster you build during the test, so the only thing a leaked answer file could describe is a task statement, and the CNCF rewrites those statements between sittings. What stays constant is the grading: the end state of your cluster decides the mark, which no answer file can carry.
A braindump file gives you a list of task phrasings, which is close to worthless against a live grader that checks your cluster after two hours. The proctoring contract with PSI also makes the trade worse than it looks: sitting the CKA exam means agreeing to monitoring that can void a result, and losing a sitting burns the attempt even though the purchase includes one free retake. Every hour spent on a dump is an hour the kubeadm drills in this guide would have converted into marks.
For printed material alongside the practice, the honest options are the study guides we reviewed in our roundup of the best Kubernetes certification books. The CKA is also a performance exam where the CNCF allows the docs during the test, so the return on reading a second book is much lower than the return on running the sixth upgrade drill.
The traps we hit on this cluster
Every task above ran clean the second time. First attempts left us with four genuine failures, and each one taught us something the successful run could not. If your practice run hits the same walls, the causes were all mundane once we read the errors closely.
“failed to find remote peer in cluster” after snapshot restore
Our restore command rebuilt /var/lib/etcd with a member list derived from defaults, not from our cluster. etcd derives the member identity from the peer URLs you pass, so leaving out –initial-advertise-peer-urls and –initial-cluster writes localhost into the rebuilt directory and the member ID no longer matches what the other etcd configuration expects. The pod then crash loops with that panic line. Pass the name and the peer URL of the node you are restoring, and the pod starts clean.
“Held packages were changed and -y was used without –allow-change-held-packages”
Our build held kubelet, kubeadm and kubectl with apt-mark hold so an unattended upgrade could never move the cluster version. apt remembers that during the upgrade task and refuses the install. Do not unhold the packages. Pass –allow-change-held-packages on the install line instead, because the hold is protecting you the other 364 days of the year.
“error: unable to drain node” on bare pods
kubectl drain refuses to evict pods that no controller owns, and our practice cluster accumulated bare test pods across the tasks. Refusing is correct behaviour, since nothing will recreate those pods elsewhere. On the CKA exam, add –force to drain when you know the pods are disposable, and leave –ignore-daemonsets in the command either way, because daemonset pods are not supposed to move.
“etcdserver: request timed out” on a small control plane
Our control plane runs on 3.8 GB of RAM, and during the heaviest moments, such as the upgrade apply and the restore, etcd occasionally answered requests with timeouts while the controller manager and scheduler crash looped on leader election. The API server, pods and policy enforcement kept serving through all of it, and on our small node the controller manager and scheduler took longest to settle after the burst passed. Recognise it as load rather than corruption: check kubectl get pods -n kube-system, wait out the crash loops, and only intervene if the state persists. In the CKA exam the proctoring cluster is sized properly, so this one should stay a homelab memory.