Skip to content

Facts + Implicit Variables

Objectif

Découvrir les facts (informations collectées automatiquement sur les hôtes) et les magic variables (variables implicites fournies par ansible), puis les utiliser dans des playbooks.

Durée

~40 minutes (exploration facts + 4 mini-playbooks)

Course link

Commandes de l'atelier

Les commandes pour initialiser le lab.

cd $HOME/git/gh/formation-ansible/atelier-16
vagrant up
^up^ssh ansible

J'affiche les facts récupérés de la machine Debian avec le module ansible.builtin.setup (souvent abrégé setup).

L'objectif du module est de récupérer des informations pertinentes d'un hôte pour en faire des variables utilisables par ansible.

# sur le control host
cd ansible/projets/ema/playbooks
ansible debian -m setup
!! | less    # ansible debian -m setup | less
^less^wc -l  # ansible debian -m setup | wc -l

Sortie du module setup sur debian - nombre de lignes de facts

Pour filtrer par contenu d'une liste (fnmatch), le filtre par défaut est [] (donc rien).

^| wc -l^-a "filter=ansible_distribution*"

Filtrage des facts avec ansible_distribution*

Dans le guide playbook_vars_facts, il est précisé que les facts peuvent être récupérés dans le dictionnaire ansible_facts, avec une clé comme architecture (i.e.ansible_facts.architecture).

Il est aussi possible de pouvoir récupérer les facts dans une variable avec le préfix ansible_, pour reprendre l'exemple au dessus : ansible_architecture. C'est possible car les associations dictionnaire-clés récupérées sont aussi poussées individuellement comme variable, avec un nom unique dans le namespace principal.

Si je cite le paramètre INJECT_FACTS_AS_VARS :

Facts are available inside the ansible_facts variable, this setting also pushes them as their own vars in the main namespace. Unlike inside the ansible_facts dictionary where the prefix ansible_ is removed from fact names, these will have the exact names that are returned by the module.

Par défaut, ce paramètre est à true. Donc les facts sont disponibles en dictionnaire et en variables globales injectées. Si ce paramètre est à false, alors uniquement le dictionnaire fonctionne.

De ce que j'ai compris, historiquement toutes les variables étaient injectées dans le namespace mais ansible a décidé de passer au dictionnaire ansible_facts. Mais le paramétrage de INJECT_FACTS_AS_VARS reste à true par retro-compatibilité.

J'ai pas réussi à trouver la release note, PR ou issues pointant directement vers ce changement. Mais de ce que j'ai lu sur les forums c'est sûrement apparu entre la version 2.2 et 2.3.

C'est drôle de voir que ça dure depuis plus de 5 ans.

Pour utiliser les variables dans un playbook, je préfère donc utiliser les clés du dictionnaire ansible_facts par compatibilité et démarche actuelle... Avec la syntaxe ressemblant aux FQCN par uniformité (même si ça peut porter à confusion).

La notation par crochets serait plus efficace si des clés avaient des caractères spéciaux par exemple...

facts2.yml
---

- hosts: debian

  tasks:

    - debug:
        var: ansible_facts.architecture

...

Affichage de ansible_facts.architecture via debug

Si je récupère les informations sur les OS des Target Hosts. Pour s'entrainer à utiliser les magic vars.

Ici est utilisée la magic vars inventory_hostname, tout le reste étant des ansible_facts.

distribution-info.yml
---

- hosts: testing

  tasks:

    - name: Display distribution information
      debug:
        msg: >-
          Host [{{inventory_hostname}}] is running {{ansible_distribution}}
          version {{ansible_distribution_version}}
          (family {{ansible_distribution_file_variety}}).

...

Informations de distribution pour chaque Target Host

Si on cherche à utiliser playbook_dir...

playbook-info.yml
---

- hosts: testing

  tasks:

    - name: Playbook information
      debug:
        msg: Your playbook is in {{playbook_dir}}.
      run_once: true

    - name: Inventory information
      debug:
        msg: Your inventory is in {{inventory_dir}}.
      run_once: true

...

Magic variables playbook_dir et inventory_dir

Utiliser groups...

hosts-info.yml
---

- hosts: testing

  tasks:

    - name: Display host information
      debug:
        msg: "Your hosts are: {{groups.testing}}"
      run_once: true

...

Liste des hôtes du groupe testing via groups.testing

Et enfin, pour les addresses IPv4.

ip-info.yml
---

