Aller au contenu
Infrastructure as Code medium

Block / rescue / always Ansible : try/catch/finally pour playbooks

75 min de lecture

Logo Ansible

block / rescue / always est l'équivalent Ansible de try / catch / finally. Vous groupez des tâches dans un block:, vous fournissez un rescue: qui s'exécute uniquement si une tâche du block échoue, et un always: qui s'exécute toujours (succès ou échec). Cette page explique le pattern, l'ordre d'exécution exact, et les cas pratiques de production : nettoyage, rollback, notification d'échec.

Sans block/rescue/always, la seule façon de gérer une erreur est ignore_errors: true (anti-pattern) ou failed_when: (souvent insuffisant). block/rescue/always est la gestion d'erreur structurée d'Ansible.

  • La syntaxe block: / rescue: / always:
  • L'ordre d'exécution : block → (rescue si échec) → always
  • L'usage de when: sur un block (propage à toutes les tâches enfants)
  • Patterns de production : nettoyage, rollback, notification
  • La différence avec ignore_errors: (anti-pattern)
- name: Démo block / rescue / always
hosts: db1.lab
become: true
tasks:
- name: Démontrer l'enchaînement block, rescue et always
block:
- name: Tâche A
ansible.builtin.command: /bin/true
changed_when: false
- name: Tâche B (peut échouer)
ansible.builtin.command: /bin/false
changed_when: false
- name: Tâche C (skippée si B échoue)
ansible.builtin.command: /bin/true
changed_when: false
rescue:
- name: Notifier l'erreur
ansible.builtin.debug:
msg: "Le block a échoué !"
always:
- name: Cleanup
ansible.builtin.command: /bin/true
changed_when: false

L'ordre exact :

