Andrew Mercer
on this page

This guide builds a kubeadm cluster by hand on Alma Linux (the same steps apply to RHEL and Rocky). Run the steps under all nodes on every node, then the control-plane and worker steps where indicated.

I have Ansible roles for this. These steps are the manual version, as a refresher. For a scripted version, see the Ubuntu + Ansible guide.

All nodes

Select a release

  • https://kubernetes.io/releases

Choose a release that's still supported and use it everywhere below. Each minor version has its own package repo:

KUBERNETES_VERSION=v1.34

Install the Kubernetes tools

  • https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_VERSION}/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_VERSION}/rpm/repodata/repomd.xml.key
EOF
sudo dnf -y install kubeadm kubectl kubelet
sudo systemctl enable kubelet

The kubelet restarts in a loop until kubeadm init/join runs. That's expected.

Lock the versions so a routine dnf update can't upgrade the cluster out from under you:

sudo dnf -y install 'dnf-command(versionlock)'
sudo dnf versionlock add kubelet kubeadm kubectl

Disable swap

sudo swapoff -a                                   # now
sudo sed -i '/\sswap\s/ s/^\([^#]\)/#\1/' /etc/fstab   # on boot

SELinux

kubeadm's documented setup assumes SELinux is permissive:

sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

Kernel modules

cat <<EOF | sudo tee /etc/modules-load.d/kubernetes.conf
overlay
br_netfilter
EOF

sudo modprobe overlay
sudo modprobe br_netfilter

The file loads the modules on boot, and modprobe loads them now.

sysctl

cat <<EOF | sudo tee /etc/sysctl.d/kubernetes.conf
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF

sudo sysctl --system

Firewall ports

  • https://kubernetes.io/docs/reference/networking/ports-and-protocols/

kubeadm warns if firewalld is active (please ensure ports [6443 10250] are open). Those two are only the minimum. The full set:

Control plane:

sudo firewall-cmd --permanent --add-port={6443,2379-2380,10250,10257,10259}/tcp
sudo firewall-cmd --reload

Workers:

sudo firewall-cmd --permanent --add-port={10250,10256,30000-32767}/tcp
sudo firewall-cmd --reload

Also open the ports your CNI plugin needs. For Calico, see its firewall table.

Install a container runtime

Install and configure containerd as described in containerd: install containerd.io, write the default config, set SystemdCgroup = true, and enable the service. That page also covers crictl and nerdctl for debugging.

No kubelet unit edits are needed. kubeadm autodetects the containerd socket. The --container-runtime=remote flag in older guides was removed in Kubernetes 1.27.

Control plane

Initialize the cluster

Use a pod CIDR that doesn't overlap the LAN or the service CIDR (see CNI plugins):

sudo kubeadm init --pod-network-cidr=10.244.0.0/16

On a multi-NIC host, add --apiserver-advertise-address=<node-lan-ip>.

Set up kubectl for your user:

mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

Install a CNI plugin

Nodes stay NotReady until a CNI plugin is installed:

  • Calico: set its pod CIDR to 10.244.0.0/16 to match the kubeadm init above.
  • Cilium

Workers

Join the cluster

Print a join command on the control plane:

sudo kubeadm token create --print-join-command

Run its output with sudo on each worker. Then, from the control plane:

kubectl get nodes -o wide

Join tokens expire after 24 hours. Generate a new one whenever you add a node later.

Troubleshooting

If the API server is down and kubectl can't connect, inspect the control-plane containers directly on the node with crictl:

sudo crictl ps -a
sudo crictl logs <container-id>
sudo journalctl -u kubelet -f

Other tools are covered in containerd debugging tools.

Next steps