Complete guide to install Kubernetes on Rocky Linux 9 using kubeadm. These steps also work on AlmaLinux 9 and CentOS Stream 9.
Requirements
- 2+ CPU cores, 2 GB+ RAM per node
- Rocky Linux 9 (minimal install)
- Root or sudo access
- Unique hostname, MAC, and product_uuid on each node
- Full network connectivity between nodes
Step 1: System prerequisites (all nodes)
Disable swap, load kernel modules, and configure sysctl:
# Disable swap permanently
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# Load required kernel modules
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# Set required sysctl parameters
cat <<EOF | sudo 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
sudo sysctl --system
# Set SELinux to permissive (required for kubelet)
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
# Open firewall ports (control plane)
sudo firewall-cmd --permanent --add-port=6443/tcp # API server
sudo firewall-cmd --permanent --add-port=2379-2380/tcp # etcd
sudo firewall-cmd --permanent --add-port=10250/tcp # kubelet
sudo firewall-cmd --permanent --add-port=10259/tcp # scheduler
sudo firewall-cmd --permanent --add-port=10257/tcp # controller-manager
sudo firewall-cmd --reload
# Open firewall ports (worker nodes)
sudo firewall-cmd --permanent --add-port=10250/tcp # kubelet
sudo firewall-cmd --permanent --add-port=10256/tcp # kube-proxy
sudo firewall-cmd --permanent --add-port=30000-32767/tcp # NodePort
sudo firewall-cmd --reloadStep 2: Install containerd (all nodes)
# Add Docker repo (for containerd.io package)
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# Install containerd
sudo dnf install -y containerd.io
# Generate default config
sudo containerd config default | sudo tee /etc/containerd/config.toml
# Enable SystemdCgroup (critical for Kubernetes)
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
# Start and enable
sudo systemctl restart containerd
sudo systemctl enable containerd
# Verify
sudo systemctl status containerdStep 3: Install kubeadm, kubelet, kubectl (all nodes)
# Add Kubernetes repository
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/repodata/repomd.xml.key
EOF
# Install
sudo dnf install -y kubelet kubeadm kubectl
# Enable kubelet (it will wait for kubeadm init)
sudo systemctl enable --now kubeletStep 4: Initialize the control plane (control plane only)
# Initialize with pod CIDR for Flannel
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
# Set up kubeconfig
mkdir -p $HOME/.kube
sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
# Verify the API server is running
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# master NotReady control-plane 30s v1.31.xThe node shows NotReady until a CNI plugin is installed.
Step 5: Install a CNI plugin (control plane)
Option A: Flannel (simple)
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.ymlOption B: Cilium (advanced, eBPF-based)
# Install Cilium CLI
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt)
curl -L --fail https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-amd64.tar.gz | sudo tar xz -C /usr/local/bin
# Install Cilium
cilium install
# Verify
cilium status --waitAfter CNI installation:
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# master Ready control-plane 2m v1.31.xStep 6: Join worker nodes
On the control plane, get the join command:
kubeadm token create --print-join-commandOn each worker node (after completing Steps 1-3):
# Paste the join command from above
sudo kubeadm join 192.168.1.100:6443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>Verify from the control plane:
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# master Ready control-plane 5m v1.31.x
# worker1 Ready <none> 1m v1.31.x
# worker2 Ready <none> 1m v1.31.xStep 7: Post-install verification
# All system pods should be Running
kubectl get pods -n kube-system
# Deploy a test application
kubectl create deployment nginx --image=nginx:alpine --replicas=3
kubectl expose deployment nginx --port=80 --type=NodePort
kubectl get svc nginx
# Access via http://<node-ip>:<nodeport>
# Clean up test
kubectl delete deployment nginx
kubectl delete svc nginxProduction hardening
For production clusters, also configure:
# Enable audit logging
# Add to /etc/kubernetes/manifests/kube-apiserver.yaml:
# --audit-log-path=/var/log/kubernetes/audit.log
# --audit-policy-file=/etc/kubernetes/audit-policy.yaml
# Set up etcd backup
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db \
--endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key
# Install metrics-server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yamlTroubleshooting
kubelet not starting:
sudo journalctl -xeu kubelet
# Common: containerd not running or SystemdCgroup not enabledNode stays NotReady:
kubectl describe node <name>
# Check for CNI not installed or network plugin errorskubeadm init fails with preflight errors:
# Reset and retry
sudo kubeadm reset -f
sudo rm -rf /etc/cni/net.d
sudo kubeadm init --pod-network-cidr=10.244.0.0/16