SSD Nodes Learn Hosting plans →
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-22

Playbook ou rôle Ansible : lequel choisir et quand ?

Découvrez quand un playbook Ansible suffit et quand créer un rôle : arborescence, ansible-galaxy init, appels de rôles et précédence des variables.

Verified Every command ran end-to-end on a fresh Ubuntu 24.04 server, July 30, 2026.

Différence entre un playbook Ansible et un rôle

Un playbook Ansible est le fichier que vous exécutez avec ansible-playbook. Il associe un groupe d’hôtes aux tâches à effectuer. Un rôle Ansible est un répertoire avec une arborescence fixe. Il contient des tâches, des templates, des handlers et des variables par défaut. Un playbook l’appelle par son nom. La syntaxe des tâches est identique dans les deux cas. La question n’est donc pas ce que vous pouvez exprimer, mais ce que vous pouvez réutiliser.

Commencez avec un playbook simple. Un fichier site.yml contenant une liste tasks: est la structure adaptée pour vos premières automatisations. Elle reste adaptée plus longtemps que la plupart des utilisateurs ne le pensent. Convertissez-le en rôle lorsque le même bloc de tâches doit s’exécuter pour un deuxième groupe d’hôtes, ou lorsque le fichier dépasse environ 100 lignes et que vous ne parvenez plus à retrouver une tâche en faisant défiler le contenu.

Si vous n’en avez pas encore écrit, commencez par un premier playbook avec un VPS unique, puis revenez ici lorsqu’il commence à prendre de l’ampleur.

Quand un playbook monolithique est le bon choix

Un playbook monolithique convient lorsque l’opération n’a lieu qu’une seule fois, cible un seul hôte ou ne sera lu par personne d’autre. Provisionner un serveur d’application unique ou appliquer des correctifs sur une machine avant une fenêtre de maintenance ne justifie pas une arborescence de répertoires. Un role ajoute sept répertoires et un niveau d’indirection. Si le seul appelant est le playbook placé à côté, cette indirection n’apporte rien et vous oblige à changer de fichier chaque fois que vous voulez voir ce qui est réellement exécuté.

Le playbook monolithique cesse d’être adapté à un moment précis, facile à repérer. Vous copiez un bloc de tâches dans un deuxième playbook. Cette copie est le signal. À partir de là, chaque correction doit être appliquée deux fois, et un jour elle ne le sera plus qu’une seule fois.

Ce que contient réellement un répertoire de rôle

roles/common/
  defaults/main.yml
  vars/main.yml
  tasks/main.yml
  handlers/main.yml
  templates/99-hardening.conf.j2
  files/
  meta/main.yml
  • tasks/main.yml est le point d’entrée. Ansible exécute ce fichier lorsque le rôle est appelé, et tous les autres répertoires sont facultatifs.
  • defaults/main.yml contient les variables qu’un appelant est censé remplacer. Il s’agit de la source de priorité la plus basse dans Ansible, et presque toutes les autres sources ont donc priorité sur elle.
  • vars/main.yml contient les variables qu’un appelant n’est pas censé remplacer. Sa priorité est supérieure à celle de l’inventory, ce qui est un choix fort. Utilisez-la rarement.
  • handlers/main.yml contient les tâches déclenchées par notify. Un handler s’exécute à la fin du play, une seule fois, quel que soit le nombre de tâches qui l’ont notifié.
  • files/ contient les fichiers copiés tels quels par le module copy, tandis que templates/ contient les templates Jinja2 générés par le module template. Dans un rôle, référencez les deux avec le seul nom de fichier, sans chemin, car Ansible recherche d’abord dans les répertoires propres au rôle.
  • meta/main.yml déclare les dépendances du rôle et les métadonnées lues par Ansible Galaxy.

Cette arborescence n’est pas une question de style. Ansible recherche ces chemins précis. Un template placé dans roles/common/template/ (au singulier) ne sera donc tout simplement jamais trouvé.

Créer le rôle commun avec ansible-galaxy init

mkdir -p ~/infra/roles
cd ~/infra
ansible-galaxy init --init-path roles common

Cette commande crée toute l’arborescence sous roles/common, y compris des répertoires que vous n’utiliserez pas et des fichiers main.yml qui contiennent uniquement ---. Supprimez ceux que vous laissez vides. Un vars/main.yml vide ne gêne pas Ansible, mais il masque les fichiers réellement utiles du rôle.

