Aller au contenu
Conteneurs & Orchestration medium

Déployer Kubernetes avec Kubespray

27 min de lecture

logo kubernetes

Kubespray déploie des clusters Kubernetes en utilisant Ansible. Ce guide construit un lab KVM reproductible servant de base à des déploiements plus robustes. Contrairement à kubeadm qui nécessite des commandes manuelles sur chaque nœud, Kubespray automatise tout depuis votre poste de travail.

Kubespray n'est pas une distribution Kubernetes : c'est une collection de playbooks Ansible qui pilotent kubeadm sur chaque nœud. Il se compare donc mal aux distributions allégées comme k3s ou k0s, qui embarquent leur propre binaire et leurs propres choix. Le tableau ci-dessous sert à trancher sur trois critères : la méthode d'installation, la façon dont la haute disponibilité est obtenue, et le degré de liberté laissé sur le plugin réseau.

CritèreKubespraykubeadmk0sk3sRKE2
TypePlaybooks AnsibleBootstrap toolDistributionDistributionDistribution
OrientationProd, multi-cloudStandard upstreamEdge, prod, CIEdge, IoT, devEnterprise, sécurité
InstallationAnsibleCommandesk0sctl (YAML)Script uniqueScript
CNI défautConfigurableAucunkube-routerFlannelCanal
HA native✅ AnsibleManuel✅ Multi-controller✅ Multi-server✅ Intégrée
Profil CISSelon configManuelManuelManuel✅ Intégré
Cas d'usageProd automatiséeBare metal, formationEdge, CI/CD, prodEdge, IoT, devConformité, prod

À la fin de ce guide, vous saurez :

  • Provisionner des VMs KVM configurées via cloud-init pour un cluster Kubernetes
  • Préparer l'environnement Kubespray avec les bonnes versions d'Ansible et dépendances
  • Créer un inventaire personnalisé définissant la topologie du cluster
  • Déployer un cluster complet avec Calico, containerd et CoreDNS en une seule commande
  • Valider que tous les composants fonctionnent correctement
  • Personnaliser les options (CNI, runtime, addons) selon vos besoins

Vérifiez ces points avant de commencer : un manque à ce stade se manifeste bien plus tard, sous la forme d'une erreur qui ne le désigne pas.

Kubespray déploie les mêmes composants que kubeadm mais automatise la configuration. Les besoins sont donc identiques :

RôleCPURAM (min)RAM (recommandé)DisqueQuantité
Control plane2 vCPU2 Go2 Go20 Go1 (ou 3+ pour HA)
Worker2 vCPU1 Go2 Go20 Go2+
Poste Ansible1 vCPU1 Go1 Go5 GoVotre machine locale

Le minimum Kubespray pour les workers est 1 Go, mais ce guide recommande 2 Go pour éviter les problèmes lors du téléchargement parallèle des images.

Sur votre poste de travail (machine de contrôle Ansible) :

  • Python 3.10+ avec pip
  • Git pour cloner le repository Kubespray
  • SSH avec une clé configurée (les VMs doivent être accessibles sans mot de passe)

Sur les VMs cibles :

  • Ubuntu 24.04 ou Rocky Linux 9 (distribution supportée par Kubespray)
  • Python 3 installé (requis par Ansible)
  • Accès sudo sans mot de passe pour l'utilisateur SSH
  • Accès Internet pour télécharger images et binaires (sauf configuration offline dédiée)

Ce guide a été validé avec les versions suivantes :

ComposantVersion
Kubesprayv2.30.0
Kubernetesv1.34.3
Ansiblecore 2.17.x+
CNICalico 3.30.6 (plugin par défaut)

Kubespray ne provisionne pas l'infrastructure : il prend en charge des machines déjà démarrées et joignables en SSH. Cette étape prépare donc trois VMs KVM avec cloud-init, le mécanisme d'initialisation au premier démarrage des images cloud, qui pose la clé SSH, le compte sudo sans mot de passe et désactive le swap. Les adresses IP sont fixées en statique parce qu'elles seront écrites en dur dans l'inventaire Ansible.

