Ansible Avancé : Vault, inventaires dynamiques et production
🎯 Objectifs
- Chiffrer les secrets avec Ansible Vault
- Utiliser des inventaires dynamiques (AWS, Docker, scripts)
- Écrire des tâches conditionnelles et des boucles avancées
- Optimiser l'exécution avec les tags et le parallélisme
- Appliquer les bonnes pratiques pour un usage en production
📋 Prérequis
- Ansible Intermédiaire - variables, templates, handlers, rôles
- Ansible installé et fonctionnel
- Bonne maîtrise des fichiers YAML
🔐 Ansible Vault : protéger les secrets
Le problème
Dans tes playbooks, tu as probablement des mots de passe, des clés API, des tokens. Les stocker en clair dans un fichier versionné avec Git est une très mauvaise pratique - n'importe qui ayant accès au dépôt peut les lire.
La solution : Ansible Vault chiffre tes fichiers de variables. Seule la personne qui connaît le mot de passe du vault peut les déchiffrer.
Chiffrer un fichier entier
# Créer un nouveau fichier chiffré
ansible-vault create vars/secrets.yml
# Modifier un fichier chiffré existant
ansible-vault edit vars/secrets.yml
# Chiffrer un fichier existant en clair
ansible-vault encrypt vars/secrets.yml
# Déchiffrer un fichier (attention : le résultat sera en clair)
ansible-vault decrypt vars/secrets.yml
# Voir le contenu sans le déchiffrer définitivement
ansible-vault view vars/secrets.yml
# Changer le mot de passe du vault
ansible-vault rekey vars/secrets.ymlChiffrer une seule valeur
Utile quand tu veux chiffrer uniquement un mot de passe dans un fichier autrement lisible :
ansible-vault encrypt_string 'MonMotDePasse123' --name 'db_password'Résultat à coller dans ton fichier YAML :
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
61616265353733623264343264333063...
(suite du chiffrement)Utiliser les fichiers chiffrés dans un playbook
---
- name: Déployer la base de données
hosts: databases
become: true
vars_files:
- vars/all.yml
- vars/secrets.yml # Fichier chiffré avec Vault
tasks:
- name: Créer l'utilisateur PostgreSQL
community.postgresql.postgresql_user:
name: "{{ db_user }}"
password: "{{ db_password }}" # Vient du fichier chiffré
state: present# Lancer le playbook en fournissant le mot de passe vault
ansible-playbook playbook.yml --ask-vault-pass
# Ou via un fichier contenant le mot de passe (à ne pas versionner !)
ansible-playbook playbook.yml --vault-password-file ~/.vault_passFichier de mot de passe vault
# Créer le fichier (ne jamais le committer dans Git !)
echo "MonMotDePasseVault" > ~/.vault_pass
chmod 600 ~/.vault_pass
# Ajouter dans .gitignore
echo ".vault_pass" >> .gitignore🌐 Inventaires Dynamiques
Le problème des inventaires statiques
Dans un environnement cloud, les serveurs naissent et meurent constamment. Maintenir un fichier inventaire.ini à la main devient impossible quand tu as 50 instances EC2 qui changent d'IP à chaque redémarrage.
La solution : les inventaires dynamiques interrogent une source de vérité (AWS, Azure, Docker...) en temps réel pour construire la liste des hôtes.
Plugin d'inventaire AWS EC2
# Installer la collection AWS
ansible-galaxy collection install amazon.aws
pip install boto3# inventaire_aws.yml
plugin: amazon.aws.aws_ec2
regions:
- eu-west-1
filters:
instance-state-name: running
"tag:Environment": production
keyed_groups:
- key: tags.Role # Créer des groupes basés sur le tag "Role"
prefix: role
hostnames:
- private-ip-address# Tester l'inventaire dynamique
ansible-inventory -i inventaire_aws.yml --list
# Lancer un playbook avec cet inventaire
ansible-playbook -i inventaire_aws.yml playbook.ymlInventaire dynamique basé sur un script
Tu peux écrire un script Python ou Bash qui retourne du JSON :
#!/usr/bin/env python3
# inventaire.py
import json
import subprocess
def get_docker_containers():
# Récupère les conteneurs Docker en cours d'exécution
result = subprocess.run(
['docker', 'ps', '--format', '{{.Names}}\t{{.ID}}'],
capture_output=True, text=True
)
hosts = {}
for line in result.stdout.strip().split('\n'):
if line:
name, container_id = line.split('\t')
hosts[name] = {'ansible_connection': 'docker'}
return hosts
inventory = {
'docker_containers': {
'hosts': list(get_docker_containers().keys())
},
'_meta': {
'hostvars': get_docker_containers()
}
}
print(json.dumps(inventory))chmod +x inventaire.py
ansible-playbook -i inventaire.py playbook.yml🔀 Conditions et Boucles Avancées
La directive when
tasks:
- name: Installer les paquets sur Debian/Ubuntu
ansible.builtin.apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
- name: Installer les paquets sur Red Hat/CentOS
ansible.builtin.dnf:
name: nginx
state: present
when: ansible_os_family == "RedHat"
- name: Tâche uniquement en production
ansible.builtin.debug:
msg: "Déploiement en production !"
when:
- env == "production"
- ansible_hostname.startswith("prod-")
- name: Passer si déjà fait
ansible.builtin.command: /opt/init.sh
when: not init_done.stat.existsBoucles avec loop
- name: Créer plusieurs utilisateurs
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
state: present
loop:
- { name: alice, groups: sudo }
- { name: bob, groups: www-data }
- { name: charlie, groups: docker }
- name: Installer une liste de paquets
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- git
- curl
- htop
- vim
- name: Déployer plusieurs fichiers de configuration
ansible.builtin.template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
loop:
- { src: nginx.conf.j2, dest: /etc/nginx/nginx.conf }
- { src: app.conf.j2, dest: /etc/nginx/sites-available/app }Boucles avec loop_control
- name: Créer des répertoires avec un label clair
ansible.builtin.file:
path: "{{ item }}"
state: directory
loop:
- /var/www/app1
- /var/www/app2
- /var/log/app1
loop_control:
label: "Répertoire : {{ item }}" # Personnalise l'affichage dans les logsRécupérer et réutiliser un résultat (register)
- name: Vérifier si le fichier existe
ansible.builtin.stat:
path: /etc/app/config.yml
register: config_file # Stocker le résultat dans une variable
- name: Créer la config si elle n'existe pas
ansible.builtin.copy:
src: files/config.yml
dest: /etc/app/config.yml
when: not config_file.stat.exists # Utiliser le résultat
- name: Afficher la version déployée
ansible.builtin.command: cat /etc/app/version
register: app_version
changed_when: false # Cette commande ne "change" rien
- name: Afficher la version
ansible.builtin.debug:
msg: "Version actuelle : {{ app_version.stdout }}"🏷️ Les Tags
À quoi servent les tags ?
Sur un playbook complexe avec 30 tâches, tu ne veux pas toujours tout relancer. Les tags permettent d'exécuter uniquement certaines tâches.
---
- name: Déployer l'application
hosts: webservers
become: true
tasks:
- name: Installer les dépendances système
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop: "{{ system_packages }}"
tags:
- install
- setup
- name: Déployer le code applicatif
ansible.builtin.git:
repo: "{{ app_repo }}"
dest: "{{ app_dir }}"
version: "{{ app_version }}"
tags:
- deploy
- code
- name: Recharger la configuration
ansible.builtin.service:
name: nginx
state: reloaded
tags:
- reload
- deploy# Exécuter uniquement les tâches taguées "deploy"
ansible-playbook playbook.yml --tags deploy
# Exécuter tout SAUF les tâches taguées "install"
ansible-playbook playbook.yml --skip-tags install
# Lister tous les tags disponibles
ansible-playbook playbook.yml --list-tags⚡ Optimisation et Bonnes Pratiques
Parallélisme avec forks
Par défaut, Ansible exécute les tâches sur 5 hôtes en parallèle. Augmente ce nombre pour les grands inventaires :
# En ligne de commande
ansible-playbook playbook.yml -f 20
# Dans ansible.cfg
[defaults]
forks = 20Le fichier ansible.cfg
Crée un fichier ansible.cfg à la racine de ton projet pour éviter de répéter les options :
[defaults]
inventory = inventaire.ini
remote_user = ubuntu
private_key_file = ~/.ssh/id_ed25519
forks = 10
host_key_checking = False # Utile en dev, à activer en prod
vault_password_file = ~/.vault_pass
retry_files_enabled = False
[privilege_escalation]
become = True
become_method = sudoDéléguer une tâche à un hôte différent
- name: Enregistrer l'hôte dans le load balancer
ansible.builtin.uri:
url: "http://lb.example.com/api/add"
method: POST
body_format: json
body:
host: "{{ ansible_hostname }}"
delegate_to: localhost # Cette tâche s'exécute sur ton control nodeTâches locales
- name: Générer un rapport en local
ansible.builtin.template:
src: rapport.j2
dest: /tmp/rapport_{{ inventory_hostname }}.txt
delegate_to: localhostBonnes pratiques de production
# 1. Toujours nommer tes tâches
- name: Installer Nginx version {{ nginx_version }} # ✅
ansible.builtin.apt:
name: "nginx={{ nginx_version }}"
# 2. Utiliser changed_when pour les commandes
- name: Vérifier la configuration Nginx
ansible.builtin.command: nginx -t
changed_when: false # Cette commande ne modifie rien
# 3. Utiliser failed_when pour contrôler les erreurs
- name: Vérifier si le service existe
ansible.builtin.command: systemctl status monservice
register: service_status
failed_when: service_status.rc > 1 # rc=0 OK, rc=1 inactif (acceptable)
# 4. Préférer les modules aux commandes shell
- name: Créer un utilisateur # ✅ Idempotent
ansible.builtin.user:
name: deploy
state: present
- name: Créer un utilisateur # ❌ Pas idempotent
ansible.builtin.command: useradd deploy🧪 Tester ses Playbooks avec Molecule
Molecule est l'outil standard pour tester les rôles Ansible.
# Installation
pip install molecule molecule-docker
# Initialiser les tests pour un rôle
cd roles/nginx
molecule init scenario
# Lancer les tests
molecule test
# Tester uniquement le lint
molecule lint
# Converger (appliquer le rôle) sans détruire
molecule converge# molecule/default/converge.yml
---
- name: Converge
hosts: all
tasks:
- name: Inclure le rôle nginx
ansible.builtin.include_role:
name: nginx
vars:
nginx_port: 80⚠️ Erreurs Courantes en Production
| Erreur | Cause | Solution |
|---|---|---|
Vault password required | Fichier chiffré sans mot de passe fourni | --ask-vault-pass ou --vault-password-file |
| Inventaire dynamique vide | Credentials cloud manquants ou mauvais filtre | Vérifier les variables d'environnement AWS/Azure |
changed_when oublié | Faux positifs de changements | Toujours définir changed_when: false pour les commandes de lecture |
| Playbook trop lent | forks trop bas ou pas de pipelining | Augmenter forks et activer pipelining = True dans ansible.cfg |
| Secrets en clair dans les logs | no_log non défini | Ajouter no_log: true sur les tâches avec des secrets |
Masquer les secrets dans les logs
- name: Créer l'utilisateur avec son mot de passe
ansible.builtin.user:
name: deploy
password: "{{ user_password | password_hash('sha512') }}"
no_log: true # Masque les paramètres dans les logs📊 Récapitulatif
| Fonctionnalité | Utilisation | Commande / Directive |
|---|---|---|
| Vault | Chiffrer les secrets | ansible-vault encrypt |
| Inventaire dynamique | Hôtes depuis le cloud | Plugin aws_ec2, script JSON |
when | Condition sur une tâche | when: variable == "valeur" |
loop | Répéter une tâche | loop: [item1, item2] |
register | Stocker un résultat | register: ma_variable |
| Tags | Exécution sélective | tags: [deploy], --tags deploy |
delegate_to | Déléguer à un autre hôte | delegate_to: localhost |
no_log | Masquer les secrets | no_log: true |
📚 Ressources
🚀 Prochaines étapes
Tu maîtrises maintenant Ansible en production. Les prochaines étapes naturelles :
- Intégrer Ansible dans un pipeline CI/CD avec GitHub Actions ou GitLab CI
- Explorer AWX / Ansible Automation Platform - l'interface web pour Ansible en entreprise
- Découvrir Terraform pour provisionner l'infrastructure avant de la configurer avec Ansible