Aller au contenu
Infrastructure as Code medium

Modules add_host, group_by, meta refresh_inventory : inventaire dynamique au runtime

50 min de lecture

Logo Ansible

Les plugins d'inventaire sont géniaux pour gérer un parc évolutif, mais ils s'exécutent avant le playbook. Que faire quand un play provisionne une VM dont on ne connaît pas encore le nom ou l'IP, et que le play suivant doit l'utiliser ? add_host ajoute un hôte au runtime, group_by crée des groupes basés sur les facts, et meta: refresh_inventory recharge les plugins en cours de play.

Ces trois mécanismes complètent les plugins d'inventaire, ils sont indispensables pour orchestrer du provisioning + configuration en un seul playbook.

  • Ajouter un hôte à l'inventaire au runtime avec add_host.
  • Créer des groupes dynamiques basés sur les facts avec group_by.
  • Forcer le rechargement d'un plugin d'inventaire avec meta: refresh_inventory.
  • Combiner ces 3 mécanismes pour un workflow provision + configure en un seul playbook.
  • Persister un inventaire dynamique entre plays (limites et workarounds).

add_host ajoute un hôte à l'inventaire en mémoire pendant l'exécution. L'hôte devient utilisable par les plays suivants du même playbook.

Le playbook ci-dessous illustre la seule situation où add_host est réellement irremplaçable : l'adresse de la machine n'existe pas au moment où Ansible construit son inventaire. Repérez deux choses à la lecture : le premier play cible localhost (il ne se connecte à rien), et la valeur passée à name: sort d'un register: de la tâche précédente. Tout le reste, groupes et variables de connexion, est renseigné à la main puisque aucun fichier host_vars/ ne couvre cet hôte.

---
- name: Provisionner une VM AWS
hosts: localhost
gather_facts: false
tasks:
- name: Lancer une instance EC2
amazon.aws.ec2_instance:
name: web-new-2026
instance_type: t3.micro
image_id: ami-0abcdef1234567890
wait: true
register: ec2_result
- name: Ajouter la nouvelle instance à l'inventaire
ansible.builtin.add_host:
name: "{{ ec2_result.instances[0].public_ip_address }}"
groups: webservers,new_instances
ansible_user: ec2-user
ansible_ssh_private_key_file: ~/.ssh/aws-key
- name: Configurer la nouvelle VM
hosts: new_instances
tasks:
- name: Installer nginx
ansible.builtin.dnf:
name: nginx
state: present

Le pattern : le premier play tourne sur localhost, provisionne la VM, récupère son IP, l'ajoute via add_host. Le second play cible le groupe new_instances qui vient d'être créé. Sans add_host, il faudrait deux playbooks séparés + un mécanisme de transmission de l'IP.

