Ansible — Gestion de configuration#
Pourquoi la gestion de configuration#
Dérive de configuration#
Lorsqu’une infrastructure grandit, les serveurs administrés manuellement divergent inévitablement. Un administrateur applique un correctif sur trois machines mais en oublie une quatrième. Un collègue modifie un paramètre noyau en urgence sans le documenter. Six mois plus tard, personne ne sait pourquoi le serveur de production se comporte différemment des serveurs de staging.
Ce phénomène s’appelle la dérive de configuration (configuration drift). Il se manifeste par :
des différences subtiles de versions entre environnements,
des fichiers de configuration locaux non tracés,
des cron jobs ajoutés à la main et jamais supprimés,
des paquets installés manuellement qui n’apparaissent dans aucun registre.
Snowflake servers#
Un serveur devenu unique par accumulation de modifications manuelles est appelé un snowflake server (serveur flocon de neige). Chaque flocon est irremplaçable, fragile, et opaque. Personne n’ose le toucher. S’il tombe, la restauration prend des jours.
L’antidote est le phoenix server : une machine que l’on peut détruire et reconstruire en quelques minutes à partir de la définition en code.
Infrastructure as Code#
L”Infrastructure as Code (IaC) consiste à décrire l’état désiré de l’infrastructure dans des fichiers versionnable et rejouables. Les avantages sont nombreux :
Reproductibilité : le même code produit le même résultat sur n’importe quelle machine cible.
Traçabilité : chaque changement est un commit git avec auteur et message.
Idempotence : rejouer le code n’a pas d’effet de bord si l’état est déjà atteint.
Revue de code : les changements d’infrastructure passent par une pull request.
Ansible est l’un des outils IaC les plus populaires. Il se distingue par sa simplicité : pas d’agent à installer, pas de serveur maître complexe, un inventaire de fichiers texte et des playbooks YAML lisibles.
Architecture Ansible#
Control node et managed nodes#
L’architecture Ansible repose sur deux rôles :
Control node : la machine depuis laquelle Ansible est exécuté. Elle porte le code, les inventaires, les playbooks et les rôles. Ansible doit y être installé (Python ≥ 3.8).
Managed nodes : les machines cibles. Elles n’ont besoin que d’un accès SSH et d’un interpréteur Python (généralement préinstallé).
Agentless et SSH#
La particularité d’Ansible est d’être agentless : aucun démon ne tourne sur les managed nodes. Ansible se connecte en SSH, copie un module Python dans un répertoire temporaire, l’exécute, récupère le résultat JSON et supprime le fichier temporaire. Cette simplicité facilite l’adoption et élimine une surface d’attaque.
Prérequis SSH
Les managed nodes doivent être accessibles par SSH sans mot de passe interactif (clé publique
déposée) ou via un bastion. L’utilisateur SSH doit disposer des droits sudo si les tâches
nécessitent une élévation de privilèges (become: true).
Inventaire statique et dynamique#
L”inventaire liste les hôtes gérés. En mode statique, c’est un fichier INI ou YAML. En mode dynamique, Ansible interroge une API (AWS EC2, GCP, Azure, vSphere, Netbox…) pour construire l’inventaire à la volée.
Inventaire#
Format INI#
# inventaire/hosts.ini
[webservers]
web01.example.com
web02.example.com ansible_user=deploy
[dbservers]
db01.example.com ansible_port=2222
db02.example.com
[prod:children]
webservers
dbservers
[prod:vars]
ansible_user=ansible
ansible_become=true
Format YAML#
# inventaire/hosts.yml
all:
children:
webservers:
hosts:
web01.example.com:
web02.example.com:
ansible_user: deploy
dbservers:
hosts:
db01.example.com:
ansible_port: 2222
db02.example.com:
vars:
ansible_user: ansible
ansible_become: true
Variables de groupe et d’hôte#
Les variables peuvent être définies dans des répertoires dédiés :
inventaire/
├── hosts.yml
├── group_vars/
│ ├── all.yml # variables pour tous les hôtes
│ ├── webservers.yml # variables pour le groupe webservers
│ └── dbservers.yml
└── host_vars/
├── web01.example.com.yml
└── db01.example.com.yml
Visualiser l’inventaire#
ansible-inventory -i inventaire/hosts.yml --graph
Sortie :
@all:
|--@prod:
| |--@webservers:
| | |--web01.example.com
| | |--web02.example.com
| |--@dbservers:
| | |--db01.example.com
| | |--db02.example.com
|--@ungrouped:
Modules essentiels#
Les modules sont les unités d’action d’Ansible. Chaque tâche appelle un module avec des paramètres. Ansible en propose plus de 3 000 ; voici les incontournables.
command et shell#
- name: Vérifier l'uptime
ansible.builtin.command: uptime
- name: Lister les processus avec pipeline
ansible.builtin.shell: ps aux | grep nginx | wc -l
register: nb_nginx
command n’interprète pas le shell (pas de |, >, &&). shell exécute via /bin/sh et
supporte les redirections. Préférer command quand possible pour la sécurité.
copy et template#
- name: Copier un fichier statique
ansible.builtin.copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: "0644"
- name: Déployer un template Jinja2
ansible.builtin.template:
src: templates/vhost.conf.j2
dest: /etc/nginx/sites-available/{{ site_name }}.conf
mode: "0644"
notify: Recharger nginx
file#
- name: Créer un répertoire
ansible.builtin.file:
path: /var/www/{{ site_name }}
state: directory
owner: www-data
group: www-data
mode: "0755"
- name: Supprimer un fichier
ansible.builtin.file:
path: /tmp/ancien_fichier
state: absent
user#
- name: Créer un utilisateur applicatif
ansible.builtin.user:
name: appuser
shell: /bin/bash
groups: sudo
append: true
create_home: true
service et package#
- name: Installer nginx
ansible.builtin.package:
name: nginx
state: present
- name: Démarrer et activer nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
package est un module générique qui délègue à apt, yum, dnf selon la distribution
détectée. On peut aussi appeler ansible.builtin.apt directement pour bénéficier de paramètres
spécifiques comme update_cache: true.
État vs action
Les modules Ansible expriment un état désiré, pas une action. state: started signifie
« assure-toi que le service est démarré », pas « démarre-le ». Si le service tourne déjà, Ansible ne
fait rien et rapporte ok au lieu de changed.
Playbooks#
Structure YAML#
---
- name: Configurer les serveurs web
hosts: webservers
become: true
vars:
site_name: monapp
http_port: 80
handlers:
- name: Recharger nginx
ansible.builtin.service:
name: nginx
state: reloaded
tasks:
- name: Installer nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Déployer la configuration
ansible.builtin.template:
src: templates/nginx.conf.j2
dest: /etc/nginx/sites-available/{{ site_name }}.conf
notify: Recharger nginx
- name: Activer le site
ansible.builtin.file:
src: /etc/nginx/sites-available/{{ site_name }}.conf
dest: /etc/nginx/sites-enabled/{{ site_name }}.conf
state: link
notify: Recharger nginx
Conditions et boucles#
- name: Installer des paquets supplémentaires (Debian uniquement)
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- vim
- htop
- curl
when: ansible_os_family == "Debian"
- name: Créer plusieurs utilisateurs
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
loop:
- { name: alice, groups: sudo }
- { name: bob, groups: dev }
register et debug#
- name: Lire la version de Python
ansible.builtin.command: python3 --version
register: py_version
- name: Afficher la version
ansible.builtin.debug:
msg: "Python détecté : {{ py_version.stdout }}"
Exécuter un playbook#
ansible-playbook -i inventaire/hosts.yml site.yml
ansible-playbook -i inventaire/hosts.yml site.yml --limit webservers
ansible-playbook -i inventaire/hosts.yml site.yml --tags configuration
Variables et templates Jinja2#
Hiérarchie de priorité des variables#
Ansible applique une hiérarchie stricte de priorité (du plus faible au plus fort) :
defaults → group_vars/all → group_vars/<groupe> → host_vars/<hôte> → vars dans le
play → set_fact → options -e en ligne de commande.
group_vars et host_vars#
# group_vars/webservers.yml
nginx_worker_processes: 4
nginx_keepalive_timeout: 65
site_root: /var/www/html
# host_vars/web01.example.com.yml
nginx_worker_processes: 8 # override pour cette machine plus puissante
Templates Jinja2#
Les templates .j2 utilisent la syntaxe Jinja2 : {{ variable }} pour l’interpolation,
{% if %} / {% for %} pour la logique.
# templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};
events {
worker_connections 1024;
}
http {
keepalive_timeout {{ nginx_keepalive_timeout }};
{% for vhost in virtual_hosts %}
server {
listen 80;
server_name {{ vhost.name }};
root {{ vhost.root | default(site_root) }};
}
{% endfor %}
}
Filtres Jinja2 courants#
- name: Mettre en majuscule
debug:
msg: "{{ 'hello' | upper }}" # HELLO
- name: Valeur par défaut
debug:
msg: "{{ ma_var | default('none') }}"
- name: Joindre une liste
debug:
msg: "{{ ['a','b','c'] | join(',') }}" # a,b,c
Rôles#
Structure d’un rôle#
Un rôle est une unité de réutilisation qui regroupe tâches, handlers, templates, fichiers et variables dans une structure conventionnelle.
roles/nginx/
├── defaults/
│ └── main.yml # variables par défaut (priorité minimale)
├── handlers/
│ └── main.yml # handlers déclenchés par notify
├── tasks/
│ └── main.yml # tâches principales
├── templates/
│ └── nginx.conf.j2 # templates Jinja2
├── files/
│ └── index.html # fichiers statiques
├── vars/
│ └── main.yml # variables internes (priorité haute)
└── meta/
└── main.yml # dépendances, metadata galaxy
Utiliser un rôle dans un playbook#
---
- name: Configurer les serveurs web
hosts: webservers
become: true
roles:
- nginx
- { role: certbot, site_name: monapp.fr }
ansible-galaxy#
# Installer un rôle depuis Ansible Galaxy
ansible-galaxy install geerlingguy.nginx
# Initialiser la structure d'un nouveau rôle
ansible-galaxy role init mon_role
# Installer depuis un fichier requirements.yml
ansible-galaxy install -r requirements.yml
# requirements.yml
roles:
- name: geerlingguy.nginx
version: "3.2.0"
- name: geerlingguy.postgresql
collections:
- name: community.postgresql
version: ">=2.0.0"
Idempotence#
Principe#
L’idempotence est la propriété fondamentale d’un playbook Ansible : rejouer le playbook
plusieurs fois produit exactement le même résultat que de l’exécuter une seule fois. Si l’état
désiré est déjà atteint, Ansible ne modifie rien et rapporte ok.
Cette propriété est garantie par les modules bien écrits. Le module apt vérifie si le paquet
est installé avant de l’installer. Le module file vérifie les permissions avant de les modifier.
Idempotence et commandes brutes
Les modules command et shell ne sont pas idempotents par nature. Il faut les combiner
avec creates: ou when: pour éviter les exécutions répétées.
# Non idempotent — crée le répertoire même s'il existe
- ansible.builtin.command: mkdir /opt/app
# Idempotent — crée uniquement si absent
- ansible.builtin.command:
cmd: mkdir /opt/app
creates: /opt/app
Mode –check et –diff#
# Simulation sans modification (dry-run)
ansible-playbook site.yml --check
# Afficher les différences de fichiers modifiés
ansible-playbook site.yml --check --diff
La sortie --diff montre les deltas de fichiers comme un diff -u :
--- before: /etc/nginx/nginx.conf
+++ after: /etc/nginx/nginx.conf
@@ -1,3 +1,3 @@
worker_processes 4;
-keepalive_timeout 65;
+keepalive_timeout 75;
Vault#
Chiffrement de secrets#
Ansible Vault permet de chiffrer des fichiers de variables ou des chaînes individuelles contenant des mots de passe, clés API, certificats, etc.
# Chiffrer un fichier entier
ansible-vault encrypt group_vars/all/secrets.yml
# Déchiffrer pour édition
ansible-vault edit group_vars/all/secrets.yml
# Chiffrer une valeur inline
ansible-vault encrypt_string 'MonMotDePasse!' --name 'db_password'
La commande encrypt_string produit un bloc YAML utilisable directement :
db_password: !vault |
$ANSIBLE_VAULT;1.1;AES256
62613833623333366565393431356166303366636662313564373630613531
...
Utilisation dans les playbooks#
# Fournir le mot de passe vault interactivement
ansible-playbook site.yml --ask-vault-pass
# Fournir via un fichier (pour CI/CD)
ansible-playbook site.yml --vault-password-file ~/.vault_pass
# Fichier .vault_pass sécurisé
chmod 600 ~/.vault_pass
echo 'MotDePasseVault' > ~/.vault_pass
Vault en production
Ne jamais stocker ~/.vault_pass dans git. Dans un pipeline CI/CD, injecter le mot de passe
vault comme variable secrète (GitLab CI, GitHub Actions Secrets, Jenkins credentials). Ansible
Vault n’est pas un gestionnaire de secrets complet ; pour des besoins avancés, intégrer
HashiCorp Vault via le plugin community.hashi_vault.
Résumé#
Ansible rend l’administration de parcs hétérogènes reproductible et auditable. Les concepts clés à retenir :
Concept |
Rôle |
|---|---|
Inventaire |
Liste structurée des hôtes et de leurs variables |
Module |
Unité d’action idempotente (apt, copy, template…) |
Playbook |
Orchestration de tâches sur un ou plusieurs groupes |
Rôle |
Unité de réutilisation packagée |
Handler |
Action déclenchée une seule fois sur notification |
Vault |
Chiffrement symétrique des secrets |
–check |
Simulation sans effet de bord (dry-run) |
La philosophie d’Ansible peut se résumer ainsi : décrire l”état désiré du système, pas la séquence d’opérations pour y parvenir. Cette distinction entre approche déclarative et impérative est fondamentale en IaC.
Prochaine étape
Le chapitre suivant aborde la virtualisation et les conteneurs : KVM/libvirt pour les VMs complètes, LXC/LXD pour les conteneurs système, et la base technologique (namespaces, cgroups) sur laquelle repose Docker.