Ansible
Ansible est un outil open source d'automatisation informatique développé par Red Hat. Il permet de gérer la configuration, le déploiement d'applications et l'orchestration de plusieurs machines à partir d'une machine de contrôle unique, sans avoir besoin d'installer d'agent sur les hôtes gérés.
Source : Documentation officielle
Qu'est-ce qu'Ansible ?
Ansible répond à un problème classique : maintenir un parc de serveurs dans un état cohérent et reproductible, sans exécuter manuellement les mêmes commandes machine par machine. Il repose sur quelques principes structurants :
- Agentless : rien à installer sur les machines gérées. La communication passe par SSH (Linux/Unix) ou WinRM (Windows) ; seul Python doit être présent côté cible.
- Déclaratif : on décrit l'état souhaité (« nginx doit être installé et démarré »), pas la suite d'étapes pour y arriver.
- Idempotent : rejouer un playbook plusieurs fois donne toujours le même résultat. Si l'état voulu est déjà là, Ansible ne fait rien (
ok, paschanged). - YAML : les playbooks sont écrits en YAML, un format simple à lire, même sans expérience de script.
- Push-based : c'est la machine de contrôle qui pousse la configuration vers les hôtes, contrairement à un modèle pull (Puppet par exemple).
Cas d'usage typiques
- Provisioning de serveurs (paquets, utilisateurs, fichiers de configuration).
- Déploiement d'applications (mise à jour de code, redémarrage de services).
- Orchestration multi-tiers (base de données puis API puis frontend, dans l'ordre).
- Durcissement et conformité, appliqués de façon identique sur tout un parc.
Installation
Ansible s'installe uniquement sur la machine de contrôle (celle depuis laquelle on pilote les autres). Les machines gérées n'ont besoin que de Python et d'un accès SSH.
# Debian/Ubuntu
sudo apt update
sudo apt install ansible
# Fedora/RHEL/Alma
sudo dnf install ansible
# Arch
sudo pacman -S ansible
# Via pip (dans un environnement virtuel, recommandé pour maîtriser la version)
python3 -m venv ~/.venvs/ansible
source ~/.venvs/ansible/bin/activate
pip install ansible# Vérifier l'installation et la version
ansible --versionConcepts clés
| Concept | Description |
|---|---|
| Control node | La machine depuis laquelle Ansible est exécuté. |
| Managed node | Une machine gérée par Ansible (aussi appelée host). |
| Inventory | La liste des machines gérées, organisées en groupes. |
| Module | Une unité de code réutilisable exécutant une action précise (apt, copy, service, user...). |
| Task | L'appel d'un module avec des paramètres, au sein d'un playbook. |
| Playbook | Un fichier YAML décrivant un ensemble de tâches à appliquer sur des hôtes. |
| Play | Une section d'un playbook associant un groupe d'hôtes à une liste de tâches. |
| Role | Une structure de répertoires standardisée pour organiser et réutiliser des tâches, variables, templates et handlers. |
| Fact | Une information collectée automatiquement sur un hôte (OS, IP, mémoire...) via le module setup. |
| Handler | Une tâche déclenchée uniquement par une notification (notify), typiquement pour redémarrer un service après un changement de configuration. |
| Collection | Un format de distribution regroupant modules, plugins, rôles et playbooks (ex. community.general, ansible.posix). |
Bonnes pratiques
- Structurer avec des rôles dès que le playbook dépasse quelques tâches. Un rôle isole une responsabilité (
webserver,database,firewall) et se réutilise d'un projet à l'autre. - Ne jamais coder en dur ce qui peut varier (IP, chemins, versions) : utiliser des variables (
group_vars/,host_vars/,defaults/main.yml). - Privilégier les modules déclaratifs (
apt,copy,template,service...) plutôt queshell/command, non idempotents par nature et à encadrer (creates,when,changed_when) si on doit vraiment les utiliser. - Chiffrer les secrets avec Ansible Vault, jamais de mot de passe ou de clé en clair dans un dépôt Git.
ansible-vault encrypt group_vars/prod/vault.yml
ansible-playbook site.yml --ask-vault-pass- Toujours tester avant de jouer en production :
ansible-playbook site.yml --syntax-checkpour valider la syntaxe YAML.ansible-playbook site.yml --check(dry-run) pour simuler sans appliquer.ansible-playbook site.yml --diffpour visualiser les changements de fichiers.ansible-lintpour détecter les mauvaises pratiques.
- Limiter le blast radius avec
--limitetserialpour un déploiement progressif (rolling update) plutôt que de tout jouer d'un coup sur un parc entier. - Versionner l'inventaire et les playbooks dans Git, comme n'importe quel code source (voir IaC).
- Nommer explicitement chaque tâche (
name:) : c'est ce qui s'affiche à l'exécution et rend les logs et les erreurs lisibles. - Séparer les environnements (dev/staging/prod) via des inventaires distincts plutôt qu'avec des conditions dans les playbooks.
- Épingler les versions des collections et rôles externes (
requirements.yml) pour des exécutions reproductibles.
Créer son premier playbook
Un playbook minimal se compose d'un fichier YAML décrivant un ou plusieurs plays.
Arborescence de départ recommandée :
mon-projet-ansible/
├── ansible.cfg
├── inventory.ini
└── playbook.ymlansible.cfg (configuration locale au projet, évite de dépendre de la config globale de la machine) :
[defaults]
inventory = inventory.ini
host_key_checking = False
retry_files_enabled = Falseinventory.ini (ici, uniquement la machine locale pour commencer) :
[local]
localhost ansible_connection=localplaybook.yml :
---
- name: Mon premier playbook
hosts: local
become: false
tasks:
- name: Afficher un message
debug:
msg: "Ansible fonctionne correctement !"
- name: Créer un fichier de test
copy:
content: "Bonjour depuis Ansible\n"
dest: /tmp/ansible-test.txtExécution :
ansible-playbook playbook.ymlPoints clés de ce premier exemple :
hosts:cible le groupe (ou l'hôte) défini dans l'inventaire.tasks:liste les actions à exécuter, dans l'ordre, sur chaque hôte du groupe.- Chaque tâche appelle un module (
debug,copy) avec ses paramètres. - Rejouer ce playbook une seconde fois : la tâche
debugresteok, et la tâchecopyaussi si le contenu du fichier n'a pas changé c'est l'idempotence en action.
Créer un playbook de déploiement de serveur
Exemple concret et complet : déploiement d'un serveur web Nginx, structuré en rôle pour être réutilisable et maintenable.
Arborescence
mon-projet-ansible/
├── ansible.cfg
├── inventory.ini
├── site.yml
└── roles/
└── webserver/
├── defaults/
│ └── main.yml
├── tasks/
│ └── main.yml
├── handlers/
│ └── main.yml
└── templates/
└── nginx.conf.j2roles/webserver/defaults/main.yml
webserver_package: nginx
webserver_port: 80
webserver_document_root: /var/www/mon-siteroles/webserver/tasks/main.yml
---
- name: Installer Nginx
apt:
name: "{{ webserver_package }}"
state: present
update_cache: true
- name: Créer le répertoire du site
file:
path: "{{ webserver_document_root }}"
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: Déployer une page d'accueil basique
copy:
content: "<h1>Déployé avec Ansible</h1>\n"
dest: "{{ webserver_document_root }}/index.html"
- name: Déployer la configuration Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/mon-site.conf
owner: root
group: root
mode: "0644"
notify: Recharger Nginx
- name: Activer le site
file:
src: /etc/nginx/sites-available/mon-site.conf
dest: /etc/nginx/sites-enabled/mon-site.conf
state: link
notify: Recharger Nginx
- name: S'assurer que le service est démarré et activé au boot
service:
name: nginx
state: started
enabled: trueroles/webserver/handlers/main.yml
---
- name: Recharger Nginx
service:
name: nginx
state: reloadedroles/webserver/templates/nginx.conf.j2
server {
listen {{ webserver_port }};
server_name _;
root {{ webserver_document_root }};
index index.html;
}site.yml (playbook principal)
---
- name: Déployer un serveur web
hosts: webservers
become: true
roles:
- webserverLe handler Recharger Nginx n'est déclenché que si la configuration ou le lien symbolique changent réellement s'il n'y a rien à modifier, aucun rechargement inutile n'a lieu, conformément au principe d'idempotence.
Configurations de playbook
Un play et une tâche acceptent tous les deux un ensemble de clés de configuration qui changent leur comportement (sur qui elles s'exécutent, dans quelles conditions, ce qu'elles déclenchent...). Voici les plus utilisées.
Clés au niveau d'un play
| Clé | Rôle |
|---|---|
hosts | Groupe ou hôte(s) ciblés (défini dans l'inventaire). |
become / become_user | Élève les privilèges (sudo) pour exécuter les tâches, en tant qu'utilisateur donné (root par défaut). |
gather_facts | Collecte (ou non) les facts de l'hôte en début de play. À mettre à false si inutile, pour gagner du temps. |
vars / vars_files | Variables déclarées en ligne ou importées depuis un fichier externe. |
tags | Étiquette(s) permettant de jouer ou d'exclure une partie du playbook (--tags, --skip-tags). |
serial | Nombre (ou pourcentage) d'hôtes traités simultanément, pour un déploiement progressif. |
handlers | Tâches déclenchées en fin de play par un notify, une seule fois même si plusieurs tâches l'ont notifié. |
Clés au niveau d'une tâche
| Clé | Rôle |
|---|---|
when | Condition d'exécution (when: ansible_facts['os_family'] == "Debian"). |
loop | Répète la tâche pour chaque élément d'une liste. |
register | Stocke le résultat de la tâche dans une variable, réutilisable ensuite. |
changed_when / failed_when | Redéfinit ce qui compte comme un changement ou un échec, utile avec command/shell. |
notify | Déclenche un ou plusieurs handlers si la tâche modifie l'état du système. |
delegate_to | Exécute la tâche sur un autre hôte que celui ciblé par le play (ex. mettre à jour un load-balancer). |
run_once | N'exécute la tâche que sur le premier hôte du groupe, même si le play en cible plusieurs. |
ignore_errors | Poursuit l'exécution du playbook même si la tâche échoue. |
Exemple combiné : un rôle de provisioning
L'exemple suivant illustre ces options sur un cas concret : un rôle base, appliqué à tout le parc, qui installe Docker (avec son dépôt officiel), déploie un message du jour personnalisé et durcit la configuration de sshd. Chaque partie est isolée dans son propre fichier de tâches et taguée, pour pouvoir être jouée indépendamment.
roles/base/
├── tasks/
│ ├── main.yml
│ ├── docker.yml
│ ├── motd.yml
│ └── sshd.yml
├── templates/
│ └── 99-custom-motd.sh.j2
└── handlers/
└── main.ymlroles/base/tasks/main.yml :
---
- name: Installer Docker
import_tasks: docker.yml
tags: [docker]
- name: Déployer le MOTD personnalisé
import_tasks: motd.yml
tags: [motd]
- name: Durcir la configuration sshd
import_tasks: sshd.yml
tags: [sshd]Installer Docker en ajoutant son dépôt officiel
roles/base/tasks/docker.yml :
---
- name: Installer les prérequis
apt:
name:
- ca-certificates
- curl
- gnupg
state: present
update_cache: true
- name: Créer le dossier des clés APT
file:
path: /etc/apt/keyrings
state: directory
mode: "0755"
- name: Télécharger la clé GPG officielle Docker
get_url:
url: "https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}/gpg"
dest: /etc/apt/keyrings/docker.asc
mode: "0644"
- name: Ajouter le dépôt APT Docker
apt_repository:
repo: >-
deb [arch={{ ansible_facts['architecture'] }} signed-by=/etc/apt/keyrings/docker.asc]
https://download.docker.com/linux/{{ ansible_facts['distribution'] | lower }}
{{ ansible_facts['distribution_release'] }} stable
state: present
filename: docker
- name: Installer Docker Engine
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: true
- name: Ajouter l'utilisateur courant au groupe docker
user:
name: "{{ ansible_user }}"
groups: docker
append: true
- name: Démarrer et activer Docker
service:
name: docker
state: started
enabled: trueLe dépôt est ajouté via apt_repository, en pointant vers la clé GPG téléchargée (signed-by), à la manière de la procédure officielle d'installation de Docker sur Debian/Ubuntu.
Déployer un message du jour (MOTD) personnalisé
Sur Debian/Ubuntu, chaque script exécutable placé dans /etc/update-motd.d/ est exécuté à la connexion SSH, dans l'ordre numérique, et sa sortie est affichée. On y dépose un script shell (.sh) rendu exécutable par Ansible.
roles/base/templates/99-custom-motd.sh.j2 :
#!/bin/sh
echo ""
echo "Bienvenue sur {{ inventory_hostname }}"
echo "Système : $(lsb_release -ds 2>/dev/null || cat /etc/os-release | grep PRETTY_NAME | cut -d= -f2)"
echo "Uptime : $(uptime -p)"
echo "Géré par : Ansible - ne pas modifier manuellement"
echo ""roles/base/tasks/motd.yml :
---
- name: Déployer le script de MOTD personnalisé
template:
src: 99-custom-motd.sh.j2
dest: /etc/update-motd.d/99-custom
owner: root
group: root
mode: "0755"
- name: Désactiver le MOTD statique par défaut
copy:
content: ""
dest: /etc/motdComme le fichier est un template Jinja (.j2), est remplacé par le nom réel de chaque hôte à chaque exécution le message reste personnalisé même joué sur plusieurs machines à la fois.
Modifier la configuration de sshd
roles/base/tasks/sshd.yml :
---
- name: Désactiver la connexion SSH en root
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
notify: Redémarrer sshd
- name: Désactiver l'authentification par mot de passe
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
notify: Redémarrer sshd
- name: Limiter les tentatives d'authentification
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?MaxAuthTries'
line: 'MaxAuthTries 3'
notify: Redémarrer sshdroles/base/handlers/main.yml :
---
- name: Redémarrer sshd
service:
name: sshd
state: restarted
validate: /usr/sbin/sshd -t -f %sAttention à ne jamais désactiver
PasswordAuthenticationavant d'avoir vérifié qu'une authentification par clé fonctionne déjà sur l'hôte, sous peine de se retrouver hors ligne. L'optionvalidatedu handler exécutesshd -tsur le fichier avant de redémarrer le service : si la syntaxe est invalide, Ansible s'arrête et ne coupe pas l'accès SSH existant.
Jouer le rôle en ciblant une partie précise
# Tout le rôle
ansible-playbook -i inventory.ini site.yml
# Uniquement Docker
ansible-playbook -i inventory.ini site.yml --tags docker
# Tout sauf le durcissement sshd (ex. lors d'un premier test)
ansible-playbook -i inventory.ini site.yml --skip-tags sshdDéployer son playbook sur sa machine
Pour tester localement avant de viser un vrai parc, deux approches :
1. Connexion locale explicite (recommandé, l'inventaire reste cohérent avec un futur déploiement distant) :
# inventory.ini
[webservers]
localhost ansible_connection=localansible-playbook -i inventory.ini site.yml --ask-become-pass2. Cibler localhost sans inventaire dédié, utile pour un test rapide :
ansible-playbook site.yml -i localhost, --connection=local --ask-become-passLa virgule après
localhostest nécessaire : elle indique à Ansible qu'il s'agit d'une liste d'hôtes en ligne de commande, et non d'un chemin vers un fichier d'inventaire.
--ask-become-pass demande le mot de passe sudo de façon interactive. Sur une machine où l'utilisateur courant a un accès sudo sans mot de passe (NOPASSWD), ce n'est pas nécessaire.
Déployer son playbook sur plusieurs machines
Construire l'inventaire
L'inventaire regroupe les hôtes par rôle logique, ce qui permet de cibler précisément un sous-ensemble du parc.
# inventory.ini
[webservers]
web1.example.com
web2.example.com ansible_host=192.168.1.11
[dbservers]
db1.example.com
[production:children]
webservers
dbservers
[production:vars]
ansible_user=deploy
ansible_ssh_private_key_file=~/.ssh/id_ed25519_prodUn inventaire au format YAML est équivalent et souvent plus lisible pour des structures complexes :
# inventory.yml
all:
children:
webservers:
hosts:
web1.example.com:
web2.example.com:
ansible_host: 192.168.1.11
dbservers:
hosts:
db1.example.com:Préparer l'accès SSH
La machine de contrôle doit pouvoir s'authentifier en SSH sur chaque hôte, idéalement par clé.
# Copier la clé publique sur chaque hôte cible
ssh-copy-id deploy@web1.example.com
ssh-copy-id deploy@web2.example.com
ssh-copy-id deploy@db1.example.com# Vérifier que tous les hôtes répondent avant de jouer un playbook
ansible all -i inventory.ini -m pingUne réponse pong de chaque hôte confirme la connectivité SSH et la présence de Python sur la cible.
Jouer le playbook sur le parc
# Sur l'ensemble de l'inventaire
ansible-playbook -i inventory.ini site.yml
# Uniquement sur le groupe "webservers"
ansible-playbook -i inventory.ini site.yml --limit webservers
# Sur un hôte précis
ansible-playbook -i inventory.ini site.yml --limit web1.example.comVariables par groupe et par hôte
Plutôt que de multiplier les conditions dans les playbooks, on externalise les variables spécifiques à un groupe ou à un hôte :
mon-projet-ansible/
├── group_vars/
│ ├── webservers.yml
│ └── dbservers.yml
├── host_vars/
│ └── web2.example.com.yml# group_vars/webservers.yml
webserver_port: 8080Ansible fusionne automatiquement ces variables selon leur portée (defaults du rôle < group_vars < host_vars < variables passées en ligne de commande), sans modification du playbook lui-même.
Déploiement progressif (rolling update)
Pour éviter d'appliquer un changement sur tout le parc en une seule fois (et donc de propager une éventuelle erreur partout), on utilise serial :
---
- name: Déployer un serveur web (rolling update)
hosts: webservers
become: true
serial: 1 # une machine à la fois
# serial: "30%" # ou un pourcentage du groupe
roles:
- webserverCombiné à des tâches de vérification (uri, wait_for) après chaque batch, cela permet de stopper le déploiement dès qu'un hôte échoue, avant d'affecter le reste du parc.
Parallélisme
Par défaut, Ansible traite 5 hôtes en parallèle (forks). Sur un grand parc, augmenter cette valeur accélère nettement l'exécution :
ansible-playbook -i inventory.ini site.yml --forks 20ou dans ansible.cfg :
[defaults]
forks = 20