- hosts: testing

  tasks:

    - name: Display IPv4 addresses
      debug:
        msg: "{{ansible_host}}'s IPv4 addresses: {{ansible_all_ipv4_addresses}}"

...

Adresses IPv4 de chaque Target Host

À vous de jouer !

Je vais ajouter le paramètre INJECT_FACTS_AS_VARS dans le fichier ansible.cfg

echo -e "\ninject_facts_as_vars = False\nstdout_callback = yaml" >> ../ansible.cfg

Gestionnaire de paquets

Pour ensuite chercher la clé qui me donnera le gestionnaire de paquet.

ansible debian -m setup | grep apt

Recherche du fact pour le gestionnaire de paquets avec grep

avec ansible_facts.ansible_pkg_mgr, j'ai le nom du gestionnaire de paquets des Target Hosts.

Si je rédige un playbook pour vérifier le nom sur chacun d'entre eux.

Je donne un nom au playbook entier, quand il se lance on voit l'action qu'il va faire (ici Gather Package Manager).

J'utilise aussi la restriction gather_subset.

pkg_mgr-info.yml
---  # pkg_mgr-info.yml
- name: "Gather Package Manager"
  hosts: testing
  gather_facts: true  # explicite
  gather_subset: ["!all", "min", "distribution"]  # explicite pour min car pkg_mgr
  tasks:
    - name: "Display package manager (pkg_mgr)"
      ansible.builtin.debug:
        msg: >-
          Le gestionnaire de paquets de {{ inventory_hostname }} ({{ ansible_facts.distribution }}) est {{ ansible_facts.pkg_mgr | upper }}.
...

Gestionnaire de paquets par distribution (apt, dnf, zypper)

Version de Python

Je vais utiliser la variable ansible_facts.python_version.

python-info.yml
---  # python-info.yml
- name: "Python Version"
  hosts: testing
  gather_facts: true  # explicite
  gather_subset: ["!all", "min"]  # explicite pour min
  tasks:
    - name: "Display Python Version"
      ansible.builtin.debug:
        msg: >-
          {{ inventory_hostname }} a Python {{ ansible_facts.python_version }}
...

Version de Python par Target Host

DNS utilisés

dns-info.yml
---  # dns-info.yml
- name: DNS Infos
  hosts: testing
  gather_facts: true  # explicite
  gather_subset: ["!all", "min", "network"]  # DNS dans subset network, min explicite
  tasks:
    - name: Display DNS Servers
      ansible.builtin.debug:
        msg: >-
          {{ inventory_hostname }} a comme serveurs DNS : {{ ansible_facts.dns.nameservers | join(', ') }}
...

Serveurs DNS utilisés par chaque Target Host

Je retiens

  • ansible_facts est le dictionnaire centralisé des facts. Le préfixe ansible_ (ex : ansible_distribution) est une injection rétro-compatible contrôlée par INJECT_FACTS_AS_VARS.
  • gather_subset limite la collecte aux catégories nécessaires (min, network, distribution...) pour accélérer l'exécution.
  • Les magic variables (inventory_hostname, playbook_dir, groups...) ne sont pas des facts mais des variables fournies par ansible lui-même.
  • run_once: true exécute une tâche une seule fois pour tout le play, utile pour les infos globales.

Cheatsheet

Symptôme Cause probable Correction
{{ ansible_distribution }}undefined variable gather_facts: false dans le play, ou INJECT_FACTS_AS_VARS = false dans ansible.cfg Réactiver gather_facts: true ou accéder au dictionnaire ansible_facts.distribution
ansible debian -m setup -a "filter=architecture" ne retourne rien Le filtre utilise fnmatch et la clé exacte est ansible_architecture (format d'affichage) le glob architecture ne matche rien Utiliser un glob : filter=ansible_architecture ou filter=*architecture*
Les playbooks deviennent très lents sur un gros parc gather_facts: true collecte tout par défaut (all), beaucoup de données réseau Restreindre avec gather_subset: ["!all", "min"] (ou ajouter seulement ce dont on a besoin : network, distribution…)
ansible_facts.dns.nameserversundefined Le subset network n'est pas collecté Ajouter gather_subset: ["!all", "min", "network"]
groups.testingundefined Pas de groupe testing dans l'inventaire courant Vérifier l'inventaire effectif avec ansible-inventory --graph
Après inject_facts_as_vars = false, tous les playbooks cassent Les anciennes références ansible_<fact> ne résolvent plus Migrer vers ansible_facts.<fact> (préférable à long terme)

Sources (principales)


Précédent : Stored Variables Suite : Heterogeneous Targets