Aller au contenu

Ecriture, Exécution et Debug de playbook Ansible

Mise à jour :

logo

Dans le guide précédent sur la découverte d’Ansible, nous avons exploré les bases de cet outil puissant d’automatisation, en expliquant comment il permet de gérer la configuration des serveurs de manière simple et efficace. Nous avons également abordé l’installation et la configuration initiale d’Ansible et les concepts clés tels que les inventaires, les modules et les playbooks.

Dans ce chapitre, nous allons approfondir la création de playbooks Ansible, qui sont le cœur de l’automatisation avec Ansible. Nous verrons comment structurer un playbook, définir des tâches, utiliser des variables et surtout comment exécuter et déboguer ces playbooks sur vos hôtes.

Structure d’un projet Ansible

Avant de démarrer l’écriture de playbooks, il faut dans un premier temps créer un certain nombre de répertoires et de fichiers pour organiser votre projet d’automatisation. Voici un exemple de structure :

  • Répertoireprojet_ansible
    • Répertoireinventory
      • hosts
    • Répertoiregroup_vars
      • all.yml
      • webservers.yml
    • Répertoirehost_vars
      • server1.yml
      • server2.yml
    • Répertoirefiles
      • sample_file.txt
    • Répertoiretemplates
      • service.j2
    • ansible.cfg
    • install_apache.yml
    • configure_apache.yml

Description des fichiers et répertoires :

  • ansible.cfg : Fichier de configuration d’Ansible. Il permet de personnaliser le comportement d’Ansible, comme le chemin vers l’inventaire, les privilèges de sudo, etc. C’est ici que vous pouvez définir des options globales pour Ansible. Il est optionnel.
  • inventory/ : Répertoire contenant les fichiers d’inventaire. Un inventaire spécifie les groupes d’hôtes et les serveurs sur lesquels Ansible doit agir.
    • hosts : Fichier d’inventaire principal où sont définis les hôtes et leurs groupes. Par exemple, vous y déclarez vos groupes d’hôtes comme webservers, dbservers et les serveurs qui leur sont associés.
  • group_vars/ : Ce répertoire contient des fichiers YAML définissant des variables pour des groupes d’hôtes. Chaque fichier porte le nom d’un groupe, ou peut s’appeler all.yml pour des variables globales.
    • all.yml : Variables globales appliquées à tous les hôtes.
    • webservers.yml : Variables spécifiques au groupe webservers.
  • host_vars/ : Ce répertoire contient des fichiers YAML définissant des variables spécifiques à un hôte individuel.
    • server1.yml : Variables pour l’hôte server1.
    • server2.yml : Variables pour l’hôte server2.
  • files/ : Répertoire contenant des fichiers à copier directement sur vos hôtes.
    • sample_file.txt : Un fichier exemple qui pourrait être utilisé pour des copies directes, comme un fichier de configuration ou des binaires statiques.
  • templates/ : Le dossier des templates Jinja
    • service.j2 : Un template Jinja de services utilisant des variables et conditions.
  • install_apache.yml : Playbook pour installer Apache.
  • configure_apache.yml : Playbook pour configurer Apache, en utilisant un template par exemple.

Cette structure vous permet de bien organiser vos projets Ansible, en séparant les rôles, les variables et les tâches spécifiques aux serveurs ou groupes de serveurs. Cela rend votre infrastructure plus maintenable et évolutive.

Ecriture d’un playbook Ansible

Un playbook Ansible est un fichier écrit en YAML qui permet de décrire un ensemble de tâches à exécuter sur un ou plusieurs hôtes. L’intérêt principal d’un playbook est d’automatiser des tâches répétitives, qu’il s’agisse de configurer des serveurs, de déployer des applications ou de gérer des infrastructures. Dans ce chapitre, je vais détailler les éléments essentiels d’un playbook et montrer comment le structurer.

Un playbook Ansible peut être composé de plusieurs plays :

---
- name: Commentaire du premier play
hosts: webservers
tasks:
- name: Installer Apache
ansible.builtin.apt:
name: apache2
state: present
---
- name: Commentaire du second play
hosts: dbservers
tasks:
- name: Installer Mysql
ansible.builtin.apt:
name: mysql-server
state: present

Chaque play est composé de plusieurs éléments clés :

  • name : Un nom descriptif pour le play, qui permet de comprendre son but.
  • hosts : Spécifie les hôtes sur lesquels le play sera exécuté. Cela peut être un groupe d’hôtes défini dans l’inventaire ou un hôte spécifique.
  • tasks : Une liste de tâches à exécuter sur les hôtes spécifiés. Chaque tâche est définie par un nom et un module Ansible à utiliser.
