
Vous avez besoin d’un terrain de jeu réaliste pour apprendre Ansible : pas une seule VM jouet, mais une vraie topologie multi-hôtes que vous pouvez détruire et recréer à volonté. Ce guide monte en moins de 10 minutes la topologie pédagogique de référence du parcours RHCE — 1 control node + 3 managed nodes AlmaLinux 10 sur KVM/libvirt, avec cloud-init et baux DHCP statiques. Tous les guides suivants s’appuient sur cette infrastructure. L’approche est minimale et reproductible : virt-install + cloud-init, scriptés via un Makefile unique. Pas de Terraform pour démarrer, pas d’agent à installer.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Créer un réseau libvirt isolé avec NAT et baux DHCP statiques sans toucher à NetworkManager côté VMs ;
- Provisionner 4 VMs AlmaLinux 10 via
virt-installavec une image cloud mutualisée ; - Pré-configurer chaque VM via cloud-init : utilisateur
ansible, clé SSH, sudo NOPASSWD ; - Vérifier que les 4 VMs sont accessibles en SSH avec une seule commande Make ;
- Naviguer entre les 103 labs avec la CLI
dsoxlab(suivi de progression SQLite local, README rendus en terminal, suggestion du prochain lab) ; - Détruire proprement le lab pour le recréer rapidement.
Prérequis
Section intitulée « Prérequis »make bootstrap installe tout l’outillage logiciel nécessaire : libvirt, virt-install, cloud-utils, wget, pipx, ansible, ansible-lint, pytest, testinfra. Vous n’avez pas à installer ces paquets manuellement — le script scripts/bootstrap.sh détecte votre distribution (Fedora/RHEL/AlmaLinux/Ubuntu/Debian/Arch) et lance les bonnes commandes.
Côté matériel et système, vérifiez seulement :
| Élément | Comment vérifier |
|---|---|
| Au moins 8 Go de RAM libre | free -h |
Au moins 40 Go d’espace disque dans /var/lib/libvirt/images/ | df -h /var/lib/libvirt/images |
| Une paire de clés SSH (Ed25519 recommandé) | ls ~/.ssh/id_ed25519* (sinon make bootstrap la génère sous ssh/id_ed25519) |
| Distribution Linux avec KVM activé dans le BIOS | egrep -c '(vmx|svm)' /proc/cpuinfo retourne ≥ 1 |
La topologie du lab
Section intitulée « La topologie du lab »Le lab pédagogique reprend la topologie minimale qui couvre 80 % des objectifs RHCE EX294 sans sur-dimensionner :
| Hôte | IP | MAC (fixe) | RAM | vCPU | Disque | Rôle |
|---|---|---|---|---|---|---|
control-node.lab | 10.10.20.10 | 52:54:00:ab:00:10 | 2 GiB | 2 | 20 GiB | Poste Ansible |
web1.lab | 10.10.20.21 | 52:54:00:ab:00:21 | 1 GiB | 1 | 10 GiB | Web (groupe webservers) |
web2.lab | 10.10.20.22 | 52:54:00:ab:00:22 | 1 GiB | 1 | 10 GiB | Web (groupe webservers) |
db1.lab | 10.10.20.31 | 52:54:00:ab:00:31 | 1.5 GiB | 1 | 15 GiB | DB (groupe dbservers) |
Le réseau libvirt lab-ansible utilise le sous-réseau 10.10.20.0/24 avec :
- NAT sortant pour que les VMs accèdent à Internet (mise à jour des paquets, dnf install) ;
- Plage DHCP dynamique
10.10.20.100-200(pour d’éventuels hôtes additionnels) ; - 4 baux DHCP statiques mappés par MAC pour les 4 VMs du lab — IPs garanties stables.
Cloud-init minimal : la philosophie « Ansible se prépare lui-même »
Section intitulée « Cloud-init minimal : la philosophie « Ansible se prépare lui-même » »Le cloud-init des 4 VMs ne fait que trois choses :
- Pose le hostname et le FQDN (
web1.lab,db1.lab…) ; - Crée un utilisateur
ansibleavec la clé SSH publique enauthorized_keyset sudo NOPASSWD ; - Désactive le compte root et le SSH par mot de passe.
Aucun dnf install, aucun service activé, aucune config sysctl. Tout le reste est fait par Ansible lui-même dans la page suivante (Préparer les nœuds gérés). C’est volontaire : ça matérialise concrètement le principe « Ansible converge la cible vers son état désiré » et accélère le make provision (pas de paquets à télécharger côté cloud-init).
Voici le template managed-node.user-data.tmpl (les __VARIABLES__ sont substituées par le script de provisioning) :
#cloud-confighostname: __HOSTNAME__fqdn: __HOSTNAME__.labmanage_etc_hosts: truepreserve_hostname: false
users: - name: ansible groups: [wheel] sudo: "ALL=(ALL) NOPASSWD:ALL" shell: /bin/bash lock_passwd: true ssh_authorized_keys: - __SSH_PUBKEY__
disable_root: truessh_pwauth: falseStructure du projet de lab
Section intitulée « Structure du projet de lab »Le lab pédagogique du site est hébergé dans le dépôt stephrobert/ansible-training, à cloner par exemple dans ~/Projets/ansible-training/. Voici l’arborescence racine et un aperçu des sections de labs (103 labs au total répartis sur 23 sections) :
Répertoireansible-training/
- Makefile
- ansible.cfg
- conftest.py (fixture pytest globale)
- requirements.txt
- requirements.yml
- meta.yml (source de vérité de la liste des labs)
Répertoirebin/
- dsoxlab (CLI suivi de progression)
Répertoireinventory/
- hosts.yml
Répertoireinfra/
Répertoirevirt-install/ (provision/destroy + cloud-init templates)
- …
Répertoireee/ (Execution Environment — image OCI)
- …
Répertoiredocs/ (doc interne du lab)
- …
Répertoirescripts/ (bootstrap, lint-all, snapshot, restore)
- …
Répertoiressh/ (clés SSH locales, gitignored)
- …
Répertoirecollected/ (cible des fetch, gitignored)
- …
Répertoirelabs/
Répertoirebootstrap/prepare-managed-nodes/
- …
Répertoiredecouvrir/ (4 labs : declaratif-vs-imperatif, installation-ansible, configuration-ansible, prise-en-main-cli)
- …
Répertoirepremiers-pas/ (2 labs : premier-playbook, ansible-vault)
- …
Répertoireecrire-code/ (28 labs : plays-et-tasks, handlers, tags, variables-base, jinja2-base, conditions-when, boucles-loop, block-rescue-always…)
- …
Répertoiremodules-fichiers/ (copy, file, blockinfile, fetch, archive-unarchive)
- …
Répertoiremodules-paquets/ (package, dnf-options)
- …
Répertoiremodules-services/ (systemd, cron)
- …
Répertoiremodules-utilisateurs/ (user, group, authorized-key, sudoers)
- …
Répertoiremodules-rhel/ (firewalld, selinux, sysctl, mount, lvm-storage)
- …
Répertoiremodules-reseau/ (get-url, uri)
- …
Répertoiremodules-diagnostic/ (stat, find, assert-fail, wait-for-pause)
- …
Répertoireinventaires/ (group-vars-host-vars, patterns-hotes, dynamique-kvm)
- …
Répertoireroles/ (creer-premier-role, variables-defaults-vars, handlers-meta, argument-specs, consommer-role, dependencies)
- …
Répertoiremolecule/ (introduction, installation-config, tdd-cycle, scenarios-multi-distro)
- …
Répertoiretests/ (testinfra, tox-multiversion, ansible-lint-production)
- …
Répertoireci/ (github-actions, gitlab)
- …
Répertoiregalaxy/ (ansible-galaxy-cli, installer-roles, auditer-role-existant, versionner-publier)
- …
Répertoirevault/ (introduction, chiffrer-fichier-variable, id-multiples, playbooks-mixtes, dans-roles, integration-hashicorp, integration-passbolt)
- …
Répertoireee/ (hello, inspection, builder-custom, ci-pipeline, debug)
- …
Répertoiretroubleshooting/ (verbosite, debugger, idempotence-perfs)
- …
Répertoirecollections/ (decouvrir, requirements, creer-custom, ci-tests, migration-role)
- …
Répertoirepratiques/ (ansible-pull-gitops)
- …
Répertoirerhce/ (mock-ex294 — examen blanc 4 h chrono)
- …
Chaque lab labs/<section>/<page>/ est autonome : README.md (tutoriel guidé), Makefile (cible clean pour reset), challenge/README.md (consigne du challenge final) + challenge/tests/test_*.py (validation pytest+testinfra). Le Makefile racine orchestre l’infrastructure — vous n’appelez jamais virt-install directement. La CLI dsoxlab suit votre progression dans une SQLite locale (~/.local/share/dsoxlab/progress.db).
Étapes de provisionnement
Section intitulée « Étapes de provisionnement »-
Cloner le dépôt et exécuter le bootstrap.
Le
make bootstrapinstalle les paquets système requis (libvirt, virt-install, cloud-utils), vérifie les outils Ansible (ansible-core,ansible-lint,ansible-navigator,pytest), installe les collections Galaxy depuisrequirements.yml, et génère la paire de clés SSH du lab dansssh/id_ed25519:Fenêtre de terminal git clone https://github.com/stephrobert/ansible-training.git ~/Projets/ansible-trainingcd ~/Projets/ansible-trainingmake bootstrapLa clé du lab est séparée de votre clé personnelle pour pouvoir la jeter avec le lab. Elle est gitignorée — chaque utilisateur génère la sienne au premier
make bootstrap. -
Lancer le provisioning complet.
Une seule commande crée le réseau libvirt, télécharge l’image AlmaLinux (mise en cache), crée les 4 VMs et exécute le playbook de préparation :
Fenêtre de terminal make provisionLe script affiche en couleurs ce qu’il fait :
[provision] Création du réseau libvirt 'lab-ansible' (10.10.20.0/24) avec baux DHCP statiques...[OK] Réseau 'lab-ansible' créé avec 4 baux DHCP statiques[OK] Image AlmaLinux 10 déjà présente : /var/lib/libvirt/images/AlmaLinux-10-GenericCloud-latest.x86_64.qcow2[provision] === control-node (10.10.20.10, MAC 52:54:00:ab:00:10, 2048 MiB RAM, 2 vCPU, 20 GiB) ===[provision] === web1 (10.10.20.21, MAC 52:54:00:ab:00:21, 1024 MiB RAM, 1 vCPU, 10 GiB) ===[provision] === web2 (10.10.20.22, MAC 52:54:00:ab:00:22, 1024 MiB RAM, 1 vCPU, 10 GiB) ===[provision] === db1 (10.10.20.31, MAC 52:54:00:ab:00:31, 1536 MiB RAM, 1 vCPU, 15 GiB) ===[provision] Attente SSH (~30s)...SSH OK sur les 4 hôtes[OK] Lab prêt — lance 'make verify-conn' ou 'make test-all' -
Vérifier l’état des VMs.
Fenêtre de terminal virsh list --all | grep -E 'control-node|web|db'Sortie attendue :
49 control-node running50 web1 running51 web2 running52 db1 running -
Vérifier les baux DHCP attribués.
Fenêtre de terminal virsh net-dhcp-leases lab-ansibleLes 4 IPs doivent correspondre à la topologie attendue :
Expiry Time MAC address Protocol IP address Hostname------------------------------------------------------------------------------2026-04-25 15:08:51 52:54:00:ab:00:10 ipv4 10.10.20.10/24 control-node2026-04-25 15:06:39 52:54:00:ab:00:21 ipv4 10.10.20.21/24 web12026-04-25 15:09:16 52:54:00:ab:00:22 ipv4 10.10.20.22/24 web22026-04-25 15:06:43 52:54:00:ab:00:31 ipv4 10.10.20.31/24 db1 -
Tester la connectivité Ansible.
Fenêtre de terminal make verify-connVous devez voir 4
pong:db1.lab | SUCCESS => {"changed": false,"ping": "pong"}control-node.lab | SUCCESS => {"changed": false,"ping": "pong"}web2.lab | SUCCESS => {"changed": false,"ping": "pong"}web1.lab | SUCCESS => {"changed": false,"ping": "pong"}À ce point, le lab est fonctionnel bout en bout : Ansible voit les 4 hôtes, SSH passe, Python 3 répond. Vous pouvez attaquer les guides suivants.
Naviguer dans les labs avec dsoxlab
Section intitulée « Naviguer dans les labs avec dsoxlab »Avec 103 labs répartis sur 23 sections, naviguer manuellement devient vite pénible. Le repo embarque une CLI Python appelée dsoxlab (chemin : bin/dsoxlab) qui répond à trois besoins simultanés :
- Afficher un README de lab rendu en Markdown riche directement dans le terminal (titre, gras, blocs de code colorés, tableaux), sans ouvrir un navigateur ni un éditeur.
- Suggérer le prochain lab à attaquer en fonction de votre progression, pour éviter le syndrome « par où je commence ? ».
- Suivre objectivement les résultats : chaque exécution
pytestd’un challenge est inscrite automatiquement dans une SQLite locale, avec un dashboard pour voir où vous en êtes.
Comment fonctionne le suivi (sans configuration)
Section intitulée « Comment fonctionne le suivi (sans configuration) »Le suivi est automatique et silencieux : un plugin pytest interne (livré dans conftest.py du repo) intercepte chaque run lancé sur un dossier labs/<section>/<page>/challenge/tests/ et inscrit le résultat dans la base. Vous n’avez rien à activer : dès que vous lancez pytest sur un challenge, votre progression se met à jour.
Aucun service externe n’est sollicité — pas de réseau, pas d’authentification, pas de données qui sortent de votre poste. Le suivi est conforme RGPD par construction.
Stockage : une SQLite locale dans ~/.local/share/
Section intitulée « Stockage : une SQLite locale dans ~/.local/share/ »Les résultats sont stockés dans ~/.local/share/dsoxlab/progress.db (fichier SQLite). Cet emplacement suit la spec XDG Base Directory — propre, isolé, sauvegardable, gitignored.
Chaque ligne de la base correspond à un run pytest : timestamp, lab concerné, nombre de tests passed/failed, statut dérivé. La même base sert à plusieurs repos ansible-training clonés (utile si vous avez un fork). Pour utiliser une base alternative (sandbox, formation), exportez ANSIBLE_TRAINING_DB=/tmp/test.db avant pytest.
Installer dsoxlab dans votre PATH
Section intitulée « Installer dsoxlab dans votre PATH »Pour pouvoir taper dsoxlab depuis n’importe où sans préfixer bin/, créez un lien symbolique dans ~/.local/bin/ (qui doit être dans votre PATH) :
mkdir -p ~/.local/binln -sf "$(pwd)/bin/dsoxlab" ~/.local/bin/dsoxlab
# Vérificationwhich dsoxlab # doit afficher /home/<vous>/.local/bin/dsoxlabdsoxlab show # plus besoin de bin/dsoxlabSi ~/.local/bin n’est pas dans votre PATH, ajoutez à ~/.bashrc ou ~/.zshrc :
export PATH="$HOME/.local/bin:$PATH"Si vous gérez plusieurs forks d’ansible-training, utilisez plutôt un wrapper qui pointe vers le bon repo :
cat > ~/.local/bin/dsoxlab <<'EOF'#!/usr/bin/env bashexec "$HOME/Projets/ansible-training/bin/dsoxlab" "$@"EOFchmod +x ~/.local/bin/dsoxlabCommandes essentielles
Section intitulée « Commandes essentielles »| Commande | Effet |
|---|---|
dsoxlab | Tableau de bord par section avec icônes (✅ ⏳ ❌ · ⊘) |
dsoxlab next | Suggère le prochain lab à attaquer (selon progression) |
dsoxlab stats | Pourcentage de réussite par section |
dsoxlab show --section vault | Filtrer le dashboard sur une section |
dsoxlab show --status completed | Lister uniquement les labs validés |
dsoxlab lab decouvrir/installation-ansible | Afficher le README du tutoriel (rendu Markdown) |
dsoxlab lab vault/introduction --challenge | Afficher le challenge/README.md |
dsoxlab lab vault/introduction --both | Tutoriel + challenge à la suite |
dsoxlab reset --lab vault/introduction -y | Effacer la progression d’un lab pour le rejouer |
dsoxlab reset --all | Reset complet (avec confirmation) |
dsoxlab export -o progress.json | Exporter la progression en JSON portable |
L’option --width 100 force la largeur de rendu (par défaut 80, 0 = largeur du terminal). Le commande dsoxlab lab est l’usage le plus fréquent — elle remplace l’aller-retour vers GitHub ou un éditeur Markdown.
Statuts d’un lab
Section intitulée « Statuts d’un lab »| Icône | Statut | Signification |
|---|---|---|
| ✅ | completed | Au moins un run avec 100 % de tests passed |
| ⏳ | in_progress | Run(s) existant(s) avec une partie passed/failed |
| ❌ | failed | Tous les tests échouent |
| · | not_started | Aucun run inscrit en base |
| ⊘ | skipped | Run skippé (challenge non écrit, replay désactivé) |
Désactiver le suivi ponctuellement
Section intitulée « Désactiver le suivi ponctuellement »Si vous voulez lancer pytest sans inscrire le résultat (debug, expérimentation) :
PROGRESS_DISABLED=1 pytest -v labs/ecrire-code/block-rescue-always/challenge/tests/Workflow type sur la formation
Section intitulée « Workflow type sur la formation »dsoxlab next # 1. Que faire ensuite ?dsoxlab lab decouvrir/installation-ansible # 2. Lire le tutoriel# ... vous écrivez lab.yml et challenge/solution.yml ...pytest -v labs/decouvrir/installation-ansible/challenge/tests/ # 3. Validerdsoxlab show # 4. Voir l'avancement globalLes cibles Make équivalentes existent (utiles si vous n’avez pas mis ~/.local/bin dans le PATH) : make dsoxlab, make dsoxlab-next, make dsoxlab-stats, make lab LAB=vault/introduction CHALLENGE=1.
Détruire le lab proprement
Section intitulée « Détruire le lab proprement »Quand vous voulez repartir d’une base propre — pour rejouer le provisioning de zéro, par exemple — :
make destroyLe script demande confirmation, arrête les 4 VMs, supprime leurs disques et démantèle le réseau libvirt. L’image cloud AlmaLinux est conservée dans /var/lib/libvirt/images/ pour ne pas la retélécharger au prochain make provision.
Pour automatiser dans un script, utilisez :
FORCE_DESTROY=1 make destroySnapshot / restore avant un test risqué
Section intitulée « Snapshot / restore avant un test risqué »Avant de tester un playbook destructif (changement de SELinux, partitionnement, durcissement OS), prenez un snapshot des 4 VMs :
make snapshot # Crée un snapshot 'lab-checkpoint-<timestamp>' sur les 4 VMsmake restore # Restaure les 4 VMs depuis le dernier snapshotC’est un filet de sécurité pédagogique — sur une vraie fleet, vous ne snapshotez pas avant chaque playbook.
Variantes pour les environnements sans KVM
Section intitulée « Variantes pour les environnements sans KVM »| Environnement | Alternative |
|---|---|
| Mac M1/M2 (pas de KVM nested) | UTM + virtio-fs, ou Multipass + Lima |
| Windows + WSL2 | KVM-on-WSL2 (KVM nested activé), ou Multipass natif |
| Pas d’accès root | Vagrant + VirtualBox (config Vagrantfile équivalente) |
| VMs cloud (Hetzner, OVH, AWS) | Provisionnement via Terraform (cf. cours Terraform du site) |
| Tout-en-un Docker | docker-compose avec 4 conteneurs rastasheep/ubuntu-sshd (limité : pas de systemd PID 1) |
Les commandes Ansible des guides suivants restent identiques — seule l’infrastructure d’hébergement change.
Dépannage
Section intitulée « Dépannage »| Symptôme | Cause | Fix |
|---|---|---|
error: Failed to start network lab-ansible | Conflit de sous-réseau avec un autre réseau libvirt | Lister avec virsh net-list --all puis adapter la plage IP dans provision.sh |
Permission denied sur /var/lib/libvirt/images/ | Utilisateur pas dans le groupe libvirt | sudo usermod -aG libvirt $USER && newgrp libvirt |
VM bloquée à [OK] Started cloud-init.service | Image qcow2 corrompue | Supprimer /var/lib/libvirt/images/AlmaLinux-10-*.qcow2, rejouer make provision |
ssh: connect to host 10.10.20.10 port 22: Connection refused | VM démarrée mais SSH pas encore prêt | Attendre 30 s supplémentaires, ou virsh console control-node pour suivre le boot |
Permission denied (publickey) sur le premier ping | Mauvais utilisateur SSH ou clé mal propagée | Vérifier ansible_user: ansible dans inventory/hosts.yml et ~/.ssh/authorized_keys côté VM |
| Connexion redirigée vers la mauvaise IP | ~/.ssh/config avec un bloc Host 10.* ProxyJump | Lancer ssh -vvv ansible@10.10.20.21 pour voir l’IP réelle, surcharger via ~/.ssh/config.d/lab-ansible.conf |
À retenir
Section intitulée « À retenir »- Le lab utilise virt-install + cloud-init pour provisionner 4 VMs AlmaLinux 10 reproductibles.
- Les baux DHCP statiques côté libvirt garantissent des IPs stables sans toucher à NetworkManager — la parade qui marche sur RHEL 10.
- Le cloud-init reste minimal : utilisateur, clé SSH, sudo NOPASSWD. Le reste est fait par Ansible.
- Une seule commande
make provisionmonte tout,make destroydémantèle proprement. - L’image cloud AlmaLinux est mise en cache entre runs pour économiser le téléchargement.
- Si KVM n’est pas disponible, Vagrant, Multipass ou des VMs cloud prennent le relais sans changer les commandes Ansible.