Setting up a Kubernetes cluster with kubeadm
A walkthrough for setting up a 2-node Kubernetes cluster using kubeadm. Both nodes here are Ubuntu Server 20.04 LTS. The same flow works for larger clusters — you just join more workers at the end.
Prep both nodes
Disable swap
Kubernetes requires swap to be off. There's a flag to ignore swap, but it's not recommended. Disable swap on every node in the cluster:
swapoff -asudo rm /swap.imgswapon --showTo make it permanent, also remove the swap entry from /etc/fstab.
Network and iptables
Load br_netfilter and tell iptables to see bridged traffic. (See the official kubeadm install docs for the canonical version.) Run on both nodes:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.confbr_netfilterEOFcat <<EOF | sudo tee /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-ip6tables = 1net.bridge.bridge-nf-call-iptables = 1EOFInstall containerd
Containerd is the container runtime Kubernetes will talk to.
cat <<EOF | sudo tee /etc/modules-load.d/containerd.confoverlaybr_netfilterEOFmodprobe overlaymodprobe br_netfiltercat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.confnet.bridge.bridge-nf-call-iptables = 1net.ipv4.ip_forward = 1net.bridge.bridge-nf-call-ip6tables = 1EOFsysctl --systemapt-get updateapt-get upgrade -yapt-get install containerd -ymkdir -p /etc/containerdcontainerd config default | tee /etc/containerd/config.tomlsystemctl restart containerdInstall kubeadm, kubelet, kubectl
apt-get updateapt-get install -y apt-transport-https ca-certificates curlcurl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpgecho "deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main" | sudo tee /etc/apt/sources.list.d/kubernetes.listapt-get updateapt-get install -y kubelet kubeadm kubectlapt-mark hold kubelet kubeadm kubectlapt-mark hold pins the versions so a routine apt upgrade doesn't yank Kubernetes out from under you.
The repo URL
apt.kubernetes.iowas retired in 2023 — newer installs should usepkgs.k8s.iowith a per-minor-version repo. Check the current docs for the latest paths.
Init the control-plane node
Run this on the master only — not on the workers.
Pull the images first:
kubeadm config images pullNow bring up the control plane:
kubeadm init --pod-network-cidr=172.16.0.0/16 \ --apiserver-advertise-address=192.168.10.40 \ --control-plane-endpoint=192.168.10.40What the flags mean:
--pod-network-cidr— subnet for the pod network. Has to match what your CNI plugin expects.--apiserver-advertise-address— IP the API server will advertise. Optional, but useful when the host has multiple NICs.--control-plane-endpoint— endpoint clients use to reach the API server (a DNS name or IP). Set this even on a single-master cluster — it's a pain to add later if you ever add more masters.
Configure kubectl access
mkdir -p $HOME/.kubecp -i /etc/kubernetes/admin.conf $HOME/.kube/configchown $(id -u):$(id -g) $HOME/.kube/configVerify:
kubectl get nodesJoin the worker
kubeadm init printed a kubeadm join command at the end of its output. Copy that and run it on the worker node — it'll register and join the cluster.
If you've lost the join command, regenerate it on the master with
kubeadm token create --print-join-command.
Install a CNI plugin
Out of the box, the nodes are NotReady because there's no pod network. Install Calico:
curl https://docs.projectcalico.org/manifests/calico-typha.yaml -o calico.yamlkubectl apply -f calico.yamlOnce Calico's pods are up, the node statuses flip to Ready and the cluster is usable.
That's it 🙂