Installation de Kubernetes sur Ubuntu 24.04

Dans mon tutoriel précédent, j’ai montré comment installer Kubernetes sur Ubuntu 22.04 . Par contre, dans le monde IT, un an c’est une éternité. Avec la sortie de Ubuntu 24.04 LTS (Noble Numbat), les règles du jeu ont un peu changé, et les anciennes instructions ne donnent plus la stabilité qu’on attend. Et dans le cas d’une nouvelle installation, c’est toujours mieux de partir sur une version plus récente pour être tranquille.
Aujourd’hui, je vais montrer comment déployer un cluster sur la distro la plus récente. Même si le principe général reste familier, le « diable » est comme toujours dans les détails : des nouveaux standards pour le placement des clés GPG et les méthodes pour désactiver le SWAP via systemd, jusqu’aux configs spécifiques des ports du firewall.
En plus de la base, j’ai ajouté un bloc critique pour les vrais projets : la réservation des ressources système (Node Allocatable). On va apprendre à faire en sorte que le cluster se protège lui-même, pour que même les pods utilisateurs les plus « lourds » ne puissent pas faire planter l’OS ou les composants clés de Kubernetes.

Préparation de l’infrastructure

Pour cette démonstration, j’ai installé Ubuntu 24.04 sur 3 VMs tournant sous Proxmox. Chaque machine dispose de 4 cœurs CPU et 8 Go de RAM.

Note importante : Il est impératif d’utiliser des machines virtuelles (VM) et non des conteneurs LXC. Kubernetes a besoin d’un accès direct aux modules du kernel, ce qui rend l’installation complexe et instable dans un environnement LXC non privilégié.

Voici l’adressage IP utilisé pour ce guide :

Master (k8smast): 192.168.0.231
Worker 1 (k8swork1): 192.168.0.232
Worker 2 (k8swork2): 192.168.0.233

Sur tous les nodes, les commandes sont exécutées avec un utilisateur ayant les droits sudo. Pour plus de commodité, avant de commencer, il faut temporairement désactiver le firewall sur tous les nodes :

sudo ufw disable

On va le reconfigurer à la fin, une fois qu’on s’assure que tout fonctionne correctement.

Étape 1 : Configuration des hostnames et du DNS (sur tous les nodes)

On configure les noms d’hôtes (il faut exécuter la commande correspondante sur chaque machine) :

Sur le Master :

sudo hostnamectl set-hostname k8smast

Sur le Worker 1

sudo hostnamectl set-hostname k8swork1

Sur le Worker 2

sudo hostnamectl set-hostname k8swork2

On ajoute les entrées dans /etc/hosts sur les trois nodes pour qu’ils puissent se résoudre entre eux :

sudo tee -a /etc/hosts > /dev/null <<EOF

# Kubernetes Cluster
192.168.0.231 k8sm k8smast.lan
192.168.0.232 k8sw1 k8swork1.lan
192.168.0.233 k8sw2 k8swork2.lan
EOF

Si vous avez accès à un serveur DNS, c’est mieux de le faire là, mais vous pouvez aussi garder la config dans le fichier hosts pour plus de fiabilité.

 

Étape 2 : Désactivation du SWAP (sur tous les nodes)

Kubernetes exige que le SWAP soit complètement désactivé. Sur Ubuntu 24.04, on va le faire au niveau du kernel et de systemd.

1. Désactiver le SWAP actuel

sudo swapoff -a

2. Supprimer l’entrée dans fstab

sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab

3. Désactiver le swap via systemd (spécifique aux nouvelles versions Ubuntu)

sudo systemctl mask swap.target

 

Étape 3 : Configuration du kernel et des modules (sur tous les nodes)

Containerd et K8s ont besoin des modules overlay et br_netfilter, ainsi que du forwarding IPv4 activé.

Chargement des modules au boot

sudo tee /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF

Appliquer immédiatement

sudo modprobe overlay
sudo modprobe br_netfilter

Configuration sysctl pour Kubernetes

sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

Appliquer sans reboot

sudo sysctl --system

 

Étape 4 : Installation de Containerd (sur tous les nodes)

Ici on utilise la méthode moderne pour gérer les clés GPG (via /etc/apt/keyrings).

1. Installer les dépendances

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg

2. Créer le dossier pour les clés

sudo install -m 0755 -d /etc/apt/keyrings

3. Télécharger la clé officielle Docker au bon format

curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

4. Ajouter le repo (support Ubuntu 24.04 Noble)

echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

5. Installer containerd

sudo apt-get update
sudo apt-get install -y containerd.io

6. Configurer Containerd pour utiliser Systemd Cgroup

sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml >/dev/null
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml

