A repeatable script that builds a kubeadm cluster (one control plane, one worker) in about ten minutes, a tasks/ folder of forty timed exam-style tasks with solutions and verification commands, a breaks/ folder of scripts that sabotage the cluster in exam-realistic ways, and a log of your times per task and per fix. By the end, the upgrade and the etcd backup-and-restore each take you under ten minutes without looking anything up.
- The DevOps track's Kubernetes module and the Certifications track's CKA guide (this is its practice half)
- A laptop with 8 GB RAM free for two VMs (4 GB and 2 GB) and about 20 GB disk
- Comfortable in a terminal and with vim or nano
- Multipass — Ubuntu VMs on macOS, Windows or Linux with one command ↗
- kubeadm, kubelet, kubectl — the exam is built on kubeadm clusters; these are the tools it expects ↗
- containerd — the container runtime kubeadm clusters use ↗
- Calico (or Cilium) — a CNI that supports NetworkPolicy, which the exam tests ↗
- etcdctl — backup and restore tasks ↗
cka-lab/
├── cluster/
│ ├── up.sh # create VMs, install runtime + kubeadm, init, join, CNI
│ ├── node-setup.sh # runs inside each VM: containerd, kubeadm packages, sysctl
│ └── down.sh # delete the VMs
├── tasks/
│ ├── 01-pods-deployments.md
│ ├── 02-services-ingress.md
│ ├── 03-storage.md
│ ├── 04-scheduling.md
│ ├── 05-rbac.md
│ ├── 06-networkpolicy.md
│ ├── 07-upgrade.md
│ ├── 08-etcd.md
│ └── solutions/
├── breaks/
│ ├── break-kubelet.sh
│ ├── break-apiserver.sh
│ ├── break-cni.sh
│ ├── break-service.sh
│ └── break-scheduler.sh
└── LOG.md # task, date, time taken, notesTick each step as you finish it — your progress is saved in this browser only (back up or restore on the hub). Every code block has a copy button. If something goes wrong, the troubleshooting section at the end covers the usual suspects.
Build the cluster
A kubeadm cluster with one control plane and one worker on a recent Kubernetes minor version, built by a script you can rerun in ten minutes.
- Install Multipass and create the two VMs. The control plane gets 2 CPUs and 4 GB; the worker 2 CPUs and 2 GB. Use the same Ubuntu LTS the exam environment uses (currently 24.04).bash
brew install --cask multipass # macOS; Windows: winget install Canonical.Multipass; Linux: snap install multipass multipass launch 24.04 --name cp --cpus 2 --memory 4G --disk 12G multipass launch 24.04 --name w1 --cpus 2 --memory 2G --disk 12G multipass listCheck: Both VMs showRunningwith an IPv4 address. - Write the per-node setup script: kernel modules and sysctl for networking, containerd with the systemd cgroup driver, and the Kubernetes apt repository pinned to the exam's minor version (adjust
1.35to the version on the CKA FAQ when you do this).bash#!/bin/bash # cluster/node-setup.sh (run as root inside each VM) set -euxo pipefail K8S_MINOR="1.35" cat <<'EOF' >/etc/modules-load.d/k8s.conf overlay br_netfilter EOF modprobe overlay && modprobe br_netfilter cat <<'EOF' >/etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sysctl --system >/dev/null swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab apt-get update && apt-get install -y containerd apt-transport-https ca-certificates curl gpg mkdir -p /etc/containerd containerd config default >/etc/containerd/config.toml sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml systemctl restart containerd && systemctl enable containerd curl -fsSL "https://pkgs.k8s.io/core:/stable:/v${K8S_MINOR}/deb/Release.key" | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v${K8S_MINOR}/deb/ /" >/etc/apt/sources.list.d/kubernetes.list apt-get update && apt-get install -y kubelet kubeadm kubectl apt-mark hold kubelet kubeadm kubectl systemctl enable kubeletThe version pin matters twice: it matches the exam, and it leaves room for the upgrade task (you will deliberately install the previous patch release first in Phase 4). - Write the orchestration script that copies the setup into both VMs, initialises the control plane, installs Calico, joins the worker and pulls the kubeconfig to your laptop.bash
#!/bin/bash # cluster/up.sh set -euo pipefail for n in cp w1; do multipass transfer cluster/node-setup.sh "$n:/tmp/node-setup.sh" multipass exec "$n" -- sudo bash /tmp/node-setup.sh done CP_IP=$(multipass info cp --format json | python3 -c "import sys,json; print(json.load(sys.stdin)['info']['cp']['ipv4'][0])") multipass exec cp -- sudo kubeadm init --apiserver-advertise-address "$CP_IP" --pod-network-cidr 192.168.0.0/16 multipass exec cp -- bash -c 'mkdir -p ~/.kube && sudo cp /etc/kubernetes/admin.conf ~/.kube/config && sudo chown $(id -u):$(id -g) ~/.kube/config' # CNI with NetworkPolicy support multipass exec cp -- kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml JOIN=$(multipass exec cp -- sudo kubeadm token create --print-join-command) multipass exec w1 -- sudo bash -c "$JOIN" mkdir -p ~/.kube multipass exec cp -- cat ~/.kube/config | sed "s/127.0.0.1/$CP_IP/" > ~/.kube/cka-lab.config echo "export KUBECONFIG=~/.kube/cka-lab.config"The kubeconfig rewrite letskubectlon your laptop reach the API server through the VM's IP. Practise on the control plane VM itself too (multipass shell cp), because the exam terminal is a Linux host, not your laptop. - Run it, then wait for both nodes to be Ready.bash
chmod +x cluster/*.sh && ./cluster/up.sh export KUBECONFIG=~/.kube/cka-lab.config kubectl get nodes -o wide kubectl get pods -n kube-systemCheck:cpandw1areReady; allkube-systempods (including calico) are Running within a couple of minutes. - The exam switches clusters with
kubectl config use-context. Create a second named context on the same cluster so you build the habit of running the context line before every task.bashexport KUBECONFIG=~/.kube/cka-lab.config kubectl config get-contexts kubectl config set-context lab-apps --cluster=kubernetes --user=kubernetes-admin --namespace=apps kubectl config use-context lab-apps && kubectl config current-contextCheck:current-contextprintslab-apps;kubectl get podswithout-nnow targets theappsnamespace. - Write
down.sh(multipass delete cp w1 && multipass purge) and prove the round trip: tear down and rebuild. Time it; that number is your safety net for every drill that goes wrong.Check: A full rebuild takes about ten minutes. Commit thecluster/scripts to a new repository.
Exam habits and the core tasks
The aliases and speed techniques become reflexes, and you have timed results for the workloads, services, storage and scheduling domains.
- Every session starts on the control plane VM with the exam setup, typed from memory in under thirty seconds.bash
multipass shell cp alias k=kubectl export do="--dry-run=client -o yaml" export now="--force --grace-period=0" source <(kubectl completion bash) && complete -o default -F __start_kubectl k printf 'set et ts=2 sw=2\n' > ~/.vimrc - Create the task files. Each task states a namespace, a goal, a time budget and a verification command, exactly like the exam. Write the first ten workload tasks; the solution file holds the imperative command sequence.text
# tasks/01-pods-deployments.md T1 (4 min) In namespace `apps` (create it), create a Deployment `web` with 3 replicas of `nginx:1.27`, container port 80, and label `tier=frontend` on the pods. verify: k -n apps get deploy web -o jsonpath='{.status.readyReplicas}' -> 3 T2 (3 min) Scale `web` to 5, then roll its image to `nginx:1.27.3`, then roll back to the previous revision. verify: k -n apps rollout history deploy web shows 3 revisions; image is nginx:1.27 T3 (5 min) Create a ConfigMap `web-config` with key `INDEX=hello` and a Secret `web-secret` with key `TOKEN=s3cr3t`. Mount the ConfigMap at /etc/config and expose the Secret as env var TOKEN in `web`. verify: k -n apps exec deploy/web -- sh -c 'cat /etc/config/INDEX; echo $TOKEN' T4 (4 min) Add a readiness probe (HTTP GET / on 80, every 5 s) and resource requests (100m CPU, 64Mi) with limits (200m, 128Mi) to `web`. verify: k -n apps get deploy web -o yaml | grep -A4 readinessProbe T5 (3 min) Create a CronJob `report` in `apps` running `busybox:1.36` with command `date` every 5 minutes; keep 2 successful job histories. verify: k -n apps get cronjob report -o jsonpath='{.spec.successfulJobsHistoryLimit}' -> 2 T6 (4 min) Create a DaemonSet `node-agent` in `kube-system` running `busybox:1.36` with `sleep 3600`, tolerating the control-plane taint so it runs on both nodes. verify: k -n kube-system get ds node-agent -> DESIRED 2, READY 2 T7 (4 min) A pod `crash` in `apps` must run `busybox:1.36` with command `sh -c 'exit 1'` and restart policy Never. Create it, then find the exit code from its status. verify: k -n apps get pod crash -o jsonpath='{.status.containerStatuses[0].state.terminated.exitCode}' -> 1 T8 (5 min) Create a HorizontalPodAutoscaler for `web`: min 2, max 6, target 60% CPU. Install metrics-server first if `k top nodes` fails (see solutions). verify: k -n apps get hpa web T9 (3 min) Export the `web` Deployment to /root/web.yaml, delete it, recreate it from the file. verify: k -n apps get deploy web T10 (3 min) Find which node each `web` pod runs on and write the pod name and node, one per line, to /root/pods.txt. verify: cat /root/pods.txt shows 5 linesSolutions live intasks/solutions/01.md; write them yourself the first time you solve each task, using generators (k create deploy ... $do > f.yaml) rather than hand-written YAML. - Do T1–T10 with a timer and record each time in
LOG.md. Anything over budget goes on a redo list.text# LOG.md | date | task | budget | took | notes | |------------|------|--------|-------|-----------------------------------------| | 2026-09-22 | T1 | 4:00 | 2:10 | | | 2026-09-22 | T3 | 5:00 | 7:40 | forgot envFrom vs env valueFrom syntax |Check: Ten timed entries; a redo list of the tasks that ran over. - Install the metrics server (the HPA task and
kubectl topneed it). On a kubeadm lab the kubelet serves a self-signed certificate, so the insecure-TLS flag is required.bashkubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.7.2/components.yaml kubectl -n kube-system patch deploy metrics-server --type=json \ -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]' sleep 30 && kubectl top nodesCheck:kubectl top nodesprints CPU and memory for both nodes. - Services and Ingress tasks. Install the ingress-nginx controller once (it is what most exam environments provide) and write tasks for ClusterIP, NodePort, endpoints debugging and an Ingress with two paths.bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.0/deploy/static/provider/baremetal/deploy.yaml kubectl -n ingress-nginx get pods -w # until the controller is RunningTasks for02-services-ingress.md: exposewebas ClusterIP on 80→80 and curl it from a temporary pod; expose as NodePort and curl the worker's IP; create a Service whose selector is deliberately wrong and fix it by readingk get ep; create an Ingressshoprouting/toweband/apito a second Deploymentapi(hashicorp/http-echo), and verify withcurl -H 'Host: shop.example' http://W1_IP:NODEPORT/api. - Storage and scheduling tasks: a hostPath PersistentVolume and matching claim, a StorageClass with
volumeBindingMode: WaitForFirstConsumer, and scheduling with nodeSelector, node affinity, taints and tolerations, plus a pod that stays Pending until you fix the reason.yaml# tasks/solutions/03-pv.yaml (the shape to be able to write from memory) apiVersion: v1 kind: PersistentVolume metadata: name: pv-data spec: capacity: storage: 1Gi accessModes: [ReadWriteOnce] persistentVolumeReclaimPolicy: Retain storageClassName: manual hostPath: path: /mnt/data --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-data namespace: apps spec: accessModes: [ReadWriteOnce] storageClassName: manual resources: requests: storage: 500MiTasks for03-storage.mdand04-scheduling.md: bind the claim and mount it in a pod that writes a file; delete the pod and confirm the file persists on the node; taintw1withenv=prod:NoScheduleand makewebtolerate it; add a nodeSelector nobody matches and diagnose the Pending event; userequiredDuringSchedulingIgnoredDuringExecutionaffinity for a label you add tocp. - Add a Gateway API task, since the current curriculum lists it alongside Ingress: install a Gateway API implementation (the NGINX Gateway Fabric or Envoy Gateway quickstart), create a
Gatewayand anHTTPRoutesendingshop.example/apito theapiService, and verify with curl exactly as for the Ingress task.Keep the task even if it takes longer the first time; HTTPRoute manifests are copied from the docs in the exam, and knowing which page to open is the skill.
RBAC and NetworkPolicy
You can create least-privilege access for a user and a service account and write a NetworkPolicy from memory, verifying both.
- RBAC tasks: create a ServiceAccount
deployerinappsthat may only get, list and update Deployments inapps; verify withauth can-i; then create a ClusterRole for reading nodes bound to a userops.bashk -n apps create sa deployer k -n apps create role deploy-editor --verb=get,list,update --resource=deployments k -n apps create rolebinding deployer-binding --role=deploy-editor --serviceaccount=apps:deployer k auth can-i update deployments -n apps --as=system:serviceaccount:apps:deployer # yes k auth can-i delete deployments -n apps --as=system:serviceaccount:apps:deployer # no k create clusterrole node-reader --verb=get,list,watch --resource=nodes k create clusterrolebinding ops-nodes --clusterrole=node-reader --user=ops k auth can-i list nodes --as=ops # yesCheck: The threeauth can-ianswers are yes, no, yes. Add a task to05-rbac.mdthat requires a ClusterRole bound with a RoleBinding (namespace-scoped use of a cluster-wide role) and explain the difference in your solution notes. - NetworkPolicy tasks: default deny in
apps, then allowwebto be reached only from pods labelledrole=lb, and allowwebegress only to DNS and toapion 8080. Verify each rule with a temporary pod before and after.yamlapiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: default-deny namespace: apps spec: podSelector: {} policyTypes: [Ingress, Egress] --- apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: web-policy namespace: apps spec: podSelector: matchLabels: {tier: frontend} policyTypes: [Ingress, Egress] ingress: - from: - podSelector: matchLabels: {role: lb} ports: [{protocol: TCP, port: 80}] egress: - to: - namespaceSelector: matchLabels: {kubernetes.io/metadata.name: kube-system} podSelector: matchLabels: {k8s-app: kube-dns} ports: [{protocol: UDP, port: 53}, {protocol: TCP, port: 53}] - to: - podSelector: matchLabels: {app: api} ports: [{protocol: TCP, port: 8080}]Check:k -n apps run t --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- --timeout=2 webfails; the same with--labels role=lbsucceeds; fromweb,nslookup apiworks andwget api:8080works whilewget example.comtimes out. - Write five more NetworkPolicy tasks with different selectors (namespace-only, ipBlock with except, port ranges) and solve them from the docs page for NetworkPolicy — the one page you are allowed to open in the exam. Time how long it takes to find and adapt the example.Check: Under three minutes per policy by the fifth one.
The long tasks: upgrade and etcd
A control-plane and worker upgrade and an etcd backup-and-restore, each performed three times until they take under ten minutes without notes.
- Set up the upgrade drill: rebuild the cluster with the previous patch release so there is something to upgrade to. In
node-setup.shchange the install line to pin a specific older patch (for examplekubelet=1.35.0-1.1 kubeadm=1.35.0-1.1 kubectl=1.35.0-1.1), rebuild, and confirm the version.bash./cluster/down.sh && ./cluster/up.sh kubectl get nodes # VERSION column shows v1.35.0 (or whatever you pinned) multipass exec cp -- apt-cache madison kubeadm | head -5 # the available newer patch versionsUpgrading across a minor version (1.34 → 1.35) is the more realistic exam task and follows the same steps; do a patch upgrade first, then rebuild on the previous minor and do the minor upgrade. - Perform the control-plane upgrade with a stopwatch running. This is the exact sequence; do not deviate from the order.bash
# on your laptop or the cp node with KUBECONFIG set kubectl drain cp --ignore-daemonsets --delete-emptydir-data # on cp multipass shell cp sudo apt-mark unhold kubeadm && sudo apt-get update && sudo apt-get install -y kubeadm=1.35.1-1.1 && sudo apt-mark hold kubeadm sudo kubeadm upgrade plan sudo kubeadm upgrade apply v1.35.1 -y sudo apt-mark unhold kubelet kubectl && sudo apt-get install -y kubelet=1.35.1-1.1 kubectl=1.35.1-1.1 && sudo apt-mark hold kubelet kubectl sudo systemctl daemon-reload && sudo systemctl restart kubelet exit kubectl uncordon cp kubectl get nodesCheck:cpshows the new version and is Ready. Record the time inLOG.md. - Upgrade the worker the same way with
kubeadm upgrade nodeinstead ofupgrade apply.bashkubectl drain w1 --ignore-daemonsets --delete-emptydir-data multipass exec w1 -- sudo bash -c 'apt-mark unhold kubeadm && apt-get update && apt-get install -y kubeadm=1.35.1-1.1 && apt-mark hold kubeadm && kubeadm upgrade node && 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 w1 kubectl get nodesCheck: Both nodes on the new version, both Ready, allappspods back to Running. - etcd backup: take a snapshot using the certificate paths from the etcd static pod manifest, then verify it. Read the paths from the manifest instead of memorising them.bash
multipass shell cp sudo grep -E 'cert-file|key-file|trusted-ca-file|listen-client' /etc/kubernetes/manifests/etcd.yaml sudo apt-get install -y etcd-client 2>/dev/null || true # or use the etcdctl binary from the etcd release sudo ETCDCTL_API=3 etcdctl snapshot save /opt/etcd-backup.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 sudo ETCDCTL_API=3 etcdctl snapshot status /opt/etcd-backup.db --write-out=tableCheck: The status table shows a hash, a revision and a total key count. - Prove the restore matters: create a Deployment
after-backupinapps, then restore the snapshot into a new data directory and repoint the etcd static pod at it. The Deployment created after the backup must disappear.bashkubectl -n apps create deploy after-backup --image=nginx:1.27 multipass shell cp sudo ETCDCTL_API=3 etcdctl snapshot restore /opt/etcd-backup.db --data-dir /var/lib/etcd-restored sudo sed -i 's#path: /var/lib/etcd$#path: /var/lib/etcd-restored#' /etc/kubernetes/manifests/etcd.yaml # kubelet notices the manifest change and recreates the etcd pod; watch it come back sudo crictl ps | grep etcd exit kubectl -n apps get deploy # after-backup is gone; web is backCheck:after-backupno longer exists; the cluster is healthy. Time the whole backup-and-restore sequence and log it. Repeat the upgrade and restore drills twice more on fresh clusters.
Break it, fix it
Five realistic sabotage scripts and the procedure that finds each cause from symptoms, timed.
- Write the break scripts. Each one changes a single thing an exam scenario might; you run one without reading it, then diagnose from the symptoms.bash
# breaks/break-kubelet.sh (run on w1 as root): wrong kubelet config path sed -i 's#/var/lib/kubelet/config.yaml#/var/lib/kubelet/config.yml#' /var/lib/kubelet/kubeadm-flags.env 2>/dev/null || \ sed -i 's#config.yaml#config.yml#' /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf systemctl daemon-reload && systemctl restart kubelet # breaks/break-apiserver.sh (run on cp as root): a typo in a flag sed -i 's/--etcd-servers=/--etcd-server=/' /etc/kubernetes/manifests/kube-apiserver.yaml # breaks/break-scheduler.sh (run on cp as root): scheduler cannot start sed -i 's/kube-scheduler/kube-schedulerr/' /etc/kubernetes/manifests/kube-scheduler.yaml # breaks/break-service.sh (run anywhere with kubectl): selector mismatch kubectl -n apps patch svc web -p '{"spec":{"selector":{"tier":"frontendd"}}}' # breaks/break-cni.sh (run on w1 as root): CNI config removed mv /etc/cni/net.d /etc/cni/net.d.bak && systemctl restart kubeletKeep the scripts in the repo but do not memorise them; the value is in diagnosing fromk get nodes,k describe,journalctl -u kubelet,crictl ps -aandcrictl logsas the CKA guide's procedure describes. - Run each break, diagnose, fix, and log the time. The expected symptoms and the tools that reveal the cause:text
break-kubelet symptom: w1 NotReady find: journalctl -u kubelet on w1 -> 'config.yml: no such file' fix: correct the path, daemon-reload, restart kubelet break-apiserver symptom: kubectl times out find: on cp: crictl ps -a (apiserver exited), crictl logs CID -> unknown flag fix: correct the manifest; kubelet restarts the static pod break-scheduler symptom: new pods stay Pending find: k -n kube-system get pods (no scheduler), crictl ps -a on cp with no events fix: correct the image name in the manifest break-service symptom: curl to web fails find: k -n apps get ep web -> <none>; compare selector with pod labels fix: patch the selector back break-cni symptom: new pods on w1 stuck find: k describe pod -> 'network plugin not ready'; ls /etc/cni/net.d on w1 ContainerCreating fix: restore the directory, restart kubeletCheck: All five fixed; each under ten minutes; the times inLOG.md. - Write five more breaks of your own (a wrong
--advertise-address, a missing kube-proxy DaemonSet, a full disk on a node via a large file, an expired-looking certificate path, a badimagePullPolicywith a private image) and have a study partner run them for you — the diagnosis is only real when you do not know what changed.Check: Ten breaks total, each diagnosed with the procedure rather than by guessing. - Run a full mock: rebuild the cluster on the previous minor version, then in one two-hour sitting do fifteen tasks drawn from every file plus one break, one upgrade and one etcd restore, with a timer and the docs as the only reference. Score yourself on end state.Check: Above 66 % of tasks fully correct in the time. If not, the
LOG.mdredo list tells you exactly what to drill before the real exam, and the killer.sh sessions that come with registration are the final check.
Troubleshooting
kubeadm initfails with a preflight error about swap or bridge-nf-call-iptables- The node-setup script disables swap and sets the sysctls; if you rebuilt the VM without rerunning it, run it again.
kubeadm init --ignore-preflight-errors=...hides the problem rather than fixing it. - Nodes stay NotReady after init
- The CNI is not installed or not running yet.
kubectl -n kube-system get podsshould show calico pods;kubectl describe node cpshowsnetwork plugin not readyuntil they are Running. - The worker cannot join: token or connection refused
- Tokens expire after 24 hours; generate a new join command with
kubeadm token create --print-join-commandon cp. Confirm the worker can reach the cp IP on 6443 (nc -zv CP_IP 6443). kubectlfrom the laptop reports a certificate error for the VM IP- The API server certificate includes the advertise address given at init; if the VM's IP changed after a restart, either re-init or set
--apiserver-cert-extra-sans. Practising from inside the cp VM avoids this entirely. etcdctlnot found- Install
etcd-clientfrom apt, or download the matching etcd release tarball and use itsetcdctl; the exam environment provides the binary. - After the etcd restore, the API server stays down
- Check that the
hostPathinetcd.yamlpoints at the restored directory and that the directory is owned correctly;crictl logson the etcd container shows the reason. If the manifest was edited with a typo, kubelet will not start the pod at all —journalctl -u kubeletsays why. - Multipass VMs are slow or fail to start on macOS
- Reduce the memory allocations, close other VMs, and check
multipass get local.driver; on Apple Silicon the default driver is fine, on Intel Macs HyperKit or QEMU may need selecting.
Where to go from here
- Register for the CKA and use the two included killer.sh sessions as the final mocks; they mirror the real difficulty.
- Add a third node and practise the tasks that need it: pod anti-affinity across nodes, draining with a PodDisruptionBudget, and a control-plane node with a taint you must tolerate.
- Install a Gateway API implementation and redo the routing tasks with HTTPRoute, which the current curriculum includes.
- Continue to the CKS (security specialist) with the Cybersecurity track's container module: the same cluster serves for RBAC hardening, Pod Security Standards, admission and runtime security drills.
- Turn the cluster into the target for the DevOps track's GitOps on Kubernetes project.
Did a step fail or feel unclear? Tell me which one →