Sans agent
Rien à installer sur les machines gérées : un accès SSH et Python suffisent. On peut commencer à l’utiliser tout de suite sur un parc existant.
Installer des paquets, créer des utilisateurs, déposer des fichiers de configuration, redémarrer des services… Sur un serveur, on peut le faire à la main. Sur dix ou cent serveurs, c’est long, répétitif et source d’erreurs.
Ansible permet de décrire l’état souhaité de ses machines dans des fichiers texte, puis de l’appliquer automatiquement, autant de fois que nécessaire. Cette fiche présente les notions essentielles pour écrire et lancer ses premiers playbooks.
Ansible est un outil open source d’automatisation et de gestion de configuration, maintenu par Red Hat. Il se connecte aux machines en SSH, sans agent à installer, et exécute des tâches décrites en YAML. Ses tâches sont idempotentes : les relancer ne change rien si la machine est déjà dans l’état voulu.
Sans agent
Rien à installer sur les machines gérées : un accès SSH et Python suffisent. On peut commencer à l’utiliser tout de suite sur un parc existant.
Lisible
Les playbooks sont écrits en YAML. Ils se lisent presque comme une liste de consignes, et servent aussi de documentation de l’infrastructure.
Idempotent
On décrit un état (« nginx est installé ») plutôt qu’une action (« installer nginx »). Relancer un playbook est sans danger.
Versionnable
Tout tient dans des fichiers texte : on les range dans Git, on les relit en merge request et on garde l’historique des changements.
C’est ce qu’on appelle l’Infrastructure as Code (IaC) : la configuration de l’infrastructure est décrite dans du code, plutôt que dans des manipulations manuelles dont personne ne garde la trace.
Nœud de contrôle
La machine où Ansible est installé et d’où on lance les commandes : ton poste, ou un serveur dédié. Elle doit tourner sous Linux ou macOS (sous Windows, on passe par WSL).
Nœuds gérés
Les machines que l’on configure. Elles n’ont besoin que d’un accès SSH et de Python. Ansible y envoie de petits programmes (les modules), les exécute, puis les supprime.
Le vocabulaire de base :
| Terme | Définition |
|---|---|
| Inventaire | La liste des machines gérées, rangées en groupes (web, bdd…). |
| Module | Une unité de travail réutilisable : installer un paquet, copier un fichier, gérer un service… Ansible en fournit des milliers. |
| Tâche | L’appel d’un module avec ses paramètres. |
| Play | Un ensemble de tâches appliquées à un groupe de machines. |
| Playbook | Un fichier YAML qui contient un ou plusieurs plays. |
| Rôle | Un paquet réutilisable de tâches, fichiers, templates et variables. |
| Collection | Un format de distribution qui regroupe des modules, des rôles et des plugins. |
Ansible s’installe uniquement sur le nœud de contrôle. La méthode recommandée passe par pipx, qui l’isole dans son propre environnement Python :
pipx install --include-deps ansibleansible --versionIl existe aussi des paquets pour la plupart des distributions (sudo apt install ansible sur Debian/Ubuntu), mais leur version est souvent plus ancienne.
L’inventaire indique à Ansible quelles machines gérer et comment s’y connecter. Il peut être écrit en format INI ou en YAML :
all: children: web: hosts: web1.exemple.fr: web2.exemple.fr: bdd: hosts: bdd1.exemple.fr: ansible_host: 192.168.1.20 ansible_user: admin production: children: web: bdd:[web]web1.exemple.frweb2.exemple.fr
[bdd]bdd1.exemple.fr ansible_host=192.168.1.20 ansible_user=admin
[production:children]webbddall (toutes les machines) et ungrouped (celles qui ne sont dans aucun groupe).ansible_host, ansible_user ou ansible_port précisent la connexion quand le nom ne suffit pas.production regroupe web et bdd (avec children en YAML, :children en INI). Lancer une commande sur production la lance sur les trois machines.Pour vérifier ce qu’Ansible comprend de l’inventaire :
ansible-inventory -i inventory.yml --graphUne commande ad hoc exécute un seul module, directement depuis le terminal, sans écrire de playbook. C’est pratique pour une vérification ou une action ponctuelle.
# Vérifier qu'Ansible peut joindre toutes les machinesansible all -i inventory.yml -m ansible.builtin.ping
# Afficher l'espace disque des serveurs webansible web -i inventory.yml -m ansible.builtin.command -a "df -h"
# Installer un paquet avec les droits administrateur (-b pour become)ansible web -i inventory.yml -b -m ansible.builtin.apt -a "name=htop state=present"La structure est toujours la même : ansible <groupe> -m <module> -a "<arguments>".
Pour tout ce qui dépasse une action ponctuelle, on écrit un playbook. Voici un premier exemple, qui installe et démarre nginx sur les serveurs web :
- name: Configurer les serveurs web hosts: web become: true
tasks: - name: Installer nginx ansible.builtin.apt: name: nginx state: present update_cache: true
- name: Démarrer nginx et l'activer au démarrage ansible.builtin.service: name: nginx state: started enabled: truehosts : le groupe de l’inventaire sur lequel s’applique le play.become: true : les tâches s’exécutent avec les droits administrateur (via sudo par défaut).tasks : la liste des tâches, exécutées dans l’ordre, sur toutes les machines du groupe en parallèle.name : une description lisible, affichée pendant l’exécution. Toujours en mettre une.On le lance avec :
ansible-playbook -i inventory.yml site.ymlÀ la fin de l’exécution, Ansible affiche un récapitulatif par machine :
PLAY RECAP *********************************************************web1.exemple.fr : ok=3 changed=2 unreachable=0 failed=0 skipped=0web2.exemple.fr : ok=3 changed=0 unreachable=0 failed=0 skipped=0| Statut | Signification |
|---|---|
| ok | La tâche s’est bien passée, la machine était déjà dans l’état voulu. |
| changed | Ansible a dû modifier la machine pour atteindre l’état voulu. |
| failed | La tâche a échoué. Par défaut, Ansible arrête de traiter cette machine. |
| unreachable | Ansible n’a pas pu se connecter à la machine. |
| skipped | La tâche a été ignorée, car sa condition n’était pas remplie. |
Si on relance le playbook sans rien modifier, toutes les tâches doivent être en ok et aucune en changed : c’est le signe que le playbook est bien idempotent.
| Module | Usage |
|---|---|
ansible.builtin.apt / ansible.builtin.dnf |
Gérer les paquets (Debian/Ubuntu, Red Hat/Fedora) |
ansible.builtin.package |
Gérer les paquets, quel que soit le gestionnaire du système |
ansible.builtin.service |
Démarrer, arrêter, activer un service |
ansible.builtin.copy |
Copier un fichier vers la machine gérée |
ansible.builtin.template |
Générer un fichier à partir d’un modèle Jinja2 |
ansible.builtin.file |
Créer un dossier, un lien, changer des droits |
ansible.builtin.lineinfile |
Ajouter ou modifier une ligne dans un fichier |
ansible.builtin.user |
Gérer les comptes utilisateurs |
ansible.builtin.command / ansible.builtin.shell |
Exécuter une commande (en dernier recours) |
ansible.builtin.debug |
Afficher un message ou une variable |
La documentation de chaque module est disponible directement dans le terminal :
ansible-doc ansible.builtin.aptLes noms de modules comme ansible.builtin.apt sont des noms complets (FQCN, Fully Qualified Collection Name) : ansible.builtin est la collection, apt le module. On peut souvent écrire seulement apt, mais le nom complet évite toute ambiguïté et c’est la pratique recommandée.
Les variables évitent de répéter des valeurs en dur et permettent d’adapter un même playbook à plusieurs environnements.
- name: Configurer les serveurs web hosts: web become: true vars: paquets: - nginx - curl - htop
tasks: - name: Installer les paquets ansible.builtin.apt: name: "{{ paquets }}" state: presentOn appelle une variable avec la syntaxe Jinja2 {{ nom_de_la_variable }}. Quand une valeur YAML commence par {{, il faut l’entourer de guillemets.
Plutôt que de tout mettre dans le playbook, on range généralement les variables dans des dossiers dédiés, à côté de l’inventaire :
Ansible les charge automatiquement. Quand une même variable est définie à plusieurs endroits, c’est la définition la plus précise qui l’emporte : host_vars passe avant group_vars/web.yml, qui passe avant group_vars/all.yml. Les variables passées en ligne de commande avec -e l’emportent sur tout le reste.
Au début de chaque play, Ansible collecte automatiquement des informations sur chaque machine : système d’exploitation, adresses IP, mémoire, nombre de processeurs… Ce sont les facts, accessibles dans la variable ansible_facts.
- name: Afficher la distribution ansible.builtin.debug: msg: "Cette machine tourne sous {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}"Pour voir tous les facts d’une machine :
ansible web1.exemple.fr -i inventory.yml -m ansible.builtin.setupwhen n’exécute une tâche que si une condition est vraie. Pas besoin de {{ }} : la condition est déjà une expression Jinja2.
- name: Installer nginx sur Debian et Ubuntu ansible.builtin.apt: name: nginx state: present when: ansible_facts['os_family'] == "Debian"loop répète une tâche pour chaque élément d’une liste. L’élément en cours est disponible dans la variable item.
- name: Créer les comptes de l'équipe ansible.builtin.user: name: "{{ item }}" groups: sudo append: true loop: - alice - bobLe module template génère un fichier de configuration à partir d’un modèle Jinja2 (extension .j2), en remplaçant les variables par leurs valeurs pour chaque machine.
server { listen {{ nginx_port }}; server_name {{ inventory_hostname }}; root /var/www/{{ nom_du_site }};
{% if activer_cache %} location ~* \.(css|js|jpg|png|webp)$ { expires 30d; } {% endif %}}- name: Déposer la configuration du site ansible.builtin.template: src: templates/site.conf.j2 dest: /etc/nginx/sites-available/site.conf mode: "0644"Jinja2 permet aussi des conditions ({% if %}), des boucles ({% for %}) et des filtres ({{ nom | upper }}, {{ port | default(80) }}).
Certaines actions ne doivent avoir lieu que si quelque chose a changé : on ne redémarre nginx que si sa configuration a été modifiée. C’est le rôle des handlers.
tasks: - name: Déposer la configuration du site ansible.builtin.template: src: templates/site.conf.j2 dest: /etc/nginx/sites-available/site.conf notify: Recharger nginx
handlers: - name: Recharger nginx ansible.builtin.service: name: nginx state: reloadednotify, en donnant son nom exact.Quand un playbook grossit, on le découpe en rôles : chaque rôle regroupe tout ce qu’il faut pour une fonction (installer nginx, configurer une base de données, sécuriser SSH…) et peut être réutilisé d’un projet à l’autre.
Un rôle a une structure de dossiers standard, que l’on peut créer avec :
ansible-galaxy role init roles/nginxDans chaque dossier, Ansible charge automatiquement le fichier main.yml. Le playbook principal devient alors très court :
- name: Configurer les serveurs web hosts: web become: true roles: - nginxAnsible Galaxy est le catalogue public de rôles et de collections partagés par la communauté. Avant d’écrire un rôle, il vaut la peine de vérifier s’il n’existe pas déjà.
# Installer une collectionansible-galaxy collection install community.general
# Installer toutes les dépendances d'un projetansible-galaxy install -r requirements.ymlcollections: - name: community.general - name: community.dockerLes mots de passe, clés d’API et autres secrets ne doivent jamais être écrits en clair dans un dépôt Git. Ansible Vault chiffre les fichiers de variables sensibles.
# Créer un fichier chiffréansible-vault create group_vars/bdd/vault.yml
# Chiffrer un fichier existantansible-vault encrypt group_vars/bdd/vault.yml
# Modifier un fichier chiffréansible-vault edit group_vars/bdd/vault.yml
# Lancer un playbook qui utilise des fichiers chiffrésansible-playbook -i inventory.yml site.yml --ask-vault-passLe fichier chiffré peut être versionné sans risque : sans le mot de passe du vault, son contenu est illisible.
Pour mettre tout ça en pratique, voici un petit projet qui installe nginx, dépose une configuration générée à partir d’un template et publie une page d’accueil.
Configurer Ansible pour le projet, afin de ne pas répéter l’inventaire à chaque commande :
[defaults]inventory = inventory.ymlDéfinir les valeurs par défaut du rôle :
nginx_port: 80nom_du_site: mon-sitemessage_accueil: "Bonjour !"Personnaliser pour le groupe web :
nom_du_site: je-suis-lamessage_accueil: "Ce serveur a été configuré par Ansible."Écrire les tâches du rôle :
- name: Installer nginx ansible.builtin.apt: name: nginx state: present update_cache: true
- name: Créer le dossier du site ansible.builtin.file: path: "/var/www/{{ nom_du_site }}" state: directory mode: "0755"
- name: Publier la page d'accueil ansible.builtin.template: src: index.html.j2 dest: "/var/www/{{ nom_du_site }}/index.html" mode: "0644"
- name: Déposer la configuration du site ansible.builtin.template: src: site.conf.j2 dest: /etc/nginx/sites-available/{{ nom_du_site }}.conf mode: "0644" notify: Recharger nginx
- name: Activer le site ansible.builtin.file: src: /etc/nginx/sites-available/{{ nom_du_site }}.conf dest: /etc/nginx/sites-enabled/{{ nom_du_site }}.conf state: link notify: Recharger nginx
- name: Désactiver le site par défaut ansible.builtin.file: path: /etc/nginx/sites-enabled/default state: absent notify: Recharger nginx
- name: Démarrer nginx ansible.builtin.service: name: nginx state: started enabled: trueÉcrire le handler :
- name: Recharger nginx ansible.builtin.service: name: nginx state: reloadedÉcrire les templates :
server { listen {{ nginx_port }}; server_name {{ inventory_hostname }}; root /var/www/{{ nom_du_site }}; index index.html;}<!doctype html><html lang="fr"> <head><meta charset="utf-8"><title>{{ nom_du_site }}</title></head> <body> <h1>{{ message_accueil }}</h1> <p>Machine : {{ inventory_hostname }}</p> </body></html>Assembler le playbook :
- name: Configurer les serveurs web hosts: web become: true roles: - nginxTester, puis lancer :
ansible-playbook site.yml --check --diffansible-playbook site.ymlEn relançant le playbook une seconde fois, toutes les tâches doivent être en ok : rien n’a changé, donc nginx n’est pas rechargé.
Voici les principales. La documentation d’Ansible les détaille dans son guide Tips and tricks (en anglais).
Toujours nommer les tâches, pour que la sortie d’Ansible se lise comme un journal.
Préférer les modules dédiés à command et shell, pour garder l’idempotence.
Utiliser les noms complets des modules (ansible.builtin.copy plutôt que copy).
Tester avec --check --diff avant de toucher à la production, et relancer une seconde fois pour vérifier qu’aucune tâche n’est en changed.
Ne jamais versionner de secret en clair : utiliser Ansible Vault.
Découper en rôles dès que le playbook dépasse quelques dizaines de lignes.
Vérifier son code avec ansible-lint, qui repère les erreurs et les mauvaises pratiques :
pipx install ansible-lintansible-lint site.yml| Commande | Usage |
|---|---|
ansible all -m ansible.builtin.ping |
Tester la connexion à toutes les machines |
ansible-inventory --graph |
Afficher l’inventaire sous forme d’arbre |
ansible-playbook site.yml |
Lancer un playbook |
ansible-playbook site.yml --check --diff |
Simuler l’exécution et afficher les différences |
ansible-playbook site.yml --limit web1.exemple.fr |
Limiter l’exécution à certaines machines |
ansible-playbook site.yml -e "nginx_port=8080" |
Passer une variable en ligne de commande |
ansible-playbook site.yml -v (jusqu’à -vvvv) |
Afficher plus de détails pour déboguer |
ansible-doc <module> |
Lire la documentation d’un module |
ansible-galaxy role init <nom> |
Créer la structure d’un rôle |
ansible-vault edit <fichier> |
Modifier un fichier chiffré |