7. Restart et activer au boot

sudo systemctl restart containerd
sudo systemctl enable containerd

 

Je mets SystemdCgroup = true parce que Linux gère les ressources avec les cgroups. Systemd est l’init system qui a son propre cgroup manager. Si kubelet utilise cgroupfs et que le système utilise systemd, ça crée un conflit de contrôleurs, ce qui mène à de l’instabilité des nodes sous load.

Étape 5 : Installation des outils Kubernetes (sur tous les nodes)

On utilise le repo officiel K8s v1.31.

1. Télécharger la clé GPG pour K8s

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
sudo chmod 644 /etc/apt/keyrings/kubernetes-apt-keyring.gpg

2. Ajouter le repo

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo chmod 644 /etc/apt/sources.list.d/kubernetes.list

 

3. Installer les tools et fixer les versions

sudo apt-get update
sudo apt-get install -y conntrack
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl

 

Étape 6 : Initialisation du cluster (UNIQUEMENT sur le Master node)

Exécutez la commande suivante pour initialiser le Master :

sudo kubeadm init \
--control-plane-endpoint=k8smast.lan \
--pod-network-cidr=10.244.0.0/16 \
--cri-socket unix:///var/run/containerd/containerd.sock

Pourquoi utiliser 10.244.0.0/16 et pas une autre valeur ?
C’est un détail architectural critique. Dans Kubernetes, il existe trois réseaux distincts qui ne doivent absolument jamais se chevaucher, sinon le routage (kube-proxy) sera cassé :

Le réseau physique de vos serveurs (Host Network) : C’est le réseau de votre infrastructure (généralement 192.168.x.x ou 10.10.x.x).

Le réseau des Services Kubernetes : Par défaut, kubeadm réserve automatiquement la plage 10.96.0.0/12 pour les services internes (ce qui couvre les adresses de 10.96.0.0 à 10.111.255.255).

Le réseau des Pods (défini par –pod-network-cidr) : L’utilisation de 10.244.0.0/16 est le standard de facto (parfaitement géré par Calico) car elle se place en dehors des deux réseaux précédents. Cela garantit un fonctionnement stable du cluster et évite tout conflit d’adresses IP.

(Note : Le paramètre –cri-socket garantit que kubeadm utilise bien containerd comme runtime).

ATTENTION : Il est OBLIGATOIRE de sauvegarder la commande kubeadm join affichée à la fin de l’initialisation. Elle contient le token et le hash que nous allons utiliser pour ajouter les Worker nodes au cluster.

Le paramètre –cri-socket garantit que kubeadm utilise bien containerd comme runtime.
Une fois l’init terminée, configurer l’accès pour l’utilisateur :

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

Vérifier le cluster — seul le master doit apparaître

kubectl get nodes

Étape 7 : Join des Worker nodes (sur Worker 1 et Worker 2)

Exécuter la commande sauvegardée à l’étape précédente (ne pas oublier sudo si nécessaire). Exemple :

sudo kubeadm join k8smast.lan:6443 --token <votre-token> \
--discovery-token-ca-cert-hash sha256:<votre-hash>

 

 

Vérifier sur le master que tout est OK

kubectl get nodes

 

Étape 8 : Installation du réseau CNI Calico (UNIQUEMENT sur le Master)

Dans les versions modernes de Kubernetes, le networking est beaucoup simplifié : le déploiement et la gestion du CNI passent généralement par des operators qui automatisent l’installation, les updates et le maintien des composants réseau.
Même s’il existe des alternatives (comme Cilium basé sur eBPF), dans ce tutoriel j’utilise Tigera Calico Operator parce que c’est une solution stable et éprouvée en production.

1. Installer Tigera Operator (version v3.31.4)

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

2. Créer les Custom Resources avec le bon CIDR (10.244.0.0/16)

cat <<EOF > custom-resources.yaml
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
  name: default
spec:
  calicoNetwork:
    ipPools:
    - blockSize: 26
      cidr: 10.244.0.0/16
      encapsulation: VXLANCrossSubnet
      natOutgoing: Enabled
      nodeSelector: all()
---
apiVersion: operator.tigera.io/v1
kind: APIServer
metadata:
  name: default
spec: {}
EOF

3. Appliquer la config

kubectl create -f custom-resources.yaml

4. Vérifier le statut (attendre 2–3 minutes que tous les pods soient Running)

watch -n 2 kubectl get pods -n calico-system

Après ça,

kubectl get nodes

doit montrer Ready pour tous les nodes.

 

Étape 9 : Configuration correcte du firewall UFW

Configurer les nouvelles règles firewall.

Sur le Master node (k8sm) :

