
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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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)
Prérequis
Section intitulée « Prérequis »- Avoir lu Conditions, when et Boucles, loop.
La syntaxe
Section intitulée « La syntaxe »- 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: falseL'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) : toujoursSi 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.
L'ordre d'exécution complet
Section intitulée « L'ordre d'exécution complet »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'erreur3. always: ← TOUJOURS exécuté (succès ou échec) - tâche finalisationSi une tâche du rescue elle-même échoue, le play plante (sauf si failed_when: ou un autre rescue parent).
Cas pratique, capture d'échec
Section intitulée « Cas pratique, capture d'échec »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=0rescued=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).
Patterns de production
Section intitulée « Patterns de production »Pattern 1, Rollback en cas d'échec
Section intitulée « Pattern 1, Rollback en cas d'échec »- 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: POSTPattern 2, Nettoyage de fichiers temporaires
Section intitulée « Pattern 2, Nettoyage de fichiers temporaires »- 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: absentalways: garantit que le tempdir est supprimé, même si le command: plante.
Pattern 3, when: sur un block entier
Section intitulée « Pattern 3, when: sur un block entier »- 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: startedLe when: est propagé à toutes les tâches du block. Plus DRY que de répéter le when: sur chacune.
block vs autres mécanismes d'erreur
Section intitulée « block vs autres mécanismes d'erreur »| Mécanisme | Usage |
|---|---|
block / rescue / always | Gestion d'erreur structurée (try/catch/finally) |
ignore_errors: true | Ignorer une erreur ponctuelle (à éviter) |
failed_when: <expr> | Redéfinir ce qui constitue un échec |
any_errors_fatal: true | Arrê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.
Variables et block
Section intitulée « Variables et block »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: fooPièges fréquents
Section intitulée « Pièges fréquents »| Symptôme | Cause | Fix |
|---|---|---|
rescue: exécuté même quand block réussit | Faux : rescue ne s'exécute QUE si block échoue | Vérifier le code, ce n'est pas le comportement réel |
always: non exécuté quand block échoue | Faux : always s'exécute toujours | Vérifier qu'il y a bien une section always: |
Une tâche qui échoue dans rescue: plante le play | Comportement attendu : si rescue échoue, le play plante | Ajouter 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 always | Vérifier la portée |
failed=0 dans le PLAY RECAP malgré une erreur | Le rescue a capturé, rescued=1 apparaît à la place | Lire correctement le PLAY RECAP |
À retenir
Section intitulée « À retenir »block / rescue / always=try / catch / finallyAnsible.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: truedans 99 % des cas.
Mettre en pratique
Section intitulée « Mettre en pratique »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.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- ignore_errors : la directive que
block/rescueremplace 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.