Aller au contenu principal

Ansible : avancé

· 8 minutes de lecture

L'article d'introduction à Ansible couvrait l'inventaire, les playbooks et les modules. Dès que le nombre de playbooks et d'environnements augmente, d'autres mécanismes deviennent nécessaires pour éviter la duplication : rôles, collections, gestion des secrets, organisation des inventaires et traitement des erreurs.

Les rôles Ansible : modularité et réutilisabilité​

Les rôles sont l'unité de réutilisation d'Ansible : un rôle regroupe les tâches, handlers, variables, templates et fichiers nécessaires à une fonction (serveur web, base de données), et s'applique à n'importe quel groupe d'hôtes.

Structure d'un rôle​

Un rôle suit une structure de répertoires standardisée :

roles/
└── webserver/
├── defaults/ # Variables par défaut (priorité la plus basse)
│ └── main.yml
├── vars/ # Variables du rôle (priorité haute)
│ └── main.yml
├── tasks/ # Tâches principales du rôle
│ └── main.yml
├── handlers/ # Gestionnaires d'événements
│ └── main.yml
├── templates/ # Templates Jinja2
│ └── nginx.conf.j2
├── files/ # Fichiers statiques
│ └── index.html
├── meta/ # Métadonnées et dépendances
│ └── main.yml
└── README.md # Documentation du rôle

Créer un rôle avec ansible-galaxy​

# Créer la structure d'un nouveau rôle
ansible-galaxy role init webserver

# Créer un rôle dans un répertoire spécifique
ansible-galaxy role init --init-path roles webserver

# Voir la structure créée
tree roles/webserver

Exemple de rôle complet : webserver​

tasks/main.yml

---
# Installation et configuration de Nginx
- name: Installer les paquets requis
apt:
name:
- nginx
- python3-pip
state: present
update_cache: yes

- name: Configurer Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/sites-available/{{ site_name }}
owner: root
group: root
mode: '0644'
notify: reload nginx

- name: Activer le site
file:
src: /etc/nginx/sites-available/{{ site_name }}
dest: /etc/nginx/sites-enabled/{{ site_name }}
state: link
notify: reload nginx

- name: Déployer le contenu du site
template:
src: index.html.j2
dest: /var/www/{{ site_name }}/index.html
owner: www-data
group: www-data
mode: '0644'

- name: S'assurer que Nginx est démarré
service:
name: nginx
state: started
enabled: yes

defaults/main.yml

---
# Variables par défaut du rôle webserver
site_name: example.com
document_root: /var/www/{{ site_name }}
server_port: 80
server_name: "{{ site_name }}"

document_root et server_name référencent site_name : les variables Ansible sont évaluées paresseusement, au moment de leur utilisation. Surcharger site_name au niveau du playbook modifie donc aussi document_root, sans avoir à le redéfinir.

vars/main.yml

---
# Variables spécifiques au rôle (priorité plus haute)
nginx_worker_processes: auto
nginx_worker_connections: 1024

La différence entre defaults/ et vars/ tient à leur place dans l'ordre de priorité des variables, qui compte plus de vingt niveaux. Les plus courants, du plus faible au plus fort :

  1. defaults/main.yml du rôle
  2. group_vars/all, puis group_vars/<groupe>, puis host_vars/<hôte> de l'inventaire
  3. vars: du play et fichiers vars_files
  4. vars/main.yml du rôle
  5. set_fact et variables enregistrées (register), puis paramètres passés au rôle dans le playbook
  6. -e / --extra-vars en ligne de commande, qui l'emportent sur tout

Un rôle place donc dans defaults/ tout ce que l'utilisateur doit pouvoir adapter par l'inventaire, et dans vars/ les constantes internes qu'aucune variable d'inventaire ne doit écraser.

handlers/main.yml

---
# Gestionnaires pour Nginx
- name: reload nginx
service:
name: nginx
state: reloaded

- name: restart nginx
service:
name: nginx
state: restarted

templates/nginx.conf.j2

