Ecriture et Exécution de playbooks

Dans le précédent article vous aviez découvert l’installation et l’exécution de module ansible. Ajourd’hui attardons sur l’écriture de playbooks en utilisant toutes les fonctions mises à notre disposition.

Si vous utilisez un environnement virtuel python, avant tout exécution de vos playbooks ou installation de librairies n’oublier pas d’activer l’environnement virtuel python avec la commande suivante :

. venv/bin/activate

Construction d’un playbook

Nous avons vu qu’un playbook est une une séquence de tâches ou de rôles décrits dans un fichier ou format yaml. Pour le moment nous n’utiliserons que des tâches (tasks). Un playbook est un fichier au format yaml. YAML est un langage de description comme l’est le HTML. En YAML Le plus important est de respecter l’indentation sans quoi la structure n’est plus compréhensible. Le débogage est de ce fait complexe quand le fichier devient volumineux, mais des EDI ont de très nombreux plugins permettant de détecter les erreurs.

Exemple de playbook permettant d’ajouter le repo epel sur Centos :

---
- hosts: localhost
  tasks:
    - name: Install EPEL repo.
      yum:
        name: epel-release
        state: present
    - name: Install ntop
      yum:
        name: ntop
        state: present

Pour lancer un playbook on utilise la commande ansible-playbook. Pour lancer un playbook sur un inventaire on ajoute comme pour la commande ansible l’argument -i avec le chemin de celui-ci. On indique ensuite la localisation du ou des playbooks. Il est possible d’enchaîner plusieurs groupes d’exécution sur différentes cibles. Pour cela il suffit de rajouter des blocs -hosts. Ce qui donne :

---
- hosts: target1
  tasks:
  - name: Install EPEL repo.
    yum:
      name: epel-release
      state: present
- hosts: target2
  - name: Install ntop
    yum:
      name: ntop
      state: present

Pour vérifier la syntaxe d’un playbook on peut ajouter –syntax-check et le lancer en mode dry-run avec –check

Les variables

Les facts

Les « gather facts », sont des variables qu’Ansible recueille sur les machines cibles: le système d’exploitation, les adresses IP, la mémoire, le disque, etc. Ensuite, il stocke ces informations dans des variables qu’on nomme facts. Le moyen le plus simple est de lancer le module setup sur localhost : ansible -m setup localhost

Ces variables “prédéfinies peuvent ensuite être utilisées dans les playbooks. Pour cela il suffit de les mettre entre double accolades :

- name: Show ansible_selinux var
  debug:
    msg: "{{ ansible_selinux }}"

Ce qui donne :

ok: [localhost] => {
    "msg": {
        "config_mode": "enforcing",
        "mode": "enforcing",
        "policyvers": 28,
        "status": "enabled",
        "type": "targeted"
    }
}

Dans le cas où vous n’utilisez pas ces facts vous pouvez désactiver sa collecte en ajoutant dans l’entête de votre playbook [gather_facts: no] Cela va accélérer l’exécution de votre playbook.

Vos variables

Le plus simple pour ajouter une variable à votre playbook est de définir une section vars. Ensuite vous pouvez les récupérer comme pour les facts avec des doubles accolades.

---
- hosts: all
  vars:
    nginx_ssl: /etc/nginx/ssl
    nginx_conf_file: /etc/nginx/nginx.conf

Il est possible de définir des variables dans un fichiers séparé en ajoutant la section vars_files.


 ---
  hosts: all

  vars_files:
    - myvarsfile.yml

Définir des variables par group et host

Il est possible de créer des variables dans des fichiers séparés qui seront automatiquement chargé par ansible. Il faut qu’il se nomme host_vars et group_vars et doivent se trouver là où se trouve l’inventaire.

Pour définir une variable à un host ou un groupe spécifique il suffit de nommer le fichier par le nom du host ou du groupe.

Ensuite leur syntaxe est fonction de l’extension du fichier.

---
test2: test2

Sauvegarde du résultat d’une tache dans une variable

Pour enregistrer une variable il suffit d’ajouter à votre tache le mot-clé register. Dans le cas de dictionnaire il suffit d’ajouter un . entre les champs (test.ping)

---
- hosts: localhost

  tasks:
    - name: Ping localhost
      ping:
      register: test
    - name: display var
      debug:
        msg: "{{ test.ping }}"

Les boucles

Dans certains cas, nous avons besoin de réaliser une tâche sur plusieurs cibles, comme par exemple installer plusieurs paquets et tous cela en une seule opération. Ansible intègre les boucles de différentes manières:

Les boucles standards: _withitems ou loop

Le premier type de boucle permet de répeter une action sur une liste de valeurs, comme par exemple une liste de packages:

...
- name: add several package
    yum:
      name={{ item }}
      state=present
    with_items:
    - nginx
    - python

Les boucles imbriquées: _withnested

Cet exemple permettra de comprende le fonctionnement de ce type de boucle :

vars:
  keys:
    - key1
    - key2
    - key3
tasks:
  - name: Distribute SSH keys among multiple users
    lineinfile:
      dest: /home/{{ item[0] }}/.ssh/authorized_keys
      line: {{ item[1] }}
      state: present
    with_nested:
      - [ ’calvin’, ’josh’, ’alice’ ]
      - ’{{ keys }}’

Ici ansible va boucler sur chaque utilisateur et remplira leur fichier authorized_keys avec les 3 clés définies dans la liste.

Les boucles sur dictionnaire: _withdict

Les boucles sur dictionnaires : En terminologie Python, un dictionnaire est un ensemble défini de variables possedant plusieurs valeurs :

vars:
  users:
      alice:
          name: Alice
          telephone: 123-456-7890
      bob:
          name: Bob
          telephone: 987-654-3210
