
Un style guide Ansible n'est pas du purisme : c'est ce qui rend un playbook lisible six mois après, et relisable par un collègue qui ne l'a pas écrit. Cette page donne les conventions attendues sur du code Ansible en 2026, avec la règle ansible-lint qui contrôle chacune : FQCN obligatoire, tâches toujours nommées, modules command et shell réservés au dernier recours, tags orthogonaux, variables préfixées.
Ces conventions ne sont pas une affaire de goût, et c'est ce qui les rend utiles : chacune correspond à une règle outillée, donc vérifiable en intégration continue plutôt que discutée en revue de code. Le référentiel couvre aussi le périmètre de la RHCE EX294, sans s'y limiter.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Les conventions de nommage des tâches, plays, variables, tags ;
- L'usage du FQCN (Fully Qualified Collection Name), et pourquoi il ne se discute plus ;
- Les modules à éviter (
command,shell) et quand ils restent légitimes ; - Les commentaires utiles, par opposition à ceux qui répètent le code ;
- L'usage d'
ansible-linten intégration continue, et ce que chacun de ses six profils exige.
Conventions de nommage
Section intitulée « Conventions de nommage »Chaque play a un name: qui décrit l'objectif métier, pas la liste des modules :
# ❌ Trop technique, paraphrase les modules- name: Run dnf install nginx and systemd start hosts: webservers
# ✅ Décrit l'objectif métier- name: Déployer nginx sur les serveurs web hosts: webserversCe nom s'affiche en tête d'exécution, sous la forme PLAY [Déployer nginx sur les serveurs web] ****. C'est le premier repère que lit celui qui diagnostique une sortie de plusieurs centaines de lignes, souvent des semaines après avoir écrit le playbook. Ne pas le confondre avec le PLAY RECAP de fin d'exécution, qui ne contient aucun nom : il ne liste que les hôtes et leurs compteurs ok, changed, failed.
Chaque tâche porte un name:, sans exception. La règle name[missing] d'ansible-lint est active dès le profil basic, c'est-à-dire pratiquement partout :
# ❌ Pas de name → ansible-lint refuse- ansible.builtin.dnf: name: nginx state: present
# ✅ Name explicite- name: Installer nginx ansible.builtin.dnf: name: nginx state: presentAttention au piège de lecture : le name: de la tâche, celui qui manque dans le premier bloc, n'a rien à voir avec le name: du module dnf, qui désigne le paquet à installer. Deux clés homonymes à deux niveaux d'indentation différents, et c'est la plus fréquente confusion des débutants sur ce module.
Verbe à l'infinitif, capitale en début, pas de point final. Le nom choisi s'affiche dans la bannière de tâche : TASK [Installer nginx] *******.
Variables
Section intitulée « Variables »Les variables sont préfixées par le rôle ou le composant, ce qui évite les collisions quand plusieurs rôles cohabitent dans le même play :
# ❌ Variables génériques, conflits garantis entre rôlesport: 80user: ansibleversion: "1.26"
# ✅ Préfixées par le rôle/composantnginx_port: 80nginx_user: ansiblenginx_version: "1.26"pg_port: 5432pg_user: postgresapp_version: "3.4.1"Le préfixe n'est pas forcément le nom d'un service : app_version préfixe par le composant déployé, ce qui suffit dès lors que deux rôles du même play ne se disputent plus la variable version. La règle var-naming d'ansible-lint, active dès le profil basic, contrôle la forme du nom (minuscules et underscores) mais pas ce choix de préfixe, qui reste le vôtre.
Snake_case pour les variables (jamais kebab-case ni camelCase, c'est la convention Ansible).
Tags orthogonaux et descriptifs. Le rôle d'un tag est de pouvoir cibler un sous-ensemble de tâches via --tags ou les ignorer via --skip-tags :
# ❌ Tags non descriptifs, redondants avec le nametags: - tag1 - step2
# ✅ Tags descriptifs et orthogonauxtags: - nginx # service concerné - firewall # type d'opération - configuration # phaseConvention : nom de service, type d'opération, phase. Pas de pluriel (nginx pas nginxs).
FQCN : obligatoire en 2026
Section intitulée « FQCN : obligatoire en 2026 »Le FQCN (Fully Qualified Collection Name) prend la forme <namespace>.<collection>.<module> :
| Forme courte | FQCN |
|---|---|
dnf | ansible.builtin.dnf |
systemd | ansible.builtin.systemd_service |
firewalld | ansible.posix.firewalld |
selinux | ansible.posix.selinux |
timezone | community.general.timezone |
La deuxième ligne mérite un arrêt, parce qu'elle piège même les praticiens confirmés : le nom canonique du module est désormais ansible.builtin.systemd_service, systemd n'étant conservé que comme alias historique. Les deux fonctionnent aujourd'hui, mais un seul est le nom réel, et c'est celui qu'affiche la documentation.
Le FQCN est obligatoire :
- Pour
ansible-lintà partir du profilproduction, qui active la règlefqcn. - Pour éviter les collisions : deux collections peuvent fournir un module portant le même nom court.
- Pour la RHCE EX294, dont les objectifs publiés demandent d'installer des Content Collections et de s'en servir dans un playbook. Red Hat ne publie aucun barème, donc personne ne peut affirmer combien coûte une forme courte ; ce qui est certain, c'est que le FQCN désigne sans ambiguïté le module voulu, y compris sur une machine d'examen dont vous ne connaissez pas la configuration.
# ❌ Forme courte : le module retenu dépend des collections chargées- name: Ouvrir SSH dans firewalld firewalld: # ← peut désigner un autre module que celui attendu service: ssh state: enabled
# ✅ FQCN explicite- name: Ouvrir SSH dans firewalld ansible.posix.firewalld: service: ssh state: enabledModules command et shell : derniers recours
Section intitulée « Modules command et shell : derniers recours »Les modules command et shell sont non-idempotents par défaut et constituent un anti-pattern dès qu'un module dédié existe. Avant d'écrire un shell:, posez-vous trois questions :
- Existe-t-il un module dédié ?
dnf,service,user,file,copy,lineinfile,template,git,unarchive. Privilégiez-les systématiquement. - Si vous insistez sur
shell: avez-vous posé une garde,creates:,removes:ouchanged_when:, pour qu'il cesse de se déclarerchangedà chaque passage ? - Avez-vous justifié dans un commentaire pourquoi
shellest nécessaire ?
# ❌ shell sans garde, non-idempotent- name: Initialiser la base ansible.builtin.shell: pg_ctl initdb -D /var/lib/pgsql/data
# ✅ shell avec creates: (idempotent)- name: Initialiser la base si pas déjà fait ansible.builtin.shell: pg_ctl initdb -D /var/lib/pgsql/data args: creates: /var/lib/pgsql/data/PG_VERSIONDeux règles d'ansible-lint travaillent ici, et elles ne regardent pas la même chose. no-changed-when est celle qui traque l'absence de garde : elle se déclenche sur command, shell et raw dès qu'aucun de changed_when, creates ou removes n'est présent, et elle n'est active qu'à partir du profil shared. command-instead-of-shell, active dès le profil basic, pose une autre question : elle signale un shell: dont la commande ne contient aucun caractère réclamant un shell, comme un tube ou une redirection, et qui aurait donc dû être un command:.
Commentaires : pourquoi, pas quoi
Section intitulée « Commentaires : pourquoi, pas quoi »Les commentaires Ansible (lignes commençant par #) servent à expliquer le pourquoi d'une décision non-évidente, pas à paraphraser le code :
# ❌ Commentaire qui paraphrase le code- name: Installer nginx ansible.builtin.dnf: name: nginx # On installe nginx state: present # avec l'état présent
# ✅ Commentaire qui explique le pourquoi- name: Installer nginx (version figée car la 1.27 casse notre configuration TLS) ansible.builtin.dnf: name: "nginx-1.26.*" state: presentUn playbook gagne en revanche à porter un commentaire d'en-tête, qui dit à quoi il sert et sur quoi il s'applique. C'est le seul endroit où un commentaire apporte ce que le code ne dit pas : la liste des tâches se lit toute seule, l'intention générale ne se lit nulle part. Les fichiers de configuration que vous déposez avec template: suivent la même logique, avec une ligne supplémentaire qui prévient qu'ils sont générés et qu'une édition à la main sera écrasée.
Indentation et formatage
Section intitulée « Indentation et formatage »Ces quatre points ne changent rien au comportement d'un playbook, et c'est précisément pourquoi ils se négligent. Leur intérêt est ailleurs : un formatage homogène rend les diffs lisibles, et un diff lisible est ce qui permet à une revue de porter sur la logique plutôt que sur des déplacements de lignes. La règle yaml d'ansible-lint, active dès le profil basic, les contrôle pour vous.
- 2 espaces d'indentation (jamais de tabulation), convention Ansible.
---au début de chaque fichier YAML.- Une ligne vide entre les tâches d'un même play pour aérer.
- Quotes systématiques sur les valeurs ambiguës (cf. YAML pour Ansible).
L'outil de garde-fou : ansible-lint
Section intitulée « L'outil de garde-fou : ansible-lint »ansible-lint est l'outil de référence pour faire respecter le style guide automatiquement. Configuration via .ansible-lint à la racine du projet :
---profile: production # min < basic < moderate < safety < shared < productionexclude_paths: - .ansible/ - tests/
skip_list: - meta-runtime # règles que vous décidez d'ignorer
warn_list: - experimental # règles en avertissement plutôt qu'en erreurLes six profils forment une chaîne, et c'est ce qui rend le tableau lisible : chacun reprend tout le précédent et y ajoute ses propres règles. Monter d'un cran n'échange donc jamais une exigence contre une autre, il en empile une de plus. Le tableau les donne dans cet ordre, du plus permissif au plus strict.
| Profil | Ce qu'il ajoute | Quand l'utiliser |
|---|---|---|
min | Les erreurs qui empêchent d'analyser le fichier | Démarrage rapide, projets hérités |
basic | Le gros du style : name, var-naming, yaml, command-instead-of-shell | Projets en cours de reprise |
moderate | La forme des noms : name[casing], name[imperative] | Projets matures |
safety | Ce qui expose : risky-file-permissions, risky-octal, latest | Audit sécurité avant production |
shared | Ce qu'exige un code publié : galaxy, no-changed-when, meta-* | Galaxy, Automation Hub |
production | Le dernier cran : fqcn, meta-no-dependencies, use-loop | Production attendue à terme |
Lancer ansible-lint
Section intitulée « Lancer ansible-lint »L'outil s'exécute sur un fichier ou sur un répertoire entier, et l'option --profile surcharge ce que déclare le .ansible-lint. C'est le moyen de mesurer la dette avant de s'engager : lancez le profil production sur un projet configuré en basic pour voir ce qu'un passage au cran supérieur coûterait, sans rien changer au dépôt.
ansible-lint playbooks/site.ymlansible-lint --profile production playbooks/Sortie typique :
WARNING Listing 3 violation(s) that are fatalfqcn[action-core]: Use FQCN for builtin module actions (dnf).playbooks/site.yml:8 Task/Handler: Installer nginx
name[missing]: All tasks should be named.playbooks/site.yml:15 Task/Handler: ansible.builtin.systemd_service
no-changed-when: Commands should not change things if nothing needs doing.playbooks/site.yml:22 Task/Handler: Initialiser la baseChaque violation porte la règle qui l'a levée et la ligne fautive, ce qui permet de traiter la dette par règle plutôt que fichier par fichier : on corrige tous les fqcn[action-core] d'un coup, puis on passe à la suivante.
Intégration en CI
Section intitulée « Intégration en CI »Le workflow ci-dessous applique trois précautions qui n'ont rien d'optionnel sur une chaîne d'intégration publique. Les actions sont épinglées au SHA du commit, la version en commentaire, parce qu'un tag se redéplace ; les permissions du jeton sont vidées puis rouvertes au strict nécessaire ; et persist-credentials: false empêche le jeton de rester écrit dans le dépôt cloné, où n'importe quelle étape suivante pourrait s'en servir.
on: [push, pull_request]
permissions: {}
jobs: lint: runs-on: ubuntu-latest permissions: contents: read steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - run: pip install ansible-lint==26.9.0 - run: ansible-lint --profile production playbooks/La version d'ansible-lint est figée elle aussi, et ce n'est pas un détail de confort : l'outil ajoute des règles à chaque version, si bien qu'une chaîne non épinglée peut échouer du jour au lendemain sur du code que personne n'a touché. Vous relevez cette version quand vous décidez de traiter les nouvelles règles, pas quand l'amont publie.
Anti-patterns récurrents
Section intitulée « Anti-patterns récurrents »Ce tableau récapitule les huit défauts qui reviennent le plus souvent dans du code Ansible repris, et il se lit dans le sens de la correction : la dernière colonne donne le geste, pas un principe. Les quatre premières lignes sont couvertes par les règles vues plus haut ; les quatre dernières relèvent de la relecture humaine, aucun outil ne décidant à votre place si un ignore_errors: est justifié.
| Anti-pattern | Pourquoi c'est faux | Correction |
|---|---|---|
Tâche sans name: | Sortie illisible, la bannière affiche TASK [ansible.builtin.dnf] | Ajouter un name: qui décrit l'objectif |
Forme courte de module (dnf:) | Ambigu dès que deux collections se ressemblent | Utiliser le FQCN (ansible.builtin.dnf) |
shell: sans garde | Non idempotent, changed à chaque exécution | Utiliser un module dédié, ou ajouter une garde |
Variables génériques (port, user) | Conflits entre rôles | Préfixer (nginx_port, pg_user) |
Secrets en clair dans host_vars/ | Visibles dans git log | ansible-vault ou gestionnaire de secrets externe |
Tags step1, step2 | Non descriptif | Tags orthogonaux : service + opération + phase |
command: qui appelle un gestionnaire de paquets (pip install) | Contourne le module idempotent | Utiliser le module dédié (ansible.builtin.pip) |
ignore_errors: true partout | Masque les vraies erreurs | Justifier par un commentaire ; préférer failed_when: |
Contrôle de connaissances
Section intitulée « Contrôle de connaissances »Vérifiez que l'essentiel de ce guide est acquis. Les questions portent uniquement sur ce qui vient d'être expliqué ici.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
À retenir
Section intitulée « À retenir »name:obligatoire sur chaque tâche, décrit l'objectif métier, pas le module utilisé.- FQCN systématique : exigé par le profil
productiond'ansible-lint, et seule forme non ambiguë sur une machine que vous ne configurez pas. - Variables préfixées par le rôle/composant pour éviter les collisions.
- Modules
command/shellen dernier recours, toujours préférer un module dédié. - Commentaires expliquent le pourquoi, pas le quoi (le code est déjà documentation).
ansible-linten intégration continue : profilproduction, version figée, blocage à chaque demande de fusion.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Modules built-in : où chercher le module dédié qui remplace un
command:ou unshell:mal placé. - Plays et tasks : les mots-clés d'un play, pour appliquer ces conventions au bon niveau.
- Tags : les tags orthogonaux évoqués ici, avec leur syntaxe, leur héritage et
--list-tags.