server {
listen {{ server_port }};
server_name {{ server_name }};

root {{ document_root }};
index index.html;

location / {
try_files $uri $uri/ =404;
}

access_log /var/log/nginx/{{ site_name }}_access.log;
error_log /var/log/nginx/{{ site_name }}_error.log;
}

meta/main.yml

---
# Métadonnées et dépendances du rôle
galaxy_info:
author: Nom de l'auteur
description: Installation et configuration de Nginx
company: Organisation
license: MIT
min_ansible_version: "2.15"

platforms:
- name: Ubuntu
versions:
- jammy
- noble
- name: Debian
versions:
- bookworm
- trixie

galaxy_tags:
- nginx
- webserver
- web

dependencies: []

dependencies liste des rôles exécutés automatiquement avant celui-ci (par exemple un rôle common qui configure le pare-feu et les utilisateurs).

Utiliser un rôle dans un playbook​

---
- name: Configurer les serveurs web
hosts: webservers
become: yes

roles:
- role: webserver
vars:
site_name: monsite.com
server_port: 8080

# Ou avec une syntaxe plus détaillée
- name: Configurer avec des conditions
hosts: webservers
become: yes

roles:
- role: webserver
when: ansible_os_family == "Debian"
tags:
- nginx
- web

Collections Ansible​

Les collections sont le format de distribution du contenu Ansible : un paquet versionné qui regroupe modules, plugins, rôles et playbooks sous un espace de noms (community.general, amazon.aws). Depuis Ansible 2.10, la quasi-totalité des modules, auparavant livrés avec le moteur, sont distribués ainsi ; ansible-core ne contient plus que la collection ansible.builtin.

Installer une collection​

# Installer depuis Ansible Galaxy
ansible-galaxy collection install community.general

# Installer depuis un fichier requirements.yml
ansible-galaxy collection install -r requirements.yml

# Installer une version spécifique
ansible-galaxy collection install community.general:5.8.0

Fichier requirements.yml​

---
collections:
# Depuis Ansible Galaxy
- name: community.general
version: ">=5.0.0"

- name: ansible.posix
version: "1.5.1"

- name: community.docker

# Depuis un dépôt Git
- name: https://github.com/organisation/ma-collection.git
type: git
version: main

Utiliser une collection​

---
- name: Utiliser des modules de collections
hosts: all

tasks:
# Méthode 1 : FQCN (Fully Qualified Collection Name)
- name: Installer un paquet avec community.general
community.general.npm:
name: express
global: yes

# Méthode 2 : déclarer la collection, puis utiliser le nom court du module
- name: Docker tasks
collections:
- community.docker
block:
- docker_container:
name: nginx
image: nginx:1.27-alpine
state: started

La forme FQCN reste recommandée : elle rend chaque tâche non ambiguë, alors que le nom court dépend de l'ordre de recherche des collections déclarées.

Créer sa propre collection​

# Créer la structure d'une collection
ansible-galaxy collection init mon_namespace.ma_collection
mon_namespace/
└── ma_collection/
├── docs/
├── galaxy.yml # Métadonnées de la collection
├── plugins/
│ ├── modules/ # Modules personnalisés
│ ├── inventory/ # Plugins d'inventaire
│ └── lookup/ # Plugins lookup
├── roles/ # Rôles inclus dans la collection
├── playbooks/ # Playbooks d'exemple
└── README.md

Ansible Vault : secrets chiffrés​

Les rôles et les inventaires contiennent souvent des mots de passe ou des clés d'API. Ansible Vault les chiffre en AES-256, soit par fichier entier (ansible-vault encrypt group_vars/all/vault.yml), soit variable par variable (ansible-vault encrypt_string), et les déchiffre en mémoire au moment de l'exécution (--ask-vault-pass, --vault-password-file ou --vault-id). La convention consiste à préfixer les variables chiffrées par vault_ et à les référencer depuis un fichier en clair (db_password: "{{ vault_db_password }}"), pour que les noms de variables restent lisibles et recherchables dans le dépôt. Le fonctionnement détaillé, la gestion des mots de passe et les identifiants de Vault font l'objet de l'article Ansible Vault.