Nous allons créer un cluster avec 1 control plane et 2 workers sur le réseau libvirt par défaut :

Architecture du lab Kubespray avec Ansible et 3 VMs sur KVM

Téléchargez l'image cloud Ubuntu 24.04 si elle n'est pas déjà présente. Canonical publie à côté des images un fichier SHA256SUMS qui contient l'empreinte de chacune : téléchargez les deux, puis vérifiez l'empreinte avant d'installer l'image dans le pool libvirt. Une image altérée deviendrait le socle de tous vos nœuds.

Fenêtre de terminal
# Vérifier si l'image existe déjà
ls -lh /var/lib/libvirt/images/ubuntu-24.04-cloud.img
# Télécharger l'image et le fichier de sommes officiel dans un dossier de travail
cd /tmp
curl -LO https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
curl -LO https://cloud-images.ubuntu.com/noble/current/SHA256SUMS
# Vérifier l'empreinte : la sortie doit afficher "OK"
sha256sum --ignore-missing -c SHA256SUMS
# Installer l'image seulement si la vérification a réussi
sudo install -m 0644 noble-server-cloudimg-amd64.img \
/var/lib/libvirt/images/ubuntu-24.04-cloud.img

L'option --ignore-missing évite que sha256sum échoue sur les autres images listées dans le fichier de sommes et que vous n'avez pas téléchargées. Si la ligne affiche FAILED, ne poursuivez pas : retéléchargez l'image.

Créez un fichier user-data pour configurer l'utilisateur SSH sur les VMs. Remplacez VOTRE_CLE_SSH_PUBLIQUE par le contenu de votre ~/.ssh/id_ed25519.pub :

user-data
#cloud-config
hostname: ${HOSTNAME}
manage_etc_hosts: true
users:
- name: kube
sudo: ALL=(ALL) NOPASSWD:ALL
groups: users, sudo
shell: /bin/bash
lock_passwd: true
ssh_authorized_keys:
- VOTRE_CLE_SSH_PUBLIQUE
packages:
- qemu-guest-agent
- python3
runcmd:
- swapoff -a
- sed -i '/swap/d' /etc/fstab
- systemctl enable qemu-guest-agent

Créez un fichier network-config pour chaque VM avec son IP statique :

network-config (exemple pour ks-cp1)
version: 2
ethernets:
enp1s0:
dhcp4: false
addresses:
- 192.168.122.50/24
routes:
- to: default
via: 192.168.122.1
nameservers:
addresses:
- 192.168.122.1
- 8.8.8.8

Quatre étapes, à répéter pour chacune des trois machines. Les disques sont créés en backing file : chaque VM ne stocke que ses différences par rapport à l'image de base, ce qui rend la création quasi instantanée et économise l'espace disque. Ne supprimez donc jamais l'image de base tant que les VMs existent.

  1. Générer les ISOs cloud-init pour chaque VM :

    Fenêtre de terminal
    # Pour ks-cp1
    cloud-localds ks-cp1-cloud-init.iso user-data-cp1 \
    --network-config network-config-cp1
    # Répéter pour ks-worker1 et ks-worker2 avec leurs fichiers respectifs
  2. Créer les disques avec backing file :

    Fenêtre de terminal
    for vm in ks-cp1 ks-worker1 ks-worker2; do
    sudo qemu-img create -f qcow2 \
    -b /var/lib/libvirt/images/ubuntu-24.04-cloud.img \
    -F qcow2 \
    /var/lib/libvirt/images/${vm}.qcow2 20G
    done
  3. Créer les VMs avec virt-install :

    Fenêtre de terminal
    for vm in ks-cp1 ks-worker1 ks-worker2; do
    sudo virt-install \
    --name ${vm} \
    --memory 2048 \
    --vcpus 2 \
    --disk /var/lib/libvirt/images/${vm}.qcow2 \
    --disk /var/lib/libvirt/images/${vm}-cloud-init.iso,device=cdrom \
    --os-variant ubuntu24.04 \
    --network network=default \
    --graphics none \
    --console pty,target_type=serial \
    --noautoconsole \
    --import
    done
  4. Vérifier la connectivité SSH :

    Fenêtre de terminal
    for ip in 192.168.122.50 192.168.122.51 192.168.122.52; do
    ssh -o StrictHostKeyChecking=accept-new kube@${ip} hostname
    done