Remplissez maintenant les fichiers qui exécutent le travail. Commencez par les valeurs par défaut, car elles constituent l’interface publique du rôle.

# roles/common/defaults/main.yml
---
common_packages:
  - ufw
  - fail2ban
  - unattended-upgrades
common_admin_group: admins
common_permit_root_login: "no"
common_password_authentication: "no"

Mettez "no" et "yes" entre guillemets. Ansible analyse le YAML avec PyYAML, qui interprète un no non quoté comme la valeur booléenne false. La ligne de configuration générée devient donc PermitRootLogin False et sshd la rejette. Les guillemets conservent la valeur sous forme de chaîne.

# roles/common/tasks/main.yml
---
- name: Install the base packages
  ansible.builtin.apt:
    name: "{{ common_packages }}"
    state: present
    update_cache: true
    cache_valid_time: 3600

- name: Create the admin group
  ansible.builtin.group:
    name: "{{ common_admin_group }}"
    state: present

- name: Install the sshd hardening drop-in
  ansible.builtin.template:
    src: 99-hardening.conf.j2
    dest: /etc/ssh/sshd_config.d/99-hardening.conf
    owner: root
    group: root
    mode: "0644"
    validate: /usr/sbin/sshd -t -f %s
  notify: Restart sshd
# roles/common/handlers/main.yml
---
- name: Restart sshd
  ansible.builtin.service:
    name: ssh
    state: restarted
# roles/common/templates/99-hardening.conf.j2
# Managed by Ansible. Local edits are overwritten on the next run.
PermitRootLogin {{ common_permit_root_login }}
PasswordAuthentication {{ common_password_authentication }}

Sur Debian et Ubuntu, l’unité systemd s’appelle ssh. Sur les systèmes de la famille RHEL, elle s’appelle sshd. Un handler qui utilise le mauvais nom échoue uniquement lorsqu’une modification est effectivement apportée au template. C’est pourquoi le problème n’apparaît généralement que plusieurs semaines plus tard.

La ligne validate est l’élément le plus utile de cette tâche. Ansible génère le template dans un fichier temporaire, remplace %s par le chemin de ce fichier, puis exécute la commande. La destination n’est remplacée que si la commande se termine avec le code 0. Ajoutez une directive invalide au template et relancez la tâche : elle échoue avec failed to validate, le véritable /etc/ssh/sshd_config.d/99-hardening.conf reste intact et vous disposez toujours d’un serveur auquel vous pouvez vous connecter. Notez que ce contrôle vérifie davantage que la syntaxe. Si sshd -t ne peut pas lire les host keys, il se termine avec sshd: no hostkeys available -- exiting. et Ansible signale le même failed to validate. Consultez donc msg du module avant d’accuser le template.

Comment un playbook appelle un rôle

# site.yml
---
- name: Base configuration for every server
  hosts: all
  become: true
  roles:
    - common
# inventory.ini
[local]
localhost ansible_connection=local
ansible-playbook -i inventory.ini site.yml

Le play doit se terminer par failed=0 dans le récapitulatif. Transmettez les paramètres au point d’appel avec la forme développée. C’est ainsi qu’un même rôle peut servir deux groupes d’hôtes :

  roles:
    - role: common
      common_admin_group: ops
      common_permit_root_login: prohibit-password

Une règle d’ordre surprend presque tout le monde. Un play peut contenir pre_tasks, roles, tasks et post_tasks. Ansible les exécute dans cet ordre, quelle que soit leur position dans le fichier. Placez tasks: au-dessus de roles: : les rôles s’exécuteront quand même en premier. Si une action doit avoir lieu avant un rôle, elle doit figurer dans pre_tasks:, et non au début de tasks:.

- name: Ordering demonstration
  hosts: local
  gather_facts: false
  pre_tasks:
    - name: Runs first
      ansible.builtin.debug:
        msg: pre
  roles:
    - common
  tasks:
    - name: Runs after the role
      ansible.builtin.debug:
        msg: task
  post_tasks:
    - name: Runs last
      ansible.builtin.debug:
        msg: post

Pour appeler un rôle depuis une liste de tâches plutôt qu’avec la clé roles:, utilisez import_role ou include_role.

  tasks:
    - name: Static, read when the playbook is parsed
      ansible.builtin.import_role:
        name: common

    - name: Dynamic, resolved when the task runs
      ansible.builtin.include_role:
        name: postgres
      when: "'db' in group_names"