Application / Projet lié​

Mis en pratique dans le projet2024 → aujourd'huiCluster Kubernetes interne SONUPlaybooks avancés et structures de rôles pour la gestion complexe du cluster Kubernetes, configurations persistantes et secrets sécurisés.KuberneteskubeadmCalicoTailscaleHelmGitHub ActionsPrometheusGrafana

Bonnes pratiques avancées​

1. Structure de projet recommandée​

ansible-project/
├── ansible.cfg
├── inventories/
│ ├── production/
│ │ ├── hosts.yml
│ │ └── group_vars/
│ │ ├── all.yml
│ │ └── webservers.yml
│ └── staging/
│ ├── hosts.yml
│ └── group_vars/
├── roles/
│ ├── common/
│ ├── webserver/
│ └── database/
├── playbooks/
│ ├── deploy.yml
│ ├── rollback.yml
│ └── maintenance.yml
├── collections/
│ └── requirements.yml
├── group_vars/
│ └── all/
│ ├── vars.yml
│ └── vault.yml
└── README.md

Un répertoire d'inventaire par environnement porte ses propres group_vars : le même playbook s'applique à la production ou à la préproduction selon l'inventaire passé avec -i inventories/production, sans condition sur l'environnement dans le code.

2. Utiliser des tags stratégiquement​

---
- name: Configuration complète
hosts: webservers

tasks:
- name: Installer les paquets
apt:
name: # une liste : un seul appel au gestionnaire de paquets
- nginx
- python3
state: present
tags:
- install
- packages

- name: Configurer Nginx
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags:
- config
- nginx

- name: Déployer l'application
copy:
src: app/
dest: /var/www/app/
tags:
- deploy
- app

# Exécuter uniquement certaines tâches
ansible-playbook deploy.yml --tags "config"
ansible-playbook deploy.yml --skip-tags "deploy"

# Lister les tâches et tags sans rien exécuter
ansible-playbook deploy.yml --list-tasks --list-tags

Un handler notifié par une tâche taguée ne s'exécute que si sa propre tâche est retenue ; les tags spéciaux always et never forcent ou excluent une tâche indépendamment de la sélection.

3. Gestion des erreurs robuste​

---
- name: Gestion d'erreurs avancée
hosts: all

tasks:
- name: Exécuter le script avec reprise sur erreur
block:
- name: Tâche qui peut échouer
command: /opt/script.sh
register: script_result

- debug:
msg: "Script réussi : {{ script_result.stdout }}"
rescue:
- debug:
msg: "Script échoué : {{ ansible_failed_result.stderr | default('') }}"
- include_tasks: rollback.yml
always:
- name: Nettoyage
file:
path: /tmp/script-lock
state: absent

block/rescue/always fonctionne comme try/except/finally : la section rescue s'exécute seulement si une tâche du block échoue, et always dans tous les cas. La variable ansible_failed_result contient le résultat de la tâche en échec. À l'inverse, ignore_errors: yes sur une tâche masque l'échec sans déclencher rescue ; failed_when et changed_when redéfinissent ce qu'est un échec ou un changement à partir du résultat (code de retour, contenu de la sortie).

Conclusion​

Les rôles structurent le code en composants réutilisables, avec une séparation claire entre valeurs par défaut et constantes. Les collections distribuent modules et rôles de façon versionnée, Vault protège les secrets versionnés avec le code, et l'organisation des inventaires par environnement évite les conditions dans les playbooks.

Points clés à retenir :

  • Rôles : modulariser et réutiliser le code, defaults/ pour ce qui se surcharge
  • Priorité des variables : de defaults/ (plus faible) à --extra-vars (plus forte)
  • Collections : distribution versionnée des modules, référencés par leur FQCN
  • Vault : chiffrer les données sensibles dans le dépôt
  • Tags et blocs : exécution sélective et gestion structurée des erreurs

Ressources utiles​