Qu'est-ce qu'un Playbook Ansible ?
Un playbook Ansible est un fichier YAML qui décrit de manière déclarative l'état désiré de vos systèmes et les actions nécessaires pour y parvenir. C'est le cœur d'Ansible, l'élément qui transforme un simple outil de commandes à distance en une véritable plateforme d'automatisation d'infrastructure.
Si les commandes ad-hoc sont l'équivalent de commandes bash individuelles, les playbooks sont l'équivalent de scripts complets, mais en beaucoup plus puissant. Un playbook peut orchestrer des déploiements multi-serveurs, gérer des configurations complexes, et garantir l'idempotence de chaque opération.
Architecture en un coup d'œil

Dans ce guide complet, nous allons explorer en profondeur tous les aspects des playbooks Ansible : leur structure, les modules essentiels, les variables, les conditionnels, les boucles, les handlers, les templates Jinja2 et bien plus encore. À la fin de cet article, vous serez capable d'écrire des playbooks professionnels pour automatiser n'importe quelle tâche d'administration système.
Structure d'un Playbook : Plays, Tasks et Handlers
Un playbook est composé d'un ou plusieurs plays. Chaque play cible un groupe de machines et contient une liste de tasks (tâches) à exécuter. Voici la structure fondamentale :
---
# Un playbook peut contenir plusieurs plays
- name: Premier play - Configuration des serveurs web
hosts: webservers
become: true
gather_facts: true
vars:
http_port: 80
tasks:
- name: Première tâche
ansible.builtin.debug:
msg: "Configuration du serveur web en cours..."
- name: Deuxième tâche
ansible.builtin.apt:
name: nginx
state: present
handlers:
- name: Redémarrer Nginx
ansible.builtin.service:
name: nginx
state: restarted
- name: Deuxième play - Configuration des bases de données
hosts: databases
become: true
tasks:
- name: Installer PostgreSQL
ansible.builtin.apt:
name: postgresql
state: presentAnatomie d'un Play
Chaque play contient les éléments suivants :
- name : description lisible du play (fortement recommandé)
- hosts : les machines cibles (groupe d'inventaire, pattern ou
all) - become : activer l'élévation de privilèges (sudo)
- gather_facts : collecter les informations système (
truepar défaut) - vars : variables locales au play
- tasks : liste ordonnée des tâches à exécuter
- handlers : tâches déclenchées uniquement sur notification
- pre_tasks : tâches exécutées avant les rôles
- post_tasks : tâches exécutées après les rôles
- roles : liste des rôles à appliquer
Anatomie d'une Task
Chaque tâche utilise un module Ansible et peut inclure des paramètres supplémentaires :
tasks:
- name: Description claire de la tâche
ansible.builtin.module_name:
parametre1: valeur1
parametre2: valeur2
become: true # Élévation de privilèges
when: condition # Condition d'exécution
register: variable_result # Stocker le résultat
notify: Nom du handler # Déclencher un handler
tags: # Tags pour filtrage
- configuration
- nginx
ignore_errors: true # Ignorer les erreurs
changed_when: false # Contrôler le statut "changed"Les modules essentiels d'Ansible
Ansible dispose de milliers de modules, mais certains sont utilisés quotidiennement par la majorité des administrateurs. Voici les modules incontournables que vous devez maîtriser.
Gestion des paquets : apt, yum, dnf et package
tasks:
# Module apt (Debian/Ubuntu)
- name: Installer un paquet avec apt
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Installer plusieurs paquets
ansible.builtin.apt:
name:
- nginx
- certbot
- python3-certbot-nginx
- htop
- vim
state: present
- name: Supprimer un paquet
ansible.builtin.apt:
name: apache2
state: absent
purge: true
- name: Mettre à jour tous les paquets
ansible.builtin.apt:
upgrade: dist
update_cache: true
# Module yum (CentOS/RHEL 7)
- name: Installer avec yum
ansible.builtin.yum:
name: httpd
state: present
# Module dnf (CentOS/RHEL 8+, Fedora)
- name: Installer avec dnf
ansible.builtin.dnf:
name: httpd
state: latest
# Module package (multi-distribution)
- name: Installer un paquet (indépendant de la distribution)
ansible.builtin.package:
name: git
state: presentGestion des fichiers : copy, template et file
tasks:
# Module copy - Copier un fichier local vers la cible
- name: Copier un fichier de configuration
ansible.builtin.copy:
src: files/nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: true
notify: Redémarrer Nginx
# Module copy - Créer un fichier avec du contenu inline
- name: Créer un fichier avec du contenu
ansible.builtin.copy:
content: |
# Configuration générée par Ansible
server_name {{ ansible_hostname }};
listen {{ http_port }};
dest: /etc/nginx/conf.d/default.conf
owner: root
group: root
mode: '0644'
# Module template - Utiliser un template Jinja2
- name: Déployer un template de configuration
ansible.builtin.template:
src: templates/vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ site_name }}.conf"
owner: root
group: root
mode: '0644'
validate: nginx -t -c %s
notify: Recharger Nginx
# Module file - Gérer les fichiers et répertoires
- name: Créer un répertoire
ansible.builtin.file:
path: /var/www/mon-site
state: directory
owner: www-data
group: www-data
mode: '0755'
recurse: true
- name: Créer un lien symbolique
ansible.builtin.file:
src: /etc/nginx/sites-available/mon-site.conf
dest: /etc/nginx/sites-enabled/mon-site.conf
state: link
- name: Supprimer un fichier
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absentGestion des services : service et systemd
tasks:
# Module service (compatible avec tous les systèmes d'init)
- name: Démarrer et activer Nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Redémarrer un service
ansible.builtin.service:
name: nginx
state: restarted
- name: Recharger la configuration sans redémarrer
ansible.builtin.service:
name: nginx
state: reloaded
# Module systemd (spécifique à systemd)
- name: Recharger les unités systemd après modification
ansible.builtin.systemd:
daemon_reload: true
- name: Activer et démarrer un service avec systemd
ansible.builtin.systemd:
name: nginx
state: started
enabled: true
masked: falseGestion des utilisateurs : user et group
tasks:
- name: Créer un groupe
ansible.builtin.group:
name: deployers
gid: 1500
state: present
- name: Créer un utilisateur
ansible.builtin.user:
name: deploy
comment: "Utilisateur de déploiement"
uid: 1500
group: deployers
groups:
- sudo
- www-data
shell: /bin/bash
home: /home/deploy
create_home: true
generate_ssh_key: true
ssh_key_bits: 4096
state: present
- name: Ajouter une clé SSH autorisée
ansible.posix.authorized_key:
user: deploy
key: "{{ lookup('file', '~/.ssh/deploy_key.pub') }}"
state: presentExécution de commandes : command, shell et raw
tasks:
# Module command - Exécuter une commande (sans shell)
- name: Vérifier la version de Node.js
ansible.builtin.command: node --version
register: node_version
changed_when: false
failed_when: false
# Module shell - Exécuter via le shell (permet pipes, redirections)
- name: Compter les processus Nginx
ansible.builtin.shell: ps aux | grep nginx | grep -v grep | wc -l
register: nginx_processes
changed_when: false
# Module raw - Exécuter sans Python (utile pour bootstrap)
- name: Installer Python sur une machine vierge
ansible.builtin.raw: apt-get install -y python3
changed_when: falseModification de fichiers : lineinfile et blockinfile
tasks:
# Module lineinfile - Modifier une ligne dans un fichier
- name: S'assurer que le port SSH est configuré
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port '
line: 'Port 2222'
state: present
backup: true
notify: Redémarrer SSH
- name: Désactiver l'authentification par mot de passe SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
state: present
notify: Redémarrer SSH
- name: Ajouter une entrée dans /etc/hosts
ansible.builtin.lineinfile:
path: /etc/hosts
line: '192.168.1.50 app.codeclan.local'
state: present
# Module blockinfile - Insérer un bloc de texte
- name: Ajouter un bloc de configuration
ansible.builtin.blockinfile:
path: /etc/nginx/nginx.conf
insertafter: 'http {'
marker: "# {mark} ANSIBLE MANAGED - Rate limiting"
block: |
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;Les variables dans Ansible
Les variables sont un élément fondamental des playbooks Ansible. Elles permettent de rendre vos playbooks dynamiques, réutilisables et adaptables à différents environnements.
Définir des variables dans le playbook (vars)
---
- name: Déploiement avec variables
hosts: webservers
become: true
vars:
app_name: mon-application
app_version: "2.4.1"
app_port: 8080
app_user: appuser
app_directory: "/opt/{{ app_name }}"
allowed_ips:
- 192.168.1.0/24
- 10.0.0.0/8
database:
host: db.codeclan.fr
port: 5432
name: app_production
user: app_db_user
tasks:
- name: Créer le répertoire de l'application
ansible.builtin.file:
path: "{{ app_directory }}"
state: directory
owner: "{{ app_user }}"
mode: '0755'
- name: Afficher la configuration de la base de données
ansible.builtin.debug:
msg: "Connexion à {{ database.host }}:{{ database.port }}/{{ database.name }}"Variables dans des fichiers externes (vars_files)
---
- name: Playbook avec fichiers de variables
hosts: webservers
become: true
vars_files:
- vars/common.yml
- vars/{{ env }}.yml
- vars/secrets.yml # Chiffré avec ansible-vault
tasks:
- name: Afficher l'environnement
ansible.builtin.debug:
msg: "Déploiement en {{ env }} - Version {{ app_version }}"Exemple de fichier vars/production.yml :
# vars/production.yml
---
env: production
app_version: "2.4.1"
debug_mode: false
log_level: warn
replicas: 3
ssl_enabled: true
domain_name: app.codeclan.fr
monitoring_enabled: trueRegister : capturer le résultat d'une tâche
Le mot-clé register permet de stocker le résultat d'une tâche dans une variable, utilisable ensuite dans les tâches suivantes :
tasks:
- name: Vérifier si Nginx est installé
ansible.builtin.command: which nginx
register: nginx_check
failed_when: false
changed_when: false
- name: Afficher le résultat
ansible.builtin.debug:
msg: "Nginx est {{ 'installé' if nginx_check.rc == 0 else 'absent' }}"
- name: Afficher les détails complets du résultat
ansible.builtin.debug:
var: nginx_check
- name: Installer Nginx seulement s'il n'est pas présent
ansible.builtin.apt:
name: nginx
state: present
when: nginx_check.rc != 0
- name: Lire le contenu d'un fichier
ansible.builtin.command: cat /etc/hostname
register: hostname_content
changed_when: false
- name: Utiliser le contenu du fichier
ansible.builtin.debug:
msg: "Le nom d'hôte est : {{ hostname_content.stdout }}"Les Facts Ansible
Les facts sont des variables collectées automatiquement par Ansible sur chaque machine cible. Ils fournissent une mine d'informations sur le système : distribution, version du noyau, adresses IP, mémoire, disques, interfaces réseau, etc.
Collecte et utilisation des facts
---
- name: Utilisation des facts
hosts: all
gather_facts: true # Activé par défaut
tasks:
- name: Afficher les informations système
ansible.builtin.debug:
msg: |
Hostname: {{ ansible_hostname }}
FQDN: {{ ansible_fqdn }}
Distribution: {{ ansible_distribution }} {{ ansible_distribution_version }}
Famille OS: {{ ansible_os_family }}
Noyau: {{ ansible_kernel }}
Architecture: {{ ansible_architecture }}
Processeurs: {{ ansible_processor_vcpus }}
Mémoire totale: {{ ansible_memtotal_mb }} MB
Adresse IPv4: {{ ansible_default_ipv4.address }}
Interface réseau: {{ ansible_default_ipv4.interface }}
- name: Actions conditionnelles basées sur les facts
ansible.builtin.apt:
update_cache: true
when: ansible_os_family == "Debian"
- name: Configuration spécifique à Ubuntu 22.04
ansible.builtin.debug:
msg: "Configuration spécifique à Ubuntu 22.04 Jammy"
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version == "22.04"Désactiver la collecte des facts
La collecte des facts prend quelques secondes par hôte. Si vous n'en avez pas besoin, vous pouvez la désactiver pour accélérer l'exécution :
---
- name: Playbook rapide sans facts
hosts: all
gather_facts: false
tasks:
- name: Simple vérification
ansible.builtin.ping:Les conditionnels avec when
Le mot-clé when permet d'exécuter une tâche uniquement si une condition est remplie. Les conditions sont exprimées en Jinja2 et supportent les opérateurs Python.
tasks:
# Condition simple
- name: Installer Nginx sur Debian/Ubuntu
ansible.builtin.apt:
name: nginx
state: present
when: ansible_os_family == "Debian"
# Condition avec ET (and)
- name: Installer sur Ubuntu 22.04 uniquement
ansible.builtin.apt:
name: nginx
state: present
when:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version == "22.04"
# Équivalent à : when: ansible_distribution == "Ubuntu" and ansible_distribution_version == "22.04"
# Condition avec OU (or)
- name: Installer sur Debian ou Ubuntu
ansible.builtin.apt:
name: nginx
state: present
when: ansible_distribution == "Debian" or ansible_distribution == "Ubuntu"
# Condition basée sur une variable
- name: Activer SSL si demandé
ansible.builtin.template:
src: ssl.conf.j2
dest: /etc/nginx/conf.d/ssl.conf
when: ssl_enabled | default(false)
# Condition basée sur le résultat d'une tâche précédente
- name: Vérifier si le fichier de config existe
ansible.builtin.stat:
path: /etc/app/config.yml
register: config_file
- name: Créer la configuration par défaut si absente
ansible.builtin.template:
src: config.yml.j2
dest: /etc/app/config.yml
when: not config_file.stat.exists
# Condition sur une liste
- name: Installer uniquement sur les serveurs de production
ansible.builtin.apt:
name: monitoring-agent
state: present
when: inventory_hostname in groups['production']
# Condition avec is defined
- name: Configurer le proxy si défini
ansible.builtin.lineinfile:
path: /etc/environment
line: "http_proxy={{ proxy_url }}"
when: proxy_url is defined
# Condition numérique
- name: Alerte si mémoire insuffisante
ansible.builtin.debug:
msg: "ATTENTION : Mémoire faible ({{ ansible_memtotal_mb }} MB)"
when: ansible_memtotal_mb < 2048Les boucles : loop et with_items
Les boucles permettent de répéter une tâche pour chaque élément d'une liste, évitant ainsi la duplication de code.
La directive loop (recommandée)
tasks:
# Boucle simple sur une liste
- name: Installer plusieurs paquets
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- certbot
- htop
- vim
- curl
- wget
# Boucle sur une liste de dictionnaires
- name: Créer plusieurs utilisateurs
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell | default('/bin/bash') }}"
state: present
loop:
- { name: alice, groups: "sudo,developers" }
- { name: bob, groups: "developers" }
- { name: charlie, groups: "sudo,devops" }
# Boucle sur une variable
- name: Ouvrir les ports du pare-feu
community.general.ufw:
rule: allow
port: "{{ item.port }}"
proto: "{{ item.proto }}"
loop: "{{ firewall_rules }}"
# où firewall_rules est défini dans vars:
# firewall_rules:
# - { port: 22, proto: tcp }
# - { port: 80, proto: tcp }
# - { port: 443, proto: tcp }
# Boucle avec index
- name: Créer des fichiers numérotés
ansible.builtin.copy:
content: "Fichier numéro {{ index }}"
dest: "/tmp/fichier_{{ index }}.txt"
loop: "{{ range(1, 6) | list }}"
loop_control:
loop_var: index
# Boucle avec label personnalisé (pour une sortie propre)
- name: Déployer les configurations
ansible.builtin.template:
src: "vhost.conf.j2"
dest: "/etc/nginx/sites-available/{{ item.domain }}.conf"
loop: "{{ virtual_hosts }}"
loop_control:
label: "{{ item.domain }}"L'ancienne syntaxe with_items (encore supportée)
tasks:
# with_items - équivalent de loop
- name: Installer des paquets
ansible.builtin.apt:
name: "{{ item }}"
state: present
with_items:
- nginx
- postgresql
# with_dict - itérer sur un dictionnaire
- name: Configurer les variables d'environnement
ansible.builtin.lineinfile:
path: /etc/environment
line: "{{ item.key }}={{ item.value }}"
with_dict:
APP_ENV: production
APP_DEBUG: "false"
APP_PORT: "8080"
# with_fileglob - itérer sur des fichiers
- name: Copier tous les fichiers de configuration
ansible.builtin.copy:
src: "{{ item }}"
dest: /etc/app/conf.d/
with_fileglob:
- "files/conf.d/*.conf"Les Handlers et Notify
Les handlers sont des tâches spéciales qui ne s'exécutent que lorsqu'elles sont notifiées par une autre tâche, et uniquement si cette tâche a effectué un changement (statut changed). Ils sont exécutés une seule fois à la fin du play, quel que soit le nombre de notifications reçues.
---
- name: Configuration complète de Nginx
hosts: webservers
become: true
vars:
nginx_port: 80
tasks:
- name: Installer Nginx
ansible.builtin.apt:
name: nginx
state: present
notify: Démarrer Nginx
- name: Déployer la configuration principale
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: Recharger Nginx
- name: Déployer la configuration du site
ansible.builtin.template:
src: vhost.conf.j2
dest: /etc/nginx/sites-available/default
notify: Recharger Nginx
- name: Activer le site
ansible.builtin.file:
src: /etc/nginx/sites-available/default
dest: /etc/nginx/sites-enabled/default
state: link
notify: Recharger Nginx
handlers:
- name: Démarrer Nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
- name: Recharger Nginx
ansible.builtin.service:
name: nginx
state: reloaded
- name: Redémarrer Nginx
ansible.builtin.service:
name: nginx
state: restartedLes handlers sont essentiels pour éviter les redémarrages inutiles de services. Par exemple, si vous modifiez 3 fichiers de configuration qui notifient tous le même handler, le service ne sera rechargé qu'une seule fois, à la fin du play. C'est à la fois plus efficace et plus sûr.
Pour forcer l'exécution des handlers avant la fin du play, vous pouvez utiliser le module meta :
tasks:
- name: Modifier la configuration
ansible.builtin.template:
src: config.j2
dest: /etc/app/config.yml
notify: Redémarrer application
- name: Forcer l'exécution des handlers maintenant
ansible.builtin.meta: flush_handlers
- name: Vérifier que l'application fonctionne
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200Les Tags
Les tags permettent de marquer des tâches pour les exécuter ou les exclure sélectivement lors du lancement du playbook. C'est extrêmement utile pour les playbooks volumineux où vous ne souhaitez exécuter qu'une partie des tâches.
tasks:
- name: Mettre à jour les paquets
ansible.builtin.apt:
upgrade: dist
tags:
- update
- maintenance
- name: Installer Nginx
ansible.builtin.apt:
name: nginx
state: present
tags:
- install
- nginx
- name: Configurer Nginx
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags:
- configure
- nginx
- name: Déployer l'application
ansible.builtin.git:
repo: https://github.com/codeclan/app.git
dest: /var/www/app
tags:
- deploy
- appUtilisation des tags en ligne de commande :
# Exécuter uniquement les tâches taguées "nginx"
ansible-playbook site.yml --tags nginx
# Exécuter plusieurs tags
ansible-playbook site.yml --tags "install,configure"
# Exclure des tags
ansible-playbook site.yml --skip-tags "update,maintenance"
# Lister tous les tags d'un playbook
ansible-playbook site.yml --list-tags
# Lister toutes les tâches qui seraient exécutées
ansible-playbook site.yml --list-tasksImport et Include : organiser vos tâches
Pour les playbooks complexes, il est essentiel de diviser le code en fichiers séparés. Ansible offre deux mécanismes : import_tasks (statique) et include_tasks (dynamique).
import_tasks : inclusion statique
Les tâches importées sont intégrées au playbook lors de son analyse, avant l'exécution. Les tags et les conditions du play parent s'appliquent aux tâches importées.
# playbook principal : site.yml
---
- name: Configuration complète du serveur
hosts: webservers
become: true
tasks:
- name: Tâches communes
ansible.builtin.import_tasks: tasks/common.yml
- name: Installation de Nginx
ansible.builtin.import_tasks: tasks/nginx.yml
tags: nginx
- name: Configuration SSL
ansible.builtin.import_tasks: tasks/ssl.yml
when: ssl_enabled | default(false)
tags: ssl# tasks/common.yml
---
- name: Mettre à jour le cache apt
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Installer les paquets de base
ansible.builtin.apt:
name:
- vim
- htop
- curl
- wget
- unzip
- net-tools
state: present
- name: Configurer le fuseau horaire
community.general.timezone:
name: Europe/Parisinclude_tasks : inclusion dynamique
Les tâches incluses dynamiquement sont chargées pendant l'exécution. Cela permet d'utiliser des variables dans le nom du fichier ou de conditionner l'inclusion.
tasks:
# Inclusion conditionnelle basée sur l'OS
- name: Inclure les tâches spécifiques à la distribution
ansible.builtin.include_tasks: "tasks/{{ ansible_os_family | lower }}.yml"
# Inclusion dans une boucle
- name: Configurer chaque application
ansible.builtin.include_tasks: tasks/deploy_app.yml
loop: "{{ applications }}"
loop_control:
loop_var: app
# Inclusion conditionnelle
- name: Configurer le monitoring si activé
ansible.builtin.include_tasks: tasks/monitoring.yml
when: monitoring_enabled | default(false)Les Templates Jinja2
Les templates Jinja2 sont l'un des outils les plus puissants d'Ansible. Ils permettent de générer des fichiers de configuration dynamiques en combinant un modèle (template) avec des variables.
Syntaxe Jinja2 de base
# templates/nginx-vhost.conf.j2
# Configuration générée par Ansible - NE PAS MODIFIER MANUELLEMENT
# Dernière mise à jour : {{ ansible_date_time.iso8601 }}
upstream {{ app_name }}_backend {
{% for server in backend_servers %}
server {{ server.host }}:{{ server.port }} weight={{ server.weight | default(1) }};
{% endfor %}
}
server {
listen {{ http_port | default(80) }};
server_name {{ domain_name }};
{% if ssl_enabled | default(false) %}
# Redirection HTTP vers HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name {{ domain_name }};
ssl_certificate {{ ssl_cert_path }};
ssl_certificate_key {{ ssl_key_path }};
ssl_protocols TLSv1.2 TLSv1.3;
{% endif %}
root {{ document_root }};
index index.html;
# Logs
access_log /var/log/nginx/{{ app_name }}_access.log;
error_log /var/log/nginx/{{ app_name }}_error.log {{ log_level | default('warn') }};
location / {
proxy_pass http://{{ app_name }}_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
{% if monitoring_enabled | default(false) %}
# Endpoint de monitoring
location /nginx_status {
stub_status on;
allow 127.0.0.1;
{% for ip in monitoring_ips | default([]) %}
allow {{ ip }};
{% endfor %}
deny all;
}
{% endif %}
# Pages d'erreur personnalisées
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}Utilisation du template dans un playbook
---
- name: Déployer la configuration Nginx
hosts: webservers
become: true
vars:
app_name: codeclan
domain_name: app.codeclan.fr
http_port: 80
ssl_enabled: true
ssl_cert_path: /etc/ssl/certs/codeclan.pem
ssl_key_path: /etc/ssl/private/codeclan.key
document_root: /var/www/codeclan
log_level: warn
monitoring_enabled: true
monitoring_ips:
- 10.0.0.50
- 10.0.0.51
backend_servers:
- { host: "127.0.0.1", port: 8001, weight: 3 }
- { host: "127.0.0.1", port: 8002, weight: 2 }
- { host: "127.0.0.1", port: 8003, weight: 1 }
tasks:
- name: Déployer le virtual host
ansible.builtin.template:
src: templates/nginx-vhost.conf.j2
dest: "/etc/nginx/sites-available/{{ app_name }}.conf"
owner: root
group: root
mode: '0644'
notify: Recharger Nginx
handlers:
- name: Recharger Nginx
ansible.builtin.service:
name: nginx
state: reloadedÉlévation de privilèges : become
La plupart des tâches d'administration système nécessitent des privilèges root. Ansible gère cela via le mécanisme become.
---
# Become au niveau du play (s'applique à toutes les tâches)
- name: Configuration système
hosts: all
become: true # Activer sudo pour toutes les tâches
become_user: root # Utilisateur cible (root par défaut)
become_method: sudo # Méthode d'élévation (sudo par défaut)
tasks:
- name: Cette tâche s'exécute en tant que root
ansible.builtin.apt:
name: nginx
state: present
# Become au niveau de la tâche (override)
- name: Exécuter en tant qu'utilisateur spécifique
ansible.builtin.command: whoami
become: true
become_user: www-data
register: current_user
# Désactiver become pour une tâche spécifique
- name: Cette tâche s'exécute en tant qu'utilisateur normal
ansible.builtin.command: whoami
become: false# Options en ligne de commande
ansible-playbook site.yml -b # Activer become
ansible-playbook site.yml -K # Demander le mot de passe sudo
ansible-playbook site.yml --become-user=postgres # Devenir un utilisateur spécifiqueGestion des erreurs
Une bonne gestion des erreurs est cruciale pour des playbooks robustes et fiables. Ansible offre plusieurs mécanismes pour gérer les situations d'erreur.
ignore_errors : continuer malgré les erreurs
tasks:
- name: Tenter d'arrêter un service qui n'existe peut-être pas
ansible.builtin.service:
name: ancien-service
state: stopped
ignore_errors: true
- name: Cette tâche s'exécutera même si la précédente a échoué
ansible.builtin.debug:
msg: "On continue le traitement"failed_when : personnaliser les conditions d'échec
tasks:
- name: Exécuter un script de vérification
ansible.builtin.command: /opt/scripts/check_health.sh
register: health_result
failed_when:
- health_result.rc != 0
- "'CRITICAL' in health_result.stdout"
- name: Vérifier la connexion à la base de données
ansible.builtin.shell: pg_isready -h localhost
register: pg_check
failed_when: pg_check.rc != 0 and pg_check.rc != 1block / rescue / always : try-catch-finally
La structure block/rescue/always est l'équivalent Ansible du try/catch/finally des langages de programmation. C'est le mécanisme le plus puissant pour la gestion des erreurs.
tasks:
- name: Déploiement avec rollback automatique
block:
- name: Sauvegarder la version actuelle
ansible.builtin.command: cp -r /var/www/app /var/www/app.backup
args:
creates: /var/www/app.backup
- name: Déployer la nouvelle version
ansible.builtin.git:
repo: https://github.com/codeclan/app.git
dest: /var/www/app
version: "{{ app_version }}"
- name: Installer les dépendances
ansible.builtin.command: pip install -r requirements.txt
args:
chdir: /var/www/app
- name: Exécuter les migrations de base de données
ansible.builtin.command: python manage.py migrate
args:
chdir: /var/www/app
- name: Redémarrer l'application
ansible.builtin.service:
name: app
state: restarted
- name: Vérifier que l'application répond
ansible.builtin.uri:
url: "http://localhost:8080/health"
status_code: 200
retries: 5
delay: 3
rescue:
- name: ÉCHEC - Restaurer la version précédente
ansible.builtin.command: cp -r /var/www/app.backup /var/www/app
- name: Redémarrer avec l'ancienne version
ansible.builtin.service:
name: app
state: restarted
- name: Envoyer une alerte
ansible.builtin.debug:
msg: "ALERTE : Le déploiement de la version {{ app_version }} a échoué. Rollback effectué."
always:
- name: Nettoyer la sauvegarde
ansible.builtin.file:
path: /var/www/app.backup
state: absent
- name: Enregistrer le résultat du déploiement dans les logs
ansible.builtin.lineinfile:
path: /var/log/deployments.log
line: "{{ ansible_date_time.iso8601 }} - Version {{ app_version }} - {{ 'SUCCÈS' if ansible_failed_task is not defined else 'ÉCHEC' }}"
create: truePlaybook complet : configuration d'un serveur web de production
Pour conclure ce guide, voici un playbook complet et fonctionnel qui configure un serveur web de production avec Nginx, des certificats SSL, un pare-feu, du monitoring et un durcissement de la sécurité. Ce playbook illustre la plupart des concepts abordés dans cet article.
---
# configure_webserver.yml
# Playbook complet de configuration d'un serveur web de production
# Auteur : CodeClan.fr
- name: Configuration complète du serveur web de production
hosts: webservers
become: true
gather_facts: true
vars:
# Configuration de l'application
app_name: codeclan-app
app_user: deploy
app_group: www-data
app_directory: "/var/www/{{ app_name }}"
app_port: 8080
# Configuration Nginx
nginx_port: 80
nginx_ssl_port: 443
domain_name: app.codeclan.fr
# Configuration sécurité
ssh_port: 2222
allowed_ssh_ips:
- 10.0.0.0/24
- 192.168.1.0/24
# Paquets à installer
system_packages:
- nginx
- ufw
- fail2ban
- htop
- curl
- wget
- unzip
- git
- vim
- logrotate
- ntp
# Configuration NTP
ntp_servers:
- 0.fr.pool.ntp.org
- 1.fr.pool.ntp.org
- 2.fr.pool.ntp.org
tasks:
# ===== PHASE 1 : Préparation du système =====
- name: Mettre à jour le cache APT
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
tags: system
- name: Installer les paquets système
ansible.builtin.apt:
name: "{{ system_packages }}"
state: present
tags: system
- name: Configurer le fuseau horaire
community.general.timezone:
name: Europe/Paris
tags: system
- name: S'assurer que NTP est activé
ansible.builtin.service:
name: ntp
state: started
enabled: true
tags: system
# ===== PHASE 2 : Utilisateurs et permissions =====
- name: Créer le groupe de l'application
ansible.builtin.group:
name: "{{ app_group }}"
state: present
tags: users
- name: Créer l'utilisateur de déploiement
ansible.builtin.user:
name: "{{ app_user }}"
group: "{{ app_group }}"
groups: sudo
shell: /bin/bash
create_home: true
state: present
tags: users
- name: Configurer sudo sans mot de passe pour l'utilisateur deploy
ansible.builtin.lineinfile:
path: /etc/sudoers.d/{{ app_user }}
line: "{{ app_user }} ALL=(ALL) NOPASSWD:ALL"
create: true
mode: '0440'
validate: 'visudo -cf %s'
tags: users
# ===== PHASE 3 : Sécurité SSH =====
- name: Configurer le port SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?Port '
line: "Port {{ ssh_port }}"
notify: Redémarrer SSH
tags: security
- name: Désactiver l'authentification root par SSH
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PermitRootLogin'
line: 'PermitRootLogin no'
notify: Redémarrer SSH
tags: security
- name: Désactiver l'authentification par mot de passe
ansible.builtin.lineinfile:
path: /etc/ssh/sshd_config
regexp: '^#?PasswordAuthentication'
line: 'PasswordAuthentication no'
notify: Redémarrer SSH
tags: security
# ===== PHASE 4 : Pare-feu =====
- name: Configurer UFW - politique par défaut (entrée)
community.general.ufw:
direction: incoming
default: deny
tags: firewall
- name: Configurer UFW - politique par défaut (sortie)
community.general.ufw:
direction: outgoing
default: allow
tags: firewall
- name: Autoriser SSH depuis les IPs autorisées
community.general.ufw:
rule: allow
port: "{{ ssh_port }}"
proto: tcp
src: "{{ item }}"
loop: "{{ allowed_ssh_ips }}"
tags: firewall
- name: Autoriser HTTP et HTTPS
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- "80"
- "443"
tags: firewall
- name: Activer UFW
community.general.ufw:
state: enabled
tags: firewall
# ===== PHASE 5 : Configuration Nginx =====
- name: Créer le répertoire de l'application
ansible.builtin.file:
path: "{{ app_directory }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
tags: nginx
- name: Créer les sous-répertoires
ansible.builtin.file:
path: "{{ app_directory }}/{{ item }}"
state: directory
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0755'
loop:
- public
- logs
- shared
- releases
tags: nginx
- name: Déployer la page d'accueil par défaut
ansible.builtin.copy:
content: |
{{ domain_name }}
body { font-family: Arial, sans-serif; text-align: center; padding: 50px; background: #f4f4f4; }
h1 { color: #333; }
p { color: #666; }
Bienvenue sur {{ domain_name }}
Le serveur est opérationnel. Déployé avec Ansible.
Serveur : {{ ansible_hostname }} | IP : {{ ansible_default_ipv4.address }}
dest: "{{ app_directory }}/public/index.html"
owner: "{{ app_user }}"
group: "{{ app_group }}"
mode: '0644'
tags: nginx
- name: Supprimer la configuration par défaut de Nginx
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Recharger Nginx
tags: nginx
- name: Démarrer et activer Nginx
ansible.builtin.service:
name: nginx
state: started
enabled: true
tags: nginx
# ===== PHASE 6 : Fail2ban =====
- name: Configurer fail2ban pour SSH
ansible.builtin.copy:
content: |
[sshd]
enabled = true
port = {{ ssh_port }}
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
[nginx-http-auth]
enabled = true
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 5
bantime = 3600
dest: /etc/fail2ban/jail.local
mode: '0644'
notify: Redémarrer Fail2ban
tags: security
- name: Activer fail2ban
ansible.builtin.service:
name: fail2ban
state: started
enabled: true
tags: security
# ===== PHASE 7 : Configuration logrotate =====
- name: Configurer la rotation des logs de l'application
ansible.builtin.copy:
content: |
{{ app_directory }}/logs/*.log {
daily
missingok
rotate 30
compress
delaycompress
notifempty
create 0640 {{ app_user }} {{ app_group }}
sharedscripts
postrotate
[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
endscript
}
dest: "/etc/logrotate.d/{{ app_name }}"
mode: '0644'
tags: logs
# ===== PHASE 8 : Vérifications finales =====
- name: Forcer l'exécution des handlers
ansible.builtin.meta: flush_handlers
- name: Vérifier que Nginx répond
ansible.builtin.uri:
url: "http://localhost:{{ nginx_port }}"
status_code:
- 200
- 301
- 302
register: nginx_response
retries: 3
delay: 5
until: nginx_response.status in [200, 301, 302]
tags: verify
- name: Vérifier que le pare-feu est actif
ansible.builtin.command: ufw status
register: ufw_status
changed_when: false
tags: verify
- name: Résumé de la configuration
ansible.builtin.debug:
msg: |
====================================
CONFIGURATION TERMINÉE AVEC SUCCÈS
====================================
Serveur : {{ ansible_hostname }}
IP : {{ ansible_default_ipv4.address }}
OS : {{ ansible_distribution }} {{ ansible_distribution_version }}
Nginx : Port {{ nginx_port }}
SSH : Port {{ ssh_port }}
Application : {{ app_directory }}
Utilisateur : {{ app_user }}
Pare-feu : Actif
Fail2ban : Actif
====================================
tags: verify
# ===== HANDLERS =====
handlers:
- name: Redémarrer SSH
ansible.builtin.service:
name: sshd
state: restarted
- name: Recharger Nginx
ansible.builtin.service:
name: nginx
state: reloaded
- name: Redémarrer Nginx
ansible.builtin.service:
name: nginx
state: restarted
- name: Redémarrer Fail2ban
ansible.builtin.service:
name: fail2ban
state: restartedPour exécuter ce playbook :
# Exécution complète
ansible-playbook configure_webserver.yml -i inventaire/production/hosts.yml
# Mode simulation (dry-run)
ansible-playbook configure_webserver.yml -i inventaire/production/hosts.yml --check --diff
# Exécuter uniquement la partie sécurité
ansible-playbook configure_webserver.yml -i inventaire/production/hosts.yml --tags security
# Exécuter tout sauf le pare-feu
ansible-playbook configure_webserver.yml -i inventaire/production/hosts.yml --skip-tags firewall
# Limiter à un seul serveur
ansible-playbook configure_webserver.yml -i inventaire/production/hosts.yml --limit web1.codeclan.fr
# Mode verbose pour le débogage
ansible-playbook configure_webserver.yml -i inventaire/production/hosts.yml -vvvBonnes pratiques pour vos Playbooks
Pour conclure ce guide, voici un résumé des bonnes pratiques à suivre pour écrire des playbooks professionnels et maintenables.
- Toujours nommer vos tâches : des noms clairs et descriptifs facilitent le débogage et la lisibilité
- Utiliser les FQCN (Fully Qualified Collection Names) : préférez
ansible.builtin.aptàapt - Utiliser des variables plutôt que des valeurs en dur dans les tâches
- Séparer les variables sensibles dans des fichiers chiffrés avec
ansible-vault - Tester en mode check (
--check --diff) avant d'appliquer en production - Utiliser des tags pour permettre l'exécution partielle des playbooks
- Structurer en rôles pour la réutilisabilité et la maintenabilité
- Versionner dans Git tous vos playbooks, rôles et inventaires
- Documenter vos playbooks avec des commentaires YAML clairs
- Utiliser les handlers pour les redémarrages de services au lieu de les mettre dans les tâches
- Préférer les modules dédiés aux modules
commandetshellquand ils existent - Gérer les erreurs avec
block/rescue/alwayspour les opérations critiques
Un playbook bien écrit est comme une documentation vivante de votre infrastructure. Toute personne de votre équipe devrait pouvoir le lire et comprendre comment vos serveurs sont configurés. Investissez du temps dans la qualité de vos playbooks, c'est un investissement qui vous sera rendu au centuple.
Conclusion
Vous disposez maintenant de toutes les connaissances nécessaires pour écrire des playbooks Ansible professionnels. Depuis la structure de base avec les plays et les tasks, en passant par les modules essentiels, les variables, les conditionnels, les boucles, les handlers, les templates Jinja2 et la gestion des erreurs, vous avez couvert l'ensemble du spectre des fonctionnalités des playbooks.
Le playbook complet de configuration de serveur web que nous avons présenté peut servir de base solide pour vos propres projets. N'hésitez pas à l'adapter, à le découper en rôles et à l'enrichir en fonction de vos besoins spécifiques.
La clé pour maîtriser Ansible est la pratique. Commencez par automatiser les tâches simples et répétitives de votre quotidien, puis augmentez progressivement la complexité. Avec le temps, vous développerez des réflexes et des patterns qui rendront l'écriture de playbooks de plus en plus naturelle et efficace.
Bonne automatisation avec Ansible !