The CKA is a 2-hour, hands-on, performance-based exam on the current Kubernetes v1.35 curriculum — no multiple choice, just a live cluster and a terminal. 15–20 tasks, weighted by domain, pass mark 66%, one free retake. Five domains: Troubleshooting 30% · Cluster Architecture 25% · Services & Networking 20% · Workloads & Scheduling 15% · Storage 10%. The whole game is speed and correctness at the keyboard — this sheet is command-first. Kubernetes docs (kubernetes.io) are allowed during the exam; bookmark them. Study order and exam-day playbook at the bottom.
0. Exam-day setup (do this in the first 60 seconds)
Every task runs against a specific cluster/context. Two habits save the most points: switch context first, and generate YAML imperatively instead of typing it.
# the alias that pays for itself alias k=kubectl export do="--dry-run=client -o yaml" # generate manifests fast export now="--force --grace-period=0" # delete pods immediately source <(kubectl completion bash) ; complete -o default -F __start_kubectl k # ALWAYS switch to the cluster the question names, first thing kubectl config get-contexts kubectl config use-context <cluster-name> # vim: set these in ~/.vimrc so YAML indentation doesn't fight you # set expandtab tabstop=2 shiftwidth=2 number autoindent
kubectl config use-context at the start of every question. Also note the task's
namespace — add -n <ns> everywhere or k config set-context --current --namespace=<ns>.1. kubectl speed & imperative generation
You rarely write YAML from scratch. Generate a skeleton with $do, edit, apply.
# generate manifests (don't create) — then edit the file k create deploy web --image=nginx --replicas=3 $do > web.yaml k run pod1 --image=nginx $do > pod.yaml k create svc clusterip web --tcp=80:80 $do > svc.yaml k create cm app --from-literal=KEY=val $do > cm.yaml k create secret generic db --from-literal=pass=s3cr3t $do > sec.yaml k create job pi --image=perl -- perl -e 'print 1' $do > job.yaml k create cronjob c --image=busybox --schedule="*/1 * * * *" -- date $do > cj.yaml # quick imperative actions (no file needed) k expose deploy web --port=80 --target-port=8080 # make a Service k scale deploy web --replicas=5 k set image deploy/web nginx=nginx:1.27 k label pod pod1 tier=frontend k annotate pod pod1 note="exam" k rollout restart deploy/web # inspect fast k get pods -A -o wide # all namespaces, node + IP k get pods --show-labels k get po -l app=web # label selector k describe pod pod1 # events at the bottom = gold k explain pod.spec.containers # field reference, offline
k apply -f. kubectl explain --recursive is the offline field lookup when
you forget a nesting.2. Cluster Architecture, Installation & Config (25%)
The control plane, in one breath
| Component | Job |
|---|---|
| kube-apiserver | The front door — every request goes through it; the only thing that talks to etcd. |
| etcd | The database — all cluster state. Back this up (high-value exam task). |
| kube-scheduler | Picks a node for each unscheduled Pod (filters + scores). |
| kube-controller-manager | Reconciliation loops (Deployment→ReplicaSet→Pods, node, endpoints…). |
| kubelet | Node agent — runs containers, reports status. A systemd service, not a pod. |
| kube-proxy | Programs Service networking (iptables/IPVS) on each node. |
| CNI plugin | Pod networking (Calico/Cilium/Flannel). No CNI ⇒ nodes stay NotReady. |
On a kubeadm cluster the control-plane components run as static pods — manifests in /etc/kubernetes/manifests/ on the control-plane node. Edit a file there and the kubelet restarts that pod automatically.
kubeadm — create, join, HA
# init a control plane (pick a pod CIDR your CNI expects) kubeadm init --pod-network-cidr=10.244.0.0/16 --control-plane-endpoint="LB:6443" mkdir -p ~/.kube && cp -i /etc/kubernetes/admin.conf ~/.kube/config # then apply a CNI (e.g. calico/cilium manifest) — nodes go Ready after # join a worker (token printed by init; regenerate if expired) kubeadm token create --print-join-command kubeadm join LB:6443 --token <t> --discovery-token-ca-cert-hash sha256:<h> # join another control plane (HA) — add --control-plane --certificate-key kubeadm init phase upload-certs --upload-certs # prints the cert key
:6443, joined with --control-plane. etcd quorum needs a
majority — 3 nodes tolerate 1 failure, 5 tolerate 2.Cluster upgrade (kubeadm) — control plane then workers
# --- on the first control-plane node --- apt-get update && apt-get install -y kubeadm=1.35.x-* # upgrade kubeadm first kubeadm upgrade plan kubeadm upgrade apply v1.35.x # then upgrade the node's kubelet + kubectl kubectl drain <cp-node> --ignore-daemonsets apt-get install -y kubelet=1.35.x-* kubectl=1.35.x-* systemctl daemon-reload && systemctl restart kubelet kubectl uncordon <cp-node> # --- on each worker (one at a time) --- kubeadm upgrade node # NOT 'upgrade apply' on workers kubectl drain <worker> --ignore-daemonsets --delete-emptydir-data # (install kubelet/kubectl, restart kubelet, then) kubectl uncordon <worker>
apply (control plane) / node (workers),
then the kubelet. Always drain before and uncordon after. Skipping minor versions
(1.33→1.35) is unsupported — go one minor at a time.etcd backup & restore (high-value — practice cold)
# find the certs (from the etcd static-pod manifest or describe) export E="--cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ --endpoints=https://127.0.0.1:2379" # BACKUP ETCDCTL_API=3 etcdctl $E snapshot save /opt/snap.db ETCDCTL_API=3 etcdctl $E snapshot status /opt/snap.db -w table # RESTORE to a new data dir ETCDCTL_API=3 etcdctl snapshot restore /opt/snap.db \ --data-dir=/var/lib/etcd-restore # then point the etcd static pod at the new dir: # edit /etc/kubernetes/manifests/etcd.yaml -> hostPath + --data-dir # kubelet restarts etcd automatically
snapshot restore does not need the cert flags — only the snapshot + --data-dir.
After restore you must repoint the etcd manifest's hostPath volume and the
--data-dir arg to the new directory, then wait for the static pod to come back.RBAC — who can do what
# Role (namespaced) + RoleBinding k create role dev --verb=get,list,watch --resource=pods -n team k create rolebinding dev-bind --role=dev --user=jane -n team # ClusterRole (cluster-wide) + ClusterRoleBinding k create clusterrole reader --verb=get,list --resource=nodes k create clusterrolebinding read-nodes --clusterrole=reader --user=jane # bind a ClusterRole into ONE namespace with a RoleBinding k create rolebinding x --clusterrole=reader --serviceaccount=team:sa1 -n team # TEST permissions (do this to verify!) k auth can-i list pods --as=jane -n team k auth can-i '*' '*' --as=system:serviceaccount:team:sa1
kubectl auth can-i --as=....CRDs, Operators, Helm & Kustomize
- CRD — extends the API with a new
kind.k get crd, thenk get <newkind> -A. An Operator is a CRD + a controller watching it. - Helm — package manager.
helm repo add & update,helm install <rel> <chart> -n ns,helm upgrade,helm list -A,helm uninstall,helm template(render without installing). - Kustomize — template-free overlays.
kubectl apply -k ./dir(dir has akustomization.yamllisting resources + patches).kubectl kustomize ./dirrenders it.
3. Workloads & Scheduling (15%)
Deployments & rollouts
k create deploy web --image=nginx:1.26 --replicas=3 k set image deploy/web nginx=nginx:1.27 # trigger a rollout k rollout status deploy/web k rollout history deploy/web k rollout undo deploy/web --to-revision=1 # roll back k scale deploy/web --replicas=6 k autoscale deploy/web --min=2 --max=10 --cpu-percent=70 # HPA
ConfigMaps & Secrets into Pods
# env from a key, and mount as a volume
spec:
containers:
- name: app
image: nginx
envFrom: [{ configMapRef: { name: app } }] # all keys as env
env:
- name: PASS
valueFrom: { secretKeyRef: { name: db, key: pass } }
volumeMounts: [{ name: cfg, mountPath: /etc/cfg }]
volumes:
- name: cfg
configMap: { name: app }
k get secret x -o jsonpath='{.data.pass}' | base64 -d to read. Base64 is encoding, not
security — encryption-at-rest is a separate EncryptionConfiguration on the apiserver.Scheduling — where a Pod lands
| Mechanism | What it does |
|---|---|
| nodeSelector | Hard match on node labels: nodeSelector: {disk: ssd}. |
| nodeName | Bypass the scheduler entirely — pin to one node by name. |
| Affinity / anti-affinity | Soft/hard rules (required…/preferred…) over node or pod labels. |
| Taint + Toleration | Taint repels pods from a node; a matching toleration lets a pod stay. |
| Resources | requests drive scheduling; limits cap usage (CPU throttled, memory OOMKilled). |
# taints (control-plane nodes carry one by default)
k taint node n1 key=val:NoSchedule
k taint node n1 key=val:NoSchedule- # remove (trailing minus)
# toleration on the pod:
# tolerations: [{key: key, operator: Equal, value: val, effect: NoSchedule}]
# cordon/drain (also a Troubleshooting skill)
k cordon n1 # no new pods
k drain n1 --ignore-daemonsets --delete-emptydir-data
k uncordon n1
Static pods, DaemonSets, Jobs
- Static pod — managed by the kubelet directly from
/etc/kubernetes/manifests/(orstaticPodPathin the kubelet config). Name gets the node suffix. Delete the file to remove it. - DaemonSet — one pod per (matching) node; how kube-proxy and CNI agents run.
- Job (run-to-completion) / CronJob (scheduled).
completions,parallelism,backoffLimit.
4. Services & Networking (20%)
Services — the four types
| Type | Reach |
|---|---|
| ClusterIP (default) | In-cluster virtual IP. The DNS name is svc.ns.svc.cluster.local. |
| NodePort | Opens a port (30000–32767) on every node → routes to the Service. |
| LoadBalancer | Cloud LB in front of a NodePort (needs a cloud/MetalLB provider). |
| ExternalName | CNAME alias to an external DNS name; no proxying. |
k expose deploy web --port=80 --target-port=8080 # ClusterIP k expose deploy web --type=NodePort --port=80 k get ep web # endpoints = the pod IPs behind it (empty ⇒ selector wrong)
k get endpoints <svc> is empty,
the Service's selector doesn't match any pod's labels, or the pods aren't Ready. This is the #1 "service
not working" cause.Ingress & Gateway API
Ingress = L7 HTTP routing to Services, via an ingress controller (nginx, etc.). Gateway API is the newer, more expressive successor now on the curriculum — three roles: GatewayClass (the controller), Gateway (the listener/IP), and HTTPRoute (the routing rules, attached to a Gateway).
k get ingress -A ; k describe ingress web k get gatewayclass ; k get gateway -A ; k get httproute -A # HTTPRoute: parentRefs -> the Gateway; rules -> matches + backendRefs (Services)
NetworkPolicy — deny-by-default is opt-in
# default-deny all ingress in a namespace, then allow specific traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: default-deny, namespace: team }
spec:
podSelector: {} # all pods
policyTypes: [Ingress]
# (no ingress rules = deny all ingress)
---
spec: # allow from pods labelled role=api on port 8080
podSelector: { matchLabels: { app: db } }
policyTypes: [Ingress]
ingress:
- from: [{ podSelector: { matchLabels: { role: api } } }]
ports: [{ protocol: TCP, port: 8080 }]
policyTypes, and rules only add allowances. Flannel doesn't enforce NetworkPolicy;
Calico/Cilium do.CoreDNS & service discovery
# DNS lives in kube-system as a Deployment 'coredns' + Service 'kube-dns' k get pods -n kube-system -l k8s-app=kube-dns k get svc kube-dns -n kube-system # test from a throwaway pod: k run t --image=busybox:1.28 --rm -it --restart=Never -- nslookup web.team # pod A record: 10-244-1-5.team.pod.cluster.local ; svc: web.team.svc.cluster.local
5. Storage (10%)
The chain: a PersistentVolume (PV) is a piece of storage; a PersistentVolumeClaim (PVC) is a request that binds to a matching PV; a Pod mounts the PVC. A StorageClass enables dynamic provisioning (create a PV on demand when a PVC asks).
| Field | Meaning |
|---|---|
| accessModes | RWO (one node RW), ROX (many nodes RO), RWX (many nodes RW), RWOP (one pod). |
| reclaimPolicy | Retain (keep data after PVC delete), Delete (remove the volume). |
| storageClassName | Empty "" = static/no dynamic; a name = dynamic via that class. |
| volumeBindingMode | Immediate vs WaitForFirstConsumer (bind when a pod is scheduled). |
k get pv,pvc,sc k describe pvc data # 'Pending' ⇒ no matching PV / no default SC k get sc # is there a (default) StorageClass?
Pending means no PV satisfies its size/accessMode/class, or there's no default
StorageClass to provision one. Check describe pvc events. A PVC can't shrink; expansion needs
allowVolumeExpansion: true on the StorageClass.6. Troubleshooting (30% — the biggest domain)
Highest weight, so drill it. The method: look at events and logs before touching anything.
# the universal first four commands k get pods -A -o wide # what's not Running/Ready k describe pod <p> # Events section = the cause k logs <p> [-c <container>] [--previous] # --previous = last crash k get events -A --sort-by=.lastTimestamp # node level k get nodes ; k describe node <n> # conditions: MemoryPressure, DiskPressure, Ready ssh <node>; systemctl status kubelet; journalctl -u kubelet -f crictl ps -a ; crictl logs <id> # container runtime, on the node
Pod statuses → cause
| Status | Usual cause |
|---|---|
| Pending | Unschedulable — no node fits (resources, taints, nodeSelector), or PVC unbound. |
| ImagePullBackOff | Bad image name/tag, private registry without a pull secret. |
| CrashLoopBackOff | Container starts then exits — check logs --previous; bad command, missing config, failing probe. |
| OOMKilled | Exceeded its memory limit — raise the limit or fix the leak. |
| 0/1 Ready | Running but readiness probe failing — app not serving on the probe path/port yet. |
| Init:… | An init container hasn't finished — check its logs specifically. |
NotReady node
# almost always the kubelet or the CNI ssh <node> systemctl status kubelet && journalctl -u kubelet --no-pager | tail -40 systemctl restart kubelet # check: kubelet config (/var/lib/kubelet/config.yaml), certs, CNI plugin present, # container runtime up (systemctl status containerd)
NotReady is nearly always: kubelet down/misconfigured, container runtime down, or no CNI.
journalctl -u kubelet tells you which. A fresh cluster with all nodes NotReady = you forgot to
install a CNI.Broken control plane (static pods)
# apiserver down? kubectl won't respond. Inspect static pods on the node: ssh <cp-node> ls /etc/kubernetes/manifests/ # apiserver/etcd/scheduler/ccm crictl ps -a | grep -E 'apiserver|etcd|scheduler' crictl logs <id> # why it won't start # fix the manifest yaml (bad flag, wrong etcd endpoint), kubelet re-creates the pod
Service / DNS not reachable
k get ep <svc> # empty ⇒ selector/labels mismatch k get pods -n kube-system -l k8s-app=kube-dns # CoreDNS healthy? k run t --image=busybox:1.28 --rm -it --restart=Never -- \ sh -c 'nslookup <svc>.<ns>; wget -qO- <svc>.<ns>:<port>' # also: NetworkPolicy blocking it? kube-proxy running on the node?
7. jsonpath, sorting & scripting shortcuts
# custom columns / jsonpath (exam loves "get X and write it to a file")
k get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}'
k get pods -o custom-columns='NAME:.metadata.name,NODE:.spec.nodeName'
k get pods --sort-by=.metadata.creationTimestamp
k get pods --field-selector status.phase=Running
k top nodes ; k top pods -A # needs metrics-server
k get po -o yaml | k neat 2>/dev/null || k get po -o yaml # strip noise if kubectl-neat present
8. What to keep in mind (exam mindset)
- Switch context first, every task. Then check the namespace. Most lost points are "right answer, wrong cluster/ns."
- Never hand-write YAML you can generate.
$doa skeleton, edit, apply. Speed is the exam. - Read events first.
describeandlogs --previousanswer most troubleshooting tasks before you change anything. - Verify every task. Re-run a
get/auth can-i/nslookupto confirm the change actually worked — partial credit is per-task. - Flag and move on. ~6–8 min per task average. If one fights you, note it and return; a 30%-weighted troubleshooting task is worth more than a stuck 4% one.
- Use the docs. kubernetes.io is allowed — bookmark NetworkPolicy, PV, RBAC, and the kubeadm upgrade page; copy YAML from there and edit.
- Static pods restart themselves. Edit the manifest in
/etc/kubernetes/manifests/; don'tkubectl deleteand expect it gone. - Practice etcd backup/restore cold. It's a near-guaranteed, high-value, all-or-nothing task.
9. Common mistakes
- Wrong context / namespace — the biggest silent point-loser. Set both at task start.
- Forgetting
--ignore-daemonsets(and often--delete-emptydir-data) ondrain— it errors out otherwise. - Upgrading kubelet before
kubeadm upgrade apply— wrong order; kubeadm first, then kubelet. - Passing cert flags to
etcdctl snapshot restore— restore only needs the snapshot +--data-dir; and don't forget to repoint the etcd manifest. - Service selector ≠ pod labels — empty endpoints. Always
k get epto confirm. - Expecting NetworkPolicy to work on Flannel — it won't enforce; needs Calico/Cilium.
- PVC size/accessMode/class not matching any PV — stays Pending; read
describe pvcevents. - Editing an immutable field (e.g. a Job's
selector, a Pod's containers) — delete & recreate instead. - Trusting a task is done without verifying — always re-query. Partial, silent failures cost whole tasks.
- Reading a Secret and forgetting
base64 -d— the value in.datais base64-encoded. - Deleting a static pod with kubectl — the kubelet re-creates it from the manifest file; remove/edit the file.
- Not using imperative generation — hand-writing PodSpecs burns the clock you needed for troubleshooting.
10. Study priority order
- Troubleshooting (30%) — pod statuses, NotReady nodes, control-plane static pods, service/DNS
- etcd backup & restore — drill until automatic
- kubeadm upgrade + drain/cordon/uncordon
- RBAC (Role/ClusterRole/bindings +
auth can-i) - Services & NetworkPolicy (endpoints, selectors, deny-by-default)
- Scheduling (taints/tolerations, affinity, nodeSelector, resources)
- Storage (PV/PVC/StorageClass binding, accessModes, reclaim)
- Deployments & rollouts, ConfigMap/Secret injection
- Ingress / Gateway API, CoreDNS
- CRDs/Operators, Helm, Kustomize (recognize + basic ops)
Exam: 2 hours, ~15–20 performance-based tasks on Kubernetes v1.35, pass at 66%, one free retake.
Lean on imperative kubectl, verify every task, and spend your time where the weight is —
troubleshooting and cluster architecture are more than half the marks.
11. Official sources & freshness
This guide was re-checked against the official Linux Foundation exam page and CNCF curriculum on 19 July 2026. The performance environment is currently Kubernetes v1.35; re-check before booking because versions and policies change.