This guide walks you through setting up a fully functional single-node Kubernetes cluster using Kubeadm, with containerd as the container runtime and Tigera Calico as the CNI plugin — all running on AlmaLinux 9.8 (Olive Jaguar).
By the end, you’ll have a working Kubernetes node capable of scheduling and running pods, complete with networking provided by Calico.
Server Specifications
| Component | Details |
|---|---|
| OS | AlmaLinux 9.8 (Olive Jaguar) |
| CPU | 4 vCPU |
| RAM | 10 GB |
| Container Runtime | containerd |
| CNI Plugin | Tigera Calico |
| Installer | kubeadm |
Table of Contents
- System Preparation
- Installing containerd
- Configuring containerd for Kubernetes
- Installing kubeadm, kubelet, kubectl
- Initializing the Cluster with kubeadm
- Configuring kubectl
- Installing Tigera Calico
- Verifying the Cluster
1. System Preparation
1.1 Set Hostname
hostnamectl set-hostname <your-hostname>1.2 Disable Swap
Kubernetes requires swap to be disabled.
# Disable swap immediatelyswapoff -a
# Disable swap permanentlysed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab1.3 Disable SELinux
# Disable temporarilysetenforce 0
# Disable permanentlysed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config1.4 Disable Firewall (or Configure Required Ports)
systemctl disable --now firewalldTip: If you prefer to keep the firewall active, open the following ports instead:
6443/tcp— Kubernetes API Server2379-2380/tcp— etcd server client API10250/tcp— kubelet API10251/tcp— kube-scheduler10252/tcp— kube-controller-manager179/tcp— Calico BGP4789/udp— Calico VXLAN (optional)
1.5 Load Required Kernel Modules
# Create module configurationcat <<EOF | tee /etc/modules-load.d/k8s.confoverlaybr_netfilterEOF
# Load modules immediatelymodprobe overlaymodprobe br_netfilter1.6 Configure Sysctl Parameters
cat <<EOF | tee /etc/sysctl.d/k8s.confnet.bridge.bridge-nf-call-iptables = 1net.bridge.bridge-nf-call-ip6tables = 1net.ipv4.ip_forward = 1EOF
# Apply the configurationsysctl --system1.7 Verify Kernel Modules Are Loaded
lsmod | grep br_netfilterlsmod | grep overlayExpected output (values may vary):
br_netfilter 36864 0bridge 421888 1 br_netfilteroverlay 237568 02. Installing containerd
Reference: Docker Engine on RHEL — docs.docker.com
Since we only need containerd (not the full Docker Engine), we’ll use the Docker repository to install just the containerd.io package.
2.1 Remove Potentially Conflicting Packages
dnf remove -y docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine \ podman \ runc2.2 Install DNF Plugin and Add Docker Repository
dnf -y install dnf-plugins-corednf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo2.3 Install containerd.io
Only install the containerd.io package — no need for docker-ce or other Docker components.
dnf install -y containerd.io2.4 Enable and Start containerd
systemctl enable --now containerd2.5 Verify containerd
systemctl status containerdcontainerd --version3. Configuring containerd for Kubernetes
3.1 Generate Default Configuration
mkdir -p /etc/containerdcontainerd config default | tee /etc/containerd/config.toml3.2 Enable SystemdCgroup
Kubernetes requires containerd to use systemd as the cgroup driver.
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.tomlVerify the change:
grep 'SystemdCgroup' /etc/containerd/config.toml# Expected output: SystemdCgroup = true3.3 Restart containerd
systemctl restart containerdsystemctl status containerd4. Installing kubeadm, kubelet, kubectl
Reference: Installing kubeadm — kubernetes.io
4.1 Add the Kubernetes Repository
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo[kubernetes]name=Kubernetesbaseurl=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/enabled=1gpgcheck=1gpgkey=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/repodata/repomd.xml.keyexclude=kubelet kubeadm kubectl cri-tools kubernetes-cniEOFNote: Replace
v1.36with your desired Kubernetes version. Check the latest releases at kubernetes.io/releases.
4.2 Install kubelet, kubeadm, kubectl
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes4.3 Enable kubelet
systemctl enable --now kubeletNote: kubelet will enter a restart loop until
kubeadm initcompletes — this is expected behavior.
4.4 Verify Versions
kubeadm versionkubectl version --clientkubelet --version5. Initializing the Cluster with kubeadm
5.1 Initialize the Control Plane
kubeadm init \ --pod-network-cidr=10.244.0.0/16 \ --cri-socket unix:///run/containerd/containerd.sockImportant:
- The
--pod-network-cidr=10.244.0.0/16flag is used here because some server networks (e.g.,192.168.x.x/24) may overlap with Calico’s default CIDR (192.168.0.0/16). Using10.244.0.0/16avoids routing conflicts.- If your server has multiple network interfaces, add
--apiserver-advertise-address=<your-server-ip>to specify which IP the API server should bind to.
Make sure to save the kubeadm join command from the output — you’ll need it if you ever want to add worker nodes.
6. Configuring kubectl
6.1 Set Up kubeconfig for Root User
mkdir -p $HOME/.kubecp -i /etc/kubernetes/admin.conf $HOME/.kube/configchown $(id -u):$(id -g) $HOME/.kube/config6.2 Verify the Node (NotReady Is Expected)
kubectl get nodesExpected output:
NAME STATUS ROLES AGE VERSION<your-hostname> NotReady control-plane Xs v1.36.xThe NotReady status is expected because the CNI plugin (Calico) hasn’t been installed yet.
7. Installing Tigera Calico
Reference: Calico on single-host Kubernetes — docs.tigera.io
7.1 Install the Tigera Operator
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml7.2 Create the Custom Resource for Calico
Since we’re using CIDR 10.244.0.0/16 (instead of the default 192.168.0.0/16), download the manifest first, update the CIDR, then apply:
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/custom-resources.yaml
# Replace Calico's default CIDR with ourssed -i 's|192.168.0.0/16|10.244.0.0/16|g' custom-resources.yaml
# Verify the changegrep 'cidr' custom-resources.yaml
# Apply the configurationkubectl create -f custom-resources.yaml7.3 Wait for Calico to Deploy
watch kubectl get pods -n calico-systemWait until all pods show Running status:
NAME READY STATUS RESTARTS AGEcalico-kube-controllers-xxx 1/1 Running 0 Xmcalico-node-xxx 1/1 Running 0 Xmcalico-typha-xxx 1/1 Running 0 Xmcsi-node-driver-xxx 2/2 Running 0 XmPress Ctrl+C to exit watch.
7.4 Untaint the Control Plane Node
By default, Kubernetes prevents workloads from being scheduled on control plane nodes. For a single-node setup, we need to remove this taint:
kubectl taint nodes --all node-role.kubernetes.io/control-plane-8. Verifying the Cluster
8.1 Check Node Status
kubectl get nodes -o wideThe node status should now show Ready:
NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE CONTAINER-RUNTIME<your-hostname> Ready control-plane Xm v1.36.x <your-ip> AlmaLinux 9.8 (Olive Jaguar) containerd://2.x.x8.2 Check All System Pods
kubectl get pods -AAll pods across kube-system, calico-system, and tigera-operator namespaces should show Running status.
8.3 Test with a Simple Deployment
kubectl run nginx --image=nginx --port=80kubectl get podsExpected output:
NAME READY STATUS RESTARTS AGEnginx 1/1 Running 0 15sVerify the pod details:
kubectl describe pod nginxThe pod should be scheduled on your node with a Calico-assigned IP from the 10.244.x.x range.
8.4 Check Cluster Information
kubectl cluster-infokubectl versionTroubleshooting
Node remains NotReady after installing Calico
Inspect the node events and Calico logs:
kubectl describe node <your-hostname>kubectl logs -n calico-system -l k8s-app=calico-nodePod stuck in ContainerCreating
Check the pod events and kubelet logs:
kubectl describe pod <pod-name>journalctl -u kubelet -fReset the cluster (start over)
If you need to completely reset and start fresh:
kubeadm reset -frm -rf /etc/cni/net.drm -rf $HOME/.kubeiptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -Xipvsadm --clear 2>/dev/null || trueReferences
- Docker Engine on RHEL — docs.docker.com
- Installing kubeadm — kubernetes.io
- Calico on single-host Kubernetes — docs.tigera.io
This guide is based on a real-world deployment. Adjust hostnames, IPs, and version numbers to match your own environment.