Prérequis
Ce guide suppose une bonne connaissance d'Ansible (playbooks, inventaires, modules). Si vous débutez, commencez par le
guide d'introduction Ansible. Pour ce tutoriel : Ansible Core 2.15+, Python 3.10+, Docker installé localement.
Pourquoi Ansible basique ne suffit pas à grande échelle
Ansible est séduisant dans sa forme la plus simple : un fichier hosts, un playbook YAML, et le tour est joué. Cette simplicité cache cependant des limitations structurelles qui deviennent douloureuses à mesure que l'infrastructure grandit.
Les symptômes sont toujours identiques. Des playbooks de 1500 lignes impossibles à maintenir. Des variables copiées-collées entre dix projets. Impossible de savoir quelle version d'un rôle est déployée en production. Aucun moyen de tester un changement avant de le pousser en prod. Des secrets en clair dans le dépôt git. Et zéro visibilité sur qui a lancé quoi et quand.
Ce guide couvre les solutions à chacun de ces problèmes :
- Roles avancés : structure professionnelle, variables, dépendances, tags
- Collections : packaging, versioning, Galaxy vs Automation Hub
- Inventaires dynamiques : AWS EC2, GCP, groupes construits
- AWX : interface web, RBAC, workflows graphiques, schedules
- CI/CD : lint, test Molecule, déploiement automatisé
- Sécurité : Vault avancé, secrets CI/CD, no_log
1. Roles avancés
Structure best practice d'un rôle
Un rôle bien structuré est un composant autonome, testable et réutilisable. La commande ansible-galaxy role init crée le squelette standard, que vous pouvez enrichir selon vos besoins.
# Créer un role avec la structure complète
ansible-galaxy role init mon_role
# Structure générée
mon_role/
├── README.md # Documentation obligatoire
├── meta/
│ └── main.yml # Métadonnées et dépendances
├── defaults/
│ └── main.yml # Variables par défaut (basse priorité)
├── vars/
│ └── main.yml # Variables internes (haute priorité)
├── tasks/
│ ├── main.yml # Point d'entrée des tâches
│ ├── install.yml # Tâches d'installation
│ ├── configure.yml # Tâches de configuration
│ └── debian.yml # Tâches spécifiques Debian
├── handlers/
│ └── main.yml # Handlers (restart service, reload config)
├── templates/
│ └── nginx.conf.j2 # Templates Jinja2
├── files/
│ └── logrotate.conf # Fichiers statiques
├── tests/
│ ├── inventory # Inventaire de test minimal
│ └── test.yml # Playbook de test basique
└── molecule/ # Tests Molecule (voir section 7)
└── default/
defaults/main.yml vs vars/main.yml
La distinction entre ces deux répertoires est fondamentale et souvent mal comprise. Elle détermine qui peut surcharger quelle variable et à quel niveau.
# defaults/main.yml — Variables configurables par l'utilisateur
# Priorité la plus BASSE → facilement surchargées
nginx_port: 80
nginx_ssl_port: 443
nginx_worker_processes: auto
nginx_user: www-data
nginx_log_path: /var/log/nginx
nginx_sites_available: /etc/nginx/sites-available
nginx_sites_enabled: /etc/nginx/sites-enabled
# Ces valeurs peuvent être surchargées depuis :
# - group_vars/, host_vars/
# - -e "nginx_port=8080" en ligne de commande
# - Le playbook appelant
# vars/main.yml — Constantes internes du role
# Priorité HAUTE → difficiles à surcharger (usage avancé uniquement)
_nginx_packages:
Debian:
- nginx
- nginx-extras
RedHat:
- nginx
- nginx-mod-http-geoip2
_nginx_service_name: nginx
_nginx_config_dir: /etc/nginx
_nginx_pid_file: /run/nginx.pid
# Règle : jamais de secrets ici, uniquement des constantes structurelles
# tasks/main.yml — Point d'entrée avec include conditionnel
---
- name: Inclure les variables selon l'OS
ansible.builtin.include_vars: "{{ ansible_os_family }}.yml"
tags: always
- name: Installation Nginx
ansible.builtin.include_tasks: install.yml
tags: [nginx, install]
- name: Configuration Nginx
ansible.builtin.include_tasks: configure.yml
tags: [nginx, configure]
- name: Activation et démarrage du service
ansible.builtin.service:
name: "{{ _nginx_service_name }}"
state: started
enabled: true
tags: [nginx, service]
meta/main.yml : dépendances et métadonnées
Le fichier meta/main.yml est crucial pour deux raisons : il documente le rôle pour Galaxy et définit ses dépendances qui seront résolues automatiquement.
# meta/main.yml
galaxy_info:
author: votre_pseudo
description: Role Nginx production-ready avec SSL et hardening
company: Mon Entreprise
license: MIT
min_ansible_version: "2.15"
platforms:
- name: Ubuntu
versions:
- "22.04"
- "24.04"
- name: Debian
versions:
- "12"
- name: EL
versions:
- "9"
galaxy_tags:
- nginx
- web
- proxy
- ssl
# Dépendances : ces roles seront exécutés AVANT celui-ci
dependencies:
- role: geerlingguy.certbot
vars:
certbot_email: admin@example.com
when: nginx_ssl_enabled | bool
- role: common.firewall
vars:
firewall_allowed_tcp_ports:
- "{{ nginx_port }}"
- "{{ nginx_ssl_port }}"
handlers/main.yml
# handlers/main.yml
---
- name: Restart nginx
ansible.builtin.service:
name: "{{ _nginx_service_name }}"
state: restarted
listen: restart nginx
- name: Reload nginx
ansible.builtin.service:
name: "{{ _nginx_service_name }}"
state: reloaded
listen: reload nginx
- name: Test nginx config
ansible.builtin.command: nginx -t
changed_when: false
listen: test nginx config
include_role dynamique et when conditionnel
La directive include_role permet d'inclure dynamiquement des roles à l'intérieur d'un play, avec des conditions et des variables calculées à l'exécution — impossible avec roles: statique.
# playbook avec include_role dynamique
---
- name: Configuration des serveurs web
hosts: webservers
become: true
tasks:
- name: Installer Nginx ou Apache selon le groupe
ansible.builtin.include_role:
name: "{{ 'nginx' if 'nginx_servers' in group_names else 'apache2' }}"
vars:
web_port: 80
- name: Configurer SSL uniquement en production
ansible.builtin.include_role:
name: ssl_termination
when: ansible_env.ENVIRONMENT | default('dev') == 'prod'
- name: Appliquer les roles de monitoring pour chaque service
ansible.builtin.include_role:
name: "monitoring_{{ item }}"
loop:
- nginx
- php_fpm
- postgresql
when: monitoring_enabled | default(true)
Commentaires