Installation complète, propre et reproductible de Kubernetes 1.33 avec CRI-O sur Fedora / RHEL

(avec résolution des pièges réels rencontrés : kubelet, CNI, Flannel, CoreDNS, reset incomplet)

Ce tutoriel décrit l’intégralité du chemin correct, depuis une machine Fedora/RHEL vierge jusqu’à un cluster Kubernetes 1.33 fonctionnel, en utilisant CRI-O 1.33 comme runtime et Flannel (VXLAN) comme CNI. Il inclut les erreurs réelles rencontrées en pratique, leurs causes exactes, et les correctifs supportés.

Note de version : Kubernetes 1.30 est en fin de vie (EOL) depuis octobre 2025 et ne reçoit plus de correctifs de sécurité. Ce tutoriel cible la version 1.33 (supportée jusqu’en juin 2026). Les versions maintenues à la date de rédaction sont 1.33, 1.34 et 1.35. Remplacer v1.33 par la version souhaitée dans toutes les commandes si nécessaire.

Objectifs :

  • 1 nœud control-plane fonctionnel
  • kubeadm 1.33 / kubelet 1.33 / kubectl 1.33
  • CRI-O 1.33
  • Flannel VXLAN
  • CoreDNS opérationnel
  • État final : kubectl get nodesReady

1. Préparation système (OBLIGATOIRE)

1.1 Désactivation du swap

Kubernetes refuse de démarrer avec le swap actif.

sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab

Vérification :

swapon --show

Aucune sortie ne doit apparaître.


1.2 SELinux en mode permissif

CRI-O + CNI VXLAN sans politiques personnalisées exigent SELinux permissif.

sudo setenforce 0
sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

1.3 Modules kernel requis

sudo modprobe overlay
sudo modprobe br_netfilter

Persistant :

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

1.4 Paramètres sysctl réseau Kubernetes

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

1.5 Ouverture des ports firewalld (OBLIGATOIRE sur Fedora/RHEL)

firewalld est actif par défaut sur Fedora et RHEL. Sans ces règles, le CNI Flannel et les composants kubelet échouent silencieusement même si les pods semblent démarrer.

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 API
sudo firewall-cmd --permanent --add-port=10257/tcp     # kube-controller-manager
sudo firewall-cmd --permanent --add-port=10259/tcp     # kube-scheduler

Port Flannel VXLAN (UDP 8472) — indispensable pour la communication inter-pods :

sudo firewall-cmd --permanent --add-port=8472/udp      # Flannel VXLAN

Interfaces CNI en zone trusted (permet le trafic pod-to-pod sans interférence firewalld) :

sudo firewall-cmd --permanent --zone=trusted --add-interface=cni0
sudo firewall-cmd --permanent --zone=trusted --add-interface=flannel.1

Application :

sudo firewall-cmd --reload

Diagnostic : si CoreDNS répond no route to host alors que les pods ont des IPs correctes et que flannel.1 est UP, la cause est presque toujours firewalld qui bloque le trafic sur l’interface Flannel. Vérifier avec sudo firewall-cmd --list-all.


2. Installation de CRI-O 1.33 et des outils Kubernetes

CRI-O doit être aligné exactement sur la version mineure de Kubernetes.

2.1 Dépôts

KUBERNETES_VERSION=v1.33
CRIO_VERSION=v1.33

cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_VERSION}/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_VERSION}/rpm/repodata/repomd.xml.key
EOF

cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
[cri-o]
name=CRI-O
baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/${CRIO_VERSION}/rpm/
enabled=1
gpgcheck=1
gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/${CRIO_VERSION}/rpm/repodata/repomd.xml.key
EOF

Le dépôt CRI-O a migré de pkgs.k8s.io/addons:/cri-o vers download.opensuse.org/repositories/isv:/cri-o. Utiliser exclusivement la nouvelle URL.

2.2 Installation des paquets

sudo dnf install -y \
  cri-o \
  kubelet \
  kubeadm \
  kubectl \
  container-selinux \
  conntrack \
  socat

Sur Fedora 41+, les paquets sont versionnés. Utiliser les noms versionnés si dnf install cri-o installe une version différente :