import_role est statique. Ansible lit le rôle lors de l’analyse, puis ses tâches deviennent partie intégrante du play. Ainsi, ansible-playbook --list-tasks site.yml les affiche et une étiquette placée sur l’import s’applique à toutes les tâches qu’il contient. include_role est dynamique. Rien n’est lu avant l’exécution de la tâche. Cela permet de déterminer le nom du rôle à partir d’une variable ou d’une boucle. En contrepartie, ces tâches sont invisibles pour --list-tasks et --start-at-task.

Un piège se trouve ici. Un when: sur une tâche include_role est évalué avant que le defaults/main.yml du rôle inclus soit disponible. Si vous écrivez when: common_packages | length > 0 sur l’inclusion, l’exécution s’arrête avec 'common_packages' is undefined, même si cette variable est définie dans le rôle que vous incluez. Pour corriger cela, déplacez ce paramètre hors du rôle : placez-le dans group_vars/all.yml, où il est disponible partout, et réservez les valeurs par défaut du rôle aux paramètres qu’il utilise lui-même.

Quelle variable l’emporte : valeurs par défaut, group_vars, vars, variables supplémentaires

Ansible documente plus de vingt niveaux de priorité des variables. Quatre d’entre eux suffisent pour presque tous les cas réels. Les voici du moins prioritaire au plus prioritaire.

  • roles/<name>/defaults/main.yml se trouve presque au bas de la hiérarchie. Presque toute valeur définie ailleurs l’emporte, ce qui en fait l’emplacement adapté aux paramètres configurables d’un rôle.
  • group_vars/ et host_vars/ se trouvent au milieu. C’est ici que doivent figurer les valeurs propres à votre site. Elles remplacent proprement les valeurs par défaut du rôle.
  • roles/<name>/vars/main.yml est prioritaire sur host_vars. Une valeur définie ici ne peut pas être remplacée par l’inventaire. Réservez cet emplacement aux éléments que le rôle doit conserver cohérents en interne, comme un nom de paquet qui doit correspondre à un nom de service.
  • Un paramètre de rôle transmis lors de l’appel l’emporte sur vars/main.yml, et -e défini sur la ligne de commande l’emporte sur tout le reste, y compris les paramètres de rôle.

Vous pouvez observer cette résolution en environ une minute. Donnez à un petit rôle une valeur par défaut et une variable de rôle, puis définissez les mêmes noms dans host_vars.

# roles/prec/defaults/main.yml
---
prec_tunable: from-defaults
prec_internal: from-defaults
# roles/prec/vars/main.yml
---
prec_internal: from-rolevars
# host_vars/localhost.yml
---
prec_tunable: from-hostvars
prec_internal: from-hostvars
# roles/prec/tasks/main.yml
---
- name: Show which value survived
  ansible.builtin.debug:
    msg: "tunable={{ prec_tunable }} internal={{ prec_internal }}"
ansible-playbook -i inventory.ini prec.yml
ansible-playbook -i inventory.ini prec.yml -e prec_internal=from-cli

La première exécution affiche tunable=from-hostvars internal=from-rolevars. L’inventaire a remplacé la valeur par défaut du rôle, mais a été remplacé par la variable de rôle. La deuxième exécution affiche internal=from-cli, car les variables supplémentaires se trouvent tout en haut de la hiérarchie et aucune valeur moins prioritaire ne peut les remplacer. C’est aussi pourquoi -e convient pour une exécution ponctuelle, mais pas dans un script que vous conservez : cette valeur prend silencieusement le pas sur toutes les décisions prises dans votre dépôt.

La règle pratique est la suivante : si une valeur doit pouvoir être définie, placez-la dans defaults/. La placer dans vars/ indique à tous les futurs utilisateurs du rôle que l’inventaire ne peut pas la modifier. C’est parfois voulu, mais il s’agit généralement d’une erreur.

Prouver que le rôle est idempotent : l’exécuter deux fois

Une exécution Ansible fiable produit le même résultat la deuxième fois et indique qu’aucune modification n’a été nécessaire. Exécutez le playbook deux fois, puis lisez le récapitulatif.

ansible-playbook -i inventory.ini site.yml
ansible-playbook -i inventory.ini site.yml