tasks:
- name: Installer Apache
ansible.builtin.apt: # Le nom du module
name: apache2 # paramètre 1 du module
state: present # paramètre 2 du module

Les modules sont au cœur des tâches dans un playbook. Ils permettent d’exécuter une grande variété d’actions, telles que :

Il existe d’autres composantes dans un play que nous verrons plus loin, comme les handlers, les variables, …

Exécution du playbook

Pour exécuter un playbook, il suffit d’utiliser la commande ansible-playbook suivie du nom du fichier YAML contenant le playbook.

Par exemple :

Terminal window
ansible-playbook -i hosts install_apache.yml

Dans cet exemple, -i hosts spécifie le fichier d’inventaire, et install_apache.yml est le playbook à exécuter.

Les inventaires Ansible

Un inventaire Ansible est un fichier qui liste les hôtes sur lesquels Ansible doit opérer. Il peut s’agir d’un simple fichier texte ou d’une source dynamique. L’inventaire définit les groupes d’hôtes et les variables associées, permettant ainsi de cibler des machines spécifiques lors de l’exécution des playbooks.

Exemple d’inventaire statique :

[webservers]
web1.example.com
web2.example.com
[dbservers]
db1.example.com
db2.example.com

Ici dans cet inventaire, nous avons deux groupes : webservers et dbservers chacun contenant des hôtes spécifiques.

Utilisation des variables

Ansible permet d’utiliser des variables pour rendre vos playbooks plus dynamiques, réutilisables et adaptés à différents environnements. Ces variables s’emploient avec la syntaxe {{ nom_de_variable }} dans les tâches, chemins, ou fichiers de configuration.

Créer des variables dans un playbook

Le moyen le plus simple de définir une variable est d’utiliser la section vars: dans le playbook :

- hosts: all
vars:
serveur_http: apache2
port_http: 8080
tasks:
- name: Installer le serveur web
ansible.builtin.apt:
name: "{{ serveur_http }}"
state: present

Ces variables peuvent ensuite être utilisées dans tout le bloc tasks du playbook.

Définir des variables dans un fichier externe

Pour structurer vos playbooks, vous pouvez placer vos variables dans un fichier séparé et les inclure avec vars_files :

- hosts: all
vars_files:
- vars/main.yml

Fichier vars/main.yml :

document_root: /var/www/html

Pour découvrir les autres types de variables (listes, dictionnaires, facts, variables d’environnement, précédence, sécurité), consultez le guide complet sur les variables Ansible.

Les Lookup en Ansible

Un lookup en Ansible permet de récupérer des informations provenant de sources externes, comme des fichiers, des variables d’environnement ou des résultats de commandes, au moment de l’exécution du playbook. Cela permet d’ajouter de la flexibilité aux playbooks, en s’appuyant sur des données dynamiques plutôt que des valeurs statiques.

Pourquoi utiliser un lookup ?

Les lookups sont particulièrement utiles lorsque vos données doivent provenir de sources externes ou être récupérées dynamiquement en fonction du contexte d’exécution. Par exemple, vous pouvez lire des configurations à partir de fichiers ou de variables d’environnement sans avoir à les coder directement dans le playbook.

Exemple :

- hosts: all
vars:
config_data: "{{ lookup('file', '/etc/app/config.txt') }}"
tasks:
- name: Afficher le contenu du fichier de configuration
ansible.builtin.debug:
msg: "{{ config_data }}"

Dans cet exemple, le lookup lit le fichier /etc/app/config.txt et stocke son contenu dans la variable config_data, qui est ensuite utilisée dans une tâche Ansible. Cela permet de centraliser la gestion des données externes tout en maintenant un playbook flexible.

Pour une explication plus complète sur l’utilisation des lookups et les différentes options disponibles, vous pouvez consulter le guide détaillé sur l’utilisation des lookups dans Ansible.

Utilisation des boucles Ansible

Dans certains cas, nous avons besoin de réaliser une tâche sur plusieurs cibles, ou de répéter une action plusieurs fois. Ansible propose plusieurs types de boucles pour répondre à ces besoins.

Le premier type de boucle permet de répéter une action sur une liste de valeurs, par exemple créer plusieurs utilisateurs :

- name: Créer des utilisateurs
ansible.builtin.user:
name: "{{ item }}"
state: present
loop:
- alice
- bob
- carol