Si les trois hostnames s'affichent (ks-cp1, ks-worker1, ks-worker2), vos VMs sont prêtes.

Kubespray s'installe sur votre poste, pas sur les nœuds : c'est un dépôt Git de playbooks accompagné d'un requirements.txt qui épingle les versions d'Ansible et de ses dépendances Python. Ces versions sont contraignantes, chaque release de Kubespray n'étant testée qu'avec la combinaison qu'elle déclare. Un environnement virtuel Python dédié évite tout conflit avec un Ansible déjà présent sur la machine.

Utilisez toujours une version taggée pour la stabilité :

Fenêtre de terminal
git clone https://github.com/kubernetes-sigs/kubespray.git \
--depth 1 --branch v2.30.0
cd kubespray

Kubespray nécessite des versions spécifiques d'Ansible et de ses dépendances :

Fenêtre de terminal
# Créer un environnement virtuel isolé
python3 -m venv venv
source venv/bin/activate
# Installer les dépendances
pip install -U pip
pip install -r requirements.txt
# Vérifier Ansible
ansible --version

La sortie doit indiquer ansible [core 2.17.x] ou supérieur pour Kubespray v2.30.0.

L'inventaire est le seul endroit qui décrit la topologie du cluster : quelles machines existent, et quel rôle chacune tient. Kubespray ne déduit rien, il applique strictement les groupes Ansible que vous déclarez, kube_control_plane pour le plan de contrôle, kube_node pour les workers, etcd pour la base de données du cluster. Travaillez toujours sur une copie du template fourni : un git pull écraserait vos modifications faites dans inventory/sample.

Kubespray fournit un inventory sample que vous personnalisez :

Fenêtre de terminal
cp -r inventory/sample inventory/mycluster

Remplacez le contenu de inventory/mycluster/hosts.yaml par votre topologie :

inventory/mycluster/hosts.yaml
all:
hosts:
ks-cp1:
ansible_host: 192.168.122.50
ip: 192.168.122.50
access_ip: 192.168.122.50
ks-worker1:
ansible_host: 192.168.122.51
ip: 192.168.122.51
access_ip: 192.168.122.51
ks-worker2:
ansible_host: 192.168.122.52
ip: 192.168.122.52
access_ip: 192.168.122.52
children:
kube_control_plane:
hosts:
ks-cp1:
kube_node:
hosts:
ks-worker1:
ks-worker2:
etcd:
hosts:
ks-cp1:
k8s_cluster:
children:
kube_control_plane:
kube_node:
calico_rr:
hosts: {}

Avant le déploiement, vérifiez que Ansible peut se connecter à tous les nœuds :

Fenêtre de terminal
ansible all -i inventory/mycluster/hosts.yaml -m ping -u kube

Résultat attendu :

ks-cp1 | SUCCESS => { "ping": "pong" }
ks-worker1 | SUCCESS => { "ping": "pong" }
ks-worker2 | SUCCESS => { "ping": "pong" }

Avant de déployer, vous pouvez ajuster les paramètres dans inventory/mycluster/group_vars/.

Éditez inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml :

# Version Kubernetes (dans la plage supportée par votre release Kubespray)
kube_version: v1.34.3
# Nom DNS du cluster
cluster_name: cluster.local

