948 words
5 minutes
Kubernetes Single-Node Setup on AlmaLinux 9.8 with Kubeadm, Containerd & Calico

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#

ComponentDetails
OSAlmaLinux 9.8 (Olive Jaguar)
CPU4 vCPU
RAM10 GB
Container Runtimecontainerd
CNI PluginTigera Calico
Installerkubeadm

Table of Contents#

  1. System Preparation
  2. Installing containerd
  3. Configuring containerd for Kubernetes
  4. Installing kubeadm, kubelet, kubectl
  5. Initializing the Cluster with kubeadm
  6. Configuring kubectl
  7. Installing Tigera Calico
  8. Verifying the Cluster

1. System Preparation#

1.1 Set Hostname#

Terminal window
hostnamectl set-hostname <your-hostname>

1.2 Disable Swap#

Kubernetes requires swap to be disabled.

Terminal window
# Disable swap immediately
swapoff -a
# Disable swap permanently
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

1.3 Disable SELinux#

Terminal window
# Disable temporarily
setenforce 0
# Disable permanently
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

1.4 Disable Firewall (or Configure Required Ports)#

Terminal window
systemctl disable --now firewalld

Tip: If you prefer to keep the firewall active, open the following ports instead:

  • 6443/tcp — Kubernetes API Server
  • 2379-2380/tcp — etcd server client API
  • 10250/tcp — kubelet API
  • 10251/tcp — kube-scheduler
  • 10252/tcp — kube-controller-manager
  • 179/tcp — Calico BGP
  • 4789/udp — Calico VXLAN (optional)

1.5 Load Required Kernel Modules#

Terminal window
# Create module configuration
cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# Load modules immediately
modprobe overlay
modprobe br_netfilter

1.6 Configure Sysctl Parameters#

Terminal window
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
# Apply the configuration
sysctl --system

1.7 Verify Kernel Modules Are Loaded#

Terminal window
lsmod | grep br_netfilter
lsmod | grep overlay

Expected output (values may vary):

br_netfilter 36864 0
bridge 421888 1 br_netfilter
overlay 237568 0

2. 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#

Terminal window
dnf remove -y docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine \
podman \
runc

2.2 Install DNF Plugin and Add Docker Repository#

Terminal window
dnf -y install dnf-plugins-core
dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo

2.3 Install containerd.io#

Only install the containerd.io package — no need for docker-ce or other Docker components.

Terminal window
dnf install -y containerd.io

2.4 Enable and Start containerd#

Terminal window
systemctl enable --now containerd

2.5 Verify containerd#

Terminal window
systemctl status containerd
containerd --version

3. Configuring containerd for Kubernetes#

3.1 Generate Default Configuration#

Terminal window
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml

3.2 Enable SystemdCgroup#

Kubernetes requires containerd to use systemd as the cgroup driver.

Terminal window
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

Verify the change:

Terminal window
grep 'SystemdCgroup' /etc/containerd/config.toml
# Expected output: SystemdCgroup = true

3.3 Restart containerd#

Terminal window
systemctl restart containerd
systemctl status containerd

4. Installing kubeadm, kubelet, kubectl#

Reference: Installing kubeadm — kubernetes.io

4.1 Add the Kubernetes Repository#

Terminal window
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF

Note: Replace v1.36 with your desired Kubernetes version. Check the latest releases at kubernetes.io/releases.

4.2 Install kubelet, kubeadm, kubectl#

Terminal window
dnf install -y kubelet kubeadm kubectl --disableexcludes=kubernetes

4.3 Enable kubelet#

Terminal window
systemctl enable --now kubelet

Note: kubelet will enter a restart loop until kubeadm init completes — this is expected behavior.

4.4 Verify Versions#

Terminal window
kubeadm version
kubectl version --client
kubelet --version

5. Initializing the Cluster with kubeadm#

5.1 Initialize the Control Plane#

Terminal window
kubeadm init \
--pod-network-cidr=10.244.0.0/16 \
--cri-socket unix:///run/containerd/containerd.sock

Important:

  • The --pod-network-cidr=10.244.0.0/16 flag 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). Using 10.244.0.0/16 avoids 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#

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

6.2 Verify the Node (NotReady Is Expected)#

Terminal window
kubectl get nodes

Expected output:

NAME STATUS ROLES AGE VERSION
<your-hostname> NotReady control-plane Xs v1.36.x

The 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#

Terminal window
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/tigera-operator.yaml

7.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:

Terminal window
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.32.0/manifests/custom-resources.yaml
# Replace Calico's default CIDR with ours
sed -i 's|192.168.0.0/16|10.244.0.0/16|g' custom-resources.yaml
# Verify the change
grep 'cidr' custom-resources.yaml
# Apply the configuration
kubectl create -f custom-resources.yaml

7.3 Wait for Calico to Deploy#

Terminal window
watch kubectl get pods -n calico-system

Wait until all pods show Running status:

NAME READY STATUS RESTARTS AGE
calico-kube-controllers-xxx 1/1 Running 0 Xm
calico-node-xxx 1/1 Running 0 Xm
calico-typha-xxx 1/1 Running 0 Xm
csi-node-driver-xxx 2/2 Running 0 Xm

Press 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:

Terminal window
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

8. Verifying the Cluster#

8.1 Check Node Status#

Terminal window
kubectl get nodes -o wide

The 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.x

8.2 Check All System Pods#

Terminal window
kubectl get pods -A

All pods across kube-system, calico-system, and tigera-operator namespaces should show Running status.

8.3 Test with a Simple Deployment#

Terminal window
kubectl run nginx --image=nginx --port=80
kubectl get pods

Expected output:

NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 15s

Verify the pod details:

Terminal window
kubectl describe pod nginx

The pod should be scheduled on your node with a Calico-assigned IP from the 10.244.x.x range.

8.4 Check Cluster Information#

Terminal window
kubectl cluster-info
kubectl version

Troubleshooting#

Node remains NotReady after installing Calico#

Inspect the node events and Calico logs:

Terminal window
kubectl describe node <your-hostname>
kubectl logs -n calico-system -l k8s-app=calico-node

Pod stuck in ContainerCreating#

Check the pod events and kubelet logs:

Terminal window
kubectl describe pod <pod-name>
journalctl -u kubelet -f

Reset the cluster (start over)#

If you need to completely reset and start fresh:

Terminal window
kubeadm reset -f
rm -rf /etc/cni/net.d
rm -rf $HOME/.kube
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
ipvsadm --clear 2>/dev/null || true

References#


This guide is based on a real-world deployment. Adjust hostnames, IPs, and version numbers to match your own environment.

Kubernetes Single-Node Setup on AlmaLinux 9.8 with Kubeadm, Containerd & Calico
https://im-gatan.com/posts/kubernetes-single-host-setup/
Author
Gatan
Published at
2026-06-19
License
CC BY-NC-SA 4.0