sudo dnf install -y cri-o1.33 kubernetes1.33 kubernetes1.33-client kubernetes1.33-kubeadm

2.3 Démarrage de CRI-O

sudo systemctl enable --now crio
sudo systemctl status crio

Vérifications :

# Socket
ls -l /var/run/crio/crio.sock

# cgroup driver (doit être "systemd")
grep cgroup_manager /etc/crio/crio.conf

Résultat attendu pour cgroup_manager :

cgroup_manager = "systemd"

2.4 Activation de kubelet

sudo systemctl enable kubelet

Ne pas démarrer kubelet manuellement : kubeadm le démarre lors de l’init.


3. kubeadm init (fondation du cluster)

3.1 Nettoyage préalable (IMPORTANT si machine déjà utilisée)

Avant toute init :

sudo kubeadm reset -f
sudo rm -rf \
  /etc/kubernetes \
  /var/lib/kubelet \
  /var/lib/etcd \
  /etc/cni/net.d \
  /var/lib/cni \
  /var/run/flannel \
  "$HOME/.kube"

3.2 Initialisation du control-plane

sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --cri-socket=unix:///var/run/crio/crio.sock

⚠️ Ne jamais continuer si cette commande échoue.


3.3 Configuration de kubectl

À exécuter en tant qu’utilisateur normal (pas root) :

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

3.4 Vérification immédiate (critique)

kubectl get nodes -o wide

Résultat attendu :

STATUS: NotReady

C’est normal : aucun CNI n’est encore installé.


4. Installation du CNI Flannel (VXLAN)

4.1 Déploiement Flannel

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

4.2 Attente de convergence

Attendre que le pod Flannel soit Running avant de continuer :

kubectl rollout status daemonset/kube-flannel-ds -n kube-flannel --timeout=120s

4.3 Vérification Flannel

kubectl get pods -n kube-flannel -o wide

Attendu :

kube-flannel-ds-*   Running

5. Problème réel rencontré : CoreDNS bloqué en ContainerCreating

5.1 Symptôme

kubectl get pods -n kube-system
coredns-*   0/1   ContainerCreating

5.2 Cause réelle (non théorique)

Après plusieurs kubeadm init/reset, le bridge CNI cni0 reste DOWN, même si :

  • flannel.1 est UP
  • le PodCIDR est correct
  • le Node est Ready

Résultat :

  • aucun pod non-hostNetwork ne peut créer son sandbox
  • CoreDNS bloque indéfiniment

6. Correctif définitif du CNI (clé du tutoriel)

6.1 Arrêt kubelet

sudo systemctl stop kubelet

6.2 Suppression explicite du bridge CNI

sudo ip link delete cni0

6.3 Purge complète de l’état CNI local

