Aller au contenu
Infrastructure as Code medium

Facts et magic vars Ansible : ansible_facts, gather_subset, hostvars

75 min de lecture

Logo Ansible

Au début de chaque play, Ansible peut collecter automatiquement des centaines de variables sur chaque managed node : distribution, version, IP, mémoire, CPU, montages, packages, services, utilisateurs. Ce sont les facts, exposés sous la racine ansible_facts.* (et historiquement aussi en ansible_* directement). Cette page explique comment les utiliser, comment limiter leur collecte pour gagner en performance (gather_subset:), et comment lire les magic vars d'Ansible : inventory_hostname, groups, hostvars, ansible_play_hosts.

Maîtriser cette couche, c'est passer du playbook qui cible 1 hôte au playbook qui s'adapte automatiquement à la distribution, à la mémoire disponible, à la composition du groupe, sans aucune config manuelle.

  • Activer / désactiver gather_facts: au niveau du play
  • Limiter la collecte avec gather_subset: pour gagner du temps sur grandes fleets
  • Lire les facts les plus utiles : ansible_distribution, ansible_default_ipv4, ansible_memtotal_mb, ansible_memory_mb
  • Utiliser les magic vars : inventory_hostname, groups, hostvars, ansible_play_hosts, ansible_play_batch
  • Accéder aux facts d'un autre hôte via hostvars['<host>'].ansible_*

Au démarrage de chaque play (par défaut), Ansible exécute le module setup sur chaque hôte cible. Ce module remonte des centaines de variables :

Fenêtre de terminal
ansible web1.lab -m setup | head -40

Sortie (extrait) :

web1.lab | SUCCESS => {
"ansible_facts": {
"ansible_distribution": "AlmaLinux",
"ansible_distribution_version": "10.1",
"ansible_distribution_major_version": "10",
"ansible_default_ipv4": {
"address": "10.10.20.21",
"interface": "eth0",
"netmask": "255.255.255.0"
},
"ansible_memtotal_mb": 1740,
"ansible_processor_cores": 1,
...
}
}

Ces facts sont disponibles dans toutes les tâches qui suivent, directement par leur nom ({{ ansible_distribution }}) ou via ansible_facts.*.