Deux variables ressemblent à de simples choix dans une liste mais engagent en réalité toute l'architecture du cluster : le plugin réseau (kube_network_plugin) et le runtime de conteneurs (container_manager). Les modifier après un premier déploiement impose une réinstallation complète.

Ces sept variables sont celles que l'on consulte le plus souvent. Les deux dernières méritent une attention particulière : les plages d'adresses des pods et des services doivent rester disjointes du réseau de vos VMs, faute de quoi le routage devient imprévisible. Les valeurs par défaut conviennent au réseau libvirt 192.168.122.0/24 utilisé ici.

VariableFichierValeur par défautDescription
kube_versionk8s-cluster.ymlv1.34.3Version Kubernetes
kube_network_plugink8s-cluster.ymlcalicoCNI, garder par défaut en lab
container_managerk8s-cluster.ymlcontainerdRuntime, containerd est le standard actuel
kube_proxy_modek8s-cluster.ymlipvsMode kube-proxy (iptables, ipvs)
cluster_namek8s-cluster.ymlcluster.localNom DNS du cluster
kube_pods_subnetk8s-cluster.yml10.233.64.0/18CIDR des pods
kube_service_addressesk8s-cluster.yml10.233.0.0/18CIDR des services

Le déploiement s'effectue avec le playbook cluster.yml. Comptez 10-20 minutes selon votre connexion Internet (téléchargement des images) :

Fenêtre de terminal
ansible-playbook -i inventory/mycluster/hosts.yaml cluster.yml \
-u kube -b --become-user=root

Le playbook exécute plusieurs phases :

  1. Préparation : configuration système, désactivation swap, modules kernel
  2. Téléchargement : binaires (kubectl, kubeadm, kubelet) et images conteneurs
  3. etcd : installation et configuration du cluster etcd
  4. Control plane : initialisation avec kubeadm, certificats
  5. Workers : jointure au cluster
  6. Réseau : déploiement de Calico (ou CNI choisi)
  7. Addons : CoreDNS, metrics-server (si activé)

Ansible termine par un récapitulatif par hôte. Le compteur skipped élevé est normal : Kubespray couvre de nombreuses distributions et options, et saute toutes les tâches qui ne correspondent pas à votre configuration.

PLAY RECAP *********************************************************************
ks-cp1 : ok=624 changed=135 unreachable=0 failed=0 skipped=1090
ks-worker1 : ok=413 changed=82 unreachable=0 failed=0 skipped=640
ks-worker2 : ok=413 changed=82 unreachable=0 failed=0 skipped=636

failed=0 sur tous les nœuds indique un déploiement réussi.

Un failed=0 signifie que les playbooks se sont exécutés, pas que le cluster fonctionne. La validation se fait en quatre temps, du plus structurel au plus applicatif : récupérer le kubeconfig, vérifier que les nœuds sont Ready, contrôler les pods système, puis déployer une application de test. Les nœuds ne passent Ready qu'une fois le plugin réseau opérationnel : c'est le signal que le CNI a bien démarré.

Le fichier kubeconfig se trouve sur le control plane. Récupérez-le sur votre poste :

Fenêtre de terminal
# Méthode simple avec scp (recommandée)
ssh kube@192.168.122.50 "sudo cat /etc/kubernetes/admin.conf" > ~/.kube/kubespray-config
# Remplacer l'adresse localhost par l'IP du control plane
sed -i 's/127.0.0.1/192.168.122.50/g' ~/.kube/kubespray-config
# Configurer kubectl
export KUBECONFIG=~/.kube/kubespray-config

L'option -o wide ajoute trois colonnes déterminantes : l'IP interne, qui doit correspondre à celle de l'inventaire, l'image système et le runtime effectivement utilisé. Un nœud en NotReady juste après le déploiement pointe presque toujours vers le plugin réseau.

Fenêtre de terminal
kubectl get nodes -o wide

