Ecriture, Exécution et Debug de playbook Ansible
Mise à jour :
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.
- 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
- 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
.
- server1.yml : Variables pour l’hôte
- 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 :
- Installer ou supprimer des
paquets
(comme dans l’exemple avec le module
apt
). - Gérer des
services
(
service
). - Gérer des fichiers ou des
répertoires
(
file
). - Exécuter des
commandes
(
command
,shell
) en dernier recours quand les modules n’existent pas.
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 :
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.comweb2.example.com
[dbservers]db1.example.comdb2.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 :
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.
ansible-playbook -vvv -i inventory --limit host1 test.ymlansible-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
etshell
, seront ignorés. - Ceux qui ne font que rendre une information comme les modules
find
etstat
seront tout de même exécutés.
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
):
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 :
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 :
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)> redook: [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
.
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 :
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.