
Quand une tâche échoue dans un playbook de 100 lignes, relancer tout est lent et frustrant. Ansible fournit un débogueur interactif qui ouvre un REPL Python au moment de l'échec : on inspecte les variables (p task_vars['x']), on modifie les arguments à chaud (task.args['name'] = 'nginx'), on rejoue la tâche modifiée (redo), on continue le playbook (continue). Activable au niveau task ou play avec debugger: on_failed.
Cette page détaille les 6 commandes du REPL, le workflow recommandé pour fixer une variable au runtime, et les 2 cas où ne pas activer le débogueur (CI/CD, AWX/AAP, cron).
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Activer
debugger: on_failedau niveau task ou play. - Inspecter variables, arguments, résultat avec
p. - Modifier les args (
task.args['x'] = 'y') et rejouer avecredo. - Injecter une variable manquante via
task_vars. - Quitter proprement (
continue,quit). - Quand NE PAS utiliser le débogueur (interactif requis).
Prérequis
Section intitulée « Prérequis »- Avoir lu Verbosité Ansible.
- Comprendre les variables et précédence.
Activer le débogueur
Section intitulée « Activer le débogueur »Trois niveaux d'activation, du plus fin au plus large.
Niveau task
Section intitulée « Niveau task »C'est le réglage le plus sûr, et celui à privilégier quand vous savez déjà quelle tâche pose problème. Le mot-clé debugger est un keyword d'héritage Ansible standard : il s'écrit au même niveau que name ou when, et il ne concerne que la tâche qui le porte. Aucune autre tâche du playbook ne s'arrêtera, même si elle échoue.
tasks: - name: Tâche fragile ansible.builtin.dnf: name: nginx state: present debugger: on_failed # ← s'active uniquement sur cette tâcheNiveau play
Section intitulée « Niveau play »Posé sur le play, le mot-clé est hérité par toutes les tâches, y compris celles des rôles importés. C'est le bon réglage quand vous ne savez pas encore où ça casse, mais il a un coût : sur un playbook qui traite plusieurs dizaines d'hôtes, chaque échec ouvre un prompt et bloque l'exécution tant que vous n'avez pas répondu. Les niveaux intermédiaires block et role acceptent le même mot-clé et permettent de cibler une portion du play sans l'armer en entier.
- name: Déployer avec le débogueur armé hosts: webservers debugger: on_failed # ← s'active sur toutes les tasks de ce play tasks: - name: Installer nginx ansible.builtin.dnf: name: nginx state: presentGlobalement (variable d'environnement)
Section intitulée « Globalement (variable d'environnement) »Cette variable correspond au paramètre ENABLE_TASK_DEBUGGER de la configuration Ansible. Elle arme le débogueur sur toutes les tâches de tous les playbooks lancés depuis ce shell, sans toucher au moindre fichier YAML. C'est précisément ce qui en fait le bon choix pour une session de dépannage ponctuelle : rien à écrire, donc rien à oublier de retirer avant de commiter.
ANSIBLE_ENABLE_TASK_DEBUGGER=True ansible-playbook lab.ymlGranularité : task > block > role > play > configuration globale. Le niveau le plus proche de la tâche gagne, et un debugger: never posé sur une tâche neutralise donc la variable d'environnement.
Valeurs possibles de debugger:
Section intitulée « Valeurs possibles de debugger: »Ansible n'accepte que ces cinq chaînes et rien d'autre : une faute de frappe déclenche un AnsibleParserError au chargement du playbook, pas au moment de l'échec. Vous le saurez donc immédiatement. Dans la pratique, seules deux valeurs servent : on_failed pour le dépannage, et never pour désarmer explicitement une tâche sensible dans un play où le débogueur est armé plus haut.
| Valeur | Effet |
|---|---|
always | Ouvre le REPL après chaque tâche (TDD, très lent) |
never | Désactive (default sauf si globalement activé) |
on_failed | Recommandé, REPL uniquement en cas d'échec |
on_unreachable | REPL uniquement si l'host devient unreachable |
on_skipped | REPL si la tâche est skippée (rare) |
Le REPL au runtime
Section intitulée « Le REPL au runtime »Quand une tâche échoue, vous voyez :
TASK [Installer nginx-impossible] ***fatal: [db1.lab]: FAILED! => {"msg": "...nginx-impossible..."}
[db1.lab] TASK: Installer nginx-impossible (debug)>Le prompt (debug)> indique que vous êtes dans le REPL. Tapez help pour la liste.
Les 6 commandes essentielles
Section intitulée « Les 6 commandes essentielles »Deux lignes de ce tableau ne sont pas des commandes du débogueur mais du Python brut. Le REPL fonctionne ainsi : tout ce qu'il ne reconnaît pas comme une commande est compilé et exécuté dans un espace de noms qui contient task, task_vars, host, play_context et result. C'est ce qui permet d'écrire une affectation directe, mais aussi de provoquer une exception si vous vous trompez de nom ; le REPL affiche alors la classe de l'erreur et reste ouvert.
Deux pièges à connaître avant de manipuler ces commandes. quit ne rend pas la main proprement au shell : il termine ansible-playbook avec le code de sortie 99, ce qui fera échouer un script appelant. Et redo ne rejoue pas forcément la tâche dans l'instant : la tâche est replanifiée, son résultat n'est donc pas nécessairement le prochain que vous verrez passer.
| Commande | Effet |
|---|---|
p <expr> | Pretty-print d'une expression Python, ex p task, p task.args, p task_vars['x'], p result._result |
task.args['x'] = 'y' | Modifie un argument de la tâche en mémoire |
task_vars['x'] = 'y' | Injecte une variable dans le scope de la tâche |
update_task (u) | Recrée la tâche avec les nouvelles variables/args |
redo (r) | Rejoue la tâche modifiée |
continue (c) / quit (q) | Continuer / abandonner le playbook |
Workflow type, fix d'un mauvais nom de paquet
Section intitulée « Workflow type, fix d'un mauvais nom de paquet »La séquence ci-dessous est la boucle de base du débogueur : observer, corriger, rejouer. Elle vaut d'être exécutée au moins une fois sur un lab, car elle fixe le réflexe qui compte. À l'étape 2, p task.args affiche les arguments après templating, c'est-à-dire les valeurs réellement envoyées au module, pas ce que vous avez écrit dans le YAML. C'est souvent là que le problème saute aux yeux, quand une variable s'est résolue en chaîne vide.
-
La tâche échoue :
fatal: [db1.lab]: FAILED! => {"msg": "Failed to install: nginx-impossible..."}[db1.lab] TASK: Installer nginx-impossible (debug)> -
Inspecter ce qui a été passé :
(debug)> p task.args{'name': 'nginx-impossible', 'state': 'present'} -
Modifier l'argument :
(debug)> task.args['name'] = 'nginx' -
Rejouer :
(debug)> redochanged: [db1.lab] -
Continuer :
(debug)> continueTASK [Tâche suivante] ***...
Ce qu'il faut retenir : le bug est corrigé sans modifier le YAML ni relancer le playbook depuis le début. Sur un parc de 50 hôtes où une seule tâche plante en fin de course, cela évite de tout rejouer. La correction reste évidemment en mémoire uniquement : le fichier YAML contient toujours l'erreur, et il faudra la corriger avant le prochain run.
Workflow, injecter une variable manquante
Section intitulée « Workflow, injecter une variable manquante »Le cas précédent modifiait un argument déjà présent. Celui-ci est différent : la variable n'existe pas du tout, et changer task.args ne suffirait pas puisque le template {{ target_dir }} échouerait à nouveau. Il faut donc injecter la variable dans le scope, puis demander à Ansible de reconstruire la tâche. C'est le rôle exact de update_task, qui recharge la tâche depuis sa structure de données d'origine et la re-template avec le task_vars mis à jour.
- name: Déposer un fichier ansible.builtin.copy: dest: "{{ target_dir }}/file.txt" content: "Hello" mode: "0644"Si target_dir n'est pas défini :
fatal: [db1.lab]: FAILED! => {"msg": "...'target_dir' is undefined..."}[db1.lab] TASK: copy (debug)>Au REPL :
(debug)> p task_vars.get('target_dir', 'undefined')'undefined'(debug)> task_vars['target_dir'] = '/tmp'(debug)> update_task(debug)> redochanged: [db1.lab]L'ordre des trois commandes n'est pas interchangeable : task_vars['x'] = 'y' injecte la variable, update_task (raccourci u) recrée la tâche et la re-template avec le nouveau scope, puis redo la replanifie. Sauter update_task rejouerait la tâche avec l'ancien templating, donc avec la même erreur undefined variable.
strategy: linear obligatoire en debug
Section intitulée « strategy: linear obligatoire en debug »- hosts: webservers strategy: free # ← DANGER avec le débogueur debugger: on_failedLe prompt (debug)> bloque la boucle principale de la stratégie, pas les processus de travail déjà lancés. Avec strategy: linear, cela suffit : tous les hôtes avancent au même rythme, ils sont donc tous à l'arrêt sur la même tâche pendant que vous inspectez. Avec strategy: free, chaque hôte progresse à sa vitesse : pendant que vous lisez task.args pour web1.lab, les tâches déjà distribuées à web2.lab s'exécutent, et l'état que vous observez ne correspond plus à celui du parc.
Le code d'Ansible confirme cette contrainte : la restauration d'état déclenchée par redo sur une tâche run_once n'est prise en charge que pour la stratégie linear. Gardez strategy: linear (la valeur par défaut) dès que le débogueur est armé.
Quand NE PAS utiliser le débogueur
Section intitulée « Quand NE PAS utiliser le débogueur »Le débogueur est interactif. À ne jamais activer dans :
- CI/CD (GitHub Actions, GitLab CI) : pas de stdin → le pipeline freeze indéfiniment.
- AWX / AAP : les jobs n'ont pas de prompt utilisateur.
- Cron / systemd timers : pareil.
- Production : laisser
debugger: on_faileddans un playbook commit en prod = bombe à retardement.
Pour ces contextes, préférer :
-vvv+ logs détaillés (page Verbosité).ANSIBLE_KEEP_REMOTE_FILES=1+ inspection forensic.ansible-navigator replay <artifact.json>(page EE) pour rejouer un échec sans relancer.
Mettre en pratique
Section intitulée « Mettre en pratique »Le REPL ne s'apprend pas en lecture : il faut une tâche réellement en échec devant soi. Le lab arme debugger: on_failed sur un playbook qui casse volontairement, d'abord sur un nom de paquet inexistant, puis sur une variable non définie que rien ne fournit. Vous lisez au prompt ce qu'Ansible a réellement envoyé au module, corrigez en mémoire, rejouez, et constatez pourquoi strategy: linear reste obligatoire dès que le débogueur est armé. Le challenge final rejoue le second défaut, un répertoire cible absent à combler au runtime.
À retenir
Section intitulée « À retenir »debugger: on_failedau niveau task ou play, pas en production.pprint accepte n'importe quelle expression Python (vars, args, result).task.args['x'] = ...+redomodifie un argument et rejoue.task_vars['x'] = ...+update_task+redoinjecte une variable manquante.strategy: linearobligatoire en debug : avecfree, les autres hôtes continuent d'avancer pendant que vous êtes au prompt.quittermineansible-playbookavec le code de sortie 99.- Jamais de débogueur en CI, AAP, cron, ou prod (interactivité requise).
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Découvrir une collection : Retrouver le module derrière un nom qualifié, cause fréquente des tâches qui échouent.
- Pipeline CI matrice : En CI, le débogueur est proscrit : ce sont les tests automatisés qui prennent le relais.
- Doc pendant l'examen : Retrouver le nom exact d'un paramètre en moins de trente secondes, jusque depuis le prompt du débogueur.