Create Your Own CKA Exam Lab at Home: Multipass Kubernetes Script
- ikerdomingoperez
- Feb 25
- 4 min read
If you are considering the Certified Kubernetes Administrator (CKA) or any of the other Linux Foundation kubernetes certifications, you would appreciate having a reliable, exam-like lab. No cloud costs, no complex setups, just a script that spins up a full multi-node cluster (control plane + 2 workers) with Cilium CNI, containerd, and debug tools in minutes. But more important, also tear down and recreate in minutes.
Tested on macOS, but theoretically can work on Linux (Ubuntu/RHEL) and even on Windows.

Why This Lab?
Exam-accurate: kubeadm init/join, PodCIDR, etcd access, real networking: covers 100% CKA objectives (troubleshooting 30%, networking 15%, etc.).[training.linuxfoundation]
Lightweight: 3 VMs (2CPU/2GB/10GB each), fast bootstrap.
Cilium CNI: Matches CKA and LFS258 Lab Setup.
Debug-ready: crictl, kubectl aliases, metrics-server ready, helm. Add more tools as needed.
Prerequisites
macOS: brew install --cask multipass
Linux (Ubuntu/Debian): sudo snap install multipass --classic
Linux (RHEL/CentOS/Fedora): Enable snaps (sudo dnf install snapd fuse), sudo snap install multipass --classic
Windows: Download/run MSI from multipass.run/install. Use PowerShell/Git Bash.
The Script - CKA Exam Lab
Save as cka-lab.sh, chmod +x cka-lab.sh.
#!/bin/bash
set -e
NODES=("cp" "worker1" "worker2")
BINARIES=("kubeadm" "kubelet" "kubectl")
POD_CIDR="10.244.0.0/16"
KUBERNETES_VERSION="1.34.0"
CRICTL_VERSION="v1.30.0"
CRICTL_FILENAME="crictl-${CRICTL_VERSION}-linux-amd64.tar.gz"
log() { echo "[$(date +'%H:%M:%S')] $1"; }
case "$1" in
start)
log "CKA LAB Setup"
# Download binaries
log "Downloading K8s"
for binary in "${BINARIES[@]}"; do
[ ! -f "$binary" ] && curl -LO "https://dl.k8s.io/release/v${KUBERNETES_VERSION}/bin/linux/amd64/${binary}"
done
chmod +x kubeadm kubelet kubectl
curl -L -o crictl.tar.gz "https://github.com/kubernetes-sigs/cri-tools/releases/download/${CRICTL_VERSION}/${CRICTL_FILENAME}"
# (Re)Create VMs
log "Provisioning Cluster"
for node in "${NODES[@]}"; do
multipass delete "$node" --purge 2>/dev/null || true
multipass launch --name "$node" --cpus 2 --memory 2G --disk 10G 22.04
done
# Setup nodes
log "Setup (binaries + services)..."
for node in "${NODES[@]}"; do
log " $node"
# Transfer binaries
multipass transfer kubeadm "$node":/tmp/
multipass transfer kubelet "$node":/tmp/
multipass transfer kubectl "$node":/tmp/
multipass transfer crictl.tar.gz "$node":/tmp/
# Install packages
multipass exec "$node" -- sudo apt update -qq
multipass exec "$node" -- sudo apt install -y containerd bash-completion apt-transport-https tree software-properties-common ca-certificates socat conntrack -qq
# Configure stuff
multipass exec "$node" -- /bin/bash << 'EOF'
# Overlay
sudo modprobe overlay >/dev/null 2>&1 || true
sudo modprobe br_netfilter >/dev/null 2>&1 || true
# Swap
sudo swapoff -a >/dev/null 2>&1 || true
sudo sed -i '/ swap / s/^/#/' /etc/fstab >/dev/null 2>&1 || true
# Sysctl
sudo tee /etc/sysctl.d/kubernetes.conf >/dev/null <<SYSCTL
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
SYSCTL
sudo sysctl --system >/dev/null 2>&1 || true
# Binaries
sudo mv /tmp/{kubeadm,kubelet,kubectl} /usr/local/bin/
sudo chmod +x /usr/local/bin/kube*
# Containerd config
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# Kubelet service
sudo tee /etc/systemd/system/kubelet.service >/dev/null <<SERVICE
[Unit]
Description=Kubelet
Documentation=https://kubernetes.io/docs/
After=network.target containerd.service
Requires=containerd.service
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
EnvironmentFile=-/etc/default/kubelet
ExecStart=/usr/local/bin/kubelet \$KUBELET_KUBECONFIG_ARGS \$KUBELET_CONFIG_ARGS \$KUBELET_KUBEADM_ARGS \$KUBELET_EXTRA_ARGS
Restart=always
RestartSec=10
KillMode=process
[Install]
WantedBy=multi-user.target
SERVICE
sudo systemctl daemon-reload
sudo systemctl restart containerd
sudo systemctl enable --now containerd kubelet
sudo systemctl stop kubelet
# crictl
sudo tar zxvf /tmp/crictl.tar.gz -C /usr/local/bin
rm -f /tmp/crictl.tar.gz
sudo tee /etc/crictl.yaml >/dev/null <<CONFIG
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
CONFIG
sudo chmod 644 /etc/crictl.yaml
EOF
# User aliases
multipass exec "$node" -- bash -c "
echo 'alias k=kubectl' >> /home/ubuntu/.bashrc
echo 'complete -F __start_kubectl k' >> /home/ubuntu/.bashrc
echo 'source <(kubectl completion bash)' >> /home/ubuntu/.bashrc
"
done
# Bootstrap
log "Configuring Cluster..."
CP_IP=$(multipass info cp | grep IP | awk '{print $2}')
for node in "${NODES[@]}"; do
multipass exec "$node" -- /bin/bash << EOF
echo '${CP_IP} cp k8scp k8scp.lab.example.com' | sudo tee -a /etc/hosts > /dev/null
EOF
done
log "Init cp..."
multipass exec cp -- sudo systemctl restart containerd kubelet
multipass exec cp -- sudo kubeadm init --pod-network-cidr="$POD_CIDR" --cri-socket unix:///run/containerd/containerd.sock
log "CNI..."
multipass exec cp -- bash -c "
mkdir -p /home/ubuntu/.kube
sudo cp /etc/kubernetes/admin.conf /home/ubuntu/.kube/config
sudo chown ubuntu:ubuntu /home/ubuntu/.kube/config
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm repo add cilium https://helm.cilium.io/
helm repo update
helm install cilium cilium/cilium \\
--version 1.17.4 \\
--namespace kube-system \\
--set ipam.operator.clusterPoolIPv4PodCIDRList={$POD_CIDR} \\
--set kubeProxyReplacement=false
"
# Wait
log "Waiting for cilium config..."
multipass exec cp -- kubectl rollout status daemonset/cilium -n kube-system --timeout=600s
# Join
log "Join Workers..."
multipass exec cp -- bash -c "
TOKEN=\$(sudo kubeadm token create)
HASH=\$(openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //')
echo \"sudo kubeadm join $CP_IP:6443 --token \$TOKEN --discovery-token-ca-cert-hash sha256:\$HASH\" > /home/ubuntu/join.sh
sudo chown ubuntu /home/ubuntu/join.sh
"
multipass transfer cp:/home/ubuntu/join.sh .
for node in worker1 worker2; do
log "Joining $node to the cluster..."
multipass transfer join.sh "$node":/home/ubuntu/
multipass exec "$node" -- sudo bash /home/ubuntu/join.sh
done
# Export config
multipass transfer cp:/home/ubuntu/.kube/config ~/.kube/cka-final.conf
echo "export KUBECONFIG=~/.kube/cka-final.conf" >> ~/.bashrc
log "DONE! source ~/.bashrc && k get nodes"
# Verify
KUBECONFIG=~/.kube/cka-final.conf kubectl get nodes -o wide
;;
stop)
for node in "${NODES[@]}"; do multipass delete "$node" --purge 2>/dev/null || true; done
rm -f kubeadm kubelet kubectl ~/.kube/cka-final.conf
;;
cp|worker1|worker2) multipass shell "$1" ;;
status)
multipass list
[ -f ~/.kube/cka-final.conf ] && KUBECONFIG=~/.kube/cka-final.conf kubectl get nodes -o wide
;;
*) echo "Usage: $0 {start|stop|cp|worker1|worker2|status}"; exit 1 ;;
esac
Troubleshooting
Issue | Fix |
No internet in VMs | Linux: Restart multipass daemon (sudo snap restart multipass). Windows: Check Hyper-V firewall.[github] |
ARM host | Download arm64 binaries: Edit URLs to /arm64/.[documentation.ubuntu] |
OOM | Bump --memory 4G in launch. |
Cilium fails | --timeout=600s (usually starts in 2-3 mins); fallback Flannel: Replace Helm with kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml. |
Next Steps
Now is the time to play around with your CKA Exam Lab, or just ask your favourite AI to create some real exam scenarios for you.



Comments