Kubernetes

Déployer un cluster K8s on-premise avec kubeadm et Ansible

02.04.2026 12 min de lecture Nicolas Wibart

Monter un cluster Kubernetes "bare-metal" sur Proxmox, c'est le meilleur moyen de comprendre vraiment ce que fait EKS à ta place. Voici comment j'ai bootstrappé un cluster 3 nœuds avec kubeadm, le tout automatisé via Ansible.

Le contexte

Dans le cadre de mon projet fil rouge, j'avais besoin d'un cluster K8s reproductible, jetable et 100% IaC. Objectif : pouvoir le détruire et le recréer en moins de 30 minutes sans jamais toucher à la souris. L'infrastructure sous-jacente : 3 VMs Proxmox (1 control plane + 2 workers), Ubuntu Server 22.04, 4 vCPU / 8 GB RAM chacune.

Pourquoi on-premise ? Pour deux raisons : apprendre ce que gèrent les cloud providers (etcd, CNI, kubelet config), et ne pas exploser la facture AWS.

L'architecture cible

proxmox host control-plane kube-apiserver etcd scheduler controller-mgr CNI: Calico 192.168.1.10 worker-01 kubelet containerd kube-proxy 192.168.1.11 worker-02 kubelet containerd kube-proxy 192.168.1.12
Topologie : 1 control-plane + 2 workers, CNI Calico, réseau flat /24

Le playbook Ansible, étape par étape

Le playbook est découpé en 4 rôles réutilisables :

1. Préparer le système (rôle common)

# roles/common/tasks/main.yml
- name: Disable swap
  ansible.builtin.command: swapoff -a
  when: ansible_swaptotal_mb > 0

- name: Load kernel modules
  community.general.modprobe:
    name: "{{ item }}"
    state: present
  loop:
    - overlay
    - br_netfilter

- name: Sysctl for K8s networking
  ansible.posix.sysctl:
    name: "{{ item }}"
    value: "1"
    state: present
    reload: yes
  loop:
    - net.bridge.bridge-nf-call-iptables
    - net.ipv4.ip_forward
Gotcha #1 : swap Si tu oublies de désactiver le swap et de le retirer de /etc/fstab, kubelet refusera de démarrer après le prochain reboot. Ça m'a coûté 2h la première fois.

2. Installer containerd

Depuis K8s 1.24, Docker Engine n'est plus supporté comme runtime. J'utilise containerd directement, avec SystemdCgroup = true pour matcher le driver de kubelet.

# Key point: cgroup driver must match kubelet
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' \
  /etc/containerd/config.toml
systemctl restart containerd

3. Bootstrap du control-plane

kubeadm init \
  --apiserver-advertise-address=192.168.1.10 \
  --pod-network-cidr=10.244.0.0/16 \
  --upload-certs

La commande sort un kubeadm join à exécuter sur les workers. Je le capture via un register: Ansible puis je le passe en set_fact: avec hostvars pour que les workers puissent y accéder.

4. Installer le CNI (Calico)

kubectl apply -f https://raw.githubusercontent.com/projectcalico/\
calico/v3.27/manifests/tigera-operator.yaml

kubectl apply -f calico-custom-resources.yaml
Gotcha #2 : CIDR collision Mon réseau LAN était en 192.168.1.0/24. J'ai choisi 10.244.0.0/16 pour les pods pour éviter tout chevauchement. Si ça overlap, le routing part en sucette...

Résultat : kubectl get nodes

NAME            STATUS   ROLES           AGE     VERSION
control-plane   Ready    control-plane   4m12s   v1.29.2
worker-01       Ready    <none>          2m47s   v1.29.2
worker-02       Ready    <none>          2m45s   v1.29.2

Trois nœuds Ready, Calico qui tourne, et surtout un playbook qui me permet de détruire + recréer le cluster en ~8 minutes. Next steps : Traefik en ingress, ArgoCD pour le GitOps, et Prometheus pour le monitoring (sujets des prochains articles).

"Le meilleur moyen de comprendre un abstraction cloud, c'est de la reconstruire à la main au moins une fois."

Ce que j'en retire

  1. kubeadm peut rapidement se prendre en main ; c'est surtout les prérequis système (swap, modules, sysctl) qui font trébucher.
  2. Ansible + kubeadm = combo gagnant ; la logique impérative d'init / join se prête bien à un playbook.
  3. On-premise forme le réflexe "réseau" ; quand le CNI buggue, tu n'as personne à blâmer : tu lis les logs Calico.
Retour au journal $ echo "you build it, you run it"