tasks:
  - name: Print phone records
      debug:
        msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
      with_dict: "{{users}}"

Do until

Don until peut être utilisé pour attendre qu’une condition soit vraie où qu’elle ait atteint le nombre maxi d’itération pour sortir de la boucle.

- shell: /usr/bin/foo
  register: result
  until: result.stdout.find("OK")
  retries: 5
  delay: 10

L’exemple ci-dessus exécute le module shell de manière récursif jusqu’à ce que le résultat du module retourne “OK” dans sa sortie standard ou que la tâche ait été itérée 5 fois avec un délai de 10 secondes. La variable result aura également une nouvelle clé «attemps» qui aura le nombre des tentatives effectués par la boucle.

Et bien d’autres

Il existe toute une série de type de boucle répondant à des besoins spécifiques. Si vous avez des choses complexes à construire n’hésitez pas à jeter un oeil à cette section de la documentation Ansible.

Les conditions: when

Parfois, vous voudrez qu’une tache particulière ne s’exécute ou pas dans certaines conditions. Par exemple ne pas installer un certain paquet si le système d’exploitation correspond à une version particulière, ou encore de procéder à certaines étapes de nettoyage si un système de fichiers est saturé.

tasks:
  - name: "shut down all Debian"
    command: /sbin/shutdown -t now
    when: ansible_facts[’os_family’] == "Debian"

La syntaxe des conditions (and or, in, not in, is … ) reprend celle de Jinja2 qui nous servira à créer par la suite des modèles.

Il est possible de mettre des conditions en fonction de l’exécution ou de l’échec ou du bypass d’une tache précédente. Pour ignorer une erreur d’une tache il suffit d’ajouter la clé ignore_errors à true (Ne pas utiliser dans tous les cas).

tasks:
  - command: /bin/false
    register: result
    ignore_errors: True

  - command: /bin/something
    when: result is failed

  - command: /bin/something_else
    when: result is succeeded

  - command: /bin/still/something_else
    when: result is skipped

TP n=° 5

Jinja

Jinja est à la base un module python écrit pour Django permettant de produire rapidement du texte dynamique. L’idée principale est de fournir à un module, un modèle et une liste de valeurs pour qu’il construise dynamiquement un résultat. Jinja fournit des possibilités plus avancées, comme appliquer des filtres sur une variable, appliquer des boucles sur des listes et bien d’autre encore. Et cela va nous servir dans les playbooks.

Les filtres

Les filtres dans Ansible sont utilisés pour transformer des données. Sa syntaxe est : {{ var | filter }}

---
tasks:
  - shell: cat /some/path/to/file.json
    register: result

  - set_fact:
      myvar: "{{ result.stdout | from_json }}"

D’autres sont là pour contraindre leur existence, pour assigner une valeur par defautt ou encore leur ommision :

---
- name: touch files with an optional mode
  file: dest={{ item.path | mandatory }} state=touch mode={{ item.mode | default(omit) }}
  loop:
    - path: /tmp/foo
    - path: /tmp/bar
    - path: /tmp/baz
      mode: "0444"

Il existe toute une série de filtre, des filtres mathématiques, pour des opérations sur des listes, les dictionnaires, les addresses mac ou ip, des expressions regulières, … Leur documentation se trouve ici.

Les tests

Les tests en Jinja permettent d’évaluer les expressions de modèle et de renvoyer True ou False. La principale différence entre les tests et les filtres réside dans le fait que les tests Jinja sont utilisés à des fins de comparaison, alors que les filtres sont utilisés pour la manipulation de données.

vars:
  url: "http://example.com/users/foo/resources/bar"

tasks:
  - debug:
      msg: "matched pattern 1"
    when: url is match("http://example.com/users/.*/resources/.*")

  - debug:
      msg: "matched pattern 2"
    when: url is search("/users/.*/resources/.*")

  - debug:
      msg: "matched pattern 3"
    when: url is search("/users/")

Il peuvent être également utilisé pour tester le type de fichiers :

- debug:
    msg: "path is a directory"
  when: mypath is directory

- debug:
    msg: "path is a file"
  when: mypath is file

- debug:
    msg: "path is a symlink"
  when: mypath is link

- debug:
    msg: "path already exists"
  when: mypath is exists

- debug:
    msg: "path is {{ (mypath is abs)|ternary(’absolute’,’relative’)}}"

- debug:
    msg: "path is the same file as path2"
  when: mypath is same_file(path2)

- debug:
    msg: "path is a mount"
  when: mypath is mount

Les handlers

Les handlers permettent de déclencher des évenemenent après l’activation d’un module ou la modification d’un fichier de config (les handlers ne sont lancés que si le fichier de conf change réellement: état changed). En outre, bien que plusieurs tâches puissent nécessiter une même action, l’action en question ne sera lancée qu’après l’exécution de tous les blocs tâches.

Pour cela il suffit d’ajouter une notify à votre tache avec le nom du handler et de définir le bloc handler avec le même nom à la fin du fichier.

    - name: enable vhost
      command: a2ensite test_site
      notify:
        - restart apache

    handlers:
    - name: restart apache
      service:
        name: apache2
        state: restarted

si vous souhaitez forcer le déclenchement d’un handler après une tache ajouter la ligne suivante:

    - name: enable vhost
      command: a2ensite test_site
      notify:
        - restart apache
    # Force restart handlers
    - meta: flush_handlers

Voila pour aujourd’hui. A bientôt pour la suite …


Alimenter un blog comme celui-ci est aussi passionnant que chronophage. En passant votre prochaine commande (n'importe quel autre article) au travers des liens produits ci-contre, je touche une petite commission sans que cela ne vous coûte plus cher. Cela ne me permet pas de gagner ma vie, mais de couvrir les frais inhérents au fonctionnement du site. Merci donc à vous!

comments powered by Disqus