
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.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- 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).
Prérequis
Section intitulée « Prérequis »- Avoir lu Concepts des plugins d'inventaire.
- Comprendre la notion de plays multiples dans un même playbook.
Le module ansible.builtin.add_host
Section intitulée « Le module ansible.builtin.add_host »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.
Cas d'usage typique, provisioning cloud
Section intitulée « Cas d'usage typique, provisioning cloud »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: presentLe 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.
Paramètres clés
Section intitulée « Paramètres clés »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ètre | Effet |
|---|---|
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 leadd_host:. - Pas dans
--graphentre deux runs : mêmeansible-inventory --graphne 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:.
Le module ansible.builtin.group_by
Section intitulée « Le module ansible.builtin.group_by »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.
Cas d'usage typique, déploiement par OS
Section intitulée « Cas d'usage typique, déploiement par OS »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: presentLe 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.
Autres exemples
Section intitulée « Autres exemples »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 definedLa 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_bysont ciblables par les plays suivants seulement. Dans le play courant, ils sont lisibles viagroups['nom']mais lehosts:du play ne change plus.
meta: refresh_inventory
Section intitulée « meta: refresh_inventory »Quand un plugin d'inventaire dynamique a un cache, meta: refresh_inventory force le rafraîchissement entre deux plays.
Cas d'usage, provision via Terraform
Section intitulée « Cas d'usage, provision via Terraform »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: presentSans refresh_inventory, le second play utilise le cache de l'inventaire dynamique, qui ne contient pas encore les nouvelles VMs Terraform.
Quand l'utiliser
Section intitulée « Quand l'utiliser »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: trueconfiguré (sinon pas nécessaire).
Combinaison des 3, workflow complet
Section intitulée « Combinaison des 3, workflow complet »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: presentQuatre plays, un seul fichier. Sans ces modules, il faudrait des scripts shell intermédiaires + plusieurs invocations d'Ansible.
Persister un inventaire dynamique
Section intitulée « Persister un inventaire dynamique »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 :
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 ....
Comparaison, quand utiliser quoi ?
Section intitulée « Comparaison, quand utiliser quoi ? »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.
| Besoin | Outil |
|---|---|
| Inventaire stable connu d'avance | Statique YAML (inventory/hosts.yml) |
| Source externe (cloud, NetBox, libvirt) | Plugin d'inventaire (libvirt, AWS, Proxmox) |
| Ajouter un hôte juste-en-temps dans un play | add_host: |
| Cibler par caractéristique runtime (OS, capacité) | group_by: |
| Voir des VMs créées par le play courant | meta: refresh_inventory |
| Persister une découverte runtime | template: + fichier d'inventaire |
Pièges courants
Section intitulée « Pièges courants »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ôme | Cause | Fix |
|---|---|---|
add_host exécuté mais hôte absent au play suivant | Plays définis dans des fichiers séparés | Mettre tous les plays dans le même playbook YAML |
group_by ne crée pas le groupe attendu | gather_facts désactivé | Activer gather_facts: true dans le play parent |
| Plugin dynamique ne voit pas la nouvelle VM | Cache du plugin | meta: refresh_inventory + --refresh-cache au besoin |
Variables passées à add_host ignorées | Réservé : ne pas overrider ansible_host avec mauvaise valeur | Vérifier ansible-inventory --host <new> après le play |
La tâche est toujours changed | add_host rapporte changed à chaque exécution, même sur un hôte déjà présent | Ne pas s'en servir comme indicateur de dérive en CI ; ajouter changed_when: false si le compteur pollue le rapport |
À retenir
Section intitulée « À retenir »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_hostetgroup_byne sont visibles que dans les plays suivants du même playbook.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Plugin NetBox : l'inverse de l'inventaire volatil, une source de vérité persistante interrogée à chaque exécution.
- Écrire un script custom : persister ce que
add_hostproduit au runtime dans une source réutilisable. - Modules Ansible, vue d'ensemble : la cartographie des collections et des modules que vos playbooks vont consommer.