19
0
Fork 0
wiki/HowtoAnsible.md

32 KiB

title: Howto Ansible : automatisation d'infrastructure

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

Ansible fonctionne sans agent sur les serveurs (agent-less) et selon le concept d'idempotence : on décrit l'état d'un serveur et des actions seront exécuté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 actuellement 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 pour la connexion SSH
  • -K / --ask-become-pass : demande le mot de passe pour l'escalade (via sudo, su, doas…)
  • -l / --limit HOSTNAME : limite la connexion à un ou plusieurs serveurs (attention, par défaut c'est all, cf /etc/ansible/hosts)
  • -f / --forks N : nombre de process lancés en parallèle (par défaut 5)… peut être utile de mettre à 1 pour ne pas paralléliser

Les éléments d'Ansible

L'utilisation d'Ansible se fait principalement avec un playbook. C'est l'élément clé, qui peut suffir à réaliser des tâches utiles. Il s'agit d'un fichier qui décrit la succession d'une ou plusieurs séries d'actions (des plays). Chaque play indiques quelques réglages généraux comme le groupe de serveurs concernés, la manière de devenenir administrateur, quels fichiers de variables charger…

Un playbook va ensuite dérouler des actions qui seront organisées en roles, tasks et handlers.

Une action est l'invocation d'une fonctionnalité particulière fournie par un module. Exemple, pour s'assurer de l'absence d'un fichier :

- file: path=/etc/cron.daily/apticron state=absent

Les actions peuvent être listées dans la partie tasks du playbook et peuvent être aussi déportées dans un fichier et inclus dans le playbook.

Les handlers sont des actions qui ne sont exécutées que si une autre action en a fait la demande, via l'attribut notify.

Lorsqu'on a besoin d'utiliser des fichiers ou templates à copier, des variables avec des valeurs par défaut, des handlers… on peut organiser tout cela dans un role en respectant (idéalement) une structure conventionnelle :

foo
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

La partie inventory correspond à la description de l'inventaire des serveurs à configurer et inclus un mécanisme de configuration individuelle et par groupe. Nous y revenons plus loin.

Howto Playbook

Exemple de playbook très simple :

---
- hosts: all

  tasks:
  - shell: echo hello World

# vim:ft=ansible:

Un playbook plus complexe :

---
- hosts: all
  gather_facts: yes
  become: yes

  vars_files:
  - 'vars/main.yml'

  vars:
    external_roles: ~/GIT/ansible-roles
    external_tasks: ~/GIT/ansible-public/tasks

  pre_tasks:
  - name: Minifirewall is stopped (temporary)
    service:
      name: minifirewall
      state: stopped

  roles:
  - "{{ external_roles }}/minifirewall"

  post_tasks:
  - include: "{{ external_tasks }}/commit_etc_git.yml"
    vars:
      commit_message: "Ansible run firewall.yml"

  handlers:
  - name: restart minifirewall
    service:
      name: minifirewall
      state: restarted

# vim:ft=ansible:

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 :

- command: date
- shell: cat foo.txt | grep bar
- file:
    path: /etc/cron.daily/apticron
    state: absent
- copy
    src: files/foo
    dest: /etc/bar
    owner: root
    group: root
    mode: "644"
- lineinfile:
    dest: /etc/evocheck.cf
    insertafter: EOF
    line: "IS_APTICRON=0"
    regexp: "^IS_APTICRON="
- blockinfile:
    dest: /etc/apache2/envvars
    block: |
      ## Set umask for writing by Apache user.
      ## Set rights on files and directories written by Apache
- replace:
    dest: /etc/ssh/sshd_config
    regexp: '^(Match User ((?!{{ name }}).)*)$'
    replace: '\1,{{ name }}'
- ini_file:
    dest: /root/.my.cnf
    section: client
    option: user
    value: root
    mode: "640"
- user:
    state: present
    name: "{{ name }}"
    comment: 'John Doe'
    shell: /bin/bash
    groups: adm
    append: yes
    password: '$6$k/Fg76xH'
- stat:
    path: /etc/sudoers.d/foo
  register: foo_sudoers_file
- apt:
    name: '{{ item }}'
    state: latest
    update_cache: yes
    cache_valid_time: 3600
    with_items:
    - vim
    - htop
- name: exemple
  apt_repository:
    repo: "deb http://mirror.evolix.org/jessie stable main"
    state: present
- mysql_user:
    name: mysqladmin
    password: my_password
    priv: "*.*:ALL,GRANT"
    state: present
    config_file: /root/.my.cnf
    update_password: on_create
- name: exemple
  sysctl:
    name: vm.max_map_count
    value: 262144
    sysctl_file: /etc/sysctl.d/elasticsearch.conf
- alternatives:
    name: editor
    path: /usr/bin/vim.basic
- name: exemple pour redémarrer un service (compatible avec sysvinit, systemd…)
  service: nginx
    state: restarted

replace vs lineinfile

Le fonctionnement exacte de replace et de lineinfile est potentiellement déroutant. Voici quelques constatations :

  • avec le module lineinfile, si l'argument regexp n'est pas matché… il insère quand même la ligne ! regexp n'est pas une condition pour l'insertion mais une condition pour remplacer au lieu d'insérer !
  • avec le module lineinfile, sauf cas tordus, l'argument regexp doit matcher l'argument line (sinon il va insérer la valeur de line à chaue exécution !)
  • le module lineinfile va d'abord évaluer si regexp matche et remplacer la dernière occurrence, puis si regexp ne matche pas il ajoute alors line (sans d'autre condition… même si elle existe déjà)
  • le module replace va remplacer uniquement si regex est matché, comme la commande sed
  • avec le module lineinfile, si l'on veut utiliser une référence (\1) dans line, ça donne une erreur, il faut utiliser replace
  • avec le module lineinfile, l'argument backrefs: yes sert à utiliser une référence au sein de l'argument regexp (et non pas au sein de l'argument line).

vars

Les variables sont un élément clé de la configuration des playbooks et roles. Exemple :

vars:
  ip: 31.170.9.129
  conf_file: /etc/foo.conf

tasks:
- command: echo {{ ip }} >> {{ conf_file }}

Les variables peuvent être définies à de multiples niveaux, chacun ayant une certaine précédence (extrait de la documentation) :

  • role defaults
  • inventory vars
  • inventory group_vars
  • inventory host_vars
  • playbook group_vars
  • playbook host_vars
  • host facts
  • play vars
  • play vars_prompt
  • play vars_files
  • registered vars
  • set_facts
  • role and include vars
  • block vars (only for tasks in block)
  • task vars (only for the task)
  • extra vars (always win precedence)

Pour gérer de nombreuses variables dans un projet, on peut stocker toutes celles qui correspondent à un groupe de serveur dans un fichier portant le nom du groupe, ainsi que toutes celle d'un serveur en particulier dans un fichier du nom du serveur. Voici l'arborescence conseillée :

└── inventory
    ├── group_vars
    │   └── group1.yml
    │   └── group2.yml
    └── host_vars
        └── hostname1.yml
        └── hostname2.yml

Les groupes sont définis dans le fichier d'inventaire.

Handlers

La directive notify, disponible pour tous les modules, permet de déclencher un handler.

L'enregistrement du handler se fait seulement lorsque l'action a provoqué un changement. L'exécution effective du handler se fait une seule fois en fin de "play", quel que soit le nombre de fois où il a été demandé pendant l'exécution.

Les handlers servent le plus souvent à redémarrer des services. Exemple :

tasks:
- name: copy Apache configuration
  copy: (…)
  notify: Restart Apache

handlers:
- name: Restart Apache
  service:
    name: apache2
    state: restarted

Dans des rôles longs, il est conseillé de purger les handlers de temps en temps (en fin de groupe d'action). En effet, si un playbook est interrompu les handlers ne sont pas forcément exécutés alors que l'action qui les a déclenchés a bien eu lieu. On insère alors l'action suivante :

- meta: flush_handlers

Tags

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

Hormis le fait de ranger/trier chaque tâche dans une catégorie, ils permettent 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 attribut d'action 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.

Il est possible de consulter le contenu détaillé de la variable avec debug :

- 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 presque 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 (variable d'environnement)
  • ./ansible.cfg
  • ~/.ansible.cfg
  • /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ées liées à cet host

[commercant]
mercerie
chapeautier

[commercant:vars]
ntp_server=ntp.mercerie.example.com
proxy=proxy.mercerie.example.com
  • hostname.internal : serveur présent dans aucun groupe
  • [httpservers] : 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.netmachine57.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ées
  • 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'a pas un vrai FQDN mais pointera vers 192.168.1.50 car on a indiqué des variables propres à Ansible. Il est existe aussi 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 disponibles pour les actions sur host1
  • [commercant:vars] : des variables qui seront liées 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éfinie (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
  • 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 un guillemet mal fermé (ou mélange simples/doubles guillemets), 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 solution 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ée (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 actions qui vont être faite s(mode dry-run) sans rien exécuter :
$ ansible-playbook --check my-experimental-playbook.yml

Note : certaines actions ne sont pas exécutées en mode "check", cela peut donc perturber celles qui sont bassées dessus.

  • 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
  (…)

Note : c'est pratique pur avoir la documentation exacte pour votre version d'Ansible. En effet, celle du site corespond à la dernière version et n'indique pas toujours toutes les différences.

Stopper l'éxecution du code

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

- debug:
    var: foo

- command: /bin/false

ou

- debug:
    var: foo

- fail:
    msg: "FAIL"

ou

- debug:
    var: foo

- 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 -y install python-simplejson [bzip2 php5-cli]
  • Si pas encore fait, donner les droits mysql à l'utilisateur
> GRANT ALL ON db.* TO 'user'@'localhost';

Limiter l'exécution à certaines machines

  • Limiter aux groupes www et sql (qui peuvent être indifféremment des groupes ou des serveurs) :
$ ansible-playbook -l "www:sql" playbook.yml
  • limiter aux serveurs foo-www01, foo-lb01, foo-filer… :
$ 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 - oubli 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 l'exécution du playbook, 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

Note: 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ée plus en haut dans le texte de l'historique.

Fréquence des hosts

Lors de l'exécution d'un play, on peut indiquer une fréquence sur le nombre d'hôtes concernés par l'éxecution du playbook.

  • Fork pour le nombre d'hôtes simultanés (modifiable 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

Si la commande cowsay est disponible sur votre machine, vous verrez un message à la fin :

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

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 détails : 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 saisie 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

Exécuter un 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 indiquant l'argument creates indiquant le chemin de fichier lors de l'utilisation du module shell, cette tâche ne s'exécutera que si le fichier indiqué par creates n'existe pas. Le corollaire est possible avec l'argument removes qui empêche l'exécution si le fichier n'existe pas.

Ces arguments sont disponibles pour certains modules (comme shell).

C'est beaucoup plus simple et rapide que de tester le fichier par le module stat juste avant.

Lancer tâche sur machine précise (voire locale)

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

- debug:
    var: tmp.stdout

Pour une exécution locale, on peut aussi utiliser l'attribut local_action.

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

Ne lancer tâche qu'une seule fois

- name: Début installation, envoie email
  run_once: true
  (…)

Si cet attribut est utilisé avec delegate_to, alors cette machine sera la seule à exécuter cette tâche. Sinon, c'est la première dans la liste de l'inventaire.

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

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

with_items

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

Par exemple pour l'installation de plusieurs nouveaux paquets :

---
- hosts: localhost

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

Même si il y aura plusieurs paquets 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ésents dans with_items) pour le module.

with_nested

Pour croiser les éléments des items :

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

Cela a pour effet d'exécuter l'inclusion pour alice pour chacune des 3 machines, puis pour bob pour chacune des 3 machines.

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 }}"

with_first_found

Permet de prendre le premier fichier trouvé :

- name: Copy HAProxy configuration
  template:
    src: "{{ item }}"
    dest: /etc/haproxy/haproxy.cfg
    force: yes
  with_first_found:
  - haproxy.cfg/{{ inventory_hostname }}
  - haproxy.cfg/{{ host_group }}
  - haproxy.cfg/default

De cette manière, si un fichier portant le nom du serveur en cours existe, il sera utilisé, sinon on cherche un fichier du nom du groupe du serveur et enfin on cherche un fichier par défaut, valable pour tous les serveurs qui n'ont pas de configuration spécifique ou de groupe.

Se connecter sous un autre utilisateur UNIX

Par défaut, l'utilisateur se connectant sur le serveur distant l'utilisateur UNIX courant. On peut soit le préciser dans le fichier de conf principal d'Ansible avec remote_user: michu, dans l'inventaire pour un groupe ou un serveur précis ou encore en l'indiquant en argument lors de l'éxecution du playbook.

$ ansible-playbook -u michu -k play.yml

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

Sur tous les modules, chaque tâche retourne un statut sur son résultat :

  • ok : aucune modification n'a été nécessaire
  • changed : une modification a eu lieu par rapport à l'état précédent (droits fichiers…)
  • failed : une erreur s'est produite

Pour des modules comme shell, command… Ansible ne peut savoir si un changement a eu lieu ou pas. Il indique alors toujours changed.

Il est possible de forcer le statut du changement :

- command: 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