Le deuxième récapitulatif doit ressembler à ceci :

PLAY RECAP *********************************************************************
localhost   : ok=4  changed=0  unreachable=0  failed=0  skipped=0  rescued=0  ignored=0

changed=0 signifie que chaque module a inspecté l’état actuel et constaté que le travail était déjà terminé. changed=2 lors d’une deuxième exécution signifie que deux tâches ne peuvent pas distinguer les deux états. Elles réécriront donc indéfiniment les fichiers et redémarreront les services. La cause habituelle est command ou shell, car Ansible ne peut pas savoir ce qu’une commande arbitraire a fait.

# traps.yml
---
- name: Command modules do not know what they changed
  hosts: local
  gather_facts: false
  tasks:
    - name: This appends a line on every run
      ansible.builtin.shell: "echo run >> /tmp/grow.txt"

    - name: This appends a line only once
      ansible.builtin.shell: "echo run >> /tmp/guarded.txt"
      args:
        creates: /tmp/guarded.txt

Exécutez ce playbook deux fois, puis comptez les lignes contenant wc -l /tmp/grow.txt /tmp/guarded.txt. /tmp/grow.txt contient deux lignes et /tmp/guarded.txt en contient une. Lors de la deuxième exécution, la tâche conditionnelle ne s’est pas exécutée du tout. Son résultat contient le message skipped, since /tmp/guarded.txt exists, car creates fournit au module un résultat visible à rechercher en premier. Lorsqu’une commande ne produit aucun résultat de ce type, enregistrez sa sortie et prenez vous-même la décision avec changed_when.

ansible-playbook --check --diff site.yml prédit les modifications sans les appliquer, et --diff affiche les lignes exactes qu’un template réécrirait. Interprétez la sortie en tenant compte d’une limite : les tâches shell et command sont ignorées en mode check. Un plan qui semble propre peut donc encore masquer des modifications.

Une autre colonne de ce récapitulatif mérite la même attention : un hôte auquel Ansible n’a pas pu se connecter est comptabilisé dans unreachable plutôt que dans failed, et aucune de ses tâches ne s’est exécutée. Déterminez donc à l’avance si un hôte inaccessible doit arrêter toute l’exécution avant d’utiliser ce rôle sur plus de quelques machines.

Pourquoi Ansible indique que le rôle est introuvable

Ansible recherche un répertoire roles/ à côté du fichier playbook, puis dans roles_path. La recherche dépend du playbook, pas de votre shell.

ERROR! the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely

Ce message signifie que site.yml et roles/ ne correspondent plus. Il affiche également les chemins qu’il a testés. Placez les deux dans le même répertoire. Vous pouvez lancer la commande depuis le répertoire parent, car c’est le chemin du playbook qui compte :

ansible-playbook -i infra/inventory.ini infra/site.yml

Le même problème peut aussi être moins visible. Ansible ignore un fichier ansible.cfg dans le répertoire courant lorsque ce répertoire est accessible en écriture par tous les utilisateurs. En effet, n’importe quel utilisateur du serveur pourrait y déposer une configuration et modifier le comportement de votre exécution.

[WARNING]: Ansible is being run in a world writable directory (/tmp/infra), ignoring it as an ansible.cfg source.

Vos paramètres roles_path et inventory sont alors absents sans message explicite, et la recherche du rôle échoue pour une raison sans rapport avec les rôles. ansible --version affiche le config file réellement chargé, et ansible-config dump --only-changed affiche chaque paramètre différent des valeurs par défaut intégrées. Vérifiez les deux commandes lorsqu’une exécution se comporte comme si votre configuration n’existait pas.

Partager des rôles : requirements.yml et version figée

Un rôle écrit par quelqu’un d’autre est installé, pas copié. Déclarez-le une seule fois :

# requirements.yml
---
roles:
  - name: postgres
    src: https://github.com/example/ansible-role-postgres
    scm: git
    version: v1.4.0
ansible-galaxy install -r requirements.yml -p galaxy_roles

Définissez toujours version. Sans cela, vous récupérez le contenu de la branche par défaut au moment où vous exécutez la commande. Un déploiement qui fonctionnait le mois dernier peut donc échouer sans la moindre modification de votre dépôt. Définissez roles_path sur le répertoire de téléchargement et excluez ce répertoire de git :

