Aller au contenu
Infrastructure as Code medium

Délégation Ansible : delegate_to, run_once, local_action

75 min de lecture

Logo Ansible

La délégation Ansible permet d'exécuter une tâche sur un autre hôte que celui ciblé par le play, ou de ne l'exécuter qu'une seule fois quel que soit le nombre d'hôtes. Cas typique : pendant un déploiement sur 50 webservers, vous devez drainer chaque hôte du load-balancer (commande à lancer sur le LB, pas sur le webserver). C'est l'usage exact de delegate_to. De même, une migration de base de données ne doit tourner qu'une fois même si 5 webservers la déclenchent, c'est run_once.

Cette page couvre les trois mécanismes de délégation : delegate_to (rediriger vers un autre hôte), run_once (exécuter une seule fois), local_action (raccourci pour cibler le control node), avec les cas concrets de production où vous en aurez besoin.

  • delegate_to : exécuter une tâche sur un hôte différent du hosts: du play ;
  • run_once : forcer l'exécution sur un seul hôte quel que soit le nombre cible ;
  • local_action : raccourci pour delegate_to: localhost ;
  • delegate_facts: true : faire en sorte que les facts récupérés s'appliquent à l'hôte délégué ;
  • Les patterns de production : drain LB, migration DB one-shot, écriture inventaire dynamique.

Une tâche dans un play hosts: webservers s'exécute sur chaque webserver. Avec delegate_to, vous redirigez l'exécution :

- name: Drainer du load-balancer
hosts: webservers
serial: 1
tasks:
- name: Marquer comme drain dans HAProxy l'hôte {{ inventory_hostname }}
ansible.builtin.shell: |
set -o pipefail
echo "disable server backend/{{ inventory_hostname }}" \
| socat stdio /var/run/haproxy.sock
args:
executable: /bin/bash
delegate_to: lb1.lab # ← exécution SUR lb1, mais en boucle pour chaque webserver
changed_when: true

Le play boucle sur chaque webserver mais chaque itération exécute la commande sur lb1.lab, en injectant {{ inventory_hostname }} (le webserver courant) dans la commande. Résultat : le LB drainera web1, puis web2, puis web3 successivement.

delegate_to accepte un hôte de l'inventaire (lb1.lab), un groupe (avec groups['lbs'][0]), ou une adresse IP littérale.

Une tâche en run_once: true ne s'exécute que sur le premier hôte du play, peu importe combien d'hôtes sont ciblés :

- name: Migration de base
hosts: webservers
tasks:
- name: Lancer la migration SQL (UNE SEULE FOIS)
ansible.builtin.command: /usr/local/bin/migrate.sh
run_once: true
delegate_to: db1.lab # ← combiné : 1 fois, sur db1
changed_when: true

run_once seul exécute la tâche sur un webserver au hasard (le premier de la liste). Combiné à delegate_to: db1.lab, la tâche tourne une seule fois sur db1. C'est le pattern migration one-shot.

local_action est un alias pour delegate_to: localhost :

- name: Récupérer un certificat depuis l'API locale
ansible.builtin.uri:
url: https://ca.local/issue
delegate_to: localhost # la forme à utiliser

La forme raccourcie existe et s'écrit local_action: ansible.builtin.uri url=https://ca.local/issue, sur une seule ligne. Elle fonctionne toujours, vérifié sur ansible-core 2.20 : le moteur l'exécute sans émettre le moindre avertissement de dépréciation.

Elle est pourtant à éviter, pour deux raisons. Elle mélange deux syntaxes, les paramètres passant en clé=valeur au lieu du YAML habituel, ce qui casse la lecture d'un playbook. Et ansible-lint la refuse en profil production, sous la règle deprecated-local-action : un playbook qui l'emploie ne passera pas votre intégration continue. Retenez delegate_to: localhost, qui dit exactement la même chose en restant homogène avec le reste de vos tâches.

Cas typique : interroger une API qui n'est joignable que depuis votre poste, générer un fichier qui sera ensuite distribué via copy:.

Quand vous faites gather_facts: ou exécutez un module qui retourne des facts (setup, command avec register:), ces facts sont rattachés à l'hôte courant par défaut. Avec delegate_facts: true, ils sont rattachés à l'hôte délégué :

- name: Récupérer la config depuis lb1
ansible.builtin.command: cat /etc/haproxy/haproxy.cfg
delegate_to: lb1.lab
delegate_facts: true # le résultat va dans hostvars['lb1.lab']
register: lb_config
changed_when: false

