Setting up a K8S cluster with Cilium
Hello. This is a tutorial on how to set up a K8S cluster that uses Cilium as its CNI. We use fedora for the host VMs.
Preferred Prerequisites
- 3 different servers accessible through ssh.
- 1 IPv4 IP per server, where each server can reach another with them over a (preferably) private network.
- Enough resources and the correct set of open ports. Refer to the kubernetes documentation as well as Cilium's.
We do not aim for a minimum viable product here, but a comfortable possible way to setup a cluster with Cilium.
If your existing resources differ, adapt the tutorial accordingly. This includes the existence of a private network to your cloud setup, the number of nodes you have, the operating system you use, the container runtime environment you use, ...
Installation specifications
These are the same specifications per server:
- OS: Fedora 41
- Kernel: 6.13.6
- Shell: bash
- CPU: 2 cores, amd64
- Memory: 4 GB
- Diskspace: 10 GB
The versions of the main software used are:
- Installed kubernetes version: v1.32.0
- Containerd version: 1.7.26
- Cilium helmchart version: 1.17.2
The master node requires in general less cpu and memory than this. We will stick to these specifications for simplicity.
Each server has a unique hostname. Here we set them to master1, worker1 and worker2.
You can modify the hostnames of each machine to match its label:
hostnamectl hostname master1on master1 to-be node.hostnamectl hostname worker1on worker1 to-be node.hostnamectl hostname worker2on worker2 to-be node.
(Optional) Preconfiguration
To simplify testing and reference to servers later on, it might be useful to modify each of the servers' /etc/hosts file and define one another with their IPs.
...
# For kubernetes administration
10.0.0.2 master1
10.0.0.3 worker1
10.0.0.4 worker2
Here I have a unique private IP per server under the IP range 10.0.0.0/8. Your set of IPs might differ.
It is also a good security (and/or management) practice to have a separate user from root.
We therefore create a user user and run most commands through it.
If you already have a separate user from root, this step is redundant.
You can also optionally set your ssh access to be through this newly created user (not covered in tutorial).
Run the following as root:
useradd user # Create user "user".
echo user:user | chpasswd # Set user's password to "user". Modify to liking.
usermod -aG wheel user # Add the user to wheel group, in order to allow sudo access.
Adding user to wheel should be sufficient to give the user sudo priviledges by default.
Refer to sudo's documentation and/or visudo in case this is not the case.
Installation
All the bash commands from this point on will be run as the user user with sudo priviledges.
Installing a CRI: containerd
On each server: we first update the repositories.
sudo dnf update -y
Then we install containerd
sudo dnf install -y containerd
Now we ought to modify the containerd configuration file, as the default one for Fedora 41 is not the one kubernetes might expect.
# Backup original config and update it
sudo mv /etc/containerd/config.toml /etc/containerd/config.toml.bkup
sudo containerd config default > /etc/containerd/config.toml
If you use cgroupv2 with systemd, you need to change the configuration accordingly
# Set SystemdCgroup to true if we are using cgroupv2
sudo sh -c "if [ `stat -fc %T /sys/fs/cgroup/` = cgroup2fs ]; then sed -i 's/SystemdCgroup = .*$/SystemdCgroup = true/' /etc/containerd/config.toml; fi"
Finally, we can enable the service for containerd
sudo systemctl enable --now containerd
Installing kubelet, kubeadm and kubectl
As per the kubernetes documentation, we proceed to install the three programs, namely kubelet, kubeadm and kubectl on every server.
The kubernetes official documentation indicates that we ought to disable SELinux. Until SELinux support is improved, certain containers might require access to the host filesystem or some host resources (e.g. the CNI's). Maybe we'll attempt a proper SELinux compatible install in the future. But for now:
sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
echo "SELinux: `getenforce`"
The latter should show "SELinux: Permissive".
Note: If SELinux isn't enabled kernel-wise or is not supported by host's distribution, you can skip the commands shown above.
Next we add the kubernetes YUM repository to each server.
sudo cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
And finally we install the three tools - kubelet, kubeadm and kubectl on the servers.
# Note: --disableexcludes=kubernetes here does not work, unlike what the k8s documentation suggests.
sudo yum install -y kubelet kubeadm kubectl
Note: It is not necessary to install kubectl on servers where you will not use it. Here we only use it in master1.
Now we may enable the appropriate service for kubelet.
sudo systemctl enable --now kubelet
Initializing the first master node
To initialize the first node in the control plane, we use kubeadm.
We decided to use the CIDR 172.16.0.0/12 as the IP private range for pods.
The apiserver-advertise-address should be the private IP of master1. The following commands are to be executed on the server we denoted as 'master1'.
sudo kubeadm init --apiserver-advertise-address=10.0.0.2 --pod-network-cidr "172.16.0.0/12"
Now, for convenience, we can copy the generated /etc/kubernetes/admin.conf as /home/user/.kube/config.
This will allow us to run kubectl on this master node without sudo privileges having to provide the --kubeconfig flag every call.
mkdir -p /home/user/.kube
sudo cp -f /etc/kubernetes/admin.conf /home/user/.kube/config
sudo chown user:user /home/user/.kube/config
Now you can verify that the basic kube-system pods are present.
kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-668d6bf9bc-rw2lk 1/1 Pending 1 (1h ago) 2h
coredns-668d6bf9bc-tkjpj 1/1 Pending 1 (1h ago) 2h
etcd-master 1/1 Running 6 (1h ago) 2h
kube-apiserver-master 1/1 Running 6 (1h ago) 2h
kube-controller-manager-master 1/1 Running 1 (1h ago) 2h
kube-proxy-vrg9w 1/1 Running 1 (1h ago) 2h
kube-scheduler-master 1/1 Running 8 (1h ago) 2h
Install Cilium
In this tutorial we isntall cilium before joining the other nodes.
First we need to install helm.
sudo dnf install -y helm
Then we may add the cilium repository.
helm repo add cilium https://helm.cilium.io/
Note: helm relies on the same configuration file as kubectl.
Finally we install cilium v1.17.2, and we make sure to match the pod CIDR we defined above.
Here I decided to use kubernetes IPAM mode.
helm install cilium cilium/cilium --version 1.17.2 --namespace kube-system --set ipam.operator.clusterPoolIPv4PodCIDRList=172.16.0.0/12 --set ipam.mode=kubernetes
Now Cilium should be installed.
Join the other nodes
You can create a join token on the master1 server using kubeadm.
kubeadm token create --print-join-command
Copy the given given command and execute it on the servers worker1 and worker2. The command shown below is just for refernece to show what it looks like.
#kubeadm join 10.0.0.2:6443 --token s7iji8.uikjekosa12uum3k --discovery-token-ca-cert-hash sha256:980f1256703de29b9c34fe1ba9e5a93683f4c1d1866025881cd1a44cecce947d
(Optional) Install the cilium CLI
We can install the cilium CLI to test cilium's connectivity. Following cilium's official documentation, we run the following on the master node:
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt) # Change if fixed version
CLI_ARCH=amd64 # Change if not AMD64 architecture
curl -L --fail --remote-name-all https://github.com/cilium/cilium-cli/releases/download/${CILIUM_CLI_VERSION}/cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
sha256sum --check cilium-linux-${CLI_ARCH}.tar.gz.sha256sum
sudo tar xzvfC cilium-linux-${CLI_ARCH}.tar.gz /usr/local/bin
rm cilium-linux-${CLI_ARCH}.tar.gz{,.sha256sum}
Note: the cilium CLI relies on the same configuration file as kubectl.
You can now use cilium status --wait to check the status of the cilium installaton.
/¯¯\
/¯¯\__/¯¯\ Cilium: OK
\__/¯¯\__/ Operator: OK
/¯¯\__/¯¯\ Envoy DaemonSet: OK
\__/¯¯\__/ Hubble Relay: disabled
\__/ ClusterMesh: disabled
DaemonSet cilium Desired: 3, Ready: 3/3, Available: 3/3
DaemonSet cilium-envoy Desired: 3, Ready: 3/3, Available: 3/3
Deployment cilium-operator Desired: 2, Ready: 2/2, Available: 2/2
Containers: cilium Running: 3
cilium-envoy Running: 3
cilium-operator Running: 2
clustermesh-apiserver
hubble-relay
Cluster Pods: 10/10 managed by Cilium
Helm chart version: 1.17.2
Note: You might get an error before joining at least one worker node. This is normal (unless you have a one-node cluster).
Run cilium connectivity test to test your cilium installation. It will take quite some time, but if everything is working properly, you will see this:
[user@master1 ~]$ cilium connectivity test
ℹ Monitor aggregation detected, will skip some flow validation steps
✨ [kubernetes] Creating namespace cilium-test-1 for connectivity check...
✨ [kubernetes] Deploying echo-same-node service...
✨ [kubernetes] Deploying DNS test server configmap...
...
[=] [cilium-test-1] Test [check-log-errors] [112/112]
✅ [cilium-test-1] All 70 tests (640 actions) successful, 42 tests skipped, 1 scenarios skipped.
I hope you enjoyed this tutorial!
Hachem.