Aller au contenu
Infrastructure as Code medium

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

12 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 {{ inventory_hostname }} comme drain dans HAProxy
ansible.builtin.shell: |
echo "disable server backend/{{ inventory_hostname }}" \
| socat stdio /var/run/haproxy.sock
delegate_to: lb1.lab # ← exécution SUR lb1, mais en boucle pour chaque webserver

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

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 # version explicite
- name: Idem (forme courte)
ansible.builtin.uri:
url: https://ca.local/issue
local_action: ansible.builtin.uri url=https://ca.local/issue # version raccourcie

La forme delegate_to: localhost est préférée car plus lisible (la forme courte local_action: mélange syntaxes).

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

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 {{ inventory_hostname }} de HAProxy
ansible.builtin.shell: |
echo "disable server backend/{{ inventory_hostname }}" \
| socat stdio /var/run/haproxy.sock
delegate_to: lb1.lab
- name: Attendre la fin des connexions actives
ansible.builtin.wait_for:
timeout: 30
tasks:
- name: Déployer la nouvelle version
ansible.builtin.dnf:
name: app
state: latest
post_tasks:
- name: Réintégrer {{ inventory_hostname }} dans HAProxy
ansible.builtin.shell: |
echo "enable server backend/{{ inventory_hostname }}" \
| socat stdio /var/run/haproxy.sock
delegate_to: lb1.lab

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
- 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).

Cette page a un lab d’accompagnement : labs/ecrire-code/delegation/ dans stephrobert/ansible-training. Il contient un README.md guidé, un Makefile (make verify lance les tests), et un challenge final auto-évalué : delegate_to: db1.lab + run_once depuis un play webservers (1 seul fichier sur db1).

Une fois le lab provisionné :

Fenêtre de terminal
cd ~/Projets/ansible-training/labs/ecrire-code/delegation/
cat README.md # tuto pas à pas
cat challenge/README.md # consigne du challenge final
pytest -v challenge/tests/ # lancer les tests testinfra

Si les tests passent, vous maîtrisez les concepts couverts dans ce guide. En cas de blocage, docs/troubleshooting.md à la racine du repo couvre les pièges fréquents (rate-limit SSH, clé absente, collection manquante).

Ce site vous est utile ?

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

Je maintiens +700 guides gratuits, sans pub ni tracing. Aujourd'hui, ce site ne couvre même pas mes frais d'hébergement, d'électricité, de matériel, de logiciels, mais surtout de cafés.

Un soutien régulier, même symbolique, m'aide à garder ces ressources gratuites et à continuer de produire des guides de qualité. Merci pour votre appui.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn