SSD Nodes Learn 🎉 VPS dès $5.50/mois
Guides Matt ConnorPar Matt Connor · Mis à jour le 2026-08-02

Ansible : playbook ou rôle, lequel choisir ?

Découvrez quand un playbook Ansible suffit et quand passer à un rôle : arborescence, ansible-galaxy init, appels de rôles et priorité des variables.

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

Playbook Ansible ou rôle : quelle est la différence

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 à l’arborescence prédéfinie qui 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 ne porte donc pas sur ce que vous pouvez exprimer, mais sur la réutilisation.

Commencez avec un playbook simple. Un seul fichier site.yml contenant une liste tasks: est adapté à vos premières automatisations. Cette structure reste généralement adaptée plus longtemps que prévu. 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 pouvez plus retrouver une tâche en le faisant défiler.

Si vous n’en avez pas encore écrit, commencez par un premier playbook ciblant un seul VPS, puis revenez lorsque sa taille commencera à augmenter.

Quand un playbook plat est la bonne solution

Un playbook plat convient lorsque le travail est exécuté une seule fois, sur un seul hôte, ou lorsque personne d’autre ne le lira. 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 rôle ajoute sept répertoires et un niveau d’indirection. Si le seul appelant est le playbook placé à côté, cette indirection ne vous apporte rien et vous oblige à faire un saut chaque fois que vous voulez voir ce qui est réellement exécuté.

Le playbook plat cesse d’être adapté à un moment précis, et ce moment est 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 effectuée deux fois, et un jour elle ne le sera qu’une seule fois.

Ce que contient réellement le répertoire d’un 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é redéfinir. C’est la source ayant la priorité la plus faible dans Ansible. Presque toute autre source prend donc le dessus.
  • vars/main.yml contient les variables qu’un appelant n’est pas censé redéfinir. Ce répertoire a une priorité supérieure à celle de l’inventaire. C’est un choix important. Utilisez-le 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, et templates/ contient les templates Jinja2 générés par le module template. Dans un rôle, vous 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 structure n’est pas une simple préférence de style. Ansible recherche ces chemins précis. Un template placé dans roles/common/template/ (au singulier) n’est 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 ne contiennent que ---. Supprimez ceux que vous laissez vides. Un vars/main.yml vide ne gêne pas Ansible, mais il masque les fichiers réellement importants 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 entouré de guillemets comme la valeur booléenne false. La ligne de configuration générée devient alors PermitRootLogin False, et sshd la rejette. Les guillemets conservent une valeur de type 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 indique le mauvais nom échoue uniquement lorsqu’une modification réelle du template se produit. C’est pourquoi le problème apparaît souvent 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 incorrecte dans le template, puis relancez l’exécution : la tâche échoue avec failed to validate, le véritable /etc/ssh/sshd_config.d/99-hardening.conf reste intact, et vous pouvez toujours vous connecter au serveur. Notez que ce contrôle vérifie davantage que la syntaxe. Si sshd -t ne peut pas lire les clés d’hôte, il se termine avec sshd: no hostkeys available -- exiting., et Ansible signale le même failed to validate. Consultez donc msg avant d’incriminer 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. Passez les paramètres au point d’appel avec la forme développée. C’est ainsi qu’un même rôle sert 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, quel que soit l’ordre dans lequel vous les avez écrits dans le fichier. Placez tasks: au-dessus de roles: : les rôles s’exécutent quand même en premier. Donc, si une action doit avoir lieu avant un rôle, placez-la 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 au moment de l’analyse, et ses tâches deviennent partie intégrante du play. Ainsi, ansible-playbook --list-tasks site.yml les répertorie et un tag placé sur l’import s’applique à toutes les tâches incluses. 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’include, 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 le toggle hors du rôle : placez-le dans group_vars/all.yml, où il est disponible partout, et laissez les defaults du rôle gérer les valeurs consommées par le rôle lui-même.

Quelle variable l’emporte : valeurs par défaut, group_vars, vars, extra vars

Ansible documente plus de vingt niveaux de priorité des variables. Quatre d’entre eux règlent presque tous les cas réels. Les voici du plus faible au plus fort.

  • roles/<name>/defaults/main.yml se trouve presque au bas de la hiérarchie. Presque tout ce que vous définissez ailleurs l’emporte. C’est donc l’emplacement adapté aux paramètres configurables d’un rôle.
  • group_vars/ et host_vars/ se trouvent au milieu. C’est là 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 depuis l’inventaire. Réservez cet emplacement aux éléments dont le rôle a besoin pour rester cohérent en interne, par exemple un nom de paquet qui doit correspondre à un nom de service.
  • Un paramètre de rôle transmis lors de l’appel est prioritaire sur vars/main.yml, et -e fourni sur la ligne de commande est prioritaire sur tout le reste, y compris les paramètres de rôle.

