Installing a cluster with kubeadm
Get Ubuntu machines ready, build a control plane with kubeadm init, add a pod network and join the workers, step by step.
Key ideas in this lesson · tap one for a simple explanation
What you will be able to do
- Get a Linux machine ready for kubeadm: swap off, kernel modules and settings, containerd with the systemd cgroup driver, and the Kubernetes packages from pkgs.k8s.io held at one version.
- Run kubeadm init with a pod network range, set up kubectl and install a network plugin (CNI).
- Print a fresh join command, say what each part of it does and add a worker node.
is the setup tool that turns a few prepared Linux machines into a Kubernetes cluster. You run kubeadm init once, on the first machine, to build the . Then you run kubeadm join on every other machine to add it. kubeadm is not (your everyday command-line tool) and not the (the agent on every ); Control plane and node components keeps the three apart.
The exam rarely asks you to build a cluster from nothing. More often it gives you a half-prepared node, a worker to join, or a kubeadm error to read. This lesson covers two exam topics: Prepare underlying infrastructure for installing a Kubernetes cluster and Create and manage Kubernetes clusters using kubeadm.
1. Prepare every machine
swap off, kernel settings, containerd
2. Install the packages
kubeadm, kubelet and kubectl
3. kubeadm init
builds the control plane, then add a network plugin
4. kubeadm join
adds each worker to the cluster
Steps 1 and 2 run on every machine. Step 3 runs once, on the first control plane machine. Step 4 runs on each worker.
Step 1: prepare every machine
Run these steps on every machine, control plane and workers alike. They turn swap off and switch on the kernel modules and settings that networking needs. Then they install , the .
# Swap off now and after reboots
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# Kernel modules, loaded now and at boot
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# sysctls, persisted and applied now
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
sudo sysctl --system
sysctl net.ipv4.ip_forward net.bridge.bridge-nf-call-iptablessudo apt-get update
sudo apt-get install -y containerd
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
grep SystemdCgroup /etc/containerd/config.toml # SystemdCgroup = true
sudo systemctl restart containerd
sudo systemctl enable containerdPress Explain on each block to see what every line is for. Two steps trip people up most:
- Swap must be off. By default the kubelet refuses to start on a machine with swap on. You can allow swap with
failSwapOn: false, but on a kubeadm lab or exam node, just turn it off. - containerd and the kubelet must use the same cgroup driver. A cgroup driver is how Linux limits a container's CPU and memory. kubeadm sets the kubelet to
systemd, so containerd must usesystemdtoo. If they differ, control plane Pods keep restarting for no visible reason.
Step 2: install kubeadm, kubelet and kubectl
Install all three tools on every machine, at exactly the same version. Each pkgs.k8s.io repository serves only one minor version, such as 1.37. That is why the URL below contains v1.37, and why every upgrade starts by changing it (see Upgrading clusters).
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.37/deb/Release.key | sudo 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:/v1.37/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
apt-cache madison kubeadm | head -3 # package versions look like 1.37.1-1.1
VER=1.37.1-1.1 # pick one from the list
sudo apt-get install -y kubelet=$VER kubeadm=$VER kubectl=$VER
sudo apt-mark hold kubelet kubeadm kubectl
sudo systemctl enable --now kubeletAfter this step the kubelet restarts every few seconds. That is normal: it has no settings yet, until kubeadm init or kubeadm join writes them.
Step 3: kubeadm init and a network plugin
Run kubeadm init on the first machine only. It builds the control plane there: certificates, settings files and the for the , , the and the . (kubeadm init phase --help lists every step it runs.) It does not install a pod network. You add one right after, and until then the node stays NotReady.
sudo kubeadm init \
--pod-network-cidr=192.168.0.0/16 \
--apiserver-advertise-address=10.0.0.10 \
--kubernetes-version=v1.37.1
# kubectl for your user; kubeadm prints these lines when it finishes
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
kubectl get nodes # NotReady: no pod network yet
kubectl -n kube-system get pods # coredns Pending for the same reason
# A CNI plugin. Calico's manifest defaults to 192.168.0.0/16.
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.33.0/manifests/calico.yaml
kubectl -n kube-system rollout status ds/calico-node --timeout=300s
kubectl get nodes # ReadyPick a pod network range that does not overlap. The range in --pod-network-cidr must not overlap your machines' network or the range. For example, if your VMs have addresses like 192.168.1.20, then 192.168.0.0/16 clashes with them. Use --pod-network-cidr=10.244.0.0/16 instead, and set CALICO_IPV4POOL_CIDR in calico.yaml to the same value before you apply it.
Step 4: join the workers
A worker joins with one command, kubeadm join, run on the worker itself. The control plane prints that command for you. It has three parts:
Control plane
kubeadm token create --print-join-command
The join command
address, token and CA hash
Worker
runs it and joins the cluster
sudo kubeadm join …
# On the control plane. The token from kubeadm init expires after 24 hours, so mint a fresh command.
sudo kubeadm token create --print-join-command
# kubeadm join 10.0.0.10:6443 --token <token> --discovery-token-ca-cert-hash sha256:<hash>
# On each worker, after the node preparation above
sudo kubeadm join 10.0.0.10:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
# The hash pins the cluster CA's public key. You can compute it yourself on the control plane:
openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
# Token housekeeping
sudo kubeadm token list
sudo kubeadm token delete <token-id>
kubectl get nodes -o wideIn the official docs· open on exam day too
- Installing kubeadmsearch: installing kubeadm
- Container Runtimessearch: container runtimes systemd cgroup
- Creating a cluster with kubeadmsearch: create cluster kubeadm
- kubeadm joinsearch: kubeadm join
- Ports and Protocolssearch: ports and protocols
Prepare a node and bootstrap a control plane
cp1 is a fresh Ubuntu 24.04 VM with 2 CPUs and 4 GiB of RAM. Prepare it for Kubernetes: swap off, the kernel modules and sysctls a cluster needs, containerd using the systemd cgroup driver, and kubelet, kubeadm and kubectl v1.37 from pkgs.k8s.io held at that version. Then initialize a control plane with pod network CIDR 192.168.0.0/16, make kubectl work for the user you are logged in as, and install Calico v3.33.0 so that cp1 becomes Ready.Runs on a kubeadm cluster (VM lab), not on the kind lab.
- 1
Start from a healthy lab
No setup needed for this one.
- 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-kubeadm-1/verify | bashGot FAIL? Read which check failed, fix it, run it again.
Join a worker with a fresh token
w1 has been prepared like cp1 (containerd, v1.37 packages, kubelet enabled) but is not part of the cluster yet, and the token printed by kubeadm init has expired. Join w1 to the cluster as a worker. You can reach it from cp1 with ssh w1.Runs on a kubeadm cluster (VM lab), not on the kind lab.
- 1
Start from a healthy lab
No setup needed for this one.
- 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-kubeadm-2/verify | bashGot FAIL? Read which check failed, fix it, run it again.
Work out the join parameters without a new token
kubeadm join command. Write the API server endpoint the join must target, as host:port exactly as the cluster publishes it for joining nodes, to /tmp/cka/ar-join-endpoint.txt. Write the value for --discovery-token-ca-cert-hash, including its sha256: prefix, to /tmp/cka/ar-join-hash.txt. Do not create or delete any bootstrap tokens.Cluster:kubectl config use-context kind-cka
- 1
Set up the scenario
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-kubeadm-3/setup | bash - 2
Solve it in your terminal
Use kubectl and the Kubernetes docs, like on exam day. Save any answer files under
/tmp/cka/. - 3
Check your work
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-kubeadm-3/verify | bashGot FAIL? Read which check failed, fix it, run it again. - 4
Clean up (optional)
curl -fsSL https://codewithmuh.com/labs/cka/t/ar-kubeadm-3/cleanup | bash
Finished this lesson?
0 of 3 tasks passed. Your readiness counts tasks, so run them before you move on.