Block exécution :
Tâche A → succès
Tâche B → ÉCHEC ! Block interrompu, on saute le reste
Rescue exécuté (Notifier l'erreur)
Always exécuté (Cleanup) : toujours

Si toutes les tâches du block réussissent : rescue n'est pas exécuté, always l'est. Si une tâche du block échoue : rescue capture, always est exécuté quoi qu'il arrive.

1. block: ← exécuté en premier
- tâche 1
- tâche 2 (si elle échoue : passage direct au rescue)
- tâche 3 (skippée si tâche 2 a échoué)
2. rescue: ← UNIQUEMENT si une tâche du block a échoué
- tâche cleanup d'erreur
3. always: ← TOUJOURS exécuté (succès ou échec)
- tâche finalisation

Si une tâche du rescue elle-même échoue, le play plante (sauf si failed_when: ou un autre rescue parent).

Voici l'exemple validé sur le lab ecrire-code-block-rescue-always :

- name: Challenge block rescue always
hosts: db1.lab
become: true
tasks:
- name: Déclencher volontairement le rescue
block:
- name: Tenter une commande qui echoue
ansible.builtin.command: /bin/false
changed_when: false
rescue:
- name: Marqueur rescue
ansible.builtin.copy:
dest: /tmp/challenge-rescue.txt
content: "rescue triggered (block failed)\n"
mode: "0644"
always:
- name: Marqueur always
ansible.builtin.copy:
dest: /tmp/challenge-always.txt
content: "always executed\n"
mode: "0644"

PLAY RECAP :

db1.lab : ok=2 changed=2 rescued=1 failed=0

rescued=1 = le block a été rescuté. failed=0 = malgré l'échec du /bin/false, le play a terminé avec succès (grâce au rescue).

- name: Migrer la base avec rollback en cas d'échec
block:
- name: Snapshotter la base
ansible.builtin.command: btrfs subvolume snapshot /var/lib/pgsql /var/lib/pgsql/.snap
changed_when: true
- name: Lancer la migration
ansible.builtin.command: /usr/local/bin/migrate.sh
changed_when: true
rescue:
- name: Restaurer la base depuis le snapshot
ansible.builtin.command: btrfs subvolume rollback /var/lib/pgsql/.snap
changed_when: true
- name: Notifier l'équipe
ansible.builtin.uri:
url: https://slack.example.com/...
method: POST
- name: Travailler dans un répertoire temporaire nettoyé quoi qu'il arrive
block:
- name: Créer un répertoire temporaire
ansible.builtin.tempfile:
state: directory
register: tmpdir
- name: Travailler dedans
ansible.builtin.command: build.sh
args:
chdir: "{{ tmpdir.path }}"
changed_when: true
always:
- name: Toujours nettoyer le tempdir
when: tmpdir.path is defined
ansible.builtin.file:
path: "{{ tmpdir.path }}"
state: absent

always: garantit que le tempdir est supprimé, même si le command: plante.

- name: Installer la pile applicative sur les distributions RHEL
when: ansible_os_family == "RedHat"
block:
- name: Tâche RHEL/AlmaLinux 1
ansible.builtin.dnf:
name: foo
- name: Tâche RHEL/AlmaLinux 2
ansible.builtin.systemd:
name: foo
state: started

Le when: est propagé à toutes les tâches du block. Plus DRY que de répéter le when: sur chacune.

MécanismeUsage
block / rescue / alwaysGestion d'erreur structurée (try/catch/finally)
ignore_errors: trueIgnorer une erreur ponctuelle (à éviter)
failed_when: <expr>Redéfinir ce qui constitue un échec
any_errors_fatal: trueArrêter le play dès la première erreur sur n'importe quel hôte

block/rescue/always est de loin le plus puissant. Si vous écrivez ignore_errors: true, demandez-vous d'abord si un block ne ferait pas mieux.

vars:, become:, become_user:, tags:, when: peuvent être posés au niveau du block et propagés aux tâches du block :

- name: Exécuter un groupe de tâches en root
become: true
vars:
pkg_version: "1.2"
block:
- name: Tâche en root
ansible.builtin.dnf:
name: foo
- name: Autre tâche en root
ansible.builtin.systemd:
name: foo
SymptômeCauseFix
rescue: exécuté même quand block réussitFaux : rescue ne s'exécute QUE si block échoueVérifier le code, ce n'est pas le comportement réel
always: non exécuté quand block échoueFaux : always s'exécute toujoursVérifier qu'il y a bien une section always:
Une tâche qui échoue dans rescue: plante le playComportement attendu : si rescue échoue, le play planteAjouter un autre rescue parent ou ignore_errors: sur la tâche du rescue
Variable du block non visible dans rescue:Faux : vars: du block est visible dans rescue ET alwaysVérifier la portée
failed=0 dans le PLAY RECAP malgré une erreurLe rescue a capturé, rescued=1 apparaît à la placeLire correctement le PLAY RECAP
  • block / rescue / always = try / catch / finally Ansible.
  • block: : tâches à exécuter ; rescue: : tâches si échec ; always: : tâches toujours exécutées.
  • when: sur un block s'applique à toutes les tâches du block.
  • Patterns courants : rollback sur échec, cleanup dans always, notification Slack/Teams dans rescue.
  • Préférer block/rescue/always à ignore_errors: true dans 99 % des cas.

Un rollback ne se prouve qu'en provoquant l'échec. Le lab vous fait grouper des tâches dans un block, déclencher volontairement une erreur pour entrer dans rescue et y écrire la compensation, puis vérifier que always nettoie dans les deux cas. Vous y lisez ansible_failed_task pour identifier la tâche qui a cassé, information que le PLAY RECAP seul ne donne pas.

  • ignore_errors : la directive que block/rescue remplace presque toujours, avec ses rares cas légitimes.
  • any_errors_fatal : le cran au-dessus, quand une erreur sur un seul hôte doit arrêter tout le play.
  • Quiz Écrire du code : 30 questions pour situer votre niveau sur la gestion d'erreurs et le reste du module.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn