Aller au contenu

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 :

# Generer le squelette d'un role dans roles/
ansible-galaxy role init roles/mon_role

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)

  1. defaults/main.yml du rôle
  2. Variables d'inventaire (host_vars/, group_vars/)
  3. Variables du playbook (vars:, vars_files:)
  4. 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 }