sudo ufw allow 22/tcp # SSH
sudo ufw allow 6443/tcp # Kubernetes API
sudo ufw allow 2379:2380/tcp # etcd server API
sudo ufw allow 10250/tcp # Kubelet API
sudo ufw allow 10259/tcp # kube-scheduler
sudo ufw allow 10257/tcp # kube-controller-manager
# Calico ports
sudo ufw allow 179/tcp # Calico BGP
sudo ufw allow 4789/udp # Calico VXLAN
sudo ufw allow 5473/tcp # Calico Typha
sudo ufw allow 51820/udp # Calico Wireguard 
sudo ufw enable

Sur les Worker nodes (k8sw1, k8sw2) :

sudo ufw allow 22/tcp # SSH
sudo ufw allow 10250/tcp # Kubelet API
sudo ufw allow 30000:32767/tcp # NodePort Services
# Calico ports
sudo ufw allow 179/tcp
sudo ufw allow 4789/udp
sudo ufw allow 5473/tcp
sudo ufw allow 51820/udp
sudo ufw enable

Vérifier que rien n’a cassé

kubectl get nodes

 

Standard d’architecture Production : Protection des nodes (Node Allocatable) et setup multi-tenant

Comme le cluster est généralement utilisé par des utilisateurs et pas juste comme un lab « démo », il faut comprendre la différence clé entre un cluster « jouet » et un environnement production.
Par défaut, Kubernetes voit 100% des ressources physiques du serveur (Capacity) et est prêt à tout donner aux containers. Si les utilisateurs lancent du code lourd avec des memory leaks, le kernel Linux active le OOM Killer (Out Of Memory) et commence à tuer des processus au hasard pour sauver le système. Ça peut inclure sshd, systemd ou même kubelet. Résultat : le node drop du cluster. Même problème avec le disque : si les pods remplissent le disk avec des logs, le serveur freeze.
Pour éviter ça, on met en place Node Allocatable et des Eviction Policies. On réserve explicitement des ressources pour l’OS et les composants système Kubernetes, en les rendant invisibles pour les pods utilisateurs, et on configure le cluster pour tuer les pods avant que ça devienne critique.

Vérifier les ressources disponibles :

kubectl describe nodes | grep -A 5 "Capacity:"

 

Sur chaque node, j’ai 4 CPU et 8 Go de RAM. On va sur chaque node (y compris le master) et on édite la configuration de Kubelet pour réserver 1 CPU, 1 Go de RAM et 10 Go de disque pour le système.

On ouvre le config sur chaque node :

sudo nano /var/lib/kubelet/config.yaml

On ajoute à la toute fin du fichier les lignes suivantes :

kubeReserved:
  cpu: "500m"
  memory: "512Mi"
systemReserved:
  cpu: "500m"
  memory: "512Mi"
  ephemeral-storage: "10Gi"
enforceNodeAllocatable:
- pods
evictionHard:
  memory.available: "500Mi"
  nodefs.available: "10%"

 

On sauvegarde le fichier et on redémarre le service :

sudo systemctl restart kubelet

Maintenant, on vérifie les configurations appliquées en exécutant la commande sur le master node pour vérifier le worker :

kubectl get nodes -o custom-columns="NODE:.metadata.name,CPU-CAP:.status.capacity.cpu,CPU-ALLOC:.status.allocatable.cpu,MEM-CAP:.status.capacity.memory,MEM-ALLOC:.status.allocatable.memory,DISK-CAP:.status.capacity.ephemeral-storage,DISK-ALLOC:.status.allocatable.ephemeral-storage"

Dans la sortie, on voit deux blocs. Dans Capacity, la capacité physique totale du serveur est affichée (dans mon cas cpu: 4 et memory: 8131792Ki). Dans Allocatable (les ressources disponibles pour lancer les pods), les valeurs doivent être exactement inférieures d’1 CPU, 1 Go de RAM et 10 Go de disque.
Grâce au paramètre evictionHard, si la mémoire libre tombe en dessous de 500 Mo ou si l’espace disque disponible descend sous 10 %, Kubelet ne va pas attendre que le système plante, mais commencera à supprimer les pods utilisateurs les plus lourds. Cela confirme que les ressources système sont correctement réservées et que les nodes sont protégés contre les crashs.
À ce stade, la préparation de l’infrastructure du cluster est terminée. L’étape suivante est la configuration de l’environnement Multi-Tenant : création de Namespaces isolés, configuration des accès (RBAC) et création d’objets ResourceQuota pour partager en toute sécurité les ressources restantes entre les utilisateurs et éviter qu’ils ne fassent planter les projets des autres. Mais c’est déjà le sujet d’un autre tutoriel.

Bonne chance!

👤

📁