Vous pouvez observer cette résolution en environ une minute. Créez un petit rôle avec 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 du rôle. La deuxième exécution affiche internal=from-cli, car les extra vars se trouvent tout en haut de la hiérarchie et aucune valeur inférieure ne peut les remplacer. C’est également pourquoi -e convient à une exécution ponctuelle, mais pas dans un script que vous conservez : cette variable prend silencieusement le dessus sur toutes les décisions prises dans votre dépôt.

La règle pratique est la suivante : si vous voulez qu’une valeur puisse ê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 ce que vous vouliez, mais c’est généralement un accident.

Vérifier 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é effectuée. Exécutez le playbook deux fois, puis consultez le récapitulatif.

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

Le deuxième récapitulatif doit se présenter comme suit :

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 la tâche avait déjà été effectuée. changed=2 lors d'une deuxième exécution signifie que deux tâches ne parviennent pas à faire la différence. Elles continueront donc à réécrire les fichiers et à redémarrer les services indéfiniment. 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 protégée 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 élément visible à rechercher en premier. Lorsqu'une commande ne produit aucun élément 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 gardant une réserve à l'esprit : les tâches shell et command sont ignorées en mode check. Un plan qui semble propre peut donc encore masquer des modifications.

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 suit le playbook, et non 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 essayés. Conservez les deux dans le même répertoire. Vous pouvez exécuter 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 se produire plus discrètement. Ansible ignore un ansible.cfg dans le répertoire courant lorsque ce répertoire est accessible en écriture par tous, car n’importe quel utilisateur de la machine 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, et la recherche du rôle échoue pour une raison qui n’a rien à voir avec les rôles. ansible --version affiche les config file effectivement chargés, et ansible-config dump --only-changed affiche chaque paramètre différent des valeurs par défaut intégrées. Vérifiez les deux chaque fois qu’une exécution se comporte comme si votre configuration n’existait pas.

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

Un rôle écrit par quelqu’un d’autre est installé, il n’est 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 ce paramètre, vous obtenez 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 aucune modification de votre propre dépôt. Indiquez le répertoire de téléchargement dans roles_path 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 figée sur un tag.

Quand les rôles ne suffisent plus

Un rôle est une unité de réutilisation au cours d’une exécution Ansible. Il ne crée pas de serveurs ni d’enregistrements DNS chez votre fournisseur. Essayer de lui faire effectuer ces opérations transforme les playbooks en un ensemble que personne ne veut maintenir. la répartition du travail entre Ansible et Terraform mérite d’être lue avant de commencer. Un rôle ne remplace pas non plus la conception de l’inventaire. Dès que vous dépassez 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 hardening installé par ce rôle common doit également faire l’objet de décisions distinctes. Le fichier drop-in ci-dessus définit seulement 2 directives. Lisez donc les paramètres SSH qui méritent réellement d’être modifiés et comment faire appliquer automatiquement les mises à jour de sécurité par Ubuntu avant de décider ce qui doit appartenir au rôle pour chacun de vos hôtes.

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 deuxième play ou sur un deuxième groupe d’hôtes. Copier des tâches entre des playbooks est le signal à surveiller, car à partir de ce moment, chaque correction doit être appliquée deux fois et, un jour, elle ne le sera qu’une seule fois. Un playbook 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 plus difficile à lire.

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

Oui. Ansible exécute pre_tasks, puis tout ce qui est indiqué sous roles:, puis tasks:, et enfin post_tasks:. Il ignore l’ordre dans lequel ces clés apparaissent dans votre fichier. Placer tasks: au-dessus de roles: ne fait pas s’exécuter ces tâches en premier. Si une action doit avoir lieu 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/ a une priorité supérieure à group_vars et host_vars dans l’ordre de précédence d’Ansible. L’inventory ne peut donc pas la remplacer. Déplacez la variable dans defaults/main.yml, qui se trouve près du bas de cet ordre et qui est l’emplacement prévu pour toute valeur qu’un appelant doit pouvoir modifier. Pour confirmer que la cause est la précédence et non 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 essayés, comme dans the role 'common' was not found in /home/deploy/lonely/roles:/home/deploy/lonely. Exécuter le playbook depuis le répertoire parent ne pose aucun problème, 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 l’ignore lorsqu’un répertoire de travail est accessible en écriture par tous.

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

Non. Un rôle se compose uniquement de répertoires portant les noms attendus. mkdir -p roles/common/tasks et un tasks/main.yml suffisent donc déjà à créer un rôle fonctionnel. ansible-galaxy init --init-path roles common évite la saisie manuelle et fournit l’arborescence complète, y compris 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 effectuent réellement une action.

#ansible#roles#playbook#structure#automation