Ansible est un outil d'automatisation qui configure vos serveurs à distance. Vous décrivez dans un fichier YAML, le playbook, l'état que vous voulez obtenir : tel paquet installé, tel fichier en place, tel service démarré. Ansible se connecte en SSH à chaque machine et fait le nécessaire pour l'y amener, sans agent à installer et sans toucher à ce qui est déjà conforme.
Tout le monde a commencé autrement. Un script deploy.sh, une boucle for sur une liste d'IP collée en dur, des ssh root@$ip "apt-get install -y ..." enchaînés, et un résultat qui marche parfaitement sur trois serveurs sur quatre. Le quatrième tournait sur une autre version de Debian, ou avait un fichier de config modifié à la main par quelqu'un qui a quitté l'équipe depuis. Plus personne ne sait dans quel état est quoi, et relancer le script fait plus peur que de ne rien faire.
Ansible règle exactement ce problème. Ce n'est ni le plus rapide ni le plus élégant des outils de sa catégorie, et son YAML a des moments de grande poésie involontaire. Mais il ne demande aucune infrastructure pour démarrer : un accès SSH et Python sur les cibles, c'est tout. C'est pour ça qu'il est partout.
Ce guide est écrit pour ansible-core 2.21 (2.21.5, sortie le 5 octobre 2026) et testé sur des serveurs neufs en Debian 12 et 13 et en Ubuntu 22.04, 24.04 et 26.04. La 2.22 est prévue pour le 2 novembre 2026 : le playbook de ce guide a été rejoué sur sa version candidate avec un résultat identique, et les deux changements qui vous concernent sont signalés là où ils comptent.
Au programme : d'abord comprendre ce qu'est Ansible et où il se place par rapport à Terraform et OpenTofu. Ensuite un tutoriel complet, étape par étape, depuis un serveur neuf et un dossier vide jusqu'à un rôle qui déploie Apache sur deux serveurs, en passant par l'inventaire, les templates, les handlers et le dry run avec --check et --diff. Puis les références à garder sous la main : modules, variables, rôles, Vault, débogage et bonnes pratiques. Prérequis : une machine Linux, macOS ou WSL pour lancer Ansible, et un ou deux serveurs Debian ou Ubuntu auxquels vous avez accès en root.
D'où vient Ansible
Michael DeHaan n'en était pas à son premier outil d'administration. Il avait écrit Cobbler, un serveur d'installation de machines Linux, et co-écrit Func, un framework d'administration à distance, tous deux nés dans l'écosystème Fedora. Le 23 février 2012, il pousse le premier commit d'Ansible sur GitHub. Le message tient en un mot : "Genesis."
Puppet et Chef dominaient alors la gestion de configuration, avec un modèle commun : un agent installé sur chaque serveur, qui interroge régulièrement un serveur central pour récupérer sa configuration. Ansible part du principe inverse. Pas d'agent, pas de serveur central : la machine de l'administrateur se connecte en SSH, pousse le travail, et repart. Le pari, c'est que SSH est déjà partout, déjà sécurisé, déjà audité, et qu'il n'y a aucune raison d'ajouter un démon de plus sur chaque machine.
Le nom vient de la science-fiction. Ursula K. Le Guin a inventé le mot "ansible" en 1966, dans Le Monde de Rocannon, pour désigner un appareil de communication instantanée quelle que soit la distance. Orson Scott Card l'a repris dans La Stratégie Ender. Pour un outil qui parle à toute une flotte de machines d'un coup, l'image s'imposait.
La suite est une histoire d'entreprise classique. AnsibleWorks est fondée en 2013 pour vendre du support et une interface web, devient Ansible, Inc., et Red Hat annonce son rachat le 16 octobre 2015. Le projet reste libre, sous licence GPL version 3 ou ultérieure, et Red Hat vend autour une offre commerciale, Red Hat Ansible Automation Platform.
Dernier épisode, celui qui explique la confusion actuelle sur les numéros de version. En 2020, les milliers de modules qui vivaient dans le dépôt principal en sont sortis pour être rangés dans des collections, maintenues séparément. Le cœur restant est publié sous le nom ansible-base 2.10 le 13 août 2020, puis renommé ansible-core à partir de la 2.11, en avril 2021. Le paquet ansible continue d'exister, mais c'est désormais un assemblage : ansible-core plus une sélection de collections, avec sa propre numérotation. Aujourd'hui, ansible 14 s'appuie sur ansible-core 2.21. On y revient à l'installation.
Comment Ansible fonctionne - sans agent, par SSH
Le vocabulaire
Six mots suffisent pour lire n'importe quelle documentation Ansible :
| Terme | Ce que c'est |
|---|---|
| Nœud de contrôle | la machine où Ansible est installé et d'où vous le lancez : votre poste, un bastion, un runner de CI |
| Nœuds gérés | les serveurs que vous configurez, qui n'ont besoin que de SSH et de Python |
| Inventaire | la liste des nœuds gérés, rangés en groupes |
| Module | une unité de travail : installer un paquet, déposer un fichier, démarrer un service |
| Tâche | l'appel d'un module avec ses paramètres |
| Playbook | un fichier YAML qui applique une suite de tâches à un groupe de machines |
Ce qui se passe quand une tâche s'exécute
Lancez n'importe quelle commande Ansible avec -vvv et vous verrez toute la mécanique. Pour chaque tâche et chaque machine, Ansible :
- ouvre une connexion SSH, avec
ControlMasteretControlPersist=60s: la connexion reste ouverte pour les tâches suivantes, jusqu'à 60 secondes après sa dernière utilisation ; - crée un dossier temporaire dans
~/.ansible/tmp/sur la cible ; - au premier passage, cherche un interpréteur Python en essayant
python3.14,python3.13et ainsi de suite jusqu'à/usr/bin/python3etpython3; - copie le module sur la cible, emballé avec ses dépendances dans un fichier
AnsiballZ_<module>.py; - l'exécute avec ce Python, via
sudosi on le lui a demandé ; - récupère le résultat en JSON, puis supprime le dossier temporaire.
Rien ne reste sur la machine une fois la tâche finie, et rien n'y tourne entre deux exécutions. C'est toute la différence avec un agent. C'est aussi pour ça qu'Ansible est lent sur un gros parc : il refait ce cycle pour chaque tâche. Le guide SSH détaille les réglages qui l'accélèrent, et toute votre configuration ~/.ssh/config s'applique telle quelle.
Côté cible, ansible-core 2.21 accepte Python 3.9 à 3.14. Debian 12 et 13, Ubuntu 22.04, 24.04 et 26.04 sont toutes dans la fourchette.
L'idempotence - la promesse centrale
Un module Ansible ne décrit pas une action, il décrit un état. state: present ne veut pas dire "installe", il veut dire "fais en sorte que ce soit installé". Si le paquet est déjà là, le module vérifie, ne touche à rien et répond ok. S'il manque, il l'installe et répond changed.
Conséquence directe : relancer un playbook est sans danger. Le dixième passage ne change rien si personne n'a touché aux serveurs entre-temps, et remet en ordre ce qui a dérivé dans le cas contraire. C'est ce qui permet de lancer Ansible sur un serveur dont on ne connaît pas l'état exact, chose impensable avec le deploy.sh de l'introduction.
La promesse a une limite, et elle porte deux noms : command et shell. Ces modules exécutent ce que vous leur donnez sans savoir ce que ça fait, donc sans pouvoir dire s'il y a eu changement. On verra comment les rendre idempotents quand même.
IaC et gestion de configuration - Ansible, Terraform, OpenTofu
Ansible est souvent rangé dans la case "Infrastructure as Code", à côté de Terraform. Les deux décrivent l'infrastructure dans des fichiers texte versionnés, relus et rejouables. Mais ils ne font pas le même travail, et les confondre mène à des projets bancals.
Deux familles d'outils
Le provisionnement crée les ressources elles-mêmes : machines virtuelles, réseaux, règles de pare-feu, enregistrements DNS, bases de données managées. Ces outils parlent aux API des fournisseurs, d'AWS à Hetzner en passant par OVHcloud. Terraform, son fork OpenTofu et Pulumi, qui fait la même chose avec de vrais langages de programmation, en sont les représentants. CloudFormation joue ce rôle pour AWS seulement.
La gestion de configuration s'occupe de l'intérieur des machines : paquets, fichiers, services, utilisateurs. C'est le terrain d'Ansible, de Puppet, de Chef et de Salt.
Dans le langage courant, "IaC" désigne surtout la première famille. Ansible appartient à la seconde.
Ce qui les distingue vraiment
| Terraform / OpenTofu | Ansible | |
|---|---|---|
| Ce qu'il pilote | les API des clouds et des services | l'intérieur des machines, par SSH |
| Langage | HCL, déclaratif : vous décrivez le résultat, l'outil calcule l'ordre | YAML, des tâches exécutées dans l'ordre où vous les écrivez |
| Mémoire | un fichier d'état qui recense tout ce qu'il a créé | aucune : il relit l'état réel des machines à chaque passage |
| Aperçu avant d'agir | plan, qui liste ce qui sera créé, modifié ou détruit | --check --diff, avec des limites qu'on verra |
| Retirer un bloc du code | détruit la ressource au prochain apply | ne défait rien |
La dernière ligne est celle qui surprend le plus. Supprimez une machine virtuelle de votre code Terraform, et le prochain apply la détruit : l'état sait qu'elle existait. Supprimez du playbook la tâche qui installe Apache, et Apache reste installé sur tous les serveurs : Ansible ne se souvient de rien, il fait seulement ce que le playbook demande au moment où il tourne. Pour retirer quelque chose, il faut le demander explicitement, avec une tâche state: absent.
Modifier les serveurs ou les remplacer
Ansible modifie des serveurs qui existent déjà et qui durent : on parle d'infrastructure mutable. Le risque, c'est la dérive, ces modifications faites à la main qui s'accumulent et que le dry run sert à repérer.
L'approche inverse, l'infrastructure immuable, ne modifie jamais un serveur en place. On construit une image complète (avec Packer, ou une image de conteneur), on démarre de nouvelles instances à partir d'elle, et on détruit les anciennes. La configuration se fait alors au moment de construire l'image, et Ansible y garde sa place : Packer dispose d'un plugin qui exécute un playbook pendant la construction. Sur Kubernetes, l'essentiel de la configuration applicative passe dans les images et les manifestes, et Ansible se replie sur la préparation des machines.
Les combiner
Dans la plupart des équipes, la question n'est pas "Terraform ou Ansible" mais "où passe la frontière". Le découpage classique :
- OpenTofu ou Terraform crée les serveurs, le réseau et le DNS ;
- cloud-init, lu par les images des hébergeurs au premier démarrage, crée le compte d'administration et dépose la clé SSH, exactement ce que l'étape 2 de ce guide fait à la main ;
- Ansible configure le système et les applications.
Pour passer la liste des machines de l'un à l'autre, deux méthodes : générer le fichier d'inventaire à partir des sorties (output) de Terraform, ou utiliser un plugin d'inventaire dynamique qui interroge l'API du fournisseur à chaque exécution. amazon.aws.aws_ec2, google.cloud.gcp_compute et hetzner.hcloud.hcloud sont livrés avec le paquet ansible complet.
Chacun peut déborder sur le terrain de l'autre, et c'est rarement une bonne idée. Ansible sait créer des ressources cloud, mais ne garde aucune trace de ce qu'il a créé : pour les supprimer, il faut écrire leur suppression. Terraform sait lancer des commandes sur une machine avec ses provisioners, mais sa propre documentation demande d'épuiser toutes les autres solutions avant d'y recourir.
Terraform, OpenTofu et la licence
Le 10 août 2023, HashiCorp a annoncé que ses produits, Terraform compris, passaient de la licence libre MPL 2.0 à la Business Source License 1.1 pour toutes les versions suivantes. Terraform 1.5.7 est la dernière publiée sous MPL. La BSL autorise toujours l'usage commercial comme non commercial, sauf pour proposer une offre concurrente des versions payantes de l'éditeur (HashiCorp à l'époque, IBM aujourd'hui), mais ce n'est plus une licence open source.
En réponse, un groupe d'entreprises et de contributeurs a lancé un fork, que la Linux Foundation a officialisé sous le nom d'OpenTofu le 20 septembre 2023. Sa première version stable, OpenTofu 1.6.0, est sortie le 10 janvier 2024, toujours sous MPL 2.0, et le projet a rejoint la CNCF en avril 2025, au niveau sandbox. Les deux outils ont divergé depuis, mais partagent le langage HCL et la même logique de plan et d'état.
Un détail ne manque pas d'ironie : IBM, qui a racheté Red Hat en 2019, a finalisé le rachat d'HashiCorp le 27 février 2025. Ansible et Terraform, qu'on présente si souvent comme concurrents, appartiennent désormais au même groupe.
Installer Ansible
Ansible s'installe sur le nœud de contrôle uniquement. Rien à installer sur les serveurs gérés.
Avec pipx
La documentation officielle décrit l'installation avec pip, et propose pipx là où pip est bloqué, ce qui est le cas des Debian et Ubuntu récentes. pipx installe chaque outil Python dans son propre environnement isolé :
# Le cœur seul : le moteur et les modules de base
pipx install ansible-core
# Ou le paquet complet, avec une sélection de collections
pipx install --include-deps ansible
Un pip install ansible direct sur le Python du système échoue sur les distributions récentes avec l'erreur externally-managed-environment : Debian et Ubuntu protègent leur Python système, et c'est tant mieux. pipx s'installe lui-même avec sudo apt install pipx, suivi de pipx ensurepath pour mettre ses commandes dans le PATH.
Le nœud de contrôle demande Python 3.12 à 3.14 pour ansible-core 2.21, et 3.13 minimum à partir de la 2.22. pipx installe la dernière version compatible avec votre Python : sur Ubuntu 24.04, livrée avec Python 3.12, vous resterez donc en 2.21 après le 2 novembre, ce qui n'a rien de dramatique. Windows n'est pas pris en charge comme nœud de contrôle. La documentation propose WSL ou un conteneur, en précisant que WSL n'est pas officiellement supporté et ne convient pas à la production : pour apprendre, il fait l'affaire.
ansible ou ansible-core ?
| ansible-core 2.21.5 | ansible 14.5.0 | |
|---|---|---|
| Contenu | le moteur et les modules ansible.builtin | ansible-core + 92 collections |
| Modules | 71 | près de 8 900 au total |
| Taille installée | 21 Mo | 425 Mo de collections en plus |
Les 71 modules de base couvrent ce dont un serveur Linux a besoin : paquets, fichiers, services, utilisateurs, commandes. Les collections du paquet complet ajoutent le reste : les clouds, les équipements réseau, Kubernetes, les bases de données. Ce guide n'utilise que ansible.builtin, donc ansible-core suffit. Le jour où il vous faut une collection, installez-la seule :
ansible-galaxy collection install community.general
Avec le paquet de la distribution
sudo apt install ansible fonctionne, mais livre une version en retard : ansible-core 2.19 sur Debian 13, 2.16 sur Ubuntu 24.04. Pour débuter, c'est suffisant. Le jour où un exemple de la documentation en ligne ne marche pas chez vous, vérifiez la version avant de chercher l'erreur.
Vérifier l'installation
ansible --version
ansible [core 2.21.5]
config file = None
...
jinja version = 3.1.6
pyyaml version = 6.0.3 (with libyaml v0.2.5)
La ligne config file dit quel fichier de configuration Ansible a trouvé. Ici, aucun : on en créera un à l'étape 4.
Préparer les serveurs
C'est le début du tutoriel. Chaque étape dit ce que vous faites, sur quelle machine, et ce que vous devez voir. Ne passez à la suivante que si la précédente donne le résultat attendu.
Ce qu'il vous faut
Un ou deux serveurs Debian 12 ou 13, ou Ubuntu 22.04, 24.04 ou 26.04, fraîchement installés, dont vous connaissez l'adresse IP et auxquels vous pouvez vous connecter en SSH avec un compte administrateur. Peu importe d'où ils viennent : un petit VPS chez un hébergeur, une machine virtuelle sur votre poste (VirtualBox, UTM sur Mac...), un Raspberry Pi qui traîne.
Ce compte, que le tutoriel appelle votre compte d'accès, dépend de l'origine du serveur. Chez beaucoup d'hébergeurs, c'est root, avec la clé SSH fournie à la création. Sur une image cloud, c'est un compte comme ubuntu, debian ou admin, qui utilise sudo. Sur une machine installée à la main, c'est le compte créé pendant l'installation : SSH refuse par défaut la connexion de root par mot de passe, donc passez par ce compte.
Prenez des serveurs sur lesquels vous pouvez tout casser. Le tutoriel installe Apache et modifie sa configuration.
Dans ce guide, les deux serveurs s'appellent web1 et web2, aux adresses 192.0.2.11 et 192.0.2.12. Remplacez-les par les vôtres partout où elles apparaissent. Avec un seul serveur, ignorez simplement ce qui concerne web2.
Étape 1 : votre clé SSH
Sur votre machine. Dans ce tutoriel, Ansible se connecte par clé SSH, pas par mot de passe. Vérifiez si vous en avez déjà une :
ls ~/.ssh/id_ed25519.pub
Si le fichier n'existe pas, créez-la :
ssh-keygen -t ed25519
Acceptez l'emplacement proposé et choisissez une passphrase : sans elle, quiconque copie le fichier de clé a accès à vos serveurs. Le guide SSH explique pourquoi ne pas la sauter.
La passphrase a une conséquence pour Ansible : il ne peut pas vous la demander. Sans précaution, la connexion échoue avec un Permission denied (publickey...). Chargez donc la clé dans l'agent SSH au début de votre session, et il s'en servira à votre place :
ssh-add ~/.ssh/id_ed25519
Sur la plupart des bureaux Linux et sur macOS, un agent tourne déjà ; sinon, le guide SSH détaille ssh-agent et ssh-add.
Déposez ensuite votre clé publique sur chaque serveur, pour votre compte d'accès. Si vous l'avez fournie à l'hébergeur en créant le serveur, elle y est déjà. Sinon, en remplaçant root par le nom de votre compte d'accès si ce n'est pas root :
ssh-copy-id root@192.0.2.11
ssh-copy-id root@192.0.2.12
ssh-copy-id vous demande une dernière fois le mot de passe du compte. Puis connectez-vous, pour vérifier que la clé suffit désormais :
ssh root@192.0.2.11
À la toute première connexion à un serveur, par ssh-copy-id ou par ssh, SSH vous montre son empreinte et demande Are you sure you want to continue connecting (yes/no/[fingerprint])?. Répondez yes. Ce moment compte : il enregistre l'empreinte dans ~/.ssh/known_hosts. Ansible, lui, ne sait pas répondre à cette question, et un serveur inconnu le fait échouer avec Host key verification failed. Si vous réinstallez un serveur, son empreinte change et SSH refuse de s'y connecter : effacez l'ancienne avec ssh-keygen -R 192.0.2.11, puis reconnectez-vous une fois à la main.
Étape 2 : un compte dédié à Ansible sur chaque serveur
Sur chaque serveur, en root, dans la session SSH ouverte à l'étape 1. Si votre compte d'accès n'est pas root, passez root d'abord avec sudo -i, ou avec su - sur une Debian installée avec un mot de passe root, qui n'a pas encore sudo.
Si votre compte d'accès n'est pas root, la dernière ligne du bloc doit copier ses clés à lui : remplacez /root/.ssh/authorized_keys par /home/ubuntu/.ssh/authorized_keys, en adaptant ubuntu au nom de votre compte. Sur une machine installée à la main, le fichier de root n'existe pas. Sur les images cloud, il contient une restriction posée par cloud-init, qui affiche Please login as the user "ubuntu" rather than the user "root". et coupe la connexion : copiée telle quelle, elle bloquerait aussi deploy.
apt update && apt install -y sudo python3
useradd --create-home --shell /bin/bash deploy
echo 'deploy ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/deploy
chmod 440 /etc/sudoers.d/deploy
visudo -c
install -d -m 700 -o deploy -g deploy /home/deploy/.ssh
install -m 600 -o deploy -g deploy /root/.ssh/authorized_keys /home/deploy/.ssh/authorized_keys
Ligne par ligne :
sudoetpython3sont les deux seules choses dont Ansible a besoin sur la cible. Ils sont souvent déjà là ; dans ce cas, apt ne fait rien.useraddcrée l'utilisateurdeploy, sans mot de passe : impossible de s'y connecter autrement que par clé.- Le fichier
/etc/sudoers.d/deployl'autorise à passer root sans mot de passe, ce dont Ansible a besoin pour travailler sans vous interroger. chmod 440donne au fichier les droits recommandés pour un fichier sudoers : lecture seule, pour root et son groupe.visudo -cvérifie toute la configuration de sudo, ce nouveau fichier compris, et doit finir par/etc/sudoers.d/deploy: parsed OK. Sur Ubuntu 26.04, qui utilise sudo-rs, seule la ligne/etc/sudoers: parsed OKs'affiche. Vérifier avant de vous déconnecter évite de découvrir une erreur de sudo le jour où vous en avez besoin.- Les deux
installcréent le dossier.sshdedeployet y copient les clés autorisées de votre compte d'accès, dont la vôtre, avec les bons droits.
Pourquoi pas root directement ? Parce qu'un compte dédié se retrouve dans les journaux, se désactive sans toucher à root, et laisse fermer complètement la connexion SSH de root une fois qu'il fonctionne. Le prix à payer, c'est que quiconque obtient votre clé obtient root sur le serveur, à travers deploy. D'où la passphrase de l'étape 1. Si vous préférez un mot de passe sudo, donnez-en un à deploy avec passwd deploy, retirez NOPASSWD: du fichier sudoers, et ajoutez -K à toutes les commandes Ansible de ce guide pour qu'il vous le demande.
Faites de même sur web2. Puis, de retour sur votre machine, vérifiez les deux serveurs :
ssh deploy@192.0.2.11 sudo whoami
ssh deploy@192.0.2.12 sudo whoami
root
root
Si chaque commande répond root, les serveurs sont prêts.
Créer le projet Ansible
Étape 3 : le dossier du projet
Sur votre machine. Un projet Ansible est un simple dossier. Créez-le et placez-vous dedans, puis initialisez un dépôt Git : un playbook est du code, il se versionne dès le premier jour.
mkdir -p ~/ansible/apache
cd ~/ansible/apache
git init
Si vous n'avez jamais utilisé Git sur cette machine, déclarez aussi votre identité, sans quoi le premier commit de l'étape 15 échouera avec un message Author identity unknown :
git config --global user.name "Votre Nom"
git config --global user.email "vous@example.com"
Le guide Git détaille les autres réglages utiles.
Toutes les commandes qui suivent se lancent depuis ce dossier. À la fin de l'étape 15, il contiendra ceci :
~/ansible/apache/
├── ansible.cfg
├── inventory.ini
├── site.yml
└── templates/
└── index.html.j2
Étape 4 : ansible.cfg
Créez ansible.cfg dans le dossier du projet :
[defaults]
inventory = inventory.ini
interpreter_python = auto_silent
inventory évite de taper -i inventory.ini à chaque commande. interpreter_python = auto_silent fait taire un avertissement que vous verriez sinon à chaque lancement, une fois par machine :
[WARNING]: Host 'web1' is using the discovered Python interpreter at '/usr/bin/python3.13', but future installation of another Python interpreter could cause a different interpreter to be discovered. See https://docs.ansible.com/ansible-core/2.21/reference_appendices/interpreter_discovery.html for more information.
L'avertissement dit vrai : Ansible a trouvé python3.13 en cherchant, et en trouverait peut-être un autre si une nouvelle version de Python arrivait sur le serveur. Sur un parc homogène de Debian ou d'Ubuntu, la découverte automatique fait très bien le travail.
Vérifiez qu'Ansible trouve bien ce fichier :
ansible --version
ansible [core 2.21.5]
config file = /home/vous/ansible/apache/ansible.cfg
...
Ansible cherche sa configuration dans cet ordre et s'arrête au premier fichier trouvé : la variable d'environnement ANSIBLE_CONFIG, un ansible.cfg dans le dossier courant, ~/.ansible.cfg, puis /etc/ansible/ansible.cfg. Le fichier du projet passe donc avant votre configuration personnelle et celle du système, à condition de lancer vos commandes depuis le dossier du projet. Une exception : par sécurité, Ansible ignore le ansible.cfg d'un dossier modifiable par tout le monde. Sous WSL, c'est par défaut le cas des dossiers Windows montés dans /mnt/c, et Ansible le signale par un avertissement Ansible is being run in a world writable directory : gardez le projet dans votre dossier personnel Linux.
Étape 5 : l'inventaire
Créez inventory.ini :
[web]
web1 ansible_host=192.0.2.11
web2 ansible_host=192.0.2.12
[web:vars]
ansible_user=deploy
[web] déclare un groupe. web1 et web2 sont des alias : c'est sous ces noms qu'Ansible affichera les machines, et ansible_host dit où les joindre. [web:vars] applique des variables à tout le groupe, ici le compte SSH créé à l'étape 2.
Les variables de connexion les plus courantes :
| Variable | Rôle |
|---|---|
ansible_host | adresse ou nom DNS à joindre |
ansible_user | utilisateur SSH |
ansible_port | port SSH, 22 par défaut |
ansible_ssh_private_key_file | clé privée à utiliser |
ansible_python_interpreter | Python à utiliser sur la cible |
Le même inventaire existe en YAML, si vous préférez :
web:
hosts:
web1:
ansible_host: 192.0.2.11
web2:
ansible_host: 192.0.2.12
vars:
ansible_user: deploy
Les deux formats font exactement la même chose. L'INI est plus court pour une liste de machines, le YAML devient plus lisible quand les groupes s'imbriquent. Ce guide garde l'INI.
Vérifiez qu'Ansible comprend l'inventaire comme vous :
ansible-inventory --graph
@all:
|--@ungrouped:
|--@web:
| |--web1
| |--web2
all et ungrouped existent toujours sans qu'on les déclare : le premier contient toutes les machines, le second celles qui ne sont dans aucun groupe.
Étape 6 : premier contact avec ping
ansible all -m ping
web1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.13"
},
"changed": false,
"ping": "pong"
}
web2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.13"
},
"changed": false,
"ping": "pong"
}
Le module ping n'a rien à voir avec le ping réseau, il ne fait pas d'ICMP. Il se connecte en SSH, exécute un bout de Python sur la cible et répond pong. S'il répond, la connexion, l'authentification et Python fonctionnent.
Reste à vérifier que sudo fonctionne aussi, puisque le playbook en aura besoin. La même commande avec -b doit répondre pareil :
ansible all -b -m ping
Si ça ne répond pas :
| Message | Ce qu'il faut faire |
|---|---|
Host key verification failed | connectez-vous une fois à la main avec ssh deploy@192.0.2.11 et acceptez l'empreinte ; si le serveur a été réinstallé, effacez d'abord l'ancienne avec ssh-keygen -R 192.0.2.11 |
Permission denied (publickey...) | la clé n'est pas chargée dans l'agent (ssh-add), ou l'étape 2 n'a pas copié la bonne clé |
UNREACHABLE avec Connection timed out | mauvaise adresse IP, ou un pare-feu bloque le port 22 |
Missing sudo password (avec -b) | le fichier /etc/sudoers.d/deploy de l'étape 2 manque ou est incorrect |
Les commandes ad hoc
Le ping de l'étape 6 était une commande ad hoc : un seul module lancé sur un groupe de machines, sans écrire de playbook. C'est l'outil des questions rapides et des opérations ponctuelles.
ansible web -a "uptime"
web1 | CHANGED | rc=0 >>
13:22:30 up 7:00, 0 users, load average: 1.60, 1.11, 1.08
web2 | CHANGED | rc=0 >>
13:22:30 up 7:00, 0 users, load average: 1.60, 1.11, 1.08
Sans -m, Ansible utilise le module command. Et notez le CHANGED : uptime ne change évidemment rien, mais command n'a aucun moyen de le savoir, donc il annonce un changement par principe.
Installer un paquet sur tout le groupe :
ansible web -b -m ansible.builtin.apt -a "name=htop state=present update_cache=true"
Sans -b, la commande échoue sur une erreur qui ne parle pas d'Ansible mais d'apt : E: Could not open lock file /var/lib/apt/lists/lock - open (13: Permission denied). -b, pour become, exécute le module via sudo. Relancez la même commande : chaque machine répond cette fois SUCCESS avec "changed": false, parce que htop est déjà installé. L'idempotence, en action.
Les options à connaître :
| Option | Effet |
|---|---|
-i fichier | inventaire à utiliser, si ansible.cfg n'en définit pas |
-m module | module à lancer, command par défaut |
-a "..." | arguments du module |
-b | passer root via sudo (become) |
-K | demander le mot de passe sudo |
-u utilisateur | utilisateur SSH |
-k | demander le mot de passe SSH |
-l motif | restreindre à certaines machines (--limit) |
-C | dry run (--check), détaillé plus bas |
-c type | type de connexion : ssh par défaut, local pour la machine elle-même |
-e var=valeur | passer une variable |
-v, -vv, -vvv... | verbosité croissante |
Attention à la casse : -C majuscule lance un dry run, -c minuscule change le type de connexion. ansible localhost -c local -m ping teste Ansible sur votre propre machine, sans SSH.
Deux remarques sur les mots de passe. Si votre compte a besoin d'un mot de passe pour sudo, -K le demande, et sans lui la tâche échoue avec Missing sudo password. Quant à -k, il n'a plus besoin de sshpass depuis ansible-core 2.19 : beaucoup de tutoriels vous diront encore de l'installer, c'est devenu inutile, sauf avec une version plus ancienne comme celle qu'apt installe sur Ubuntu 24.04. La clé SSH reste la bonne méthode.
Premier playbook : installer Apache
Étape 7 : écrire site.yml
Créez site.yml dans le dossier du projet :
- name: Déployer Apache
hosts: web
become: true
tasks:
- name: Installer apache2
ansible.builtin.apt:
name: apache2
state: present
update_cache: true
cache_valid_time: 3600
- name: Démarrer apache2 et l'activer au boot
ansible.builtin.service:
name: apache2
state: started
enabled: true
Ligne par ligne :
- name: Déployer Apacheouvre un play. Un playbook est une liste de plays, et chaque play associe des machines à des tâches.hosts: webapplique le play au groupewebde l'inventaire.become: truefait passer toutes les tâches par sudo, l'équivalent du-bdes commandes ad hoc.tasks:liste les tâches. Elles s'exécutent dans l'ordre, chacune sur toutes les machines avant de passer à la suivante.- Chaque tâche a un
name, affiché à l'exécution, puis le module à appeler et ses paramètres. Ici,aptinstalle le paquetapache2après avoir rafraîchi la liste des paquets (update_cache), mais seulement si le dernier rafraîchissement date de plus d'une heure (cache_valid_time: 3600). Puisservices'assure qu'Apache tourne et démarrera au boot.
ansible.builtin.apt est le nom complet du module, son FQCN (Fully Qualified Collection Name). Le nom court apt fonctionne aussi, mais le nom complet ne laisse aucune ambiguïté quand plusieurs collections fournissent un module du même nom, et c'est la forme que recommande la documentation.
Le nom site.yml est une convention qui vient des exemples de la documentation officielle : c'est le playbook principal du projet, celui qui configure tout le parc.
Côté YAML, trois règles évitent la plupart des erreurs : deux espaces d'indentation, jamais de tabulation ; les tâches alignées les unes sous les autres ; des guillemets autour de toute valeur qui commence par {{. Vérifiez la syntaxe sans rien exécuter :
ansible-playbook --syntax-check site.yml
playbook: site.yml
Cette seule ligne veut dire que tout va bien. Une erreur de syntaxe s'afficherait à la place, avec le numéro de ligne.
Étape 8 : lancer le playbook
ansible-playbook site.yml
PLAY [Déployer Apache] *********************************************************
TASK [Gathering Facts] *********************************************************
ok: [web1]
ok: [web2]
TASK [Installer apache2] *******************************************************
changed: [web2]
changed: [web1]
TASK [Démarrer apache2 et l'activer au boot] ***********************************
ok: [web2]
ok: [web1]
PLAY RECAP *********************************************************************
web1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Gathering Facts est une tâche que vous n'avez pas écrite : avant tout le reste, Ansible collecte des informations sur chaque machine (distribution, adresses IP, mémoire...). On s'en sert dès l'étape 10.
La tâche service répond ok et pas changed : sur Debian et Ubuntu, le paquet apache2 démarre et active le service dès son installation. Le module a vérifié, constaté que tout était déjà conforme, et n'a rien fait. Sur la famille Red Hat, où le paquet s'appelle httpd et ne démarre pas son service à l'installation, la tâche équivalente répondrait changed.
Le PLAY RECAP résume tout, machine par machine :
| Compteur | Sens |
|---|---|
ok | tâches réussies, avec ou sans changement |
changed | tâches qui ont modifié quelque chose |
unreachable | machine injoignable en SSH |
failed | tâches en échec |
skipped | tâches sautées par une condition |
rescued | erreurs rattrapées par un bloc rescue |
ignored | erreurs ignorées par ignore_errors |
Un récapitulatif sans failed ne suffit pas : vérifiez le résultat sur le serveur. Ouvrez http://192.0.2.11/ dans un navigateur, vous devez voir la page "Apache2 Debian Default Page" (ou sa version Ubuntu). Puis regardez l'en-tête Server de la réponse HTTP :
curl -sI http://192.0.2.11/ | grep -i server
Server: Apache/2.4.69 (Debian)
Apache annonce sa version exacte et la distribution à quiconque l'interroge. On masquera ça à l'étape 11.
Étape 9 : relancer pour vérifier l'idempotence
Relancez exactement la même commande :
ansible-playbook site.yml
[...]
PLAY RECAP *********************************************************************
web1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
changed=0 : rien n'a bougé, parce que rien n'avait besoin de bouger. C'est le réflexe à prendre avec tout playbook que vous écrivez : le lancer deux fois. Un second passage qui affiche encore des changed signale une tâche mal écrite, et cette tâche finira un jour par faire quelque chose que vous n'aviez pas prévu.
Configurer Apache pas à pas
Apache tourne. Chaque étape de cette partie ajoute une tâche à site.yml, relance le playbook et vérifie le résultat sur le serveur.
Étape 10 : une page d'accueil avec un template Jinja2
Remplaçons la page par défaut par une page qui affiche le nom du serveur et sa distribution. Créez le dossier templates/ et, dedans, le fichier index.html.j2 :
mkdir templates
templates/index.html.j2 :
<h1>{{ inventory_hostname }}</h1>
<p>{{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}, déployé par Ansible.</p>
C'est un template Jinja2 : les doubles accolades seront remplacées par des valeurs propres à chaque serveur. inventory_hostname est le nom de la machine dans l'inventaire. ansible_facts contient les informations collectées par Gathering Facts, ici la distribution et sa version.
Ajoutez cette tâche à la fin de site.yml, après la tâche Démarrer apache2, avec la même indentation que les autres - name :
- name: Déposer la page d'accueil
ansible.builtin.template:
src: templates/index.html.j2
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: "0644"
Le module template remplit le template sur votre machine, puis copie le résultat sur le serveur avec le propriétaire et les droits demandés. Le mode est entre guillemets : écrit 644, sans zéro ni guillemets, YAML le lirait comme le nombre décimal 644, et le fichier se retrouverait avec des droits absurdes.
Relancez le playbook :
ansible-playbook site.yml
[...]
TASK [Déposer la page d'accueil] ***********************************************
changed: [web1]
changed: [web2]
PLAY RECAP *********************************************************************
web1 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=4 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Seule la nouvelle tâche a changé quelque chose, les autres répondent ok. Vérifiez :
curl http://192.0.2.11/
<h1>web1</h1>
<p>Debian 13.7, déployé par Ansible.</p>
Sur web2, la page affiche web2 : un seul template, une page différente par serveur.
Étape 11 : masquer la version d'Apache avec un handler
Sur Debian et Ubuntu, la ligne qui règle l'en-tête Server se trouve dans /etc/apache2/conf-available/security.conf et vaut ServerTokens OS. On veut ServerTokens Prod, qui n'affiche que "Apache". Pas question de remplacer tout le fichier pour une ligne : c'est le travail du module lineinfile.
Mais changer la configuration ne suffit pas, Apache doit la relire. On ne veut pas le recharger à chaque passage du playbook, seulement quand la ligne a vraiment changé. C'est le rôle des handlers : des tâches qui ne s'exécutent que si une autre tâche les a prévenues (notify) et a répondu changed.
Ajoutez la tâche à la fin des tasks, puis une section handlers au même niveau que tasks:, c'est-à-dire avec deux espaces d'indentation. Voici site.yml complet à cette étape :
- name: Déployer Apache
hosts: web
become: true
tasks:
- name: Installer apache2
ansible.builtin.apt:
name: apache2
state: present
update_cache: true
cache_valid_time: 3600
- name: Démarrer apache2 et l'activer au boot
ansible.builtin.service:
name: apache2
state: started
enabled: true
- name: Déposer la page d'accueil
ansible.builtin.template:
src: templates/index.html.j2
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: "0644"
- name: Masquer la version d'Apache
ansible.builtin.lineinfile:
path: /etc/apache2/conf-available/security.conf
regexp: '^ServerTokens '
line: "ServerTokens Prod"
notify: Recharger apache2
handlers:
- name: Recharger apache2
ansible.builtin.service:
name: apache2
state: reloaded
lineinfile cherche la dernière ligne qui correspond à l'expression régulière regexp et la remplace par line. Si aucune ne correspond, il ajoute line à la fin du fichier. Le ^ compte : le fichier de Debian contient aussi des lignes commentées comme #ServerTokens Full, auxquelles on ne veut pas toucher.
notify: Recharger apache2 désigne le handler par son nom, au caractère près. state: reloaded demande à Apache de relire sa configuration sans couper les connexions en cours.
Relancez :
ansible-playbook site.yml
[...]
TASK [Masquer la version d'Apache] *********************************************
changed: [web1]
changed: [web2]
RUNNING HANDLER [Recharger apache2] ********************************************
changed: [web2]
changed: [web1]
PLAY RECAP *********************************************************************
web1 : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=6 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Le handler s'exécute après toutes les tâches, sous RUNNING HANDLER. Vérifiez :
curl -sI http://192.0.2.11/ | grep -i server
Server: Apache
Relancez une fois de plus : la ligne est déjà bonne, la tâche répond ok, et le handler ne s'exécute pas.
Une leçon tirée de l'écriture de ce guide. Une première version déposait un fichier séparé, securite.conf, avec sa propre ligne ServerTokens. Il était en place, activé, et l'en-tête affichait toujours la version complète d'Apache. La raison : Apache lit les fichiers de conf-enabled/ par ordre alphabétique, security.conf passe après securite.conf, et sa ligne ServerTokens OS gagnait. Ansible avait fait exactement ce qu'on lui demandait, et répondait changed puis ok comme si tout allait bien. D'où le curl -I après chaque étape : un playbook qui passe au vert ne prouve pas que le serveur fait ce que vous voulez.
Étape 12 : activer des modules avec une boucle
Activons deux modules Apache, headers et rewrite. Debian fournit la commande a2enmod, et ansible-core n'a pas de module dédié : on passe par command, en lui apprenant à être idempotent.
Ajoutez cette tâche à la fin des tasks, juste avant la ligne handlers: :
- name: Activer les modules Apache
ansible.builtin.command:
cmd: "a2enmod {{ item }}"
creates: "/etc/apache2/mods-enabled/{{ item }}.load"
loop:
- headers
- rewrite
notify: Redémarrer apache2
Et ce second handler à la fin du fichier, sous le premier :
- name: Redémarrer apache2
ansible.builtin.service:
name: apache2
state: restarted
Trois nouveautés :
loopexécute la tâche une fois par élément de la liste, etitemcontient l'élément courant.createsrend la commande idempotente : si le fichier existe déjà, c'est que le module est activé, et la commande n'est pas lancée. Sans lui,commandannonceraitchangedà chaque passage.- Un handler de redémarrage, et non de rechargement. C'est la consigne de Debian : après un
a2enmod, le script afficheTo activate the new configuration, you need to run: systemctl restart apache2, alors qu'après una2enconfil demande un simplesystemctl reload apache2.
Relancez :
ansible-playbook site.yml
[...]
TASK [Activer les modules Apache] **********************************************
changed: [web1] => (item=headers)
changed: [web2] => (item=headers)
changed: [web1] => (item=rewrite)
changed: [web2] => (item=rewrite)
RUNNING HANDLER [Redémarrer apache2] *******************************************
changed: [web2]
changed: [web1]
PLAY RECAP *********************************************************************
web1 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
La tâche a notifié le handler deux fois, une par module, et Apache n'a pourtant redémarré qu'une fois. Vérifiez sur le serveur :
ssh deploy@192.0.2.11 sudo apache2ctl -M | grep -E 'headers|rewrite'
headers_module (shared)
rewrite_module (shared)
Si une ligne AH00558: apache2: Could not reliably determine the server's fully qualified domain name s'affiche au-dessus, ignorez-la : c'est un avertissement bénin, expliqué dans la partie débogage.
Ce qu'il faut retenir des handlers :
- Un handler ne s'exécute qu'une fois par passage, même si plusieurs tâches l'ont notifié.
- L'ordre est celui de la section
handlers, pas celui des notifications. - Un serveur en échec ne lance pas ses handlers. Si une tâche échoue sur un serveur après la notification, le handler ne part pas sur ce serveur, et la modification y reste en place sans avoir été prise en compte.
--force-handlersles exécute malgré l'échec. - Pour les déclencher plus tôt, une tâche
ansible.builtin.meta: flush_handlersexécute sur-le-champ ceux qui sont en attente.
Étape 13 : sortir les valeurs dans des variables
Prod et la liste des modules sont écrits en dur au milieu des tâches. Sortons-les dans une section vars:, en haut du play. Voici site.yml dans sa version finale :
- name: Déployer Apache
hosts: web
become: true
vars:
apache_server_tokens: Prod
apache_modules:
- headers
- rewrite
tasks:
- name: Installer apache2
ansible.builtin.apt:
name: apache2
state: present
update_cache: true
cache_valid_time: 3600
- name: Démarrer apache2 et l'activer au boot
ansible.builtin.service:
name: apache2
state: started
enabled: true
- name: Déposer la page d'accueil
ansible.builtin.template:
src: templates/index.html.j2
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: "0644"
- name: Masquer la version d'Apache
ansible.builtin.lineinfile:
path: /etc/apache2/conf-available/security.conf
regexp: '^ServerTokens '
line: "ServerTokens {{ apache_server_tokens }}"
notify: Recharger apache2
- name: Activer les modules Apache
ansible.builtin.command:
cmd: "a2enmod {{ item }}"
creates: "/etc/apache2/mods-enabled/{{ item }}.load"
loop: "{{ apache_modules }}"
notify: Redémarrer apache2
handlers:
- name: Recharger apache2
ansible.builtin.service:
name: apache2
state: reloaded
- name: Redémarrer apache2
ansible.builtin.service:
name: apache2
state: restarted
Les variables s'utilisent avec la même syntaxe que dans le template : {{ apache_server_tokens }}. Notez les guillemets autour de "{{ apache_modules }}" : une valeur YAML qui commence par {{ doit être entre guillemets, sinon YAML croit lire un dictionnaire.
Relancez :
ansible-playbook site.yml
[...]
PLAY RECAP *********************************************************************
web1 : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
changed=0, et c'est exactement ce qu'on voulait. On a réorganisé le code sans changer ce qu'il produit, et Ansible le confirme : aucun serveur n'a bougé. Prenez l'habitude de ce contrôle à chaque réorganisation.
Étape 14 : détecter une dérive avec --check et --diff
Simulons ce qui arrive tôt ou tard sur un vrai parc : quelqu'un modifie un serveur à la main, pour déboguer, et oublie de remettre les choses en place. Sur web2 :
ssh deploy@192.0.2.12 "sudo sed -i 's/^ServerTokens .*/ServerTokens Full/' /etc/apache2/conf-available/security.conf && sudo systemctl reload apache2"
curl -sI http://192.0.2.12/ | grep -i server
Server: Apache/2.4.69 (Debian)
La version est de retour. Avant de corriger quoi que ce soit, demandons à Ansible ce qu'il ferait, sans le laisser rien faire :
ansible-playbook site.yml --check --diff
[...]
TASK [Masquer la version d'Apache] *********************************************
ok: [web1]
--- before: /etc/apache2/conf-available/security.conf (content)
+++ after: /etc/apache2/conf-available/security.conf (content)
@@ -9,7 +9,7 @@
# Set to one of: Full | OS | Minimal | Minor | Major | Prod
# where Full conveys the most information, and Prod the least.
#ServerTokens Minimal
-ServerTokens Full
+ServerTokens Prod
#ServerTokens Full
#
changed: [web2]
[...]
RUNNING HANDLER [Recharger apache2] ********************************************
changed: [web2]
PLAY RECAP *********************************************************************
web1 : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=7 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
--check simule chaque tâche sans rien modifier, --diff montre la différence dans les fichiers. Le résultat est limpide : web1 n'a pas bougé, web2 a dérivé, et le diff montre exactement quelle ligne. Les changed de web2 ne sont que des intentions : rien n'a été modifié.
Corrigez, en ne touchant que la machine concernée :
ansible-playbook site.yml --limit web2
curl -sI http://192.0.2.12/ | grep -i server
Server: Apache
C'est le dry run d'Ansible. Lancé régulièrement, chaque nuit par exemple, il sert de détecteur : un changed inattendu signale que quelqu'un, ou quelque chose, a touché à un serveur.
Étape 15 : versionner
Le projet fonctionne, enregistrez-le :
git add .
git commit -m "Apache : premier déploiement"
Committez à chaque étape qui fonctionne. Le jour où une modification casse tout, git diff montre ce qui a changé depuis la dernière version qui marchait.
Les modules du quotidien
Le playbook du tutoriel a utilisé cinq modules. Voici ce qu'il faut savoir de plus sur chacun, et sur ceux qui les complètent.
apt, dnf et package
state vaut notamment present (installé, quelle que soit la version), latest (dernière version disponible) ou absent. name accepte aussi une liste : passez-la directement plutôt que de boucler dessus, apt installe alors tout en une seule transaction.
Le module a besoin du paquet python3-apt sur la cible. S'il manque, apt l'installe tout seul au premier passage ; en check mode, en revanche, il échoue, on y revient plus bas.
Pour la famille Red Hat, le module s'appelle ansible.builtin.dnf. ansible.builtin.package choisit tout seul le bon gestionnaire selon la distribution, au prix de la portabilité : seules les options communes sont sûres, et les noms de paquets changent d'une famille à l'autre (apache2 chez Debian, httpd chez Red Hat).
service et systemd_service
state vaut started, stopped, restarted ou reloaded, et enabled règle le démarrage au boot. Les deux sont indépendants, exactement comme systemctl start et systemctl enable.
started et stopped sont idempotents. restarted et reloaded ne le sont pas et ne peuvent pas l'être : redémarrer est une action, pas un état. Une tâche state: restarted écrite directement dans tasks: redémarre le service à chaque passage du playbook et annonce changed à chaque fois. Sa place est dans un handler.
Sur une machine systemd, ansible.builtin.systemd_service fait la même chose et ajoute daemon_reload: true, indispensable quand vous venez de déposer ou de modifier un fichier d'unité. C'est le piège classique des outils de déploiement qui écrivent des unités sans prévenir systemd.
file et copy
- name: Créer le dossier du site
ansible.builtin.file:
path: /var/www/rudeops
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: Écrire un fichier depuis une chaîne
ansible.builtin.copy:
content: "User-agent: *\nDisallow:\n"
dest: /var/www/rudeops/robots.txt
owner: www-data
group: www-data
mode: "0644"
file gère l'existence et les droits : state vaut notamment directory, file, link, touch ou absent. copy dépose un fichier tel quel, depuis le projet avec src: ou depuis une chaîne avec content:. template est son cousin qui passe d'abord le fichier dans Jinja2.
lineinfile et blockinfile
lineinfile gère une ligne, comme à l'étape 11. ansible.builtin.blockinfile gère un bloc de plusieurs lignes, délimité par deux marqueurs qu'il insère lui-même. Au-delà de quelques lignes, arrêtez de rapiécer et gérez le fichier entier avec template.
command et shell
command lance une commande sans passer par un shell : pas de pipe, pas de redirection, pas de joker comme *. Les variables d'environnement comme $HOME sont tout de même remplacées, par Ansible et non par un shell. shell passe par /bin/sh et accepte tout. Préférez command dès que possible, il n'y a alors aucune interprétation surprise des caractères spéciaux.
Les deux annoncent changed à chaque passage. creates: règle le problème quand la commande produit un fichier, comme à l'étape 12, et removes: fait l'inverse. Pour une commande qui ne fait que lire, changed_when: false dit à Ansible qu'elle ne change jamais rien.
Avant d'écrire command ou shell, cherchez un module. Il en existe pour les utilisateurs (user), les tâches cron (cron), les dépôts Git (git), les téléchargements (get_url), et ils sont idempotents d'office. uri, pour les appels HTTP, fait exception : il envoie sa requête à chaque passage. Pour a2enmod aussi, d'ailleurs : community.general.apache2_module. Le tutoriel s'en passe pour rester sur ansible-core.
ansible-doc - la documentation sans quitter le terminal
# Liste des modules installés
ansible-doc -l
# Documentation complète d'un module
ansible-doc ansible.builtin.apt
# Squelette de tâche avec tous les paramètres
ansible-doc -s ansible.builtin.service
Sur ansible-core 2.21.5, ansible-doc -l liste 71 modules. Dans la documentation d'un module, descendez directement à la section EXAMPLES : c'est presque toujours la réponse à votre question, sous forme de tâche prête à copier.
Variables et facts
Où déclarer une variable
Le tutoriel a mis ses variables dans les vars: du play. Ce n'est qu'un des endroits possibles, et c'est là que les choses se compliquent : Ansible définit plus de vingt niveaux de précédence. En pratique, retenez ceux-ci, du plus faible au plus fort :
| Où | Pour quoi |
|---|---|
roles/apache/defaults/main.yml | les valeurs par défaut d'un rôle, faites pour être surchargées |
group_vars/web.yml | ce qui vaut pour tout un groupe |
host_vars/web2.yml | ce qui est propre à une machine |
vars: du play | ce qui vaut pour ce play |
-e en ligne de commande | l'exception ponctuelle, qui gagne toujours |
group_vars/ et host_vars/ sont des dossiers à placer à côté de l'inventaire ou du playbook.
Le piège est la quatrième ligne : une variable déclarée dans les vars: du play écrase group_vars et host_vars. Avec Minimal dans les vars: du play et Major dans host_vars/web2.yml, web2 reçoit Minimal. C'est le cas du playbook de l'étape 13, et l'une des raisons de le transformer en rôle : dans les defaults d'un rôle, les valeurs deviennent surchargeables par groupe et par machine.
Les facts
ansible web1 -m ansible.builtin.setup -a 'filter=ansible_distribution*'
web1 | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "Debian",
"ansible_distribution_file_parsed": true,
"ansible_distribution_file_path": "/etc/os-release",
"ansible_distribution_file_variety": "Debian",
"ansible_distribution_major_version": "13",
"ansible_distribution_minor_version": "7",
"ansible_distribution_release": "trixie",
"ansible_distribution_version": "13.7",
"discovered_interpreter_python": "/usr/bin/python3.13"
},
"changed": false
}
Les facts, c'est ce que collecte la tâche Gathering Facts : distribution, version, interfaces réseau, mémoire, disques et bien d'autres. Sans filtre, setup en affiche plus de 600 lignes. Dans un playbook ou un template, on les lit dans ansible_facts en retirant le préfixe ansible_ : ansible_facts['distribution'] vaut Debian.
La collecte a un coût, qui se sent sur un grand parc. Un play qui n'utilise aucun fact peut s'en passer avec gather_facts: false.
Dans un template, vous avez accès à toutes les variables et à tous les facts, plus l'essentiel de Jinja2 : {% if %} et {% for %} pour la logique, {# ... #} pour les commentaires, et des filtres comme {{ port | default(80) }} ou {{ apache_modules | join(', ') }}. Le rendu se fait sur le nœud de contrôle : la cible n'a jamais besoin de Jinja2.
Boucles et conditions
loop plutôt que with_items
Dans les vieux playbooks et beaucoup de tutoriels, vous trouverez with_items: là où l'étape 12 utilise loop:. Les deux fonctionnent. loop est arrivé avec Ansible 2.5, en 2018, comme syntaxe plus simple, et la documentation le recommande dans la plupart des cas, mais with_items n'est pas déprécié. Une seule différence à connaître si vous convertissez : with_items aplatit d'un niveau les listes imbriquées, loop non. Il faut alors lui ajouter | flatten(1).
when - les conditions
- name: Installer apache2
ansible.builtin.apt:
name: apache2
state: present
when: ansible_facts['os_family'] == "Debian"
Ce when empêche la tâche de tenter un apt sur une machine de la famille Red Hat qui se serait glissée dans le groupe. Pas d'accolades dans un when : c'est déjà une expression Jinja2. Plusieurs conditions listées sous when: se combinent en and. Une tâche dont la condition est fausse s'affiche skipping et compte dans skipped au récapitulatif.
Les conditions les plus utiles portent sur les facts (os_family, distribution_major_version) et sur le résultat d'une tâche précédente, capturé avec register. On le verra dans la partie débogage.
Tester avant d'appliquer - dry run avec --check et --diff
L'étape 14 a montré le dry run dans son meilleur rôle : vérifier un parc déjà configuré. --check, ou -C, simule chaque tâche sans rien modifier, et --diff affiche la différence dans les fichiers. Lancez les deux avant chaque passage en production.
Ce que le check mode ne voit pas
Le dry run a une limite de fond : chaque tâche est simulée sur l'état actuel de la machine, sans les modifications que les tâches précédentes auraient apportées. Voici le playbook de l'étape 13 en check mode sur un troisième serveur, web3, tout neuf :
TASK [Installer apache2] *******************************************************
changed: [web3]
TASK [Démarrer apache2 et l'activer au boot] ***********************************
[...]
fatal: [web3]: FAILED! => {"changed": false, "msg": "Could not find the requested service apache2: host"}
Apache n'a pas été installé, puisqu'on simulait, donc le service n'existe pas et la tâche suivante échoue. Le playbook est correct, c'est la simulation qui ne peut pas aller plus loin. Le check mode sert à vérifier un serveur déjà configuré, pas à prévoir un premier déploiement.
Trois autres comportements à connaître :
- Le module
aptexigepython3-aptpour simuler. Sur ce mêmeweb3, qui ne l'avait pas, le premier essai a échoué dès la première tâche avecpython3-apt must be installed to use check mode, alors qu'un passage normal l'aurait installé tout seul. commandetshellsont sautés, puisqu'Ansible ne peut pas simuler une commande arbitraire : en ad hoc,ansible web1 -C -a "uptime"répondSKIPPED. Aveccreates:ouremoves:, ils annoncent ce qu'ils feraient d'après la présence du fichier.- Une tâche qui ne fait que lire peut s'exécuter malgré le check mode avec
check_mode: false. Utile pour unregisterdont dépend la suite.
Une précaution enfin : --diff affiche le contenu des fichiers modifiés, secrets compris. Sur une tâche qui dépose un fichier sensible, ajoutez diff: false.
Les autres filets de sécurité
| Option | Effet |
|---|---|
--syntax-check | vérifie la syntaxe sans se connecter à rien |
--list-hosts | affiche les machines visées |
--list-tasks | affiche les tâches qui seraient exécutées |
--limit web1 | n'agit que sur web1 |
--start-at-task "Masquer la version d'Apache" | reprend à partir d'une tâche donnée |
--step | demande confirmation avant chaque tâche |
--limit mérite qu'on insiste. Sur un parc en production, appliquer un changement à une seule machine, vérifier, puis l'appliquer au reste, c'est ce qui évite de casser tous les serveurs d'un coup.
Les rôles - ranger un playbook qui grossit
Un playbook de cinquante lignes se lit bien. À trois cents, avec ses templates, ses variables et ses handlers, il devient pénible à maintenir. Un rôle range tout ça dans une arborescence standard, réutilisable d'un projet à l'autre. Transformons le playbook du tutoriel.
Étape 16 : créer le squelette du rôle
Toujours depuis ~/ansible/apache :
ansible-galaxy role init roles/apache
- Role roles/apache was created successfully
La commande crée une arborescence complète :
| Dossier | Contenu |
|---|---|
tasks/main.yml | les tâches, point d'entrée du rôle |
handlers/main.yml | les handlers |
defaults/main.yml | les variables par défaut, de priorité la plus faible |
vars/main.yml | des variables de priorité haute, difficiles à surcharger |
templates/ | les templates Jinja2 |
files/ | les fichiers à copier tels quels |
meta/main.yml | les métadonnées du rôle, pour le publier sur Galaxy, et ses dépendances envers d'autres rôles |
tests/ | un inventaire et un playbook de test |
README.md | la documentation du rôle |
Déplacez le template dans le rôle, et supprimez ce qui ne sert pas ici :
mv templates/index.html.j2 roles/apache/templates/
rmdir templates
rm -r roles/apache/files roles/apache/vars roles/apache/tests roles/apache/meta
Supprimer meta/ a une raison précise : tel que généré, meta/main.yml contient des champs à remplir (your name, your company) et ne passe pas ansible-lint, qu'on lancera à l'étape 18. Vous le recréerez le jour où vous publierez le rôle ou lui donnerez des dépendances.
Étape 17 : déplacer le playbook dans le rôle
Le contenu de site.yml se répartit dans trois fichiers du rôle, sans les clés vars:, tasks: et handlers:, implicites, et donc avec quatre espaces d'indentation de moins. Remplacez le contenu des fichiers générés.
roles/apache/defaults/main.yml :
apache_server_tokens: Prod
apache_modules:
- headers
- rewrite
roles/apache/tasks/main.yml :
- name: Installer apache2
ansible.builtin.apt:
name: apache2
state: present
update_cache: true
cache_valid_time: 3600
- name: Démarrer apache2 et l'activer au boot
ansible.builtin.service:
name: apache2
state: started
enabled: true
- name: Déposer la page d'accueil
ansible.builtin.template:
src: index.html.j2
dest: /var/www/html/index.html
owner: www-data
group: www-data
mode: "0644"
- name: Masquer la version d'Apache
ansible.builtin.lineinfile:
path: /etc/apache2/conf-available/security.conf
regexp: '^ServerTokens '
line: "ServerTokens {{ apache_server_tokens }}"
notify: Recharger apache2
- name: Activer les modules Apache
ansible.builtin.command:
cmd: "a2enmod {{ item }}"
creates: "/etc/apache2/mods-enabled/{{ item }}.load"
loop: "{{ apache_modules }}"
notify: Redémarrer apache2
Une seule ligne change en dehors de l'indentation : src: index.html.j2, sans templates/. Dans un rôle, Ansible cherche tout seul dans le dossier templates/ du rôle.
roles/apache/handlers/main.yml :
- name: Recharger apache2
ansible.builtin.service:
name: apache2
state: reloaded
- name: Redémarrer apache2
ansible.builtin.service:
name: apache2
state: restarted
Et site.yml ne fait plus qu'appeler le rôle :
- name: Déployer Apache
hosts: web
become: true
roles:
- apache
Le projet ressemble maintenant à ceci :
~/ansible/apache/
├── ansible.cfg
├── inventory.ini
├── site.yml
└── roles/
└── apache/
├── README.md
├── defaults/main.yml
├── handlers/main.yml
├── tasks/main.yml
└── templates/index.html.j2
Relancez le playbook :
ansible-playbook site.yml
TASK [apache : Installer apache2] **********************************************
ok: [web2]
ok: [web1]
[...]
TASK [apache : Activer les modules Apache] *************************************
ok: [web1] => (item=headers)
ok: [web2] => (item=headers)
ok: [web2] => (item=rewrite)
ok: [web1] => (item=rewrite)
PLAY RECAP *********************************************************************
web1 : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
web2 : ok=6 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Chaque tâche est maintenant préfixée du nom de son rôle, et le récapitulatif affiche changed=0 : comme à l'étape 13, on a réorganisé sans rien changer sur les serveurs. Si un changed apparaît ici, une tâche a été mal recopiée.
Les variables ont quitté les vars: du play pour les defaults du rôle, ce qui règle le piège de précédence vu plus haut. Un group_vars/web.yml qui contient apache_server_tokens: Minimal, ou un host_vars/web2.yml avec Major, l'emportent désormais sur la valeur par défaut.
Étape 18 : vérifier avec ansible-lint
ansible-lint relit vos playbooks et vos rôles à la recherche d'erreurs et d'écarts aux bonnes pratiques. Il s'installe à part, comme Ansible :
pipx install ansible-lint
ansible-lint
Passed: 0 failure(s), 0 warning(s) in 6 files processed of 9 encountered. Last profile that met the validation criteria was 'production'.
production est le profil le plus exigeant : le projet du tutoriel passe tous les contrôles. Lancez-le avant chaque commit, puis enregistrez cette étape :
git add -A
git commit -m "Apache : passage en rôle"
Le rôle a aussi été rejoué sur des serveurs neufs en Debian 12 et 13 et en Ubuntu 22.04, 24.04 et 26.04 : même résultat partout, six changements au premier passage, aucun au second, et Server: Apache dans les en-têtes.
Rôles et collections de la communauté
Avant d'écrire un rôle, regardez s'il n'existe pas déjà sur Ansible Galaxy, le dépôt public de rôles et de collections. Pour un projet, listez vos dépendances dans un requirements.yml :
collections:
- name: community.general
roles:
- name: geerlingguy.apache
ansible-galaxy install -r requirements.yml
Un rôle de la communauté se lit avant de s'utiliser : il va tourner en root sur vos serveurs.
Les secrets avec Ansible Vault
Le jour où le playbook a besoin d'un mot de passe, la tentation est de l'écrire en clair dans group_vars/ et de commiter. Ansible Vault chiffre les variables sensibles en AES-256, pour qu'elles puissent vivre dans le dépôt Git avec le reste.
Chiffrer une variable
ansible-vault encrypt_string 'S3cr3t-Adm1n' --name 'apache_admin_password'
Ansible demande un mot de passe Vault, puis affiche :
apache_admin_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
63666364616661343437383432323963626535326333326238663531326632316637633366393462
3633323839653138356439643838616133396630643235630a646432373733653636636334373434
62666661303963633764353066643934636635613331396166323836653463306336336561616238
6363396230393738380a356464663435323161313735656630396563656631616638616165613964
3565
Collez ce bloc dans group_vars/web/vault.yml. group_vars/web peut en effet être un dossier plutôt qu'un fichier : Ansible charge tous les fichiers qu'il contient, ce qui permet de séparer un vars.yml en clair d'un vault.yml chiffré.
Pour chiffrer un fichier entier, ansible-vault create, edit, view, encrypt et decrypt font ce que leur nom indique.
Lancer un playbook qui utilise Vault
# Demander le mot de passe Vault
ansible-playbook site.yml --ask-vault-pass
# Ou le lire dans un fichier, hors du dépôt
ansible-playbook site.yml --vault-password-file ~/.ansible/vault-pass.txt
Sans mot de passe, l'erreur est explicite et contient toujours Attempting to decrypt but no vault secrets found.
Le fichier de mot de passe vit hors du dépôt Git et n'est lisible que par vous (chmod 600). S'il fuit, tous vos secrets fuient avec lui.
Le secret déchiffré, lui, s'affiche
Vault protège le secret au repos, pas à l'exécution. Sous ansible-core 2.21, une tâche debug qui utilise la variable l'affiche en clair, dans le terminal comme dans les logs de votre CI :
ok: [web1] => {
"msg": "Le mot de passe est S3cr3t-Adm1n"
}
Ajoutez no_log: true sur toute tâche qui manipule un secret : la tâche n'affiche plus que ok: [web1], et en mode verbeux un message indiquant que le résultat a été masqué. ansible-core 2.22 va plus loin : il masque automatiquement les secrets qu'il connaît, comme les valeurs déchiffrées par Vault, dans tout ce qu'il affiche. La même tâche, rejouée sur sa version candidate, donne "msg": "Le mot de passe est $REDACTED$". Gardez no_log sur les tâches sensibles en 2.22 aussi : sa documentation le recommande pour tout ce que le masquage ne sait pas reconnaître, comme un secret encodé ou transformé.
Débugger un playbook qui échoue
Lire l'erreur
ansible-core 2.21 affiche l'erreur avec sa position dans le playbook. Voici celle du check mode sur web3, vue plus haut, en entier :
[ERROR]: Task failed: Module failed: Could not find the requested service apache2: host
Origin: /home/vous/ansible/apache/site.yml:19:7
17 cache_valid_time: 3600
18
19 - name: Démarrer apache2 et l'activer au boot
^ column 7
La première ligne dit ce qui a échoué, Origin dit où, avec le numéro de ligne et un extrait du fichier. Lisez le message du module avant tout le reste : c'est lui qui contient la réponse, le plus souvent.
register et debug
- name: Tester la configuration Apache
ansible.builtin.command: apachectl configtest
register: configtest
changed_when: false
- name: Afficher le résultat
ansible.builtin.debug:
var: configtest.stderr
TASK [Afficher le résultat] ****************************************************
ok: [web1] => {
"configtest.stderr": "AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 192.0.2.11. Set the 'ServerName' directive globally to suppress this message\nSyntax OK"
}
register capture tout le résultat d'une tâche dans une variable : le code retour (rc), les sorties (stdout, stderr et leurs versions en liste, stdout_lines et stderr_lines), le statut changed. debug l'affiche. Pour voir tout ce qui est disponible, mettez var: configtest sans rien derrière.
Au passage, apachectl configtest écrit sur stderr, pas sur stdout. C'est le genre de détail que debug révèle en deux secondes, alors qu'on peut chercher longtemps pourquoi configtest.stdout est vide. Quant à l'avertissement AH00558, il est bénin : Apache signale qu'aucun ServerName global n'est défini. Bon candidat pour un lineinfile de plus.
changed_when et failed_when
changed_when: false déclare qu'une tâche ne change jamais rien, ce qui est le cas de toute commande de lecture. failed_when redéfinit l'échec :
- name: Tester la configuration Apache
ansible.builtin.command: apachectl configtest
register: configtest
changed_when: false
failed_when: "'Syntax OK' not in configtest.stderr"
Avec une ligne ServerTokens sans valeur ajoutée par erreur dans security.conf, la tâche échoue et le résultat dit pourquoi :
fatal: [web1]: FAILED! => {..., "rc": 1, ..., "stderr": "AH00526: Syntax error on line 59 of /etc/apache2/conf-enabled/security.conf:\nServerTokens takes one argument, Determine tokens displayed in the Server: header - Min(imal), Major, Minor, Prod(uctOnly), OS, or Full", ...}
Placée en fin de rôle, cette tâche fait office de garde-fou. Si elle échoue, le play s'arrête pour cette machine et les handlers ne partent pas : Apache continue de tourner avec l'ancienne configuration au lieu de tomber au redémarrage.
La verbosité
| Option | Ce qu'elle ajoute |
|---|---|
-v | le résultat détaillé de chaque tâche |
-vvv | les commandes SSH, les transferts de fichiers, la découverte de Python |
-vvv est celui qui sert : c'est lui qui montre, commande par commande, le cycle décrit au début de ce guide. Les niveaux suivants, jusqu'à -vvvvvv, ajoutent des détails internes. Pour déboguer la connexion SSH elle-même, ils ne suffisent plus depuis ansible-core 2.19 : la verbosité du client SSH se règle à part, par exemple avec ANSIBLE_SSH_VERBOSITY=3 ansible all -m ping -vvv.
Les erreurs classiques
| Message | Cause probable |
|---|---|
Host key verification failed | serveur jamais contacté ou réinstallé : ssh-keygen -R si besoin, puis une connexion à la main avec ssh |
Permission denied (publickey...) | clé absente du serveur, ou protégée par une passphrase et pas chargée dans l'agent |
Missing sudo password | le compte demande un mot de passe pour sudo : ajoutez -K |
Could not open lock file ... (13: Permission denied) | tâche système lancée sans become: true |
Could not find the requested service en check mode | la cascade du dry run sur une machine vierge |
python3-apt must be installed to use check mode | Debian ou Ubuntu minimale : faites d'abord un passage normal |
no vault secrets found | mot de passe Vault manquant |
Les bonnes pratiques en résumé
- Un dossier par projet, versionné dans Git, avec son
ansible.cfg: le comportement d'Ansible ne dépend plus de la machine qui le lance. - Jamais root en SSH. Un compte dédié, une clé, et une fois ce compte en place,
PermitRootLogin nodans la configuration de sshd. Le guide SSH détaille comment sécuriser sshd. - Une clé protégée par une passphrase, chargée dans
ssh-agent. - Des modules plutôt que
commandetshell, etcreates:ouchanged_when:quand on n'a pas le choix. - Des noms de tâche explicites et des noms complets de modules (
ansible.builtin.apt). - Deux passages : le second doit finir sur
changed=0, après une réorganisation aussi. - Vérifier le résultat, pas seulement le récapitulatif : un
curl, unsystemctl status. --check --diff, puis--limitsur une machine, puis tout le parc.- Les valeurs à surcharger dans les
defaultsd'un rôle, pas dans lesvars:du play. - Les secrets dans Vault, le mot de passe hors du dépôt,
no_log: truesur les tâches sensibles. ansible-lintavant chaque commit.
À retenir
| Commande | Pour quoi |
|---|---|
ssh deploy@192.0.2.11 sudo whoami | Vérifier l'accès avant de lancer Ansible |
ansible all -m ping | Tester connexion, authentification et Python |
ansible-inventory --graph | Voir l'inventaire tel qu'Ansible le comprend |
ansible web -b -m ansible.builtin.apt -a "name=htop" | Une action ponctuelle, sans playbook |
ansible-playbook --syntax-check site.yml | Vérifier la syntaxe |
ansible-playbook site.yml | Appliquer le playbook |
ansible-playbook site.yml --check --diff | Dry run avec les différences |
ansible-playbook site.yml --limit web1 | Une seule machine |
ansible-doc ansible.builtin.apt | Documentation et exemples d'un module |
ansible-galaxy role init roles/apache | Squelette de rôle |
ansible-lint | Vérifier les bonnes pratiques |
ansible-vault encrypt_string | Chiffrer une variable |
ansible-playbook site.yml -vvv | Voir ce qui se passe en SSH |
FAQ
Ansible est-il gratuit ?
Oui. ansible-core et le paquet ansible sont des logiciels libres sous licence GPL version 3 ou ultérieure, sans limite de machines. Red Hat vend autour une offre commerciale, Red Hat Ansible Automation Platform, avec interface web, gestion des droits et support. Rien de ce que décrit ce guide n'en a besoin.
Faut-il installer un agent sur les serveurs ?
Non. Ansible se connecte en SSH et n'a besoin que de Python sur la cible, de 3.9 à 3.14 pour ansible-core 2.21, et de sudo pour les tâches qui demandent root. Les fichiers qu'il dépose pour exécuter une tâche sont supprimés à la fin de celle-ci, et rien ne tourne sur le serveur entre deux exécutions.
Quelle différence entre ansible et ansible-core ?
ansible-core est le moteur, avec 71 modules de base. ansible est un paquet plus large qui ajoute 92 collections, pour près de 8 900 modules au total : clouds, réseau, Kubernetes, bases de données. Chacun a sa numérotation : ansible 14 s'appuie sur ansible-core 2.21. Pour gérer des serveurs Linux, ansible-core suffit, et vous ajoutez les collections au besoin avec ansible-galaxy collection install.
Comment faire un dry run avec Ansible ?
ansible-playbook site.yml --check --diff. --check simule chaque tâche sans rien modifier, --diff montre les différences dans les fichiers. Deux limites : le check mode ne voit pas les effets d'une tâche sur les suivantes, et il saute command et shell sauf s'ils ont creates: ou removes:. Il est fiable pour vérifier un serveur déjà configuré, beaucoup moins pour prévoir un premier déploiement.
Comment redémarrer un service avec Ansible ?
Ponctuellement, en ad hoc : ansible web -b -m ansible.builtin.service -a "name=apache2 state=restarted". Dans un playbook, avec un handler : la tâche qui modifie la configuration le notifie, et il ne s'exécute que si elle a vraiment changé quelque chose. Une tâche state: restarted en dur redémarre le service à chaque passage. Préférez state: reloaded quand le service sait relire sa configuration sans couper les connexions.
Quelle différence entre command et shell ?
command exécute un programme directement, sans shell : pas de pipe, de redirection ni de joker, même si les variables d'environnement comme $HOME sont remplacées par Ansible. shell passe par /bin/sh et accepte tout. Préférez command quand c'est possible, et dans les deux cas rendez la tâche idempotente avec creates:, removes: ou changed_when:. Avant d'utiliser l'un ou l'autre, vérifiez qu'aucun module ne fait déjà le travail.
with_items ou loop ?
loop pour tout nouveau playbook : c'est la syntaxe que la documentation recommande depuis Ansible 2.5. with_items reste valide et n'est pas déprécié. Attention en convertissant : with_items aplatit d'un niveau les listes imbriquées, loop non, et il faut alors ajouter | flatten(1).
Ansible ou Terraform ?
Les deux, le plus souvent, car ils ne font pas le même travail. Terraform, ou son fork libre OpenTofu, crée l'infrastructure (machines virtuelles, réseaux, DNS) et garde un état de ce qu'il a créé. Ansible configure l'intérieur des machines et relit leur état réel à chaque passage. L'enchaînement classique : OpenTofu ou Terraform crée les serveurs, Ansible les configure. La section IaC et gestion de configuration détaille la frontière.
Ansible fonctionne-t-il sous Windows ?
Comme nœud de contrôle, non : Ansible ne tourne pas nativement sous Windows. La documentation propose WSL ou un conteneur, en précisant que WSL n'est pas officiellement supporté et ne convient pas à la production. Comme nœud géré, oui : Ansible pilote des serveurs Windows par WinRM ou SSH, avec les modules PowerShell de la collection ansible.windows.
Qu'est-ce que le fichier site.yml ?
Le playbook principal d'un projet Ansible, par convention : celui qui configure l'ensemble du parc, souvent en appelant un rôle ou un playbook par type de serveur. Le nom vient des exemples de la documentation officielle. Rien ne l'impose, ansible-playbook accepte n'importe quel nom de fichier.
Pour aller plus loin
- Documentation d'Ansible - la référence, avec la documentation et les exemples de chaque module
- Ansible Galaxy - les rôles et collections de la communauté
- Ansible for DevOps - le livre de Jeff Geerling, auteur de nombreux rôles de Galaxy dont
geerlingguy.apache - OpenTofu - le fork libre de Terraform, pour la partie provisionnement
- Infrastructure as Code, de Kief Morris - la vue d'ensemble, dans notre sélection de livres
- Le guide SSH - les clés, l'agent,
~/.ssh/configet la sécurisation de sshd - Le guide systemd - ce que pilotent les modules
serviceetsystemd_service