Soyons honnêtes : on a tous un dossier scripts/ rempli de fichiers bash écrits à la va-vite, avec des sed imbriqués, des if sans fi qui traînent, et des commentaires du type « ne pas toucher, ça marche ». Le jour où il faut déployer la même config sur 15 serveurs, on copie-colle en SSH comme des artisans du Moyen Âge numérique. Et quand ça casse, personne ne sait ce qui a changé, ni quand, ni pourquoi.
Ansible règle ce problème. Pas de client à installer sur les machines cibles, pas de base de données centrale, pas de langage ésotérique à apprendre. Du YAML, du SSH, et une logique d'idempotence qui garantit que relancer un playbook dix fois produit toujours le même résultat. C'est l'outil que tout sysadmin devrait maîtriser avant de prétendre faire du DevOps.
Ce guide est pragmatique. On part de zéro, on monte en puissance, et on termine avec une organisation de projet propre. Pas de théorie abstraite : du code, des exemples concrets, et les erreurs classiques à éviter.
L'inventaire, la base de tout
L'inventaire Ansible décrit votre infrastructure : quelles machines existent, comment les joindre, et comment les regrouper logiquement. Sans inventaire bien structuré, vous ne faites que du SSH en boucle avec des étapes supplémentaires.
Format INI : simple et lisible
Le format INI reste le plus courant pour les petites infrastructures. Chaque section entre crochets définit un groupe :
[webservers]
web01.example.com ansible_host=192.168.1.10
web02.example.com ansible_host=192.168.1.11
[databases]
db01.example.com ansible_host=192.168.1.20 ansible_port=2222
[production:children]
webservers
databases
[production:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3
La directive :children permet de créer des groupes de groupes. La directive :vars applique des variables à tous les membres d'un groupe. C'est la base de l'organisation hiérarchique.
Format YAML : pour les infrastructures complexes
Le format YAML offre plus de flexibilité quand l'inventaire grossit :
all:
children:
webservers:
hosts:
web01.example.com:
ansible_host: 192.168.1.10
http_port: 8080
web02.example.com:
ansible_host: 192.168.1.11
http_port: 80
databases:
hosts:
db01.example.com:
ansible_host: 192.168.1.20
vars:
db_engine: postgresql
db_port: 5432
Quel que soit le format choisi, testez votre inventaire avec ansible-inventory --list -i inventory.yml pour vérifier que la résolution des groupes et des variables est correcte.
Premier playbook : sécuriser un serveur
Un playbook Ansible est un fichier YAML qui décrit l'état souhaité de vos machines. Voici un premier playbook concret qui applique un durcissement de base sur un serveur Debian/Ubuntu fraîchement installé :
---
- name: Durcissement de base du serveur
hosts: all
become: true
vars:
admin_user: deploy
ssh_port: 22
allowed_ssh_keys:
- "ssh-ed25519 AAAAC3... admin@workstation"
tasks:
- name: Mettre à jour le cache APT et upgrader les paquets
ansible.builtin.apt:
update_cache: true
upgrade: safe
cache_valid_time: 3600
- name: Installer les paquets de base
ansible.builtin.apt:
name:
- ufw
- fail2ban
- unattended-upgrades
- curl
- htop
state: present
- name: Créer l'utilisateur d'administration
ansible.builtin.user:
name: "{{ admin_user }}"
groups: sudo
shell: /bin/bash
create_home: true
state: present
- name: Déployer les clés SSH autorisées
ansible.posix.authorized_key:
user: "{{ admin_user }}"
key: "{{ item }}"
state: present
loop: "{{ allowed_ssh_keys }}"
- name: Désactiver l'authentification par mot de passe SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PasswordAuthentication"
line: "PasswordAuthentication no"
validate: "sshd -t -f %s"
notify: restart sshd
- name: Désactiver le login root par SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin"
line: "PermitRootLogin no"
validate: "sshd -t -f %s"
notify: restart sshd
- name: Configurer UFW - politique par défaut
community.general.ufw:
state: enabled
policy: deny
direction: incoming
- name: Autoriser SSH dans UFW
community.general.ufw:
rule: allow
port: "{{ ssh_port }}"
proto: tcp
handlers:
- name: restart sshd
ansible.builtin.service:
name: sshd
state: restarted
Exécutez ce playbook avec la commande suivante :
ansible-playbook -i inventory.ini harden.yml --diff --check
L'option --check simule l'exécution sans rien modifier. L'option --diff affiche les changements qui seraient appliqués. Toujours tester en dry-run avant d'appliquer sur la production.
Les modules essentiels
Ansible dispose de milliers de modules, mais une poignée couvre 90% des besoins quotidiens d'un sysadmin. Voici ceux que vous utiliserez le plus :
apt : gestion des paquets
Le module apt gère l'installation, la mise à jour et la suppression de paquets Debian/Ubuntu. L'option cache_valid_time évite de relancer un apt update à chaque exécution :
- name: Installer nginx en version précise
ansible.builtin.apt:
name: nginx=1.24.*
state: present
update_cache: true
cache_valid_time: 3600
template : fichiers de configuration dynamiques
Le module template utilise Jinja2 pour générer des fichiers de configuration à partir de variables. C'est infiniment plus propre que des sed en cascade :
- name: Déployer la configuration nginx
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ domain }}.conf
owner: root
group: root
mode: "0644"
validate: "nginx -t -c %s"
notify: reload nginx
copy, service, lineinfile
Le module copy transfère des fichiers statiques. Le module service gère l'état des services systemd. Le module lineinfile modifie une ligne précise dans un fichier existant, ce qui est idéal pour les ajustements ponctuels sans réécrire tout le fichier :
- name: Activer le forwarding IPv4
ansible.builtin.lineinfile:
path: /etc/sysctl.conf
regexp: "^#?net.ipv4.ip_forward"
line: "net.ipv4.ip_forward = 1"
notify: reload sysctl
- name: S'assurer que nginx est démarré et activé
ansible.builtin.service:
name: nginx
state: started
enabled: true
Variables et facts
La puissance d'Ansible réside dans sa gestion des variables. Elles permettent de factoriser la configuration et d'adapter le comportement en fonction de chaque machine ou groupe.
group_vars et host_vars
Ansible charge automatiquement les variables depuis des fichiers organisés par convention :
inventory/
├── hosts.yml
├── group_vars/
│ ├── all.yml # Variables globales
│ ├── webservers.yml # Variables du groupe webservers
│ └── databases.yml # Variables du groupe databases
└── host_vars/
└── db01.example.com.yml # Variables spécifiques à un hôte
L'ordre de priorité est strict : les variables d'hôte écrasent celles de groupe, qui écrasent celles de all. Gardez ce mécanisme en tête pour éviter les surprises.
Facts et register
Ansible collecte automatiquement des informations sur chaque machine cible (les facts). Vous pouvez les exploiter dans vos conditions et templates :
- name: Installer le paquet selon la distribution
ansible.builtin.apt:
name: "{{ pkg_name }}"
state: present
when: ansible_facts['os_family'] == 'Debian'
- name: Vérifier l'espace disque disponible
ansible.builtin.command: df -h /
register: disk_usage
changed_when: false
- name: Alerter si espace disque critique
ansible.builtin.debug:
msg: "Attention : espace disque critique sur {{ inventory_hostname }}"
when: disk_usage.stdout is search('9[0-9]%|100%')
Le mot-clé register capture la sortie d'une tâche dans une variable réutilisable. Combiné avec when, il permet de créer des workflows conditionnels puissants. Notez l'usage de changed_when: false pour indiquer qu'une commande de lecture ne modifie rien.
Handlers et idempotence
L'idempotence est le principe fondamental d'Ansible : une tâche ne s'exécute que si l'état actuel diffère de l'état souhaité. C'est ce qui permet de relancer un playbook sans crainte.
Les handlers : réagir aux changements
Un handler est une tâche déclenchée uniquement quand une autre tâche signale un changement via notify. Cas typique : redémarrer nginx après modification de sa configuration, mais uniquement si la configuration a réellement changé :
tasks:
- name: Déployer le virtualhost
ansible.builtin.template:
src: vhost.conf.j2
dest: /etc/nginx/sites-available/monsite.conf
notify:
- validate nginx
- reload nginx
handlers:
- name: validate nginx
ansible.builtin.command: nginx -t
changed_when: false
- name: reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
Contrôle fin avec changed_when et failed_when
Certaines commandes retournent toujours « changed » même quand rien n'a bougé. Les directives changed_when et failed_when permettent d'affiner le comportement :
- name: Vérifier si le certificat SSL expire bientôt
ansible.builtin.command: >
openssl x509 -checkend 2592000
-in /etc/ssl/certs/{{ domain }}.pem
register: cert_check
changed_when: false
failed_when: false
- name: Renouveler le certificat si nécessaire
ansible.builtin.command: certbot renew --cert-name {{ domain }}
when: cert_check.rc != 0
Sans changed_when: false, chaque exécution du playbook afficherait un changement fictif sur la tâche de vérification. Ce genre de bruit parasite rend les rapports d'exécution illisibles et masque les vrais changements.
Rôles et organisation du projet
Quand un playbook dépasse 200 lignes, il est temps de le découper en rôles. Un rôle est un module réutilisable qui encapsule tâches, handlers, templates, variables et fichiers autour d'une responsabilité unique.
Créer un rôle avec ansible-galaxy
ansible-galaxy init roles/hardening
# Crée la structure suivante :
roles/hardening/
├── defaults/main.yml # Variables par défaut (priorité basse)
├── files/ # Fichiers statiques à copier
├── handlers/main.yml # Handlers du rôle
├── meta/main.yml # Métadonnées et dépendances
├── tasks/main.yml # Tâches principales
├── templates/ # Templates Jinja2
└── vars/main.yml # Variables du rôle (priorité haute)
Structure d'un projet complet
Voici l'organisation recommandée pour un projet Ansible de taille moyenne :
ansible-project/
├── ansible.cfg
├── inventory/
│ ├── production/
│ │ ├── hosts.yml
│ │ ├── group_vars/
│ │ └── host_vars/
│ └── staging/
│ ├── hosts.yml
│ └── group_vars/
├── playbooks/
│ ├── site.yml # Playbook principal
│ ├── webservers.yml
│ └── databases.yml
├── roles/
│ ├── common/
│ ├── hardening/
│ ├── nginx/
│ └── postgresql/
└── requirements.yml # Rôles Galaxy externes
Le fichier ansible.cfg à la racine du projet centralise la configuration locale :
[defaults]
inventory = inventory/production/hosts.yml
roles_path = roles
retry_files_enabled = false
stdout_callback = yaml
[privilege_escalation]
become = true
become_method = sudo
[ssh_connection]
pipelining = true
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
L'activation du pipelining réduit significativement le temps d'exécution en diminuant le nombre de connexions SSH nécessaires.
Erreurs courantes et debugging
Même avec un outil idempotent, on fait des erreurs. Voici les pièges classiques et les outils pour les diagnostiquer.
Les niveaux de verbosité
# Verbosité croissante
ansible-playbook site.yml -v # Affiche les résultats des tâches
ansible-playbook site.yml -vv # Affiche les paramètres des modules
ansible-playbook site.yml -vvv # Affiche les connexions SSH
ansible-playbook site.yml -vvvv # Affiche tout, y compris les scripts injectés
Le mode check et diff
Toujours valider avant d'appliquer. Le duo --check --diff est votre filet de sécurité :
# Simulation avec affichage des différences
ansible-playbook site.yml --check --diff
# Limiter à un groupe ou un hôte
ansible-playbook site.yml --limit webservers --check --diff
# Limiter à une tâche spécifique par tag
ansible-playbook site.yml --tags "firewall" --check --diff
Les erreurs les plus fréquentes
Erreur : « Undefined variable » -- Vous utilisez une variable qui n'existe pas dans le contexte actuel. Vérifiez la hiérarchiegroup_vars/host_vars/defaultset utilisez le filtre| default('valeur')pour les variables optionnelles.
Erreur : « Permission denied » -- Oubli debecome: truedans le playbook ou dans la tâche. Si le sudo nécessite un mot de passe, ajoutez--ask-become-passà la commande.
Erreur : « Module not found » -- Le module appartient à une collection qui n'est pas installée. Installez-la avec ansible-galaxy collection install community.general.
Un dernier conseil de debugging : le module ansible.builtin.debug est votre meilleur allié. Utilisez-le pour inspecter les variables et les facts en cours d'exécution :
- name: Inspecter les variables pour debug
ansible.builtin.debug:
msg: |
Hostname : {{ inventory_hostname }}
OS : {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}
IP : {{ ansible_facts['default_ipv4']['address'] }}
RAM : {{ ansible_facts['memtotal_mb'] }} Mo
Conclusion
Ansible transforme l'administration système artisanale en ingénierie reproductible. On récapitule les points essentiels :
- L'inventaire structure votre infrastructure en groupes logiques avec des variables hiérarchisées.
- Les playbooks décrivent l'état souhaité de vos machines de manière déclarative et idempotente.
- Les modules
apt,template,service,lineinfileetcopycouvrent la majorité des besoins quotidiens. - Les variables et facts permettent d'adapter le comportement à chaque machine sans dupliquer le code.
- Les handlers garantissent que les services ne redémarrent que quand c'est nécessaire.
- Les rôles découpent la complexité en modules réutilisables et testables.
- Le mode check/diff est votre filet de sécurité avant toute modification en production.
Commencez petit : un playbook qui gère vos clés SSH et votre configuration de base. Puis étendez progressivement. Chaque tâche manuelle que vous convertissez en Ansible est une tâche que vous n'aurez plus jamais à refaire à la main, ni à expliquer à votre collègue un vendredi soir.
La documentation officielle Ansible reste la référence la plus complète et la plus à jour pour approfondir chaque module et chaque concept abordé dans cet article.
Commentaires