Le module ansible.builtin.user est exécuté pour chaque élément de la liste item, qui contient les noms des utilisateurs à créer. La variable item permet d’accéder à la valeur courante de la boucle.

Plus d’infos sur les boucles.

Utilisations des conditions d’Ansible: 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"
ansible.builtin.command: /sbin/shutdown -t now
when: ansible_facts[’os_family’] == "Debian"

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

Plus d’infos sur les conditions Ansible.

Utilisations de 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 playbook Ansible.

Les filtres Ansible

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

---
tasks:
- ansible.builtin.shell: cat /some/path/to/file.json
register: result
- ansible.builtin.set_fact:
myvar: "{{ result.stdout | from_json }}"

D’autres sont là pour contraindre leur existence, pour assigner une valeur par défaut ou encore leur omission :

---
- name: touch files with an optional mode
ansible.builtin.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 filtres, des filtres mathématiques, pour des opérations sur des listes, les dictionnaires, les adresses mac ou ip, des [expressions régulières](/docs/developper/expressions-regulieres/, … Leur documentation se trouve dans ce guide

Si vous ne trouvez pas votre bonheur, vous pouvez développer vos propres filtres.

Utilisation des handlers Ansible

Les handlers d’Ansible permettent de déclencher des événements après qu’une tâche soit passé au status 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 toutes les tâches.

Pour cela, il suffit d’ajouter un notify à votre tâche Ansible avec le nom du handler et de définir le bloc handlers avec le même nom à la fin du fichier.

- name: enable vhost
ansible.builtin.command: a2ensite test_site
notify:
- restart apache
handlers:
- name: restart apache
ansible.builtin.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
ansible.builtin.command: a2ensite test_site
notify:
- restart apache
# Force restart handlers
- meta: flush_handlers

Plus d’infos sur les handlers Ansible.

Élévation de privilèges

Avant tout, il faut bien se rappeler qu’Ansible utilise principalement le protocole ssh pour se connecter à vos machines hôtes. La bonne pratique est de créer sur les machines hôtes un user ansible ne possédant pas les droits root, mais de lui donner les droits sudo.

Dans ce cas, il faudra peut-être demander à Ansible de faire ce qu’on appelle de l’élévation de privilèges.

Pour cela, il est possible d’utiliser les paramètres Ansible.

become : mis à true pour activer l’élévation de privilèges become_user : défini l’utilisateur souhaité par défaut, il s’agit de root become_method: la méthode d’élévation de privilège. Par défaut sudo, mais on peut aussi utiliser su ou doas. On peut aussi développer ses propres plugins de connexion.

Exemples

Démarrer le service apache avec sudo

- name: Ensure the httpd service is running
ansible.builtin.service:
name: httpd
state: started
become: true

Utiliser le compte apache

- name: Run a command as the apache user
ansible.builtin.command: somecommand
become: true
become_user: apache

Il est possible de définir ces paramètres directement dans les variables de groupes. Vous pouvez aussi les définir au niveau le plus haut de votre playbook ou dans la commande de lancement de votre playbook (j’aime moins) :

---
- hosts: all
become: true
become_method: enable
tasks:
- name: Install EPEL repo.
ansible.builtin.package:
name: epel-release
state: present

ou dans la ligne de commande :

Terminal window
ansible-playbook -i staging --become --become-method=su --ask-become-pass

Plus d’infos sur become

Debug des playbooks Ansible

Vous avez plusieurs outils à votre disposition pour débuger vos playbooks.

Utilisons ce playbook comme exemple qui ne fonctionne pas bien sur :

---
- hosts: all
gather_facts: true
become: true
vars:
pkg_name: not_exist
tasks:
- name: Install a package
ansible.builtin.package:
name: "{{ pkg_name }}"

Le mode verbose

Un premier moyen de voir ce qui se passe lorsqu’on lance un playbook ansible, avec d’activer le mode verbose -v. Chaque v supplémentaire permet à d’augmenter la verbosité de la sortie.

Terminal window
ansible-playbook -vvv -i inventory --limit host1 test.yml
ansible-playbook 2.9.15
<host1> SSH: EXEC ssh -C -o ControlMaster=auto -o ControlPersist=60s -o StrictHostKeyChecking=no -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/vagrant/.ansible/cp/5401241998 host1 '/bin/sh -c '"'"'rm -f -r /home/vagrant/.ansible/tmp/ansible-tmp-1614782435.453363-165214-175234282313188/ > /dev/null 2>&1 && sleep 0'"'"''
<host1> (0, b'', b'')
fatal: [host1]: FAILED! => {
"changed": false,
"failures": [
"No package not_exist available."
],
"invocation": {
"module_args": {
"allow_downgrade": false,
"autoremove": false,
"bugfix": false,
"conf_file": null,
"disable_excludes": null,
"disable_gpg_check": false,
"disable_plugin": [],
"disablerepo": [],
"download_dir": null,
"download_only": false,
"enable_plugin": [],
"enablerepo": [],
"exclude": [],
"install_repoquery": true,
"install_weak_deps": true,
"installroot": "/",
"list": null,
"lock_timeout": 30,
"name": [
"not_exist"
],
"releasever": null,
"security": false,
"skip_broken": false,
"state": null,
"update_cache": false,
"update_only": false,
"validate_certs": true
}
},
"msg": "Failed to install some of the specified packages",
"rc": 1,
"results": []
}
PLAY RECAP *************************************************************************************
host1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 resc

Avec ce niveau, vous obtenez déja bcp d’informations.

Check_mode

Le second moyen est de lancer votre playbook ansible en mode check. En fait, dans ce mode :

  • Les modules qui effectuent une action de modification, d’ajout ou de suppression vont uniquement vérifier que la source et la destination sont présentes, l’action ne sera pas effectuée.
  • Les modules qui effectuent peut-être des actions de modifications, mais sans possibilité de contrôle, comme modules command et shell, seront ignorés.
  • Ceux qui ne font que rendre une information comme les modules find et stat seront tout de même exécutés.
Terminal window
ansible-playbook main.yml --check

Pour contrôler, vous pouvez activer le mode diff et vous ne verrez aucune modification (sauf pour les tasks utilisant shell ou command):

Terminal window
ansible-playbook main.yml --check --diff

Si vous voulez désactiver le mode check sur une tache, il suffit de rajouter ceci à votre task :

check_mode: no

Si certaines de vos taches retourne des erreurs, vous pouvez ajouter ceci à ces tasks pour poursuivre le playbook:

ignore_errors: '{{ ansible_check_mode }}'

Le mode debugger

Vous pouvez activer le mode débogage pour chaque exécution de vos playbooks en mettant la variable enable_task_debugger à True dans le fichier ansible.cfg:

[defaults]
enable_task_debugger = True

Un autre moyen est de le rajouter dans votre playbook :

---
- hosts: all
gather_facts: true
become: true
debugger: on_failed
vars:
pkg_name: not_exist
tasks:
- name: Install a package
ansible.builtin.package:
name: "{{ pkg_name }}"

Lorsqu’on lance ce playbook ansible, qui plante, car le package n’existe pas, on voit apparaître cette ligne :

Terminal window
TASK [Install a package] ***********************************************************************
fatal: [host2]: FAILED! => {"changed": false, "failures": ["No package not_exist available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
[host2] TASK: Install a package (debug)>

A partir de là, nous pouvons utiliser les commandes suivantes pour debugger :

  • p : affiche des informations utilisées pendant l’exécution par votre tâche module
  • task.args[key] = value: met à jour l’argument du module.
  • task_vars[key] = value: met à jour les variables de votre playbook.
  • u(pdate_task): si vous modifiez les task_vars alors utilisez cette commande pour recréer la tâche à partir de votre nouvelle structure de données.
  • r(edo): exécutez à nouveau la tâche.
  • c(ontinue): passe à la tâche suivante.
  • q(uit): quitte le débogueur. L’exécution du playbook est abandonnée.

Ici modifions le user le nom du package avant de relancer :

Terminal window
TASK [Install a package] ***********************************************************************
fatal: [host2]: FAILED! => {"changed": false, "failures": ["No package not_exist available."], "msg": "Failed to install some of the specified packages", "rc": 1, "results": []}
[host2] TASK: Install a package (debug)> p task.args
{'name': 'not_exist'}
[host2] TASK: Install a package (debug)> task.args['name'] = 'bash'
[host2] TASK: Install a package (debug)> redo
ok: [host2]

Si vous avez plusieurs hosts, il faudra le répéter autant de fois.

Relancer depuis une étape particulière

Parfois si vous voulez relancer un playbook à partir d’une étape particulière et non depuis le début, il suffit d’utiliser l’option --start-at-task.

Terminal window
ansible-playbook playbook.yml --start-at-task="install packages"

Jouer un playbook de manière interactive

Avec cette option, Ansible s’arrête à chaque tâche et demande s’il doit l’exécuter ou pas. Par exemple, si vous avez une tâche appelée configure ssh, l’exécution du playbook s’arrêtera et demandera :

Terminal window
ansible-playbook playbook.yml --step
...
Perform task: configure ssh (y/n/c):

c permet de quitter le mode interactif !

L’utilitaire ansible-console

Je l’ai documenté dans ce billet. Cet outil permet de lancer les modules ansible de manière interactifs.

Changer la sortie d’Ansible

Si comme moi, vous trouvez que la sortie standard de la commande ansible-playbook pas très pratique, vous pouvez en changer.

On va prendre cet exemple pour montrer les différences.

- name: Show ansible_selinux var
ansible.builtin.debug:
msg: "{{ ansible_selinux }}"

donne :

PLAY [Test stdout] *************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Show ansible_selinux var] ************************************************
ok: [localhost] => {
"msg": {
"status": "Missing selinux Python library"
}
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Sortie au format YAML

Il suffit dans le fichier ansible.cfg

[defaults]
stdout_callback = yaml
PLAY [Test stdout] *************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [Show ansible_selinux var] ************************************************
ok: [localhost] =>
msg:
status: Missing selinux Python library
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Sortie au format JSON

[defaults]
stdout_callback = json
{
"custom_stats": {},
"global_custom_stats": {},
"plays": [
{
"play": {
"duration": {
"end": "2021-04-10T12:36:31.761991Z",
"start": "2021-04-10T12:36:30.529488Z"
},
"id": "7d9a4545-499f-5cc6-45f2-000000000005",
"name": "Test stdout"
},
...

D’autres formats de sortie

La liste complète de la communauté des stdout_callback Ansible

D’autres qu’il faut placer dans le répertoire plugins/callback :

Exercices pratiques : Écriture de playbooks Ansible simples

Vous avez maintenant une bonne compréhension des concepts de base d’Ansible et du fonctionnement des inventaires. Passons à l’étape suivante : apprendre à écrire vos premiers playbooks.

Dans ce TP, vous allez découvrir la structure d’un fichier YAML utilisé par Ansible, manipuler quelques modules essentiels (copy, lineinfile, file) et exécuter vos tâches localement grâce au plugin de connexion local.

👉 Travaux pratiques : TP 2 : Écriture de playbooks Ansible simples ↗

Ces exercices couvrent notamment :

  • La structure minimale d’un playbook Ansible (hosts, tasks, vars)
  • L’usage du module copy pour transférer un fichier
  • L’ajout d’une ligne avec le module lineinfile
  • La modification des permissions avec le module file
  • L’exécution d’un playbook avec ansible-playbook
  • L’utilisation des options --check et -vvv pour le mode simulation et le débogage

Pourquoi pratiquer ?

Apprendre à écrire ses propres playbooks vous permettra de :

  • Comprendre la logique déclarative d’Ansible
  • Apprendre à manipuler les modules fondamentaux
  • Structurer vos automatismes de manière lisible et réutilisable
  • Prendre de bonnes habitudes pour les projets plus complexes à venir

Clonez le dépôt ou suivez les consignes directement dans le TP pour mettre en pratique ces concepts clés et renforcer vos compétences en automatisation avec Ansible.

Contrôle de connaissances : Inventaires statiques Ansible

Pourquoi ce contrôle ?

Cet contrôle va vous permettre de valider vos connaissances sur le sujet abordé dans le guide. Il comporte des QCM, des questions vrai/faux et des réponses ouvertes à un mot.

🕒 Le chronomètre commence dès que vous cliquez sur Démarrer le test. Vous devrez terminer l’examen avant la fin du temps imparti.

🎯 Pour réussir, vous devez obtenir au moins 80% de bonnes réponses.

💡 Je ne fournis pas directement les réponses aux questions. Cependant, si certaines sont complexes, des pistes d’explication pourront être proposées dans le guide ou après l’examen.

Bonne chance ! 🚀

Conclusion

En conclusion, la rédaction de playbooks Ansible est un exercice essentiel pour automatiser et orchestrer efficacement la gestion de l’infrastructure. À travers ce guide, nous avons exploré en profondeur les différentes facettes de la syntaxe des playbooks, leur structure et leur organisation. En maîtrisant la logique des tâches, l’utilisation des variables, … vous pouvez transformer des processus manuels complexes en workflows automatiques et reproductibles.

Un playbook bien conçu doit être clair, modulable et maintenable, ce qui facilitera sa réutilisation et son évolution au fil du temps. En suivant les meilleures pratiques que nous avons décrites, vous serez capable d’écrire des playbooks robustes qui répondront aux besoins spécifiques de votre environnement, tout en anticipant les futures extensions de votre infrastructure.