# ansible.cfg
[defaults]
inventory = inventory.ini
roles_path = ./galaxy_roles

Les rôles placés dans roles/ à côté du playbook sont tout de même trouvés, car ce chemin est toujours recherché en plus de roles_path. Vos propres rôles restent donc versionnés et révisés, tandis que les rôles tiers sont téléchargés de manière reproductible avec une version épinglée sur un tag.

Lorsque les rôles ne suffisent plus

Un rôle est une unité de réutilisation au sein d’une exécution Ansible. Il ne crée ni serveurs ni enregistrements DNS chez votre fournisseur. Essayer de lui faire gérer ces éléments transforme les playbooks en un ensemble que personne ne veut maintenir. Consultez la répartition du travail entre Ansible et Terraform avant de commencer. Un rôle ne remplace pas non plus la conception de l’inventaire : au-delà de quelques machines, la manière dont vous regroupez ces serveurs et y accédez compte davantage que la façon dont les tâches sont classées.

Le renforcement de la sécurité installé par ce rôle common nécessite également des décisions spécifiques. Le drop-in ci-dessus définit seulement deux directives. Consultez donc les paramètres SSH qui méritent réellement d’être modifiés et la configuration d’Ubuntu pour appliquer automatiquement les mises à jour de sécurité avant de décider ce qui doit figurer dans le rôle pour chaque hôte que vous gérez.

FAQ

Quand dois-je transformer un playbook Ansible en rôle ?

Lorsque le même bloc de tâches doit être exécuté dans un second play ou sur un second groupe d’hôtes. Copier des tâches entre des playbooks est le signal d’alerte : à partir de là, chaque correction doit être appliquée deux fois, et un jour elle ne le sera que dans un seul fichier. Un playbook unique de moins d’environ 100 lignes, qui ne cible qu’un seul groupe, ne gagne rien à être transformé en rôle. Les répertoires supplémentaires le rendent même plus difficile à lire.

Les rôles sont-ils exécutés avant les tâches du même play ?

Oui. Ansible exécute pre_tasks, puis tout ce qui est défini sous roles:, ensuite tasks:, puis post_tasks:. Il ignore l’ordre dans lequel ces clés apparaissent dans votre fichier. Écrire tasks: au-dessus de roles: ne force pas l’exécution préalable de ces tâches. Si une action doit être exécutée avant un rôle, placez-la dans pre_tasks:.

Pourquoi la valeur de group_vars ne remplace-t-elle pas celle du rôle ?

Vérifiez si la variable est définie dans vars/main.yml du rôle plutôt que dans defaults/main.yml. vars/ est prioritaire sur group_vars et host_vars dans l’ordre de priorité d’Ansible. L’inventaire ne peut donc pas remplacer cette valeur. Déplacez la variable dans defaults/main.yml, qui se trouve vers le bas de l’ordre de priorité et constitue l’emplacement approprié pour toute valeur qu’un appelant doit pouvoir modifier. Pour confirmer que la priorité est bien en cause et qu’il ne s’agit pas d’une faute de frappe, exécutez une fois la commande avec -e name=value, qui est prioritaire sur toutes les autres sources.

Pourquoi Ansible indique-t-il que le rôle est introuvable ?

La recherche commence à côté du fichier de playbook. site.yml et roles/ doivent donc se trouver dans le même répertoire. L’erreur affiche les chemins qu’Ansible a essayés, comme dans the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely. Vous pouvez exécuter le playbook depuis un répertoire parent, car la recherche suit le chemin du playbook et non le répertoire de travail de votre shell. Si vous utilisez roles_path depuis ansible.cfg, vérifiez que ce fichier a été chargé avec ansible --version, car Ansible ignore un répertoire de travail accessible en écriture par tous.

Dois-je utiliser ansible-galaxy init pour créer un rôle ?

Non. Un rôle consiste simplement en des répertoires portant les noms attendus. mkdir -p roles/common/tasks accompagné d’un tasks/main.yml constitue déjà un rôle fonctionnel. ansible-galaxy init --init-path roles common évite la saisie manuelle et fournit l’arborescence complète, notamment meta/main.yml et une ébauche de README. Supprimez les répertoires que vous laissez vides, car un vars/main.yml vide masque les fichiers du rôle qui exécutent réellement quelque chose.

#ansible#roles#playbook#structure#automation