sudo rm -rf /var/lib/cni/*
sudo rm -rf /etc/cni/net.d/*
sudo rm -rf /var/run/flannel

6.4 Redémarrage kubelet

sudo systemctl start kubelet

6.5 Recréation Flannel + CoreDNS

kubectl delete pod -n kube-flannel -l app=flannel
kubectl delete pod -n kube-system -l k8s-app=kube-dns

7. Validation réseau finale

7.1 Interfaces réseau

ip link show cni0

Attendu :

state UP
inet 10.244.0.1/24

7.2 Pods système

kubectl get pods -n kube-system -o wide

Attendu :

coredns-*   1/1   Running

7.3 Test DNS interne

kubectl run dns-test --image=busybox --restart=Never -- sleep 3600
kubectl exec dns-test -- nslookup kubernetes.default

Résultat attendu :

Address: 10.96.0.1

8. État final attendu

kubectl get nodes -o wide
STATUS: Ready
CRI-O: cri-o://1.33.x

Cluster 100 % opérationnel.


9. Ce qu’il ne faut plus jamais faire

  • Ne pas relancer kubeadm init sans purge complète
  • Ne pas empiler plusieurs CNI
  • Ne pas laisser cni0 DOWN
  • Ne pas “forcer” CoreDNS
  • Ne pas oublier d’ouvrir les ports firewalld avant kubeadm init

10. Conclusion

Ce tutoriel décrit le chemin réel, pas idéalisé. Les deux règles clés sont :

Kubernetes ne répare jamais un CNI local cassé. cni0 DOWN ⇒ purge manuelle obligatoire.

firewalld actif bloque silencieusement Flannel VXLAN si UDP 8472 n’est pas ouvert et si les interfaces CNI ne sont pas en zone trusted.

Une fois ces règles respectées, CRI-O + Flannel + Kubernetes 1.33 est parfaitement stable.


Script Bash final (exécutable, idempotent)

À exécuter en root sur une machine vierge.

#!/usr/bin/env bash
set -euo pipefail

KUBERNETES_VERSION=v1.33
CRIO_VERSION=v1.33
# Utilisateur qui recevra ~/.kube/config (ne pas laisser à root)
KUBE_USER="${SUDO_USER:-$(logname 2>/dev/null || echo root)}"

echo "[1] Désactivation swap"
swapoff -a
sed -i '/swap/d' /etc/fstab

echo "[2] SELinux permissif"
setenforce 0 || true
sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config

echo "[3] Modules kernel"
modprobe overlay
modprobe br_netfilter

cat <<EOF >/etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF

cat <<EOF >/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

sysctl --system

echo "[4] Dépôts Kubernetes et CRI-O"
cat <<EOF >/etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_VERSION}/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/${KUBERNETES_VERSION}/rpm/repodata/repomd.xml.key
EOF

cat <<EOF >/etc/yum.repos.d/cri-o.repo
[cri-o]
name=CRI-O
baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/${CRIO_VERSION}/rpm/
enabled=1
gpgcheck=1
gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/${CRIO_VERSION}/rpm/repodata/repomd.xml.key
EOF

echo "[5] Installation des paquets"
dnf install -y \
  cri-o \
  kubelet \
  kubeadm \
  kubectl \
  container-selinux \
  conntrack \
  socat

echo "[6] firewalld — ouverture des ports"
firewall-cmd --permanent --add-port=6443/tcp
firewall-cmd --permanent --add-port=2379-2380/tcp
firewall-cmd --permanent --add-port=10250/tcp
firewall-cmd --permanent --add-port=10257/tcp
firewall-cmd --permanent --add-port=10259/tcp
firewall-cmd --permanent --add-port=8472/udp
firewall-cmd --permanent --zone=trusted --add-interface=cni0    || true
firewall-cmd --permanent --zone=trusted --add-interface=flannel.1 || true
firewall-cmd --reload

echo "[7] Reset Kubernetes"
kubeadm reset -f || true
rm -rf /etc/kubernetes /var/lib/kubelet /var/lib/etcd \
       /etc/cni/net.d /var/lib/cni /var/run/flannel

KUBE_HOME=$(eval echo "~${KUBE_USER}")
rm -rf "${KUBE_HOME}/.kube"

echo "[8] Démarrage CRI-O"
systemctl enable --now crio

echo "[9] Activation kubelet"
systemctl enable kubelet

echo "[10] kubeadm init"
kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --cri-socket=unix:///var/run/crio/crio.sock

echo "[11] Configuration kubectl pour ${KUBE_USER}"
mkdir -p "${KUBE_HOME}/.kube"
cp /etc/kubernetes/admin.conf "${KUBE_HOME}/.kube/config"
chown -R "${KUBE_USER}:$(id -gn "${KUBE_USER}")" "${KUBE_HOME}/.kube"

export KUBECONFIG=/etc/kubernetes/admin.conf

echo "[12] Flannel"
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

echo "[13] Attente convergence Flannel"
kubectl rollout status daemonset/kube-flannel-ds -n kube-flannel --timeout=120s

echo "[14] Fix CNI si cni0 reste DOWN"
systemctl stop kubelet
ip link delete cni0 2>/dev/null || true
rm -rf /var/lib/cni/* /etc/cni/net.d/* /var/run/flannel
systemctl start kubelet

sleep 5
kubectl delete pod -n kube-flannel -l app=flannel || true
kubectl delete pod -n kube-system -l k8s-app=kube-dns || true

echo "[OK] Attente pods système..."
kubectl wait --for=condition=Ready pod -l k8s-app=kube-dns -n kube-system --timeout=120s

echo "[OK] Cluster prêt"
kubectl get nodes -o wide

Fin.