Sortie attendue :

NAME STATUS ROLES AGE VERSION INTERNAL-IP OS-IMAGE RUNTIME
ks-cp1 Ready control-plane 19m v1.34.3 192.168.122.50 Ubuntu 24.04.3 LTS containerd://2.2.1
ks-worker1 Ready <none> 19m v1.34.3 192.168.122.51 Ubuntu 24.04.3 LTS containerd://2.2.1
ks-worker2 Ready <none> 19m v1.34.3 192.168.122.52 Ubuntu 24.04.3 LTS containerd://2.2.1

Les trois nœuds doivent être Ready.

Tous les composants d'infrastructure tournent dans le namespace kube-system. Le tableau qui suit liste ce que Kubespray installe avec les valeurs par défaut de ce guide, y compris deux ajouts qui lui sont propres : nodelocaldns, un cache DNS local sur chaque nœud, et nginx-proxy, qui relaie les appels à l'API server depuis les workers.

Fenêtre de terminal
kubectl get pods -A

Tous les pods doivent être Running :

NamespacePods attendus
kube-systemcalico-node (3), calico-kube-controllers
kube-systemcoredns (2), dns-autoscaler
kube-systemkube-apiserver, kube-controller-manager, kube-scheduler
kube-systemkube-proxy (3), nodelocaldns (3)
kube-systemnginx-proxy (workers uniquement)

Validez que vous pouvez déployer une application :

Fenêtre de terminal
# Créer un déploiement nginx
kubectl create deployment nginx --image=nginx:alpine
# Exposer en NodePort
kubectl expose deployment nginx --port=80 --type=NodePort
# Attendre que le pod soit prêt
kubectl wait --for=condition=Ready pod -l app=nginx --timeout=60s
# Vérifier
kubectl get pods,svc

Résultat attendu :

NAME READY STATUS RESTARTS AGE
pod/nginx-6f564d4fd9-xxxxx 1/1 Running 0 30s
NAME TYPE CLUSTER-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 443/TCP 20m
service/nginx NodePort 10.233.33.101 80:30965/TCP 10s

Le service nginx est accessible sur http://<IP_WORKER>:30965.

Nettoyez après le test :

Fenêtre de terminal
kubectl delete deployment nginx
kubectl delete svc nginx

Le test nginx valide peu de choses. Pour vérifier la résolution DNS et la connectivité inter-pods :

Fenêtre de terminal
# Lancer un pod de test
kubectl run dnstest --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup kubernetes.default.svc.cluster.local

Résultat attendu : une adresse IP dans le range 10.233.0.x (service ClusterIP).

Les échecs de déploiement Kubespray se répartissent en deux familles distinctes. Ceux qui surviennent avant la première tâche relèvent d'Ansible et de l'accès SSH ; ceux qui surviennent en cours de route relèvent du cluster lui-même. Le message d'Ansible indique toujours la tâche exacte qui a échoué et sur quel hôte : c'est le point de départ du diagnostic.

Ansible ne peut pas se connecter aux VMs :

Fenêtre de terminal
# Vérifier la connectivité SSH
ssh -v kube@192.168.122.50
# Vérifier que la clé est autorisée
ssh kube@192.168.122.50 "cat ~/.ssh/authorized_keys"
# Vérifier que Python est installé
ansible all -i inventory/mycluster/hosts.yaml -m raw -a "which python3" -u kube

kubectl ne peut pas joindre l'API server :

Fenêtre de terminal
# Vérifier que l'adresse est correcte dans kubeconfig
grep server ~/.kube/kubespray-config
# Tester la connectivité réseau
nc -zv 192.168.122.50 6443
# Vérifier que l'API server tourne
ssh kube@192.168.122.50 "sudo crictl ps | grep kube-apiserver"

Généralement un problème de ressources ou de taints :