Sans delegate_facts: true, le résultat va dans hostvars['<hôte courant>'], ce qui est rarement ce que vous voulez quand la donnée concerne lb1.

Le pattern complet pour un rolling deploy avec drain :

- name: Déployer en rolling avec drain HAProxy
hosts: webservers
serial: 1
become: true
pre_tasks:
- name: Drainer de HAProxy l'hôte {{ inventory_hostname }}
ansible.builtin.shell: |
set -o pipefail
echo "disable server backend/{{ inventory_hostname }}" \
| socat stdio /var/run/haproxy.sock
args:
executable: /bin/bash
delegate_to: lb1.lab
changed_when: true
- name: Attendre la fin des connexions actives
ansible.builtin.wait_for:
timeout: 30
tasks:
- name: Déployer la version cible de l'application
ansible.builtin.dnf:
name: "app-{{ app_version }}"
state: present
post_tasks:
- name: Réintégrer dans HAProxy l'hôte {{ inventory_hostname }}
ansible.builtin.shell: |
set -o pipefail
echo "enable server backend/{{ inventory_hostname }}" \
| socat stdio /var/run/haproxy.sock
args:
executable: /bin/bash
delegate_to: lb1.lab
changed_when: true

serial: 1 + delegate_to: lb1.lab = un webserver à la fois est drainé, déployé, réintégré.

Plusieurs webservers démarrent l'application, la migration de schéma DB ne doit tourner qu'une fois :

- name: Déploiement applicatif
hosts: webservers
tasks:
- name: Lancer la migration de schéma
ansible.builtin.command: /opt/app/bin/migrate
run_once: true
delegate_to: db1.lab # une fois, sur la DB
register: migration_result
changed_when: true
- name: Démarrer l'application sur chaque webserver
ansible.builtin.systemd:
name: app
state: started
when: migration_result is defined

Après création d'une VM, vous voulez l'ajouter à l'inventaire en mémoire pour le reste du playbook :

- name: Provisionner et configurer une nouvelle VM
hosts: localhost
tasks:
- name: Créer la VM
community.libvirt.virt:
name: web3
state: running
- name: Ajouter web3 à l'inventaire en mémoire
ansible.builtin.add_host:
name: web3.lab
groups: webservers
ansible_host: 10.10.20.23
- name: Configurer la nouvelle VM
hosts: web3.lab
tasks:
- name: Installer nginx
ansible.builtin.dnf:
name: nginx

Le second play utilise l'hôte créé dynamiquement par le premier, sans avoir à réécrire l'inventaire fichier.

SymptômeCauseFix
delegate_to suivi mais facts ne s'appliquent pasManque delegate_facts: trueAjouter delegate_facts: true si le but est de récupérer des facts pour l'hôte délégué
run_once exécute sur le mauvais hôteSans delegate_to, run_once prend le premier de la liste (peut varier)Toujours combiner run_once: true + delegate_to: <hôte>
delegate_to: localhost mais SSH essaie de se connecterconnection: local n'est pas impliciteAjouter connection: local ou utiliser local_action
Variable inventory_hostname montre localhost au lieu du webserverVous lisez sur la tâche déléguée, inventory_hostname est l'hôte d'origine, pas la cible déléguéeUtiliser delegate_to_host (la cible) si besoin
delegate_to: sur un hôte hors inventaireAnsible ne sait pas comment se connecterAjouter via add_host: ou définir l'hôte dans l'inventaire
  • delegate_to: <hôte> redirige l'exécution d'une tâche vers un autre hôte, pattern drain LB.
  • run_once: true force l'exécution sur un seul hôte (le premier), combiner avec delegate_to: pour fixer la cible.
  • local_action: est l'alias de delegate_to: localhost (préférer la forme explicite).
  • delegate_facts: true rattache les facts à l'hôte délégué (utile pour les register: sur un hôte tiers).
  • add_host: ajoute un hôte à l'inventaire en mémoire pour le reste du playbook (utile après provisioning dynamique).

La délégation se comprend en regardant où le fichier atterrit. Le lab vous fait rediriger une tâche vers un autre hôte avec delegate_to, la restreindre à une seule exécution dans un play multi-hôtes avec run_once, puis viser le control node avec local_action. La validation compte les fichiers posés sur chaque machine, ce qui rend une erreur de cible immédiatement visible.

  • Facts et magic vars : hostvars et inventory_hostname pour lire les données de l'hôte d'origine depuis l'hôte délégué.
  • register et set_fact : comprendre où atterrit une variable capturée quand delegate_facts: entre en jeu.
  • Block / rescue / always : remettre l'hôte dans le load balancer même quand la mise à jour échoue.

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