Le module setup remonte plusieurs centaines de clés ; celles du tableau couvrent la quasi-totalité des besoins d'un playbook courant. Regardez la colonne Type avant d'écrire une condition : ansible_distribution_version vaut "10.1", une chaîne, donc ansible_distribution_version > 9 échoue. Pour comparer des numéros de version, utilisez le filtre is version('10', '>=') ou la clé ansible_distribution_major_version. Même piège inverse sur ansible_memtotal_mb, qui est un entier et se calcule directement. Notez enfin la différence entre ansible_hostname (nom court, tel que la machine se connaît) et inventory_hostname (nom tel que l'inventaire le désigne) : ils divergent souvent, et c'est presque toujours le second qu'il faut pour identifier un hôte.

FactTypeExemple
ansible_distributionstringAlmaLinux, Ubuntu, Debian
ansible_distribution_versionstring10.1
ansible_distribution_major_versionstring10
ansible_os_familystringRedHat, Debian
ansible_default_ipv4.addressstring10.10.20.21
ansible_all_ipv4_addresseslist["10.10.20.21"]
ansible_memtotal_mbint1740
ansible_processor_coresint1
ansible_processor_countint1
ansible_kernelstring5.14.0-...el10.x86_64
ansible_hostnamestringweb1 (court)
ansible_fqdnstringweb1.lab (FQDN)
ansible_python_versionstring3.12.x
ansible_pkg_mgrstringdnf, apt

La phase gather_facts: true prend 2-5 secondes par hôte. Sur 100 hôtes, c'est 5 minutes perdues si vous n'utilisez aucun fact :

- name: Tâche simple sans facts
hosts: webservers
gather_facts: false # désactive la collecte
tasks:
- name: Install nginx
ansible.builtin.dnf:
name: nginx
state: present

become_method: sudo et les modules courants (dnf, systemd) n'ont pas besoin des facts. Les variables comme ansible_distribution ne sont en revanche plus disponibles.

Plutôt que tout collecter ou rien, filtrez :

- name: Collecter uniquement les facts réseau
hosts: webservers
gather_facts: true
gather_subset:
- "!all" # ne pas tout collecter
- "!min" # même pas le minimum
- network # mais inclure le réseau

Subsets disponibles :

SubsetContient
allTout (par défaut)
minSous-ensemble minimal (distribution, kernel, hostname)
networkInterfaces, IPs, MAC
hardwareCPU, mémoire, disques
virtualVirtualisation détectée
facter, ohaiExternal fact gatherers

Préfixer avec ! = exclure, et l'ordre compte : la liste se lit de gauche à droite, chaque entrée retire ou rajoute par-dessus la précédente. C'est pourquoi on écrit !all puis !min puis le sous-ensemble voulu, et jamais l'inverse. Combinaisons typiques :

  • ['!all', '!min', 'network'] : seulement le réseau
  • ['!all', 'min'] : juste le minimum vital

Le gain se mesure facilement en comptant les clés remontées, sans avoir besoin d'un chronomètre :

Fenêtre de terminal
for s in all min '!all,!min,network' '!all,!min'; do
printf '%-20s ' "$s"
ansible localhost -m setup -a "gather_subset=$s" \
| sed '1s/^[^{]*//' \
| python3 -c 'import sys,json; print(len(json.load(sys.stdin)["ansible_facts"]))'
done

Sur un poste de développement, ce comptage donne environ 198 clés pour all, 55 pour min et 2 pour !all,!min (seulement les métadonnées de la collecte elle-même). Les sous-ensembles network et hardware sont les plus lourds, parce qu'ils énumèrent respectivement toutes les interfaces et tous les points de montage : une machine chargée en interfaces virtuelles, typiquement un hôte Docker, voit son temps de collecte réseau exploser. Mesurez sur vos machines avant de choisir, les écarts dépendent entièrement du parc.

Les magic vars sont injectées par Ansible lui-même à partir de l'inventaire et de l'état du play : elles n'ont rien à voir avec le module setup et restent disponibles même avec gather_facts: false. Elles répondent à des questions que la machine cible ne peut pas se poser toute seule : « comment est-ce que l'inventaire m'appelle », « qui d'autre est dans mon groupe », « quelle est l'adresse du serveur voisin ». Vous ne pouvez pas les redéfinir : Ansible les réserve et écrase toute variable du même nom.

Le nom de l'hôte courant tel que défini dans l'inventaire (web1.lab, pas web1).

- name: Saluer
ansible.builtin.debug:
msg: "Bonjour depuis {{ inventory_hostname }}"

Dict des groupes de l'inventaire. groups['webservers'] retourne la liste des hôtes du groupe :

- name: Lister les webservers
ansible.builtin.debug:
msg: "{{ groups['webservers'] }}"
# → ['web1.lab', 'web2.lab']
- name: Compter les webservers
ansible.builtin.debug:
msg: "{{ groups['webservers'] | length }} webservers"
# → "2 webservers"

Dict indexé par hostname qui contient toutes les variables et facts de chaque hôte. Pour lire un fact d'un autre hôte :

- name: Afficher l'IP de web2 depuis web1
ansible.builtin.debug:
msg: "{{ hostvars['web2.lab'].ansible_default_ipv4.address }}"

Trois variables décrivent « les hôtes du play » et elles ne renvoient pas la même chose dès que vous utilisez serial:. La distinction compte quand vous construisez une configuration de cluster : générer une liste de pairs à partir du batch courant produirait un fichier différent à chaque vague de déploiement.

  • ansible_play_hosts_all : tous les hôtes que le motif du play a sélectionnés, y compris ceux qui ont échoué depuis.
  • ansible_play_hosts : les hôtes encore actifs du play, sans filtrage par serial:. C'est celle qu'il faut pour écrire une liste de membres.
  • ansible_play_batch : uniquement les hôtes de la vague courante quand serial: N est actif. Sans serial:, elle vaut la même chose que ansible_play_hosts.
- name: Liste du batch courant
ansible.builtin.debug:
msg: "Batch : {{ ansible_play_batch }} / play : {{ ansible_play_hosts }}"

Sur un play serial: 2 ciblant trois hôtes, la première vague affiche :

"msg": "Batch : ['h1', 'h2'] / play : ['h1', 'h2', 'h3']"

Voici l'exemple validé sur le lab ecrire-code-facts-magic-vars :

---
- name: Pre-gather facts sur web1 (rend hostvars web1 disponibles)
hosts: web1.lab
gather_facts: true
tasks: []
- name: Synthese facts sur db1
hosts: db1.lab
become: true
gather_facts: true
tasks:
- name: Poser le fichier de synthese
ansible.builtin.copy:
dest: /tmp/facts-summary.txt
content: |
db1_hostname={{ inventory_hostname }}
db1_os={{ ansible_distribution }}
db1_memory={{ ansible_memtotal_mb }}
webservers_count={{ groups['webservers'] | length }}
web1_ip={{ hostvars['web1.lab'].ansible_default_ipv4.address }}
mode: "0644"

Contenu du fichier sur db1.lab :

db1_hostname=db1.lab
db1_os=AlmaLinux
db1_memory=1740
webservers_count=2
web1_ip=10.10.20.21

Le pre-gather sur web1.lab est nécessaire pour que hostvars['web1.lab'] soit peuplé au moment du second play.

Ce tableau se lit par la gauche : partez du besoin, pas du mécanisme. La ligne de démarcation à retenir est celle entre les facts (ce que la machine dit d'elle-même, disponibles uniquement après un gather) et les magic vars (ce que l'inventaire et le play savent, disponibles même avec gather_facts: false). Les trois premières lignes dépendent d'une collecte réussie, les trois dernières fonctionnent sans.

SituationMécanisme
Adapter une commande à la distributionwhen: ansible_distribution == 'AlmaLinux'
Calculer une mémoire dépendante de la machinenginx_workers: "&#123;&#123; ansible_processor_cores * 2 &#125;&#125;"
Connaître l'IP d'un hôte voisinhostvars['<host>'].ansible_default_ipv4.address
Itérer sur tous les membres d'un groupeloop: "&#123;&#123; groups['webservers'] &#125;&#125;"
Identifier l'hôte courant dans un template&#123;&#123; inventory_hostname &#125;&#125;
Connaître quels hôtes sont dans le batch courant&#123;&#123; ansible_play_batch &#125;&#125;

Quatre de ces cinq erreurs se produisent à l'exécution du template, pas au parsing du playbook : ansible-playbook --syntax-check les laisse toutes passer. Le seul contrôle qui les attrape avant la production est un --check sur un hôte réel, ou l'inspection de l'inventaire avec ansible-inventory --graph. La dernière ligne est différente : elle ne provoque pas d'erreur aujourd'hui, elle annonce une rupture à venir, et c'est la raison d'écrire ansible_facts.distribution dès maintenant.

SymptômeCauseFix
'NoneType' object has no attribute 'ansible_default_ipv4'hostvars de l'hôte n'a pas été gatherAjouter un pre-gather play sur le groupe
gather_facts: false mais le playbook utilise ansible_distributionDésactivation accidentelleRéactiver gather_facts: true ou utiliser le module setup ad-hoc
groups vide ou incorrectMauvais nom de groupe ou inventaire pas chargéVérifier avec ansible-inventory --graph
gather_subset ignore mes choixMauvaise syntaxe de l'expressionVérifier l'ordre ['!all', 'network'] (l'ordre compte)
ansible_* direct (sans ansible_facts.)INJECT_FACTS_AS_VARS désactivé (futur Ansible)Toujours préférer ansible_facts.distribution à ansible_distribution
  • gather_facts: true (défaut) collecte les facts via le module setup au début de chaque play.
  • gather_subset: filtre la collecte, passer de all à min peut diviser par 2 le temps de gather.
  • Les magic vars essentielles : inventory_hostname, groups, hostvars, ansible_play_hosts, ansible_play_batch. play_hosts est dépréciée (alias de ansible_play_batch, retrait annoncé en 2.23).
  • hostvars['<host>'].ansible_* lit les facts d'un autre hôte, nécessite que cet hôte ait été gather au préalable.
  • À l'avenir, ansible_facts.distribution sera la forme canonique, ansible_distribution deprecated.

Lire les facts d'un autre hôte suppose que cet hôte ait déjà été collecté, ce qu'un exemple statique ne montre jamais. Le lab vous fait exploiter inventory_hostname, groups et hostvars pour produire une synthèse croisée entre plusieurs machines, puis réduire le coût de la collecte avec gather_subset. Le fichier de synthèse sert de preuve : s'il manque une adresse IP, c'est que le pre-gather n'a pas eu lieu.

  • Précédence des variables : situer les facts parmi les 22 niveaux évite les surprises quand une variable les écrase.
  • register et set_fact : set_fact avec cacheable: true ajoute vos propres valeurs au cache de facts.
  • Conditions avec when : les facts servent surtout à brancher une tâche selon la distribution ou la mémoire disponible.

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