This guide explains how to set up a Kubernetes cluster from scratch using kubeadm, containerd, and Flannel as the CNI (Container Network Interface).
- Multiple Linux nodes (Ubuntu recommended)
- One control plane (master) node and one or more worker nodes
sudoprivileges on all nodes- Internet access for downloading packages
If you don’t want to prepare physical machines or cloud VMs manually, you can use Vagrant with VirtualBox to spin up the cluster nodes.
- Clone this repository:
git clone <repo-url> cd <repo-name>
## Inspect the included Vagrantfile and adjust CPU, memory, or network settings as needed.
## Bring up the VMs
```bash
vagrant up
vagrant statusvagrant ssh controlplane
vagrant ssh node01
vagrant ssh node02sudo apt-get update
# apt-transport-https may be a dummy package; if so, you can skip that package
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
# If the directory `/etc/apt/keyrings` does not exist, it should be created before the curl command, read the note below.
# sudo mkdir -p -m 755 /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.34/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
# This overwrites any existing configuration in /etc/apt/sources.list.d/kubernetes.list
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.34/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
# Install kubelet, kubeadm, and kubectl
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
kubeadm versionsudo apt install containerd -y
sudo mkdir -p /etc/containerd
# Generate default config and enable systemd cgroup driver
containerd config default | sed 's/SystemdCgroup = false/SystemdCgroup = true/' | sudo tee /etc/containerd/config.toml
# Verify
cat /etc/containerd/config.toml | grep -i SystemdCgroup
# Restart containerd
sudo systemctl restart containerd# Load module now
sudo modprobe br_netfilter
# Make sure it loads on boot
echo "br_netfilter" | sudo tee /etc/modules-load.d/br_netfilter.conf
# Add missing sysctl params
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
# Apply immediately
sudo sysctl --system
cat <<EOF | sudo tee /etc/sysctl.d/99-disable-ipv6.conf
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
sudo sysctl --system
sudo systemctl restart containerd
sudo systemctl restart kubeletCheck if the system is using systemd:
ps -p 1If output is systemd, then kubeadm v1.22+ automatically defaults to the systemd cgroup driver ✅ No need to do anything about it.
Disacle Swap Memory
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstabsudo kubeadm init \
--apiserver-advertise-address <master_node_ip> \
--pod-network-cidr "10.244.0.0/16" \
--upload-certsSet up kubeconfig:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config*before apply the manifest make sure you will set the default pod-cidr as 10.244.0.0/16. Calico will get the cidr pool automatically from kube-api-server which is running on kube-system namespace.Else you can set the cidr manually in the calico.yaml in here which lives in the calico-node DaemonSet under:
wget https://raw.githubusercontent.com/projectcalico/calico/v3.28.1/manifests/calico.yaml- name: CALICO_IPV4POOL_CIDR
value: "10.244.0.0/16"- if you set the default cidr then just apply the manifest.
kubectl apply -f calico.yaml
kubectl get pods -n kube-system -wwget https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
kubectl apply -f kube-flannel.yml
# Verify flannel pods
kubectl get po -n kube-flannel
⚠️ Make sure the CIDR block inkube-flannel.ymlmatches10.244.0.0/16.
Run the join command generated by kubeadm init. You can find that command after the cluster initialization.
kubeadm join <master_node_IP>:6443 --token <token> \
--discovery-token-ca-cert-hash <hash_token>kubectl label node node01 node-role.kubernetes.io/worker=worker1
kubectl label node node02 node-role.kubernetes.io/worker=worker2
# Verify
kubectl get nodes -o wideIf you already ran kubeadm init on your control-plane node, and forget to save the 'kubeadm join' command, you can print the kubeadm join command again with:
kubeadm token create --print-join-commandYou now have a working Kubernetes cluster with containerd and Flannel networking.
vagrant logout #run on each node
vagrant destroy -f