mirroir readonly du Gitit wiki.evolix.org (attention, ne rien commiter/merger sur ce dépôt) https://wiki.evolix.org
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

26 KiB

Cette page a été importée automatiquement de notre ancien wiki mais n'a pas encore été révisée.

Howto Ansible

http://docs.ansible.com/

Ansible est une plateforme de configuration et gestion de serveurs : il permet le déploiement de logiciels et l'execution de tâches via une connexion SSH.

Ansible est agent-less et utilise le concept d’idempotence : on décrit l'état d'un serveur dans un fichier YAML appelé playbook et des actions seront executées dans le but de rendre le serveur conforme à cette description. On pourra relancer Ansible plusieurs fois, l'état final reste le même : seules les actions nécessaires seront exécutées.

Ansible peut exécuter des actions sur des serveurs distants sous :

  • Debian 6 et supérieur (nécessite uniquement Python ce qui est le cas par défaut)
  • Debian 4 / 5 : voir [#AnsiblesurdesvieillesversionsDebian]
  • FreeBSD et OpenBSD : TODO => décrire pré-requis

Installation

Nous utilisons Ansible 2.0.2 (disponible via http://pub.evolix.net/jessie/) sous Debian 8 :

# apt install --allow-unauthenticated ansible=2.0.2.0-1~bpo8+1

Utilisation

$ ansible --version
ansible 2.0.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides

$ cat ~/.ansible.cfg
[defaults]
inventory = $HOME/.ansible/hosts

[ssh_connection]
#ssh_args = -o ControlMaster=no -o ControlPersist=no
ssh_args = -o ControlMaster=auto -o ControlPersist=300s
pipelining = True

$ echo HOSTNAME >> ~/.ansible/hosts
$ ssh-copy-id HOSTNAME

$ ansible HOSTNAME -i $HOME/.ansible/hosts -m ping
HOSTNAME | SUCCESS => {
    "changed": false,
    "ping": "pong"
}

$ ansible HOSTNAME -a "date"
HOSTNAME | SUCCESS | rc=0 >>
jeudi 26 mai 2016, 23:16:01 (UTC+0200)

$ ansible-playbook PLAYBOOK.yml --limit HOSTNAME --forks 1

$ ansible-playbook PLAYBOOK_WITH-SUDO.yml --limit HOSTNAME --ask-become-pass

Options utiles pour ansible-playbook :

  • -vvvv : très verbeux (utile notamment pour debug SSH quand on a une erreur unreachable)
  • -k / --ask-pass : demande le mot de passe utilisateur (si connexion SSH sans clé)
  • -K / --ask-become-pass : demande le mot de passe utilisateur pour sudo
  • -l / --limit HOSTNAME : limite la connexion au serveur HOSTNAME (attention, par défaut c'est all, cf /etc/ansible/hosts)
  • -f / --forks N : nombre de process lancé en parallèle (par défaut 5)... peut être utile de mettre à 1 pour ne pas paralléliser

Howto Playbook

$ vim hello_World.yml

- hosts: all

  tasks:
  - shell: echo hello World

# vim:ft=ansible:

Un playbook est un ensemble de "plays" ; un "play" contient au minimum hosts (serveurs cible) et tasks (ensemble de tâches), et peut également contenir :

  • vars : des variables
  • handlers : des actions déclenchées si une tâche a été exécutée (via l'option notify)
  • roles : des rôles pour avoir des playbooks organisés et modulaires

Modules pour tasks

http://docs.ansible.com/ansible/list_of_all_modules.html

Pour avoir la liste des modules utilisables dans tasks : ansible-doc -l

Voici quelques exemples :

- name : exemple avec le module COMMAND
  command: date

- name : exemple avec le module SHELL
  shell: time date

- name: exemple avec le module SERVICE
  service: name=<httpd> state=restarted

- name: exemple avec le module FILE
  file: path=/etc/cron.daily/apticron state=absent

- name : exemple avec le module COPY
  copy: src=files/foo dest=/etc/bar owner=root group=root mode=0644

- name : exemple avec le module LINEINFILE
  lineinfile: dest=/etc/evocheck.cf insertafter=EOF line="IS_APTICRON=0" regexp="^IS_APTICRON="

- name : exemple avec le module USER
  user:
    state: present
    name: {{ name }}
    comment: 'John Doe'
    shell: /bin/bash
    groups: adm
    append: yes
    password: '$6$k/Fg76xH'

- name : exemple avec le module REPLACE
  replace:
       dest: /etc/ssh/sshd_config
       regexp: '^(Match User ((?!{{ name }}).)*)$'
       replace: '\1,{{ name }}'

- name : exemple avec le module STAT
  stat: path=/etc/sudoers.d/foo
  register: foo_sudoers_file

- name : exemple avec le module APT
  apt:
   name: '{{ item }}'
   state: latest
   allow_unauthenticated: yes
   update_cache: yes
   cache_valid_time: 3600
   with_items:
   - vim
   - htop

- name : exemple
  apt_repository:

- name: exemple avec le module INI_FILE
  ini_file:
    dest: /root/.my.cnf
    section: client
    option: user
    value: root
    mode: 0640

- name: exemple avec le module MYSQL_USER (nécessite le paquet python_mysqldb)
  mysql_user:
    name: mysqladmin
    password: my_password
    priv: "*.*:ALL,GRANT"
    state: present
    config_file: /root/.my.cnf
    update_password: on_create

- name: exemple avec l'ajout de plusieurs lignes dans un fichier
  blockinfile:
    dest: /etc/apache2/envvars
    block: |
      ## Set umask for writing by Apache user.
      ## Set rights on files and directories written by Apache

- name: exemple
  systcl:

- name: exemple
  alternatives:

- name: exemple
  service:

- name: exemple
  mount:

replace: vs lineinfile:

  • lineinfile: si regexp= n'est pas matché... il insère quand même la ligne ! avec lineinfile: regexp= n'est pas une condition pour l'insertion mais une condition pour remplacer au lieu d'insérer !
  • avec lineinfile: sauf cas tordus, regexp= doit matcher line= (sinon il va insérer line= à l'infini !)
  • lineinfile: va 1. regarder si regexp= existe et il remplace la dernière ligne, 2. si regexp n'existe pas, il ajoute line= (sans d'autre condition... même si elle existe déjà)
  • replace: il va remplacer uniquement si regex est matché, logique (comme sed)
  • si l'on veut utiliser une référence (\1) dans line# avec lineinfile:> erreur, il faut utiliser replace:
  • avec lineinfile: backrefs=yes c'est pour utiliser une référence au sein de regexp= (et non pas au sein de line=)

vars

Gestion des variables :

  vars:

   ip: 31.170.9.129
   conf_file: /etc/foo.conf

  tasks:
  - shell: echo {{ ip }} {{ conf_file }}

Pour gérer de nombreuses variables dans un projet, voici l'arborescence conseillée :

group_vars/
   group1                 # here we assign variables to particular groups
   group2                 # ""
host_vars/
   hostname1              # if systems need specific variables, put them here
   hostname2              # ""

Handlers

La directive notify permet de déclencher un handler après la fin d'éxecution d'une tâche :

tasks:
- apt: ...
  notify: Done

handlers:
- name: Done
  shell: echo "It's done"

Le redémarrage d'un service devrait toujours être effectué via handler.

Tags

https://docs.ansible.com/ansible/playbooks_tags.html

Hormis le fait de ranger/trier chaque tâche dans une catégorie, permet de limiter/exclure des tâches.

- name: Coucou
  debug: msg="Saloute!"
  tags: message
...

Pour ne pas afficher les messages :

$ ansible-playbook ... --skip-tags "message"

On peut appliquer des tags à des rôles, ou voir directement n'éxecuter que certains tags :

$ ansible-playbook ... --tags "configuration,packages"

Note : on peut également taguer des include.

Register

register. est un élément/champ que l'on peut rajouter pour tout type de tâche et qui initialisera la variable (par le nom donné) avec les valeurs retour du module. Pour shell, on a le droit à .stdout, .stderr, .rc, ... Mais cela dépend bien des valeurs de retour du module. Mais pour être sûr des valeurs initialisées, on peut utiliser debug afin d'analyser le résultat.

- stat: path=/etc/passwd
  register: st

- debug: var=st

- fail: msg="Whoops! file ownership has changed"
  when: st.stat.pw_name != 'root'

Pour certains modules, register est un passage obligatoire pour une utilisation cohérente des éléments (stat...).

Configuration

https://docs.ansible.com/ansible/intro_configuration.html

La configuration est lue dans l'ordre suivant :


* ANSIBLE_CONFIG (an environment variable)
* ansible.cfg (in the current directory)
* .ansible.cfg (in the home directory)
* /etc/ansible/ansible.cfg

Fichier "inventory"

http://docs.ansible.com/ansible/intro_inventory.html

Permet d'indiquer la liste des machines concernées par Ansible (peut être limité lors de l'exécution de la commande par l'option -l) et de pouvoir les regrouper dans des groupes.

Exemple:

hostname.internal

[<httpservers>]
machine[01:57].example.com
<http.example.com:2222>

[dbservers]
machine12.example.com
machine50.example.com
m[a:o]chine52.example.com
alias ansible_port=2222 ansible_host=192.168.1.50

[client]
host1 <http_port=80> maxRequestsPerChild=808 #des variables qui seront automatiquement auto-completé lié à cet host

[commercant]
mercerie
chapeautier

[commercant:vars]
ntp_server=ntp.mercerie.example.com
proxy=proxy.mercerie.example.com
  • hostname.internal : host hors groupe
  • [] : le nom du groupe (pour les serveurs <http).> Les noms de hosts qui suivent appartiendront à ce groupe
  • machine[01:57].evolix.net : on peut indiquer une [pseudo-]expression régulière - ici ajoutera les machines machine01.evolix.net, machine02.evolix.net, machine03.evolix.net, ..., machine57.evolix.net
  • *http.evolix.net:2222* : ansible se connecte par ssh, et <http.evolix.net> a un port ssh d'écoute différent qui est 2222
  • [dbservers] : groupe pour les serveurs de base de donnée
  • machine50.example.com : cette machine est déjà présente dans le groupe <httpservers,> mais sera aussi accessible à partir du groupe dbservers
  • alias ansible_port=2222 ansible_host=192.168.1.50 : la machine alias n'est pas un vrai FQDN mais pointera vers 192.168.1.50 car on a indiqué des variables propre à ansible. Il est aussi disponible ansible_connection (local ou ssh) ou ansible_user (le nom de l'utilisateur de la machine distante avec lequel ansible se connectera en ssh)
  • host1 <http_port=80> maxRequestsPerChild=808 : des variables qui seront automatiquement auto-completé lié à host1
  • [commercant:vars] : des variables qui seront liés au groupe commercant. On peut aussi créer des groupes de groupes en utilisant :children

On peut aussi découper le fichier "inventory" selon les groupes et les variables : [http://docs.ansible.com/ansible/intro_inventory.html#splitting-out-host-and-group-specific-data].

Les variables propres à Ansible : [http://docs.ansible.com/ansible/intro_inventory.html#list-of-behavioral-inventory-parameters]

ansible.cfg

Options utiles (TODO : à revoir) :

  • display_args_to_stdout : mettre à True si on veut voir tout le contenu du tasks executé pour chaque étape écrit sur stdout
  • display_skipped_hosts : mettre à False si on ne veut pas voir affiché sur stdout l'information d'un task qui n'est pas exécuté (le nom de variable est confu - mais il s'agit bien de l'affichage du task)
  • error_on_undefined_vars : mettre à True pour être sûr que le script ansible s'arrête si une variable n'est pas défini (alors qu'il y a utilisation de cette dernière dans une task)
  • force_color : mettre à 1 pour forcer la couleur
  • forks : le nombre de processus en parallèle possible lors déploiement du script ansible sur nombreux hosts.
  • module_lang : changer la langue
  • module_name : le module par défaut (command) lors de l'utilisation de la commande ansible
  • hosts : accès vers les hosts par défaut (all)
  • private_key_file : le chemin pour la clé pem
  • remote_port : le port ssh par défaut (22)
  • remote_user : l'utilisateur pour la connexion ssh par défaut (root)
  • retry_files_enabled : mettre à True pour la création de fichier .retry après une failure de ansible pour reprendre le travail précédent - ajouté en argument dans l'appel de la commande

Erreurs

unbalanced jinja2 block or quotes

fatal: [test.evolix.net]: FAILED! => {"failed": true, "reason": "error while splitting arguments, either an unbalanced jinja2 block or quotes"}

Vérifier bien la syntaxe du document qui est référé pour cette erreur. Cela peut être une quote mal fermé (ou mélange simple quote, double quote), ou encore histoire de crochet devenant une parenthèse...

UNREACHABLE!

fatal: [test.evolix.net]: UNREACHABLE! => {"changed": false, "msg": "SSH encountered an unknown error during the connection. We recommend you re-run the command using -vvvv, which will enable SSH debugging output to help diagnose the issue", "unreachable": true}

Malheureusement, cela arrive souvent sur certaines machines (une?), mais pas de solutions pour le moment -> boire du café - beaucoup de café.

Du côté de la machine distante, dans le fichier /var/log/auth.log:

14:56:29 localhost sshd[19915]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=dual.evolix.net  user=service
14:56:31 localhost sshd[19915]: Accepted password for service from 192.168.4.137 port 42502 ssh2
14:56:31 localhost sshd[19915]: pam_unix(sshd:session): session opened for user service by (uid=0)
14:56:31 localhost systemd-logind[641]: New session 181 of user service.
14:56:32 localhost sshd[19915]: pam_unix(sshd:session): session closed for user service
14:56:32 localhost systemd-logind[641]: Removed session 181.

Et quand ça marche - la session ne se referme pas, et est réutilisé par les exécutions playbook suivantes:

14:58:57 localhost sshd[20339]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=dual.evolix.net  user=service
14:59:00 localhost sshd[20339]: Accepted password for service from 192.168.4.137 port 42511 ssh2
14:59:00 localhost sshd[20339]: pam_unix(sshd:session): session opened for user service by (uid=0)
14:59:00 localhost systemd-logind[641]: New session 182 of user service.
...
15:01:08 localhost sshd[23034]: Received disconnect from 192.168.4.137: 11: disconnected by user
15:01:08 localhost sshd[23024]: pam_unix(sshd:session): session closed for user service
15:01:08 localhost systemd-logind[641]: Removed session 182.

Mais si tentative de déploiement ansible juste après la session supprimé (par ex la 182), c'est à ce moment là qu'on se retrouve avec des UNREACHABLE -> savoir pourquoi sshd supprime la session ssh...

Missing required arguments

fatal: [test.evolix.net]: FAILED! => {"changed": false, "failed": true, "msg": "missing required arguments: section"}

Le message est assez clair, donc bien relire la doc du module sur ansible, et toujours ajouter les arguments obligatoires pour ce module.

Requires stdlib json or simplejson module

fatal: [lenny.evolix.net]: FAILED! => {"changed": false, "failed": true, "msg": "Error: ansible requires the stdlib json or simplejson module, neither was found!"}
# apt install python-simplejson

Astuces

Vérifier un playbook

  • Vérifier la syntaxe :
$ ansible-playbook --syntax-check my-experimental-playbook.yml

http://www.yamllint.com/

  • vérifier les action qui vont être faite (mode dry-run) sans rien exécuter :
$ ansible-playbook --check my-experimental-playbook.yml
  • avoir le diff des fichiers modifiés (ne marche pas avec les modules replace/lineinfile à priori) :
$ ansible-playbook --check --diff my-experimental-playbook.yml

Plus d'infos sur module

Lister les modules:

# ansible-doc -l

Avoir des infos sur un module:

# ansible-doc shell
> SHELL

  The [shell] module takes the command name followed by a li
  space-delimited arguments. It is almost exactly like the [
...

Stopper l'éxecution du code

Pour par exemple, stopper le code à un moment pour lire les valeurs d'une variables

- debug: var=cequejeveuxvoir
- command: /bin/false

ou

- debug: var=cequejeveuxvoir
- fail: msg="FAIL"

ou

- debug: var=cequejeveuxvoir
- pause:

Ansible sur des vieilles versions Debian

  • Installer package python

[https://docs.ansible.com/ansible/raw_module.html]

(bzip2, php, ... selon services à installer)

- raw: apt-get -y install python-simplejson [bzip2 php5-cli]
  • Si pas encore fait, donner droit mysql à l'utilisateur
> GRANT ALL ON db.* TO 'user'@'localhost';

Limiter l'exécution à certaines machines

  • Limiter aux groupes www et sql (www et sql peuvent en fait être indifféremment des groupes ou des serveurs) :
$ ansible-playbook -l "www:sql" playbook.yml
  • limiter aux serveurs foo-www01, foo-lb01, foo-filer, etc… :
$ ansible-playbook -l "foo-*" playbook.yml
  • limiter aux 10 premiers serveurs de l'inventaire (utile pour faire par paquets) :
$ ansible-playbook -l "*[0:9]" playbook.yml
  • puis à ceux restant :
$ ansible-playbook -l "*[10:]" playbook.yml

Il est de toute façon préférable de ne pas mettre all dans le champs hosts dans le playbook pour éviter un -possible- oublie de "limite" de machines.

Lancement tâches hosts asynchrone

Pour éviter que les différentes tâches s'appliquent une par une sur tout les hosts impliqués par le déploiement du play ansible, on peut utiliser l'option strategy à la valeur free pour que chaques tâches sur un host puisse continuer dès la fin de son exécution sans attendre l'état des autres hosts concernés en cours.

- hosts: all
...
  strategy: free

Ne plus se fier au texte host changed après texte de la tâche, car il pourrait s'agir d'une autre tâche affiché plus en haut dans le texte de l'historique

Fréquence des hosts

Lors d'une appel d'un play, on peut indiquer une fréquence sur le nombre d'host touché par l'éxecution du playbook.

  • Fork pour le nombre de hosts simultanés (changeable dans le fichier ansible.cfg - mettre une valeur importante > centaine).
  • serial en en-tête contenant une valeur numérique qui représente le nombre de machines pour chaque tour d'éxecution de playbook, ou un pourcentage par rapport à la liste inventory concerné.

Cowsay

 ____________________
< NO MORE HOSTS LEFT >
 --------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
 ____________
< PLAY RECAP >
 ------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

Pour avoir les messages de la vache : cowsay.

Si installer, pour le désactiver : export ANSIBLE_NOCOWS=1

Disponible aussi dans la conf du fichier /etc/ansible/ansible.cfg

https://support.ansible.com/hc/en-us/articles/201957877-How-do-I-disable-cowsay-

Conditions dans fichier jinja2

{% if python_is_installed is defined %}
Ansible devrait marcher -pardi!
{% endif %}

http://jinja.pocoo.org/docs/dev/templates/#builtin-tests

Voir la doc pour plus de features : http://jinja.pocoo.org/docs/dev/

Lire une entrée au clavier

S'il manque une valeur pour la suite du script, soit on le gère en mettant une erreur, ou une valeur par défaut, mais sinon on peut aussi demander une valeur a être tapé sur le clavier :

vars_prompt:
- name: 'prenom'
  prompt: 'Quel est votre prénom ?'
  private: no

tasks:
- debug: var=prenom

Malheureusement pour le moment, doit se situer avant tasks.

Si on veut utiliser cette variable dans une tâche, il faut simplement utiliser le nom de la variable, et si on veut l'utiliser (explicitement) pour un play ne se trouvant pas dans le même fichier (donc ici la variable dans autre.yml s'appelera prenom_de_autre et non prenom) :

- include: './tasks/autre.yml'
  vars:
    prenom_de_autre: prenom

https://docs.ansible.com/ansible/playbooks_prompts.html

Lancer playbook en mode interactif

ansible-playbook playbook.yml --step

https://docs.ansible.com/ansible/playbooks_startnstep.html

Ne pas lancer une commande shell si le fichier existe

En initialisant le champs creates indiquant le chemin de fichier lors de l'utilisation du module shell, cette tâche là ne s'executera seulement si le fichier (chemint creates correspondant) n'existe pas. Et le contraire (si le fichier existe la tâche se lancera), avec le champs removes. Disponible sur certains module (comme shell).

Plus simple et rapide que de tester le fichier par le module stat juste avant.

Lancer tâche sur machine précise (voir local)

- name: /etc/hosts
  shell: cat /etc/hosts
  register: tmp
  delegate_to: localhost

- debug: var=tmp.stdout

Pour local, peut être remplacer par la directive local_action.

https://docs.ansible.com/ansible/playbooks_delegation.html#delegation

Lancer tâche qu'une seule fois

- name: Début installation, envoie email
  run_once: true
  ...

Si ajouter avec delegate_to, seule cette machine executera cette tâche, sinon c'est la première dans la liste de l'inventory.

https://docs.ansible.com/ansible/playbooks_delegation.html#run-once

Appliquer une tâche à une liste (tableau) -> boucle

with_items

- name: Manger les fruits
  shell: eat '{{ item }}'
  with_items:
    - Apple
    - Orange
    - Strawberry
    - Mango

Par exemple pour l'installation de plusieurs nouveaux packages :

---
- hosts: localhost

  tasks:
    - apt: name='{{ item }}' state=present
      with_items: [ 'cmatrix', 'tetrinet-server', 'tetrinet-client', 'xtel', 'xtell' ]

Même si il y aura plusieurs packages installés, cela ne comptera que pour un changement (changed=1). Cette tâche appellera un par un les éléments de la liste (présent dans with_items) pour le module.

En l'appelant :

ansible-playbook -b --ask-become-pass --become-method='su' --become-user='root' apt.yml

with_nested

Pour croiser les éléments des items -> "with_nested".

  tasks:
    - include: "./ajout_utilisateur_sur_machine.yml"
      vars:
        user: "{{ item[0] }}"
        server: "{{ item[1] }}"
      with_nested:
      - [ 'alice', 'bob' ]
      - [ 'machine1', 'machine2', 'machine-backup' ]

Qui aura pour effet d'appeler le include avec comme argument : item[0]=alice, item[1]=machine1; puis item[0]=alice, item[0]=machine2; ... puis item[0]=bob, item[1]=machine1, ...

with_dict

Avec hash ...

users:
  bob:
    name: Bob
    uid: 1000
    home: /home/bob
  alice:
    name: Alice
    uid: 1001
    home:

  tasks:
  - user: name="{{ item.key }}" comment="{{ item.value.name }}" uid="{{ item.value.uid }}" home="{{ item.value.home }}"
    with_dict: "{{ users }}"

Se connecter sous un autre utilisateur UNIX

Par défaut, l'utilisateur se connectant sur le serveur distant est {{ ansible_user_id }} (càd uid courant UNIX). On peut soit le préciser dans le fichier de conf principal de ansible avec remote_user : michu ou en l'indiquant en argument lors de l'éxecution du playbook.

ansible-playbook -u michu -k play.yml

Éviter que la commande shell indique élement 'changed'

Sur tout les modules, chaque taches retourne un statut sur son résultat :

  • Ok : Tout s'est bien passé
  • Changed : Tout s'est bien passé mais il y a eu modification par rapport à l'état précédent (droits fichiers,...)
  • Failed : Raté

Le soucis est qu'il parrait bien difficile pour le module shell de connaître après execution de la commande s'il y a eu modification. Par défaut, il y a changement - quand bien même l'utilisation du shell se fait pour afficher un résultat (id, date, ...). Pour éviter cela on peut mettre :

- shell: date
  changed_when: false

Ou donner une condition

- shell: cat > {{ fichier }} </dev/null
  changed_when: {{ fichier.stats.exist }}

Voir variables disponibles

$ ansible -m setup <hostname>
service.evolix.net | SUCCESS => {
    "ansible_facts": {
        ...
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "12/01/2006",
        "ansible_bios_version": "VirtualBox",
        "ansible_cmdline": {
            "BOOT_IMAGE": "/boot/vmlinuz-3.16.0-4-amd64",
            "quiet": true,
            "ro": true,
            "root": "UUID=37de3cbb-3f28-48d2-a4eb-c893a2f2fbc3"
        },
        "ansible_date_time": {
            "date": "2016-05-06",
            "day": "06",
            "epoch": "1462546886",
            "hour": "17",
            ...
        },
        "ansible_default_ipv4": {
...
}

$ ansible -m debug -a "var=hostvars['hostname']" localhost

Pour récupérer toutes les adresses MAC des machines :

---
- hosts: all
  gather_facts: true

  tasks:
    - debug: var=ansible_eth0.macaddress

que l'on pourra combiner par exemple avec un pipe en ligne de commande :

ansible-playbook mac_address.yml | grep ansible_eth0.macaddress | sed 's/^\s*"ansible_eth0.macaddress": "\(.*\)"/\1/'

Il est possible aussi d'accéder aux variables d'environnement shell :

"{{ lookup('env','HOME') }}"

Ressources utiles