Gérer la configuration des serveurs avec Ansible
Ceci couvre comment structurer les inventaires, rôles et playbooks Ansible pour que la configuration des serveurs reste idempotente et relisable, avec un playbook de durcissement SSH et Nginx comme exemple fil rouge, plus des secrets chiffrés avec Vault.
Inventaire : hôtes statiques plus group_vars/host_vars
Résistez à l'envie de mettre les valeurs de configuration directement dans les playbooks. L'inventaire indique à Ansible quels hôtes existent et comment ils sont regroupés ; group_vars et host_vars indiquent ce qui est vrai pour chaque groupe ou chaque hôte. Cette séparation permet au même playbook de tourner sans risque contre la staging et contre la production.
# inventory/production.ini
[web]
web-01.example.internal
web-02.example.internal
[db]
db-01.example.internal
[web:vars]
nginx_worker_connections=2048
[production:children]
web
dbinventory/
├── production.ini
├── staging.ini
├── group_vars/
│ ├── all.yml
│ ├── web.yml
│ └── db.yml
└── host_vars/
└── db-01.example.internal.ymlgroup_vars/all.yml contient les valeurs valables partout (serveurs NTP, clé SSH d'administration, durée de rétention des logs). group_vars/web.yml contient les valeurs valables pour chaque node web (réglages Nginx, port de l'application). host_vars/<hostname>.yml sert pour les cas réellement particuliers. Et si ce fichier commence à accumuler plus de deux ou trois clés, c'est généralement le signe que l'hôte n'a pas sa place dans son groupe actuel.
Structure des rôles : une responsabilité par rôle
Un rôle est l'unité de réutilisation. Chacun doit faire exactement une chose : « durcir SSH » ou « installer et configurer Nginx », et non « monter un serveur web », formulation qui finit inévitablement par grossir en un tas sans structure.
roles/
└── nginx/
├── tasks/
│ └── main.yml
├── handlers/
│ └── main.yml
├── templates/
│ └── nginx.conf.j2
├── defaults/
│ └── main.yml
└── files/
└── snakeoil-fallback.confdefaults/main.yml fixe des valeurs raisonnables que l'appelant peut surcharger depuis group_vars. tasks/main.yml contient le travail proprement dit. handlers/main.yml contient les actions qui ne s'exécutent que lorsque quelque chose a réellement changé.
Un playbook réel : durcissement SSH et Nginx
Voici un exemple allégé mais fonctionnel qui couvre les deux besoins : verrouiller SSH et déployer Nginx à partir d'un template.
# playbooks/site.yml
---
- name: Harden SSH and configure web servers
hosts: web
become: true
roles:
- ssh_hardening
- nginx# roles/ssh_hardening/tasks/main.yml
---
- name: Disable password authentication
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
validate: '/usr/sbin/sshd -T -f %s'
notify: Restart sshd
- name: Disable root login
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
validate: '/usr/sbin/sshd -T -f %s'
notify: Restart sshd# roles/ssh_hardening/handlers/main.yml
---
- name: Restart sshd
ansible.builtin.service:
name: sshd
state: restartedLe module lineinfile associé à validate fait ici un vrai travail. Il ne réécrit le fichier que si la ligne diffère, et il refuse d'enregistrer une configuration qui empêcherait sshd de démarrer. Ça compte quand la ressource modifiée est celle par laquelle vous vous connectez en SSH. notify met le handler en file d'attente, mais les handlers ne se déclenchent qu'à la fin du play, et une seule fois, peu importe le nombre de tâches qui les notifient.
Le rôle Nginx applique la même logique, avec un template à la place d'éditions de lignes :
# roles/nginx/tasks/main.yml
---
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
validate: 'nginx -t -c %s'
notify: Reload nginx
- name: Ensure nginx is running and enabled
ansible.builtin.service:
name: nginx
state: started
enabled: true# roles/nginx/templates/nginx.conf.j2
user www-data;
worker_processes auto;
events {
worker_connections {{ nginx_worker_connections }};
}
http {
server_tokens off;
keepalive_timeout {{ nginx_keepalive_timeout | default(65) }};
server {
listen 80;
server_name {{ inventory_hostname }};
root {{ nginx_document_root }};
location / {
try_files $uri $uri/ =404;
}
}
}Le module template rend le Jinja2 avec les variables de l'hôte, compare le résultat à ce qui existe déjà sur le disque, et ne remonte un changement (et ne déclenche notify) que si le fichier généré diffère réellement. Relancez ce playbook une seconde fois sans rien changer en amont : chaque tâche doit remonter ok, jamais changed. Si ce n'est pas le cas, une partie du rôle n'est pas encore vraiment idempotente.
Dégainer shell ou command avant de vérifier s'il existe un module adapté, et vous perdez l'idempotence par défaut. Ansible n'a aucun moyen de savoir si shell: nginx -s reload doit s'exécuter à nouveau, donc il l'exécute à chaque fois et remonte changed quel que soit l'état réel. Si shell est vraiment incontournable, associez-le à creates, removes, ou à une condition changed_when pour que la tâche puisse honnêtement indiquer si elle a fait quelque chose.
Secrets : Ansible Vault dans group_vars
Mots de passe de base de données, clés d'API, clés privées TLS n'ont rien à faire en clair dans du YAML, même dans un dépôt privé. Vault règle ce problème en chiffrant les valeurs sur place tout en les gardant accessibles exactement comme n'importe quelle autre variable.
ansible-vault encrypt_string 'S3cr3tP@ss' --name 'db_password' \
>> inventory/group_vars/db/vault.ymlJe garde en général un group_vars/db/vars.yml en clair pour les valeurs non sensibles, et un group_vars/db/vault.yml séparé pour les valeurs chiffrées. Ansible fusionne automatiquement tout ce qui se trouve sous group_vars/db/*, donc un rôle référence simplement {{ db_password }} sans se soucier du fichier d'où elle provient. Exécuter le playbook nécessite alors seulement --ask-vault-pass, ou --vault-password-file pointant vers un fichier qui, lui, ne fait jamais partie du dépôt.
En CI, stockez le mot de passe Vault comme secret de pipeline et écrivez-le dans un fichier temporaire au démarrage du job (echo "$VAULT_PASSWORD" > /tmp/vault-pass), puis passez --vault-password-file /tmp/vault-pass. Ne l'affichez jamais dans les logs, et supprimez ce fichier dans une étape after_script. Un mot de passe Vault qui fuite déchiffre tous les secrets du dépôt.
Tags et --check pour un déploiement sans risque
Sur un parc de taille un tant soit peu réelle, vous voulez rarement exécuter tout le playbook à chaque fois ; vous voulez recharger les configurations Nginx sur tout le parc sans toucher aux réglages SSH, ou l'inverse. Les tags donnent ce contrôle sans avoir à éclater le playbook en une dizaine de fichiers.
- name: Deploy nginx configuration
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Reload nginx
tags:
- nginx
- config# uniquement les tâches taguées nginx
ansible-playbook -i inventory/production.ini playbooks/site.yml --tags nginx
# prévisualiser tous les changements, n'en appliquer aucun
ansible-playbook -i inventory/production.ini playbooks/site.yml --check --diff--check exécute le playbook sans rien modifier et indique ce qui se produirait ; --diff ajoute le texte avant/après réel pour tout ce que template ou copy réécrirait. Je fais tourner chaque playbook avec --check --diff contre la production en premier, jusqu'à ce que l'exécution réelle devienne une routine pour l'équipe d'un client.
Migrer des scripts bricolés vers des rôles est une décision qui coûte moins cher avant que le parc n'atteigne cinquante hôtes qu'après.
Envie de faire tourner ça en production ?
Ce tutoriel couvre les concepts et l'architecture. Si vous voulez l'implémenter dans votre propre infrastructure, ou monter en compétence pour posséder ce sujet durablement, je propose du mentorat individuel construit autour de votre environnement réel, pas une formation générique.
Ce tutoriel
- Concepts clés et architecture principale
- Extraits de code illustratifs
- Le raisonnement derrière chaque décision
Mentorat individuel
- Des sessions de travail sur votre propre environnement
- Des réponses directes aux cas particuliers que vous rencontrez
- Un retour sur votre implémentation réelle
- Un accompagnement continu pendant que vous la construisez