Credit : Logo officiel
Ansible pour debutants : automatiser sa config serveur
Reinstaller 3 serveurs identiques en 20 minutes au lieu de 3 jours
La semaine derniere j'ai du remonter trois VPS IONOS strictement identiques pour un client : meme distrib (Debian 12), meme stack (Nginx + PHP 8.2 + MariaDB + Redis), meme securisation (CrowdSec, Fail2ban, SSH durci, certificats SSL). Sans automatisation j'aurais facilement passe une journee complete a configurer chaque serveur a la main. Avec Ansible, j'ai branche les trois IPs dans mon inventaire, lance le playbook a 14h32, et a 14h53 les trois serveurs etaient prets a recevoir leur application. Cafe compris.
C'est ca le pouvoir d'Ansible. C'est un outil d'automatisation qui se connecte en SSH, execute des taches Python, et garantit que vos serveurs convergent vers l'etat decrit dans vos playbooks. Pas d'agent a installer cote serveur (juste Python qui est deja la sur toute distribution Linux moderne), pas d'infrastructure complexe a maintenir, juste un dossier git avec vos fichiers YAML et c'est parti.
Dans ce guide je couvre l'installation, l'inventaire, les premiers playbooks utiles, l'organisation en roles, et les modules que j'utilise au quotidien. C'est exactement la progression que je conseille a mes collegues qui demarrent.
Pourquoi Ansible plutot qu'autre chose
Il existe d'autres outils : Puppet, Chef, Salt, et plus recemment des trucs comme Pulumi pour l'infra cloud. Ansible a quelques avantages pratiques :
- Agentless : juste SSH + Python sur les machines cibles. Pas de daemon a installer, pas de port a ouvrir.
- YAML : la syntaxe est lisible meme pour quelqu'un qui ne connait pas l'outil. Vos playbooks sont auto-documentes.
- Idempotent : vous pouvez relancer le meme playbook 100 fois, il ne refait que ce qui a besoin d'etre fait.
- Modulaire : enorme bibliotheque de modules pour gerer paquets, services, fichiers, utilisateurs, cloud providers, etc.
Le seul vrai concurrent moderne c'est Saltstack, mais Ansible reste largement plus repandu donc plus de tutos, plus d'exemples sur Stack Overflow, plus de roles pre-faits sur Ansible Galaxy.
Installation d'Ansible
Ansible s'installe sur votre machine locale (votre laptop ou un serveur de management dedie), PAS sur les serveurs cibles. Les serveurs cibles n'ont besoin de rien de special.
# Sur Debian/Ubuntu local
sudo apt update
sudo apt install pipx
pipx install ansible-core
pipx install ansible-lint
# Verifier la version
ansible --version
# ansible [core 2.16.3]
# python version = 3.11.2
pipx isole Ansible dans son propre venv Python, ca evite les conflits de dependances avec d'autres outils Python systeme. C'est la methode recommandee depuis 2024.
Sur macOS :
brew install ansible
Sur les serveurs cibles, verifiez juste que Python 3 est installe :
ssh deploy@web1 "python3 --version"
# Python 3.11.2
L'inventaire : la liste de vos serveurs
L'inventaire c'est un fichier YAML qui liste vos serveurs et les groupe par role. Creez inventory.yml dans un dossier dedie :
all:
children:
webservers:
hosts:
web1:
ansible_host: 203.0.113.10
ansible_user: deploy
web2:
ansible_host: 203.0.113.11
ansible_user: deploy
vars:
domain_name: monsite.fr
php_version: "8.2"
databases:
hosts:
db1:
ansible_host: 203.0.113.20
ansible_user: deploy
vars:
mariadb_buffer_pool: 2G
production:
children:
webservers:
databases:
vars:
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
Les groupes peuvent contenir d'autres groupes (production contient webservers et databases), ce qui permet de cibler des sous-ensembles dans vos playbooks.
Testez la connectivite avec le module ping :
ansible all -i inventory.yml -m ping
Resultat attendu :
web1 | SUCCESS => { "ping": "pong" }
web2 | SUCCESS => { "ping": "pong" }
db1 | SUCCESS => { "ping": "pong" }
Si vous avez du rouge, c'est presque toujours un probleme de cle SSH. Verifiez avec un ssh deploy@web1 direct, et confirmez que vous arrivez sans mot de passe (avec authentification par cle).
Le fichier ansible.cfg
A la racine du projet, creez ansible.cfg pour eviter de retaper les options a chaque commande :
[defaults]
inventory = ./inventory.yml
host_key_checking = False
stdout_callback = yaml
bin_ansible_callbacks = True
retry_files_enabled = False
forks = 10
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True accelere significativement Ansible (parfois 2x plus rapide). Pour que ca marche, il faut que requiretty soit desactive dans sudoers cote serveur, ce qui est le cas par defaut sur Debian/Ubuntu modernes.
Votre premier playbook
Un playbook decrit l'etat souhaite de vos serveurs. Creez setup-web.yml :
---
- name: Configuration de base des serveurs web
hosts: webservers
become: yes
gather_facts: yes
vars:
packages:
- nginx
- php8.2-fpm
- php8.2-mysql
- php8.2-curl
- php8.2-xml
- php8.2-mbstring
- php8.2-zip
- php8.2-gd
- php8.2-imagick
- certbot
- python3-certbot-nginx
- fail2ban
- unattended-upgrades
tasks:
- name: Mettre a jour le cache APT
apt:
update_cache: yes
cache_valid_time: 3600
- name: Mettre a jour tous les paquets
apt:
upgrade: dist
- name: Installer les paquets requis
apt:
name: "{{ packages }}"
state: present
- name: Demarrer et activer Nginx
systemd:
name: nginx
state: started
enabled: yes
- name: Demarrer et activer PHP-FPM
systemd:
name: "php{{ php_version }}-fpm"
state: started
enabled: yes
- name: Creer le repertoire du site
file:
path: "/var/www/{{ domain_name }}/public"
state: directory
owner: deploy
group: www-data
mode: '0755'
- name: Deployer la config Nginx du site
template:
src: templates/nginx-site.conf.j2
dest: "/etc/nginx/sites-available/{{ domain_name }}.conf"
owner: root
group: root
mode: '0644'
notify: Recharger Nginx
- name: Activer le site
file:
src: "/etc/nginx/sites-available/{{ domain_name }}.conf"
dest: "/etc/nginx/sites-enabled/{{ domain_name }}.conf"
state: link
notify: Recharger Nginx
- name: Tester la config Nginx
command: nginx -t
changed_when: false
handlers:
- name: Recharger Nginx
systemd:
name: nginx
state: reloaded
Lancez :
ansible-playbook setup-web.yml
L'idempotence c'est la cle : vous pouvez relancer ce playbook 100 fois, Ansible ne reinstalle pas Nginx s'il est deja installe, ne recree pas le repertoire s'il existe deja, ne recharge Nginx que si la config a effectivement change. Ca rend les operations sures.
Le mode dry-run
Avant d'appliquer en prod, simulez :
# Dry-run : montre ce qui changerait sans rien faire
ansible-playbook setup-web.yml --check
# Avec diff sur les fichiers modifies
ansible-playbook setup-web.yml --check --diff
# Limiter a une machine seulement
ansible-playbook setup-web.yml --limit web1
Le --diff montre exactement les changements ligne par ligne sur les fichiers de config. Indispensable pour valider avant de pousser en prod.
Les templates Jinja2
Les templates permettent de generer des fichiers de config avec des variables. Creez templates/nginx-site.conf.j2 :
server {
listen 80;
listen [::]:80;
server_name {{ domain_name }} www.{{ domain_name }};
root /var/www/{{ domain_name }}/public;
index index.php index.html;
access_log /var/log/nginx/{{ domain_name }}.access.log;
error_log /var/log/nginx/{{ domain_name }}.error.log;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php{{ php_version }}-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location ~ /\.ht {
deny all;
}
{% if enable_gzip | default(true) %}
gzip on;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
gzip_vary on;
{% endif %}
}
Les {{ variable }} sont remplacees par leur valeur depuis l'inventaire ou les vars du playbook. Les {% if %} permettent de la logique conditionnelle. Tres puissant pour generer des configs adaptees au context.
Organiser le code en roles
Quand vos playbooks grossissent, regroupez la logique en roles reutilisables. Structure standard :
project/
ansible.cfg
inventory.yml
site.yml # Playbook principal
group_vars/
all.yml
webservers.yml
roles/
common/
tasks/main.yml
handlers/main.yml
defaults/main.yml
nginx/
tasks/main.yml
templates/
nginx.conf.j2
site.conf.j2
handlers/main.yml
defaults/main.yml
php/
tasks/main.yml
defaults/main.yml
mariadb/
tasks/main.yml
defaults/main.yml
Un role minimal roles/nginx/tasks/main.yml :
---
- name: Installer Nginx
apt:
name: nginx
state: present
- name: Deployer la config principale
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
notify: Tester et recharger Nginx
- name: S'assurer que Nginx tourne
systemd:
name: nginx
state: started
enabled: yes
roles/nginx/handlers/main.yml :
---
- name: Tester et recharger Nginx
block:
- name: Test config
command: nginx -t
changed_when: false
- name: Reload
systemd:
name: nginx
state: reloaded
roles/nginx/defaults/main.yml :
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: 64m
Utiliser le role dans site.yml :
---
- hosts: webservers
become: yes
roles:
- common
- nginx
- php
- certbot
- fail2ban
Les defaults peuvent etre surcharges depuis group_vars/webservers.yml ou directement dans le playbook. C'est la flexibilite du systeme.
Modules essentiels au quotidien
Voici les modules que j'utilise quasi tous les jours, avec un exemple pour chacun :
# Gestion de paquets
- name: Installer plusieurs paquets
apt:
name: [git, vim, htop, ncdu]
state: present
update_cache: yes
# Fichiers et repertoires
- name: Creer un repertoire
file:
path: /var/www/monsite
state: directory
owner: deploy
group: www-data
mode: '0755'
- name: Copier un fichier
copy:
src: files/script.sh
dest: /usr/local/bin/script.sh
mode: '0755'
- name: Modifier une ligne dans un fichier (ULTRA UTILE)
lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
notify: Restart sshd
- name: Modifier un bloc de lignes
blockinfile:
path: /etc/sysctl.conf
block: |
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv6.conf.all.disable_ipv6 = 1
# Utilisateurs
- name: Creer un user de deploiement
user:
name: deploy
shell: /bin/bash
groups: sudo,www-data
append: yes
create_home: yes
- name: Ajouter une cle SSH
authorized_key:
user: deploy
state: present
key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"
# Cron
- name: Backup quotidien
cron:
name: 'Backup MySQL quotidien'
hour: '3'
minute: '0'
job: '/usr/local/bin/mysql-backup.sh'
user: root
# Services
- name: S'assurer qu'un service tourne
systemd:
name: redis-server
state: started
enabled: yes
# Telechargement
- name: Telecharger WP-CLI
get_url:
url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
dest: /usr/local/bin/wp
mode: '0755'
# Git
- name: Cloner un repo
git:
repo: 'https://github.com/user/projet.git'
dest: /var/www/projet
version: main
force: yes
Le module lineinfile est mon prefere. Il permet de modifier UNE ligne dans un fichier de config sans tout reecrire. Parfait pour durcir SSH, modifier php.ini, ajouter des entries dans /etc/hosts, etc.
Variables, vault et secrets
Ne mettez JAMAIS de mot de passe en clair dans vos playbooks. Utilisez Ansible Vault :
# Creer un fichier de secrets chiffre
ansible-vault create group_vars/all/vault.yml
# (entrer un mot de passe maitre)
# Editer
ansible-vault edit group_vars/all/vault.yml
# Le contenu (chiffre sur disque)
vault_db_password: "MotDePasseTresSolide2026!"
vault_admin_email: "admin@monsite.fr"
Utilisation dans les playbooks :
vars:
mariadb_root_password: "{{ vault_db_password }}"
Lancement avec le mot de passe vault :
ansible-playbook site.yml --ask-vault-pass
# ou pointer vers un fichier de mot de passe
ansible-playbook site.yml --vault-password-file ~/.ansible-vault-pass
Le fichier vault peut etre commit dans git en toute securite, le mot de passe maitre reste hors du depot.
Erreurs courantes et leur fix
"UNREACHABLE" lors du ansible all -m ping. C'est presque toujours SSH. Verifiez avec ssh deploy@web1 directement. Si ca demande un mot de passe, copiez votre cle avec ssh-copy-id. Si l'host n'est pas connu, ajoutez -o StrictHostKeyChecking=accept-new dans ansible.cfg.
"sudo: a password is required". Le user de deploiement n'a pas sudo NOPASSWD. Soit configurez /etc/sudoers.d/deploy avec deploy ALL=(ALL) NOPASSWD: ALL, soit lancez avec --ask-become-pass (-K) pour entrer le mot de passe sudo.
Module Python manquant sur le serveur cible. Certains modules Ansible necessitent des bibliotheques Python sur la cible (ex: mysql_user necessite python3-pymysql). Installez-les avec une task apt avant.
"failed - retrying: ...". Reseau instable ou serveur surcharge. Augmentez les timeouts dans ansible.cfg :
[defaults]
timeout = 60
[ssh_connection]
ssh_args = -o ConnectTimeout=30 -o ServerAliveInterval=30
Vault refuse de dechiffrer. Mauvais mot de passe ou fichier corrompu. Verifiez avec ansible-vault view puis re-chiffrez si necessaire.
Pour aller plus loin
- Securiser SSH avec sshd_config
- Bases de systemd et services Linux
- Deployer une application avec GitHub Actions
- Reverse proxy Nginx avec SSL
- Hardening Linux, securiser un serveur
Conclusion : 30 minutes investies, 30 heures economisees
Ansible parait overkill quand on a un seul serveur a configurer. Effectivement, pour une seule machine que vous touchez deux fois par an, taper les commandes a la main reste plus rapide. Mais des que vous avez deux serveurs a maintenir (ce qui est tres vite le cas), ou que vous voulez pouvoir reconstruire rapidement en cas de probleme, Ansible devient indispensable.
Le vrai declic, c'est quand vous realisez que votre playbook devient une documentation vivante de votre infrastructure. Quelqu'un peut reprendre la gestion de vos serveurs juste en lisant vos fichiers YAML. Ca change la donne pour la perennite des projets, surtout en agence ou les equipes tournent.
Commencez petit : un playbook qui installe Nginx et PHP. Ajoutez progressivement. Six mois plus tard vous aurez 15 roles, vous deploierez une nouvelle infrastructure complete en 20 minutes, et vous vous demanderez comment vous faisiez avant. Promesse de quelqu'un qui a fait le chemin.