Seul name: est obligatoire. Tout ce que vous ajoutez à côté de name: et groups: devient une variable d'hôte attachée à la machine créée, ce qui explique pourquoi ansible_user: et ansible_ssh_private_key_file: s'écrivent directement dans la tâche. La ligne à retenir est la dernière du tableau : c'est elle qui permet de transmettre n'importe quelle donnée du provisioning (identifiant d'instance, zone, tag de facturation) au play suivant, sans passer par un fichier intermédiaire.

ParamètreEffet
name:Hostname ou IP de l'hôte à ajouter
groups:Groupes auxquels l'ajouter (séparés par virgule)
ansible_host:IP de connexion si différente du name:
ansible_user:User SSH pour cette VM
<var>: <valeur>Toute autre variable est attachée au host

add_host écrit dans l'inventaire en mémoire du processus ansible-playbook en cours. Rien n'est écrit sur disque, ce qui explique les trois limites ci-dessous : elles découlent toutes du même mécanisme. Anticipez-les avant de bâtir un workflow dessus, sinon vous découvrirez le problème au deuxième run, en production.

  • Volatil : l'hôte n'existe que pour ce run. Le run suivant ne le voit pas.
  • Pas de host_vars/ : les variables sont définies inline dans le add_host:.
  • Pas dans --graph entre deux runs : même ansible-inventory --graph ne le voit pas (il n'a pas exécuté le play).

Pour persister, il faut écrire l'hôte dans un fichier d'inventaire via template: ou lineinfile:.

group_by crée un groupe à la volée basé sur les facts ou variables des hôtes. Idéal pour cibler par caractéristique runtime plutôt que par config statique.

L'exemple ci-dessous découpe un parc hétérogène en groupes calculés à partir des facts. Le détail qui conditionne tout : la clé passée à key: devient littéralement le nom du groupe, donc os_{{ ansible_facts.os_family | lower }} produit os_redhat sur une Rocky Linux et os_debian sur une Ubuntu. Si vous oubliez le filtre | lower, vous obtiendrez os_RedHat et les plays suivants ne cibleront rien, sans message d'erreur.

---
- name: Grouper par famille d'OS
hosts: all
tasks:
- name: Créer un groupe par OS family
ansible.builtin.group_by:
key: "os_{{ ansible_facts.os_family | lower }}"
- name: Tâches spécifiques RHEL
hosts: os_redhat
tasks:
- name: Installer firewalld
ansible.builtin.dnf:
name: firewalld
state: present
- name: Tâches spécifiques Debian
hosts: os_debian
tasks:
- name: Installer ufw
ansible.builtin.apt:
name: ufw
state: present

Le pattern : le premier play gather_facts sur tous les hôtes et crée des groupes os_redhat, os_debian, os_archlinux selon ansible_os_family. Les plays suivants ciblent ces groupes générés au runtime.

Avantage sur when: ansible_os_family == 'RedHat' : code plus propre quand vous avez beaucoup de tâches par OS.

group_by n'est pas réservé à l'OS : toute valeur récupérable dans ansible_facts peut servir de critère de regroupement. Les trois cas ci-dessous couvrent le dimensionnement matériel, la version majeure de distribution et la présence d'un service. Attention au dernier : ansible_facts.services n'est pas rempli par la collecte de facts par défaut, il faut avoir exécuté ansible.builtin.service_facts auparavant dans le même play, sinon la condition est toujours fausse et le groupe n'apparaît jamais.

# Grouper par nombre de CPU
- name: Regrouper les hôtes dynamiquement
ansible.builtin.group_by:
key: "cpu_{{ ansible_facts.processor_cores }}"
# Grouper par version de distribution
- name: Regrouper les hôtes dynamiquement
ansible.builtin.group_by:
key: "rhel_{{ ansible_facts.distribution_major_version }}"
# Grouper par présence d'un service
# service_facts est obligatoire : gather_facts ne remplit pas ansible_facts.services
- name: Collecter l'état des services
ansible.builtin.service_facts:
- name: Regrouper les hôtes dynamiquement
ansible.builtin.group_by:
key: "has_docker"
when: ansible_facts.services['docker.service'] is defined

La contrainte qui surprend le plus vient du moteur d'exécution : Ansible fige la liste des hôtes d'un play au moment où le play démarre. Un groupe créé en cours de play apparaît bien dans la variable groups, mais il est trop tard pour que le play courant y envoie des tâches. C'est pour cela que tous les exemples de cette page découpent le travail en plusieurs plays.

  • Comme add_host, volatil, n'existe que pour le run.
  • Les groupes créés par group_by sont ciblables par les plays suivants seulement. Dans le play courant, ils sont lisibles via groups['nom'] mais le hosts: du play ne change plus.

Quand un plugin d'inventaire dynamique a un cache, meta: refresh_inventory force le rafraîchissement entre deux plays.

Ici, ce n'est pas Ansible qui crée les machines : terraform apply s'en charge, et le plugin d'inventaire doit relire sa source pour les découvrir. Notez la position de la tâche meta: : elle est la dernière du premier play, jamais au début du second. Le rafraîchissement doit avoir lieu pendant qu'un play tourne encore, sinon le play suivant a déjà résolu sa liste d'hôtes sur l'ancien cache.

---
- name: Provisionner avec Terraform
hosts: localhost
tasks:
- name: Lancer terraform apply
ansible.builtin.command: terraform apply -auto-approve
args:
chdir: ./terraform/
changed_when: true
- name: Rafraîchir l'inventaire pour voir les nouvelles VMs
ansible.builtin.meta: refresh_inventory
- name: Configurer les nouvelles VMs
hosts: webservers # ce groupe contient maintenant les VMs Terraform
tasks:
- name: Installer nginx
ansible.builtin.dnf:
name: nginx
state: present

Sans refresh_inventory, le second play utilise le cache de l'inventaire dynamique, qui ne contient pas encore les nouvelles VMs Terraform.

refresh_inventory relance tous les plugins d'inventaire configurés, ce qui peut coûter plusieurs secondes et un appel d'API par source. Ne le placez donc pas par précaution un peu partout : les trois situations ci-dessous sont les seules où il apporte quelque chose. Si aucun de vos plugins n'active cache: true, l'inventaire est déjà relu à chaque invocation et l'appel ne change rien.

  • Après une commande qui modifie l'infrastructure (Terraform, virt-install, AWS CLI).
  • Avant un play qui doit voir les nouvelles ressources.
  • Sur un plugin avec cache: true configuré (sinon pas nécessaire).

Provisioning AWS + bootstrap Linux + déploiement application en un seul playbook. Lisez ce fichier par ses frontières de plays plutôt que par ses tâches : chaque - name: de premier niveau marque le moment où Ansible recalcule sa liste d'hôtes, et c'est exactement là que les hôtes ajoutés par add_host puis les groupes créés par group_by deviennent ciblables. Le play 2 est le pivot : il exécute gather_facts (activé par défaut, contrairement au play 1) et pose donc les facts dont group_by a besoin.

---
- name: 1. Provisionner l'infra
hosts: localhost
gather_facts: false
tasks:
- name: Lancer Terraform
ansible.builtin.command: terraform apply -auto-approve
args:
chdir: ./terraform/
changed_when: true
- name: Récupérer les IPs
ansible.builtin.command: terraform output -json instance_ips
register: ips_output
changed_when: false
- name: Ajouter les nouvelles VMs à l'inventaire
ansible.builtin.add_host:
name: "{{ item }}"
groups: new_vms
ansible_user: ec2-user
loop: "{{ ips_output.stdout | from_json }}"
- name: 2. Bootstrap Linux (gather_facts pour obtenir l'OS)
hosts: new_vms
tasks:
- name: Mettre à jour les paquets
ansible.builtin.dnf:
name: "*"
state: latest
- name: Grouper par OS family pour la suite
ansible.builtin.group_by:
key: "os_{{ ansible_facts.os_family | lower }}"
- name: 3. Configuration RHEL
hosts: os_redhat
tasks:
- name: Installer firewalld
ansible.builtin.dnf:
name: firewalld
state: present
- name: 4. Configuration Debian
hosts: os_debian
tasks:
- name: Installer ufw
ansible.builtin.apt:
name: ufw
state: present

Quatre plays, un seul fichier. Sans ces modules, il faudrait des scripts shell intermédiaires + plusieurs invocations d'Ansible.

Les modules add_host et group_by sont volatils. Pour persister un host découvert au runtime, écrire l'inventaire :

- name: Persister la liste des nouvelles VMs
ansible.builtin.template:
src: inventory.j2
dest: ./inventory/runtime-discovered.yml
mode: "0644"
delegate_to: localhost
vars:
discovered_hosts: "{{ groups['new_vms'] }}"

Avec inventory.j2 :

---
all:
children:
discovered:
hosts:
{% for h in discovered_hosts %}
{{ h }}:
ansible_host: {{ hostvars[h]['ansible_host'] | default(h) }}
{% endfor %}

Les deux balises de boucle sont collées à la marge gauche, ce n'est pas une coquetterie de mise en forme. Ansible active trim_blocks par défaut : le saut de ligne qui suit une balise de bloc Jinja est supprimé, si bien que les espaces d'indentation placés devant la balise viennent se coller au début de la ligne suivante. Une balise de boucle indentée de huit espaces produit un hostname décalé de seize espaces, et le fichier obtenu n'est plus du YAML valide. Contrôlez le rendu avant de vous en servir :

Fenêtre de terminal
python3 -c "import yaml; yaml.safe_load(open('inventory/runtime-discovered.yml'))"

La commande ne doit rien afficher. Le fichier généré est alors utilisable par les commandes Ansible futures : ansible-playbook -i ./inventory/runtime-discovered.yml ....

Ce tableau se lit par la colonne de gauche : partez du besoin réel, pas de l'outil qui vous plaît. Les deux premières lignes couvrent la très grande majorité des parcs ; les modules runtime des trois lignes suivantes sont des compléments pour le cas particulier du provisioning, pas une alternative à un inventaire correctement écrit. Si vous vous surprenez à reconstruire tout votre inventaire avec add_host, c'est le signe qu'il manque un plugin d'inventaire adapté à votre source.

BesoinOutil
Inventaire stable connu d'avanceStatique YAML (inventory/hosts.yml)
Source externe (cloud, NetBox, libvirt)Plugin d'inventaire (libvirt, AWS, Proxmox)
Ajouter un hôte juste-en-temps dans un playadd_host:
Cibler par caractéristique runtime (OS, capacité)group_by:
Voir des VMs créées par le play courantmeta: refresh_inventory
Persister une découverte runtimetemplate: + fichier d'inventaire

Ces pannes partagent une racine commune : l'inventaire runtime vit dans la mémoire d'un seul processus ansible-playbook, et rien ne le signale à l'exécution. Aucune de ces situations ne produit d'erreur explicite ; vous obtenez un play qui affiche skipping: no hosts matched et poursuit sans broncher. Le réflexe de diagnostic est donc toujours le même : afficher groups avec un debug: juste avant le play qui ne cible rien.

SymptômeCauseFix
add_host exécuté mais hôte absent au play suivantPlays définis dans des fichiers séparésMettre tous les plays dans le même playbook YAML
group_by ne crée pas le groupe attendugather_facts désactivéActiver gather_facts: true dans le play parent
Plugin dynamique ne voit pas la nouvelle VMCache du pluginmeta: refresh_inventory + --refresh-cache au besoin
Variables passées à add_host ignoréesRéservé : ne pas overrider ansible_host avec mauvaise valeurVérifier ansible-inventory --host <new> après le play
La tâche est toujours changedadd_host rapporte changed à chaque exécution, même sur un hôte déjà présentNe pas s'en servir comme indicateur de dérive en CI ; ajouter changed_when: false si le compteur pollue le rapport
  • add_host: = ajouter un hôte au runtime, utilisable par les plays suivants. Volatil.
  • group_by: = créer un groupe basé sur les facts (ansible_os_family, etc.). Volatil.
  • meta: refresh_inventory = forcer le rechargement d'un plugin avec cache. À placer entre deux plays.
  • Combinaison des 3 = workflow provision + configure en un seul playbook.
  • Persister un inventaire runtime nécessite d'écrire un fichier YAML via template:.
  • Plays séparés : add_host et group_by ne sont visibles que dans les plays suivants du même playbook.

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