Playbooks Ansible en pratique : inventaire, idempotence, Vault et déploiements progressifs
EdwardMoon
Les playbooks Ansible gèrent déclarativement paquets, fichiers de configuration et états des services sur les serveurs Linux, réduisant oublis et écarts entre environnements. Mais lancer toutes les cibles après un simple ping réussi, abuser de command/shell ou placer les mots de passe en YAML peut accélérer la propagation des pannes et l'exposition des secrets.
Ce guide ne dépend ni d'une distribution unique ni d'une version Ansible obsolète. Il combine environnements Python isolés, inventaires YAML, FQCN ansible.builtin, idempotence, handlers, modes check/diff, Vault, serial et block/rescue dans une procédure vérifiable. Consultez la documentation des versions d'ansible-core et des collections installées avant application.

Composants essentiels d'un playbook
| Composant | Rôle | Pratique d'exploitation |
|---|---|---|
| Inventory | Hôtes cibles, groupes et variables de connexion | Séparer dev, stage et prod et limiter les variables dupliquées |
| Play | Groupe cible et politique d'exécution | Préciser hosts, become, serial et la politique d'échec |
| Task | Un appel de module | Utiliser un nom, un FQCN et un état souhaité explicite |
| Module | Opérations sur les paquets, fichiers, services, etc. | Privilégier les modules dédiés à command ou shell |
| Handler | Action complémentaire déclenchée par une modification | Redémarrer les services après validation de la configuration |
| Role | Tâches, handlers, modèles et valeurs par défaut réutilisables | Limiter les responsabilités et documenter les variables d'interface |
| Collection | Unité de distribution des modules, plugins et rôles | Fixer les versions testées et examiner les changements |
Préparer l'environnement d'exécution
Créez un venv de projet plutôt que d'ajouter des paquets au Python système du contrôleur. Consignez les versions d'ansible-core et Python, les fichiers de configuration et les chemins des collections pour reproduire l'automatisation.
Installer Ansible dans un venv Python
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install ansible-core ansible-lint
ansible --version
ansible-playbook --version
ansible-lint --version
python -m pip freeze > requirements-lock.txt
En production, fixez les versions validées d'ansible-core et des bibliothèques via un fichier de verrouillage ou un dépôt approuvé. Installer systématiquement la dernière version compromet la reproductibilité si la compatibilité des collections ou les prérequis Python changent.
Organisation des répertoires
install -d inventories/dev/group_vars/all inventories/prod/group_vars/all
install -d roles/web/{tasks,handlers,templates,defaults}
install -d playbooks/templates
find . -maxdepth 3 -type d | sort
Valeurs sûres dans ansible.cfg
[defaults]
inventory = inventories/dev/hosts.yml
roles_path = roles
host_key_checking = True
retry_files_enabled = False
interpreter_python = auto_silent
forks = 10
timeout = 15
[privilege_escalation]
become = False
become_ask_pass = True
Désactiver host_key_checking empêche de détecter une interception. Vérifiez les empreintes des clés hôtes SSH par un canal fiable avant de les enregistrer dans known_hosts. Activez become uniquement pour les plays ou tâches nécessaires et excluez les mots de passe de la configuration.
Créer un inventaire YAML
Les noms de groupes doivent refléter les rôles et domaines de panne ; ansible_host contient l'adresse de connexion réelle. Séparer les sources d'inventaire de production et de développement réduit les erreurs de ciblage par rapport à de simples tags dans un fichier commun.
inventories/dev/hosts.yml
all:
children:
web:
hosts:
dev-web-01:
ansible_host: 192.0.2.11
dev-web-02:
ansible_host: 192.0.2.12
database:
hosts:
dev-db-01:
ansible_host: 192.0.2.21
vars:
ansible_user: automation
ansible_become: true
Valider la syntaxe de l'inventaire et les cibles
ansible-inventory -i inventories/dev/hosts.yml --graph
ansible-inventory -i inventories/dev/hosts.yml --list | jq .
ansible web -i inventories/dev/hosts.yml -m ansible.builtin.ping --limit dev-web-01
ansible.builtin.ping vérifie l'exécution distante de Python et la connexion Ansible ; ce n'est pas un ping ICMP. Sa réussite confirme le fonctionnement de SSH, de Python et des autorisations, mais pas la disponibilité des dépôts, de l'espace disque ou des dépendances du service.
Écrire un premier playbook avec handlers
Le play suivant déclare les paquets, modèles de configuration et états des services. Chaque tâche possède un nom lisible et un FQCN. Le modèle ne notifie le handler que si la configuration produite change, évitant les redémarrages inutiles.
playbooks/web.yml
---
- name: Configure web servers
hosts: web
become: true
gather_facts: true
tasks:
- name: Ensure Nginx is installed
ansible.builtin.package:
name: nginx
state: present
- name: Render Nginx virtual host
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: '/usr/sbin/nginx -t -c %s'
notify: Restart Nginx
- name: Ensure Nginx is enabled and running
ansible.builtin.service:
name: nginx
enabled: true
state: started
handlers:
- name: Restart Nginx
ansible.builtin.service:
name: nginx
state: restarted
Cet exemple gère tout /etc/nginx/nginx.conf sur un nouvel hôte de laboratoire. Son modèle inclut events et http pour que nginx -t -c %s valide le fichier temporaire comme configuration principale autonome. Ne l'appliquez pas tel quel en production : remplacer le fichier principal peut supprimer d'autres hôtes virtuels. Un rôle déployant uniquement un fragment vhost nécessite une validation distincte assemblant la configuration complète.
Exemple de modèle Jinja
Enregistrez ce contenu sous playbooks/templates/nginx.conf.j2. Placez les variables dans inventories/dev/group_vars/all/app.yml comme indiqué et faites pointer app_port vers une application réellement active.
app_server_name: app.example.internal
app_port: 8080
events {
worker_connections 1024;
}
http {
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
server {
listen 80;
server_name {{ app_server_name }};
location /healthz {
access_log off;
return 200 "ok\n";
}
location / {
proxy_pass http://127.0.0.1:{{ app_port }};
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
Validation préalable : syntaxe, check et diff
Après validation syntaxique, exécutez check sur un seul hôte de développement et examinez le diff. Ce mode simule les changements, mais tous les modules ne le prennent pas en charge. Les plays dont les conditions dépendent de valeurs créées par des tâches précédentes peuvent différer d'une exécution réelle.
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --syntax-check
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --check --diff --limit dev-web-01
Le diff peut révéler des secrets en montrant la configuration avant et après. Envisagez diff: false et no_log: true pour les modèles sensibles et limitez l'accès et la rétention des journaux CI. Après validation, conservez --limit pour l'exécution réelle, puis élargissez un groupe à la fois.
ansible-lint playbooks/web.yml
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --limit dev-web-01
ansible-playbook -i inventories/dev/hosts.yml playbooks/web.yml --check --limit dev-web-01
Rendre les playbooks idempotents
| Approche fragile | Approche recommandée | Motif |
|---|---|---|
| shell: echo >> file | lineinfile·blockinfile·template | Évite les doublons lors des exécutions suivantes |
| shell: yum install | package·dnf | Lit l'état actuel et applique uniquement les changements nécessaires |
| Initialiser avec command à chaque exécution | creates/removes ou module dédié | Explicite la condition d'achèvement |
| Redémarrer les services systématiquement | Handler notify | Redémarre uniquement si la configuration change |
| Ignorer toutes les erreurs | failed_when·block/rescue | Distingue les erreurs attendues des véritables échecs |
Définir une condition d'achèvement pour command
- name: Initialize application database once
ansible.builtin.command:
argv:
- /usr/local/bin/myapp-init
- --database
- /var/lib/myapp/app.db
creates: /var/lib/myapp/.initialized
register: init_result
# Laisser command signaler skipped/changed selon creates.
creates ignore la commande si le chemin existe. Vérifiez que le programme d'initialisation crée effectivement ce marqueur après réussite. Définir inconditionnellement changed_when à false masque les changements réels et notifications de handlers ; fondez-le sur le sens du code de retour et de la sortie.
Protéger les secrets avec Vault
Ansible Vault chiffre variables et fichiers pour réduire les secrets en clair dans les dépôts. Comme le précise sa documentation, il protège uniquement les données au repos. Les valeurs déchiffrées à l'exécution peuvent apparaître dans les arguments des modules, diffs, sorties debug et erreurs : combinez Vault avec no_log, restrictions de diff et contrôles d'accès aux journaux.
Chiffrer une chaîne saisie au terminal
read -rsp 'Secret value: ' SECRET_VALUE
echo
printf '%s' "$SECRET_VALUE" | ansible-vault encrypt_string --vault-id prod@prompt --stdin-name 'db_password' > inventories/prod/group_vars/all/vault.yml
unset SECRET_VALUE
chmod 0600 inventories/prod/group_vars/all/vault.yml
Exécuter avec un identifiant Vault
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --vault-id prod@prompt --check --limit prod-web-01
# Les variables YAML !vault sont déchiffrées lors de l'exécution du playbook avec --vault-id.
Masquer la sortie des tâches sensibles
- name: Render application secret configuration
ansible.builtin.template:
src: app-secret.conf.j2
dest: /etc/myapp/secret.conf
owner: root
group: root
mode: '0600'
no_log: true
diff: false
no_log masque la sortie d'une tâche, mais ne protège pas contre du code malveillant, des tâches debug distinctes ou tous les journaux de la cible. Privilégiez si possible un gestionnaire de secrets externe et des identifiants de courte durée, et excluez les fichiers de mots de passe Vault de Git.
Déploiements progressifs
Après déploiement, exécutez les redémarrages notifiés avec meta: flush_handlers avant les contrôles de santé. Pour mettre à niveau un paquet installé, précisez une version approuvée dans son nom ou définissez explicitement myapp_package_state à latest pendant une maintenance. present ne met pas automatiquement les paquets à niveau.
Utilisez serial pour limiter la taille des lots plutôt que de modifier toute la production simultanément. max_fail_percentage arrête l'exécution si le taux d'échec du lot dépasse le seuil. Pour arrêter après un échec dans un lot de deux hôtes, utilisez par exemple 49 et non 50, car la comparaison est strictement supérieure.
---
- name: Roll out application safely
hosts: web
become: true
serial: 2
max_fail_percentage: 49
pre_tasks:
- name: Confirm target batch
ansible.builtin.debug:
msg: "Deploying to {{ ansible_play_batch }}"
tasks:
- name: Deploy application package
ansible.builtin.package:
name: myapp
state: "{{ myapp_package_state | default('present') }}"
notify: Restart MyApp
- name: Restart changed services before checking health
ansible.builtin.meta: flush_handlers
- name: Verify local health endpoint
ansible.builtin.uri:
url: http://127.0.0.1:8080/healthz
status_code: 200
return_content: false
register: health
retries: 10
delay: 3
until: health.status == 200
handlers:
- name: Restart MyApp
ansible.builtin.service:
name: myapp
state: restarted
Cet exemple illustre la structure minimale. Avec un véritable répartiteur, retirez les hôtes du lot du trafic, attendez la fin des connexions, déployez, vérifiez leur santé et réinscrivez-les. Avec delegate_to ou des modules API, vérifiez aussi certificats, traitement des erreurs et sûreté des nouvelles tentatives.
Gestion des erreurs et récupération
Un block applique des directives communes comme become et when à des tâches liées et exprime le traitement des erreurs avec rescue et always. Si rescue réussit, l'échec initial peut être considéré comme récupéré et le play continuer. Les erreurs syntaxiques et hôtes injoignables ne déclenchent pas rescue : prévoyez séparément politique de connexion et supervision.
- name: Back up current configuration
ansible.builtin.copy:
src: /etc/myapp/myapp.conf
dest: /var/backups/myapp.conf.pre-ansible
remote_src: true
owner: root
group: root
mode: '0600'
- name: Update service configuration with recovery
block:
- name: Render candidate configuration
ansible.builtin.template:
src: myapp.conf.j2
dest: /etc/myapp/myapp.conf
owner: root
group: root
mode: '0640'
validate: '/usr/local/bin/myapp --check-config %s'
notify: Restart MyApp
rescue:
- name: Restore previous configuration
ansible.builtin.copy:
src: /var/backups/myapp.conf.pre-ansible
dest: /etc/myapp/myapp.conf
remote_src: true
owner: root
group: root
mode: '0640'
- name: Stop this host after recovery
ansible.builtin.fail:
msg: Configuration deployment failed and was restored
always:
- name: Record completion state
ansible.builtin.debug:
msg: "Configuration block finished for {{ inventory_hostname }}"
Restaurer un fichier de configuration ne signifie pas revenir aussi à l'ancienne application ou à son schéma de base. Conservez une procédure distincte couvrant contrôles avant/après, retour arrière des paquets, changements de données et déclenchement des handlers.
Contrôles de qualité et ordre d'exécution
- Consigner ansible-core, Python, collections et commit Git à déployer.
- Vérifier cibles, variables et groupes avec inventory --graph et --list.
- Valider ansible-lint et --syntax-check.
- Exécuter --check --diff sur un hôte de développement.
- Examiner les diffs sensibles et le périmètre de no_log.
- Après exécution réelle sur un hôte, vérifier la santé et changed=0 lors d'une répétition.
- Tester les arrêts sur erreur et la récupération en préproduction avec de petits lots serial.
- Déployer pendant une maintenance approuvée en conservant le récapitulatif, les métriques et journaux.
ansible-config dump --only-changed
ansible-inventory -i inventories/prod/hosts.yml --graph
ansible-lint playbooks roles
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --syntax-check
ansible-playbook -i inventories/prod/hosts.yml playbooks/web.yml --check --diff --limit prod-web-01 --vault-id prod@prompt
Points à vérifier en exploitation
- Séparer les sources d'inventaire et droits d'écriture de dev, stage et prod.
- Conserver la vérification des clés SSH et minimiser les privilèges du compte d'automatisation et de sudo.
- Nommer chaque tâche, utiliser un FQCN et définir les conditions d'achèvement/changement pour command et shell.
- Valider la configuration avec validate et les handlers, puis confirmer changed=0 aux répétitions.
- Combiner Vault, no_log, restrictions de diff et contrôles d'accès aux journaux.
- Élargir la validation de la syntaxe et du lint à check, diff, un hôte, puis de petits lots serial.
- Préparer les cas d'hôtes injoignables, d'erreurs hors block/rescue et de retour arrière des données.
- Consigner commit, inventaire, limit, opérateur, récapitulatif et contrôles finaux dans l'historique.
Documentation officielle et articles associés
- Guide officiel de l'inventaire Ansible
- Modes check et diff
- Handlers Ansible
- Block, rescue et always
- Documentation officielle Ansible Vault
- Documentation officielle ansible-lint
- Créer et exploiter des environnements virtuels Python
Conclusion
Une automatisation Ansible sûre exprime en code l'état souhaité, les conditions de changement, les frontières d'échec et l'ordre de validation. Séparez les inventaires par environnement, construisez l'idempotence avec des modules FQCN dédiés et des handlers, et comprenez les limites de Vault. N'étendez à la production qu'après réussite des validations syntaxe, lint, check, diff, hôte unique, lots serial et récupération.