Rôles Ansible¶
Les rôles encapsulent tasks, handlers, templates et variables en composants réutilisables et partageables entre playbooks et projets.
Structure d'un rôle¶
Un rôle suit une convention de répertoires standardisée. Ansible détecte et charge automatiquement les fichiers placés aux emplacements attendus — aucune déclaration explicite n'est nécessaire.
roles/
└── mon_role/
├── tasks/
│ ├── main.yml # point d'entrée — inclut les autres fichiers
│ ├── assert.yml # validations des preconditions
│ ├── present.yml # taches d'installation / configuration
│ └── absent.yml # taches de desinstallation
├── handlers/
│ └── main.yml # handlers (reload, restart)
├── templates/
│ └── app.conf.j2 # templates Jinja2
├── files/
│ └── script.sh # fichiers statiques copies tels quels
├── defaults/
│ └── main.yml # variables par defaut (priorite basse)
├── vars/
│ └── main.yml # variables internes (priorite haute)
└── meta/
└── main.yml # metadonnees Galaxy et dependances
La commande ansible-galaxy génère cette structure automatiquement :
Chaque répertoire a un rôle précis :
| Répertoire | Contenu |
|---|---|
tasks/ | Fichiers de tâches YAML. Seul main.yml est charge au démarrage du rôle |
handlers/ | Handlers declenches par notify. Exécutés une seule fois en fin de play |
templates/ | Templates Jinja2 (extension .j2) pour générer des fichiers de config |
files/ | Fichiers statiques copies sans transformation vers les hôtes |
defaults/ | Variables surchargeables par l'utilisateur (priorité la plus basse) |
vars/ | Variables internes au rôle (priorité haute, non destinees a être surchargées) |
meta/ | Metadonnees Galaxy (auteur, plateforme, version Ansible) et dépendances |
Gestion par state¶
Le pattern <role>_state est une convention essentielle pour rendre un rôle réversible et sans danger. La variable mon_role_state peut prendre trois valeurs : present, absent ou noop.
flowchart LR
A[assert.yml] --> B{mon_role_state}
B -->|noop| C[Aucune action]
B -->|present| D[present.yml]
B -->|absent| E[absent.yml] Le fichier tasks/main.yml orchestre ce routage après avoir valide les preconditions :
# roles/mon_role/tasks/main.yml
---
- name: Valider les preconditions du role
ansible.builtin.import_tasks: assert.yml
tags: [always, assert]
- name: Installer et configurer
ansible.builtin.include_tasks: present.yml
when: mon_role_state == 'present'
tags: [present, deploy]
- name: Desinstaller
ansible.builtin.include_tasks: absent.yml
when: mon_role_state == 'absent'
tags: [absent, remove]
Le fichier assert.yml valide les parametres avant toute action :
# roles/mon_role/tasks/assert.yml
---
- name: Verifier que mon_role_state est valide
ansible.builtin.assert:
that:
- mon_role_state in ['present', 'absent', 'noop']
fail_msg: "mon_role_state doit etre 'present', 'absent' ou 'noop'. Valeur actuelle : {{ mon_role_state }}"
success_msg: "mon_role_state = {{ mon_role_state }} — OK"
La valeur par défaut est noop. Un hôte ajoute à l'inventaire sans configuration explicite de mon_role_state n'est pas impacte : aucune tâche n'est exécutée, aucune modification n'est apportee au système. Ce comportement "safe by default" evite les accidents lors de l'ajout de nouvelles machines a un inventaire existant.
import_tasks vs include_tasks
La variante import_tasks est statique : les tâches sont intégrées au moment du parsing, ce qui permet aux tags et conditions du playbook de s'appliquer directement à chaque tâche importee. La variante include_tasks est dynamique : le fichier est charge à l'exécution, ce qui permet de passer des variables mais rend les tags moins predictibles. Pour assert.yml, préférer import_tasks afin que le tag always soit toujours honore.
Variables : defaults vs vars¶
Ansible distingue deux emplacements pour les variables d'un rôle, avec des priorités très différentes.
| Fichier | Priorité | Usage |
|---|---|---|
defaults/main.yml | Basse (2) | Valeurs par défaut surchargeables par l'inventaire et le CLI |
vars/main.yml | Haute (16) | Variables internes, non destinees a être surchargées |
Ordre de surchargé (du plus faible au plus fort)¶
defaults/main.ymldu rôle- Variables d'inventaire (
host_vars/,group_vars/) - Variables du playbook (
vars:,vars_files:) - Variables en ligne de commande (
-e "var=valeur")
La convention recommandee est de prefixer toutes les variables du rôle avec son nom pour éviter les collisions entre rôles :
# roles/mon_role/defaults/main.yml
---
# Etat du role : present | absent | noop
mon_role_state: noop
# Port d'ecoute de l'application
mon_role_port: 8080
# Repertoire d'installation
mon_role_install_dir: /opt/mon_role
# Utilisateur systeme proprietaire des fichiers
mon_role_user: app
# Drapeaux fonctionnels
mon_role_validate: true
mon_role_display_summary: true
Les drapeaux booleens mon_role_validate et mon_role_display_summary permettent de contrôler le comportement du rôle sans modifier ses tâches :
# roles/mon_role/tasks/present.yml (extrait)
---
- name: Valider la configuration deployee
ansible.builtin.include_tasks: validate.yml
when: mon_role_validate | bool
tags: [validate]
- name: Afficher le recapitulatif du deploiement
ansible.builtin.include_tasks: summary.yml
when: mon_role_display_summary | bool
tags: [summary]
Ne pas mettre de secrets dans defaults/
Les fichiers defaults/main.yml et vars/main.yml sont commites dans le dépôt. Ne jamais y placer de mots de passe, clés API ou tokens. Utiliser Ansible Vault pour chiffrer les variables sensibles, et les stocker dans group_vars/ ou host_vars/ chiffres.
Handlers¶
Les handlers sont des tâches speciales declenchees par le mot-clé notify. Quelle que soit le nombre de notifications reçues pour un même handler au cours d'un play, il n'est exécuté qu'une seule fois, à la fin du play.
# roles/mon_role/handlers/main.yml
---
- name: reload mon_role
ansible.builtin.systemd:
name: mon_role
state: reloaded
- name: restart mon_role
ansible.builtin.systemd:
name: mon_role
state: restarted
- name: refresh local facts
ansible.builtin.setup:
filter: ansible_local
Le handler refresh local facts est particulièrement utile après le déploiement d'un script .fact : il force la relecture des facts locaux sans relancer la collecte complète.
# roles/mon_role/tasks/present.yml (extrait)
---
- name: Deployer le script de fact local
ansible.builtin.copy:
src: files/mon_role.fact
dest: /etc/ansible/facts.d/mon_role.fact
mode: '0755'
notify: refresh local facts
- name: Deployer la configuration
ansible.builtin.template:
src: app.conf.j2
dest: /etc/mon_role/app.conf
mode: '0644'
notify:
- reload mon_role
Ordre d'exécution des handlers
Les handlers s'exécutent dans l'ordre ou ils sont définis dans handlers/main.yml, pas dans l'ordre ou ils sont notifies. Placer reload avant restart garantit qu'une notification simultanée des deux applique d'abord le reload, puis le restart — comportement souvent souhaite.
Templates Jinja2¶
Les templates Jinja2 permettent de générer des fichiers de configuration dynamiques à partir de variables Ansible et de facts collectes sur les hôtes.
{# roles/mon_role/templates/app.conf.j2 #}
# Configuration generee par Ansible — ne pas modifier manuellement
# Hote : {{ ansible_hostname }} | OS : {{ ansible_distribution }} {{ ansible_distribution_version }}
[server]
listen_address = {{ ansible_default_ipv4.address }}
listen_port = {{ mon_role_port }}
install_dir = {{ mon_role_install_dir }}
[auth]
run_as_user = {{ mon_role_user }}
run_as_group = {{ mon_role_user }}
[features]
{% for feature in mon_role_features | default([]) %}
enable_{{ feature }} = true
{% endfor %}
[peers]
# Liste des pairs du meme groupe
peer_list = {{ groups['app_servers'] | map('extract', hostvars, 'ansible_default_ipv4') | map(attribute='address') | join(', ') }}
La tâche qui deploie ce template :
- name: Deployer la configuration de l'application
ansible.builtin.template:
src: app.conf.j2
dest: /etc/mon_role/app.conf
owner: "{{ mon_role_user }}"
group: "{{ mon_role_user }}"
mode: '0640'
notify: reload mon_role
Filtres Jinja2 utiles¶
| Filtre | Exemple | Résultat |
|---|---|---|
default | {{ var \| default('fallback') }} | fallback si var est undefined |
join | {{ list \| join(', ') }} | a, b, c |
to_yaml | {{ dict \| to_yaml }} | bloc YAML formate |
ipaddr | {{ '10.0.1.5/24' \| ansible.utils.ipaddr('network') }} | 10.0.1.0 |
upper / lower | {{ name \| upper }} | NOM |
regex_replace | {{ str \| regex_replace('-', '_') }} | remplacement par expression régulière |
map | {{ list \| map('upper') \| list }} | chaque élément transforme |
select | {{ list \| select('match', '^web') \| list }} | éléments filtres par pattern |
Tags¶
Les tags permettent d'exécuter selectivement un sous-ensemble de tâches sans modifier le playbook. La convention recommandee attribue à chaque fichier inclus ses propres tags.
# roles/mon_role/tasks/main.yml
---
- name: Valider les preconditions du role
ansible.builtin.import_tasks: assert.yml
tags: [always, assert]
- name: Installer et configurer
ansible.builtin.include_tasks: present.yml
when: mon_role_state == 'present'
tags: [present, deploy]
- name: Valider la configuration deployee
ansible.builtin.include_tasks: validate.yml
when: mon_role_state == 'present' and mon_role_validate | bool
tags: [validate]
- name: Afficher le recapitulatif
ansible.builtin.include_tasks: summary.yml
when: mon_role_display_summary | bool
tags: [summary]
- name: Desinstaller
ansible.builtin.include_tasks: absent.yml
when: mon_role_state == 'absent'
tags: [absent, remove]
Tableau des tags de référence¶
| Tag | Usage |
|---|---|
assert | Exécuter uniquement les validations de preconditions |
present, deploy | Exécuter les tâches d'installation et de configuration |
validate | Exécuter les tests de validation post-déploiement |
summary | Afficher le recapitulatif de l'état déployé |
absent, remove | Exécuter les tâches de desinstallation |
Utilisation en ligne de commande¶
# Executer uniquement les assertions (diagnostic rapide)
ansible-playbook playbook.yml --tags assert
# Deployer sans afficher le recapitulatif
ansible-playbook playbook.yml --tags present --skip-tags summary
# Desinstaller le role sur un groupe specifique
ansible-playbook playbook.yml --tags absent --limit app_servers
# Sauter la validation post-deploiement (utile en urgence)
ansible-playbook playbook.yml --skip-tags validate
Dépendances et meta¶
Le fichier meta/main.yml sert deux objectifs : fournir les metadonnees nécessaires a Ansible Galaxy pour indexer et distribuer le rôle, et déclarer les dépendances envers d'autres rôles.
# roles/mon_role/meta/main.yml
---
galaxy_info:
role_name: mon_role
author: mon-equipe
description: Installation et configuration de Mon Role
company: Mon Entreprise
license: MIT
min_ansible_version: "2.14"
platforms:
- name: Ubuntu
versions:
- "22.04"
- "24.04"
- name: EL
versions:
- "8" # Rocky Linux 8, AlmaLinux 8
- "9" # Rocky Linux 9, AlmaLinux 9
- name: Debian
versions:
- "12"
dependencies:
- role: common
- role: firewall
vars:
firewall_state: present
firewall_allowed_ports:
- "{{ mon_role_port }}/tcp"
La section platforms a un intérêt opérationnel direct : elle alimente la matrice de tests CI. En chapitre 5, les pipelines Molecule utilisent cette liste pour instancier des conteneurs sur chacune des plateformes declarees et vérifier que le rôle fonctionne sur l'ensemble des OS supportes.
Les dépendances declarees dans dependencies sont exécutées automatiquement avant le rôle lui-même, dans l'ordre de declaration. Une dépendance peut recevoir des variables (vars:) pour adapter son comportement au contexte du rôle qui en dépend.
Dépendances transitives
Ansible résout les dépendances de façon transitive : si firewall dépend lui-même de common, common n'est exécuté qu'une seule fois même s'il apparait dans plusieurs arbres de dépendance. Ce comportement evite les doubles executions mais nécessité que chaque rôle soit idempotent.
Utiliser un rôle dans un playbook¶
Syntaxe rôles:¶
La syntaxe roles: est la forme classique pour inclure des rôles dans un play. Les rôles sont exécutés avant les tâches tasks: du play.
# playbooks/webservers.yml
---
- name: Configurer les serveurs applicatifs
hosts: app_servers
become: true
roles:
- role: common
vars:
common_state: present
- role: mon_role
vars:
mon_role_state: present
mon_role_port: 9090
mon_role_validate: true
mon_role_display_summary: true
include_role vs import_role¶
| Critère | ansible.builtin.import_role (statique) | ansible.builtin.include_role (dynamique) |
|---|---|---|
| Moment de chargement | Parsing du playbook | Exécution (runtime) |
| Tags | Hérités et applicables au rôle entier | Limites à la tâche include_role |
Condition when | Appliquee à chaque tâche du rôle | Appliquee à l'inclusion seulement |
loop | Non supporte | Supporte |
| Variables | Evaluees au parsing | Evaluees à l'exécution |
| Usage typique | Rôles principaux, structure fixe | Rôles conditionnels, boucles, rôles dynamiques |
Exemple avec ansible.builtin.import_role :
# Inclusion statique — role toujours charge, condition appliquee a chaque tache
- name: Configurer le reverse proxy
ansible.builtin.import_role:
name: nginx
vars:
nginx_state: present
nginx_port: 443
Exemple avec ansible.builtin.include_role :
# Inclusion dynamique — le role n'est inclus que si la condition est vraie
- name: Configurer le monitoring selon l'environnement
ansible.builtin.include_role:
name: monitoring
vars:
monitoring_state: "{{ 'present' if env == 'production' else 'noop' }}"
when: deploy_monitoring | default(false) | bool
# Boucle sur plusieurs roles (uniquement possible avec include_role)
- name: Deployer plusieurs services
ansible.builtin.include_role:
name: "{{ item.role }}"
vars:
"{{ item.role }}_state": "{{ item.state }}"
loop:
- { role: redis, state: present }
- { role: memcached, state: present }
- { role: rabbitmq, state: noop }