Fenêtre de terminal
# Décrire le pod pour voir les événements
kubectl describe pod <pod-name>
# Vérifier les ressources disponibles
kubectl describe nodes | grep -A5 "Allocated resources"
# Vérifier les taints
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints

Si le déploiement a échoué à mi-chemin, relancez simplement la même commande. Ansible est idempotent et reprendra où il s'est arrêté :

Fenêtre de terminal
ansible-playbook -i inventory/mycluster/hosts.yaml cluster.yml \
-u kube -b --become-user=root

Une fois le cluster en place, Kubespray reste le point d'entrée pour le faire évoluer : chaque opération a son playbook dédié, et aucune ne doit être menée à la main sur les nœuds. La règle qui vaut pour les trois opérations ci-dessous : mettez toujours à jour hosts.yaml avant de lancer le playbook, l'inventaire étant la source de vérité de tout le reste.

Le playbook scale.yml n'agit que sur les nœuds absents du cluster, ce qui le rend sûr à relancer. L'option --limit restreint l'exécution à la nouvelle machine et évite de retoucher aux nœuds existants.

  1. Provisionner la nouvelle VM avec cloud-init
  2. Ajouter l'entrée dans hosts.yaml sous kube_node
  3. Exécuter le playbook scale.yml :
Fenêtre de terminal
ansible-playbook -i inventory/mycluster/hosts.yaml scale.yml \
-u kube -b --become-user=root --limit=nouveau-worker

Kubespray fournit un playbook dédié pour la suppression propre :

Fenêtre de terminal
# Méthode recommandée : playbook remove-node
ansible-playbook -i inventory/mycluster/hosts.yaml remove-node.yml \
-u kube -b --become-user=root \
-e "node=ks-worker2"

Ce playbook effectue automatiquement le drain, la suppression des composants Kubernetes sur le nœud et le nettoyage. Après exécution, retirez manuellement le nœud de votre fichier hosts.yaml.

upgrade-cluster.yml traite les nœuds un par un en les drainant au préalable, ce qui permet de garder le service disponible si vos applications ont plusieurs réplicas. Comptez nettement plus de temps que le déploiement initial, et lisez l'encadré ci-dessous avant de choisir la version cible : les sauts autorisés sont limités.

  1. Modifier kube_version dans group_vars/k8s_cluster/k8s-cluster.yml
  2. Exécuter le playbook upgrade-cluster.yml :
Fenêtre de terminal
ansible-playbook -i inventory/mycluster/hosts.yaml upgrade-cluster.yml \
-u kube -b --become-user=root

Pour supprimer complètement Kubernetes des nœuds :

Fenêtre de terminal
ansible-playbook -i inventory/mycluster/hosts.yaml reset.yml \
-u kube -b --become-user=root

Ce playbook :

  • Arrête et supprime tous les conteneurs
  • Supprime les binaires Kubernetes
  • Nettoie les règles iptables
  • Supprime les fichiers de configuration

Pour supprimer aussi les VMs :

Fenêtre de terminal
for vm in ks-cp1 ks-worker1 ks-worker2; do
sudo virsh destroy ${vm}
sudo virsh undefine ${vm} --remove-all-storage
done

Kubespray industrialise le déploiement de clusters Kubernetes :

  • Infrastructure as Code : votre cluster est défini dans des fichiers YAML versionnables
  • Reproductibilité : le même inventory produit le même cluster à chaque exécution
  • Automatisation : upgrades, scaling, suppression de nœuds via playbooks dédiés
  • Base solide : ce lab constitue un socle pour des déploiements plus robustes

Ce guide vous a permis de déployer un lab reproductible. Pour passer en production, il faut encore :

  • 3 control planes + load balancer devant l'API server
  • Backup etcd externalisé et testé régulièrement
  • Monitoring, alerting et logs centralisés
  • Sécurité réseau (NetworkPolicies, firewall configuré)
  • Plan de reprise d'activité (PRA) documenté

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn