Ansible pour gérer ses machines

Alexandre Laurent

Ansible est une plateforme permettant de gérer et d’automatiser la configuration des ordinateurs d’un parc informatique. Cet article présente l’outil et explique comment il fonctionne afin de l’utiliser pour vos systèmes et donne quelques exemples simples pour démarrer rapidement.

Commentez

- Titre : Ansible pour gérer ses machines

- Auteur : Alexandre Laurent

- Parution : 19 juin 2025

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2025 Alexandre Laurent. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Voir également :

- Tutoriel pour apprendre à utiliser Ansible de Quentin Busuttil

- Déploiement d'applications avec Fog Project

Sommaire

- I. Introduction

- I-A. Configuration de test

- II. Installation

- II-A. Configuration d’un nœud Windows

- III. Fonctionnement

- IV. Premier pas

- IV-A. Inventaire

- IV-A-1. Test

- IV-B. Tâche

- IV-C. Les modules Ansible

- IV-C-1. Copier un fichier

- IV-C-1-a. Obtenir les droits administrateur

- IV-C-1-a-i. Coffre-fort

- IV-C-2. Récupérer un fichier depuis les machines gérées

- IV-C-3. Télécharger un fichier

- IV-C-4. Cloner un dépôt Git

- IV-C-5. Exécuter une commande quelconque

- V. Variables

- VI. Template

- VII. Handler

- VIII. Rôles

- VIII-A. Galaxy

- IX. Mise en pratique

- IX-A. Configuration de git

- IX-B. Installation et configuration d’un logiciel

- IX-C. Déploiement de point de montage réseau

- X. Astuces

- X-A. Vérification d’un playbook

- X-B. Linter

- XI. Conclusion

- XII. Remerciements

I - Introduction

Ansible est un logiciel libre (licence GNU v3) permettant de déployer et lancer des tâches sur une partie ou l’ensemble d’un parc informatique. Grâce à Ansible, vous pourrez notamment installer un logiciel, mettre à jour des fichiers de configuration, configurer un service sur l’ensemble de vos machines et cela, de manière automatique et à distance. L’exploitation commerciale d‘Ansible est, depuis 2015, la propriété de Red Hat.
Même si l’outil est prévu pour un parc informatique, il peut être intéressant de l’utiliser chez soi, et ce, à partir d’un nombre restreint de machines. En effet, Ansible pourra alors permettre de dupliquer la configuration des logiciels ou même du système, sur ces machines. Par exemple, Ansible permet d’avoir une même configuration des outils sur deux machines utilisées pour du développement et de mettre à jour cette configuration sans pour autant effectuer les manipulations sur les machines en question.

Cet article n’a pas pour but de remplacer la documentation officielle (qui est d’ailleurs de bonne qualité).

I-A - Configuration de test

Pour l’élaboration de cet article, Ansible a été mis en place au sein d’un parc virtuel de trois machines sous Linux :

- une machine sur laquelle Ansible est installé ;

- deux machines recevant les ordres d‘Ansible.

Ces machines sont évidemment placées sur le même réseau Ethernet et peuvent donc communiquer entre elles.

II - Installation

Ansible est distribué sous la forme d’un paquet Python et s’installe donc avec la commande :

pip install ansible

Ansible doit uniquement être installé sur la machine de contrôle. Quant aux machines gérées par Ansible, elles ne nécessitent qu’une installation de Python pour exécuter le code généré par Ansible et un compte utilisateur auquel il est possible de se connecter au travers de SSH.

Cela correspond à un mode de fonctionnement dit « mode push » où les configurations sont envoyées par une machine de contrôle vers les machines cibles. C’est cette utilisation qui est décrite tout au long de l’article.

Ansible peut aussi être utilisé dans le mode de fonctionnement dit « mode pull » (donc, à l’inverse du « mode push »). Dans ce deuxième mode de fonctionnement, Ansible est installé directement sur les machines cibles (il n’y a pas de machine de contrôle). Les fichiers de configuration d‘Ansible devront alors être récupérés sur la machine à configurer (par exemple, en les récupérant depuis un dépôt Git) et appliqués, grâce à Ansible, localement.

Quel que soit le mode que vous utilisez, les fichiers de configuration et les commandes sont identiques. Seule la machine cible à indiquer à Ansible change (localhost, pour indiquer la machine locale, en « mode pull »).

II-A - Configuration d’un nœud Windows

Ansible peut aussi se connecter aux machines fonctionnant sous Windows. Toutefois, une machine sous Windows requiert une configuration spécifique. Premièrement, Ansible peut utiliser le protocole SSH ou le protocole WinRM pour la connexion. Dans le premier cas, un serveur SSH peut être installé sous Windows par le biais des « Fonctionnalités facultatives » présentes dans les paramètres du système.

Le service « OpenSSH SSH Server » n’est pas démarré automatiquement après installation. Il est donc nécessaire de le démarrer manuellement et si nécessaire, d’activer le démarrage automatique (par exemple, depuis le « Gestionnaire des services ».

De plus, si votre compte utilisateur n’a pas de mot de passe, il ne sera pas possible de s’y connecter à travers SSH.

WinRM peut s’activer avec la commande winrm quickconfig. Cette commande mettra en place une configuration par défaut, suffisante pour une utilisation avec Ansible, mais à modifier pour des questions de sécurité.

Que ce soit avec SSH ou WinRM, Il sera certainement nécessaire de mettre à jour les règles du pare-feu Windows pour permettre aux machines externes de se connecter à ces services.

Sur la machine de contrôle, il sera possible de spécifier le protocole, ssh ou winrm, à travers la variable ansible_connection, notamment, depuis l’inventaire. Le support de WinRM nécessite l’installation du paquet Python pywinrm.

Finalement, l’utilisation de WinRM repose sur les variables suivantes :

- ansible_port: 5985 pour changer le port utilisé par Ansible. En effet, il est possible que par défaut, Ansible utilise le port 5986 en considérant que la connexion sera sécurisée (HTTPS), alors que la configuration par défaut de WinRM utilise le port 5985 (HTTP) ;

- ansible_winrm_transport: ntlm pour définir la méthode d’authentification. Il est possible de choisir parmi basic, ntlm, kerberos, certificate et credssp. Par contre, basic et ntlm n’offrent pas de chiffrement et sont donc à éviter en HTTP.

III - Fonctionnement

Ansible vient avec son propre vocabulaire. Ainsi, vous serez rapidement confronté aux mots suivants :

- machine de contrôle : la machine qui exécute Ansible et qui va envoyer des ordres aux autres machines ;

- machine gérée : une machine gérée par Ansible, qui va donc exécuter les tâches envoyées par Ansible. En « mode pull », la machine gérée est aussi la machine de contrôle ;

- inventaire (« inventory ») : la liste de machines gérées par Ansible ;

- playbook : un recueil de tâches à effectuer ;

Le parc informatique devant être géré par Ansible doit être décrit dans un fichier (pouvant être au format INI ou YAML) nommé inventaire (« inventory »). Les tâches à effectuer sur les machines du parc sont décrites dans un fichier (au format YAML) appelé « playbook ». Grâce à ses informations, Ansible se connectera donc aux machines mentionnées dans l’inventaire, grâce à SSH, pour lancer les tâches voulues.

IV - Premier pas

Pour débuter avec Ansible, la première étape sera de créer un inventaire puis une tâche simple à exécuter. Le rôle de cette première tâche sera de joindre et d’obtenir une réponse des machines gérées par Ansible. Le but est multiple :

- créer un premier inventaire ;

- créer une première tâche ;

- vérifier que la connexion entre les machines, spécifiquement entre la machine de contrôle et les machines gérées est fonctionnelle ;

IV-A - Inventaire

Dans sa forme la plus simple, l’inventaire est une simple liste contenant l’adresse IP ou le nom de la machine. Au format INI :

inventory.ini

[group1]
192.168.100.20
192.168.100.21

Ou, son équivalent au format YAML :

inventory.yml

group1:
hosts:
computer1:
ansible_host: 192.168.100.20
computer2:
ansible_host: 192.168.100.21

Bien que les deux formats, INI et YAML, offrent les mêmes possibilités, le format YAML sera probablement privilégié en raison de sa structure plus facile à manipuler pour les parcs informatiques composés de nombreuses machines.

Dans les exemples ci-dessus, le nom du groupe (ici group1) peut être modifié pour mieux correspondre à votre objectif. Il en est de même pour le nom des machines (ici computer1 et computer2). De plus, il est possible de définir plusieurs groupes dans un même inventaire.

IV-A-1 - Test

Déjà, il est possible de vérifier si l’inventaire ne contient pas d’erreur de syntaxe avec la commande :

ansible-inventory -i inventory.ini --list

Et de tester la connexion avec les machines listées avec la commande :

ansible group1 -m ping -i inventory.ini

Le premier paramètre (group1) est l’ensemble de machines (ici, un nom de groupe) sur lequel effectué la tâche. La tâche en question est définie par le module ping (option -m). L’option -i permet d’indiquer quel inventaire utiliser et pour que cette commande fonctionne, cet inventaire doit définir le group1.

Les commandes ci-dessus utilisent l’inventaire au format INI mais auraient très bien pu utiliser le fichier inventory.yml au format YAML.

Lorsque les machines sont joignables par le contrôleur, le message suivant est affiché :

computer1 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.12"
},
"changed": false,
"ping": "pong"
}
computer2 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3.12"
},
"changed": false,
"ping": "pong"
}

Dans le cas où aucun serveur SSH n’est actif sur la machine gérée, le message suivant apparaîtra :

computer1 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.100.20 port 22: Connection refused",
"unreachable": true
}

Et si la connexion n’est pas automatique (si l’utilisateur doit entrer un mot de passe pour se connecter), le message suivant apparaîtra :

computer2 | UNREACHABLE! => {
"changed": false,
"msg": "Failed to connect to the host via ssh: alexandre@192.168.100.21: Permission denied (publickey,password).",
"unreachable": true
}

Finalement, si Python n’est pas trouvé sur la machine, vous obtiendrez le message suivant :

computer2 | FAILED! => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python3"
},
"changed": false,
"module_stderr": "Shared connection to 192.168.100.21 closed.\r\n",
"module_stdout": "/bin/sh: line 1: /usr/bin/python3: No such file or directory\r\n",
"msg": "The module failed to execute correctly, you probably need to set the interpreter.\nSee stdout/stderr for the exact error",
"rc": 127
}

Si un message similaire (toutefois avec un résultat dans module_stderr différent) apparaît pour une machine Windows, vérifiez que votre playbook utilise les modules adéquats, spécifiques à Windows. Dans l’exemple précédent, il sera nécessaire d’utiliser win_ping à la place de ping. En effet, les modules pour Linux et autres vont aussi produire un tel message d’erreur pour les machines Windows.

Avec cette première étape, il est possible de constater que :

- la connexion SSH s’effectue avec le nom d’utilisateur ayant lancé la commande Ansible : un utilisateur différent peut être renseigné dans l’inventaire (variable ansible_ssh_user) ;

- le premier argument pour Ansible est l’ensemble de machines sur lesquelles la tâche doit être exécutée. Cet argument peut être une machine unique, un ensemble ou une expression plus complexe constituée d’opérateurs binaires ou même de variables, comme l’indique la documentation.

IV-B - Tâche

Le test de l’étape précédente repose sur le module « ping » d’Ansible, ce qui est très bien pour vérifier le bon fonctionnement de l’installation. Toutefois, il deviendra vite intéressant de mettre en place des tâches personnalisées à exécuter sur les machines gérées. Cette configuration se fait au travers d’un « playbook » (au format YAML) : un fichier contenant un ensemble de tâches. Le fichier suivant est un exemple simple dans lequel deux tâches sont configurées : un ping et l’affichage d’un message de débogage (« debug ») « Bonjour monde » :

ping.yml

- name: Hello World
hosts: group1
tasks:
- name: Ping my hosts
ansible.builtin.ping:

# Une deuxième tâche:
- name: Print message
ansible.builtin.debug:
msg: Bonjour monde

Pour exécuter le playbook, il faudra utiliser la commande ansible-playbook, comme suit :

ansible-playbook -i ./inventory.yml ./ping-playbook.yml

Ce qui permettra d’obtenir la sortie suivante :

PLAY [Hello World] *************************************************************

TASK [Gathering Facts] *********************************************************
ok: [computer1]
ok: [computer2]

TASK [Ping my hosts] ***********************************************************
ok: [computer1]
ok: [computer2]

TASK [Print message] ***********************************************************
ok: [computer1] => {
"msg": "Bonjour monde"
}
ok: [computer2] => {
"msg": "Bonjour monde"
}

Aussi, ce deuxième exemple montre les points suivants :

- un playbook peut contenir plusieurs tâches ;

- les tâches sont exécutées dans l’ordre, de haut en bas (ce comportement est modifiable à travers les stratégies) ;

- la liste des machines sur lesquelles exécuter les tâches est déterminée dans le playbook (champ hosts).

La tâche « Gathering Facts » est une tâche d’Ansible effectuée par défaut (pouvant être désactivée). Celle-ci va récupérer un ensemble d’informations sur la machine cible. Ces informations seront alors disponibles au travers de la variable ansible_facts.

IV-C - Les modules Ansible

Maintenant qu‘Ansible est en place, que l’inventaire est prêt et qu’il est possible d’exécuter des tâches, voyons comment mettre en place des tâches utiles permettant de modifier la configuration des machines gérées.
Pour la première tâche, nous avons utilisé deux modules :

- ansible.builtin.ping ;

- ansible.builtin.debug ;

Ansible propose une multitude de modules, listés sur cette page. Afin de s’habituer à la mise en place de playbooks, quelques modules sont décrits ci-dessous.

Les exemples suivants se veulent simples. Chaque module décrit dans cet article, possède de nombreuses options permettant de couvrir la majorité des cas. Toutefois, ces options ne sont pas mentionnées ici et il est donc nécessaire de se référer à la documentation officielle.

IV-C-1 - Copier un fichier

Vous souhaitez envoyer un fichier à toutes les machines d’un groupe, pour ce faire, votre tâche pourra utiliser le module ansible.builtin.copy (et win_copy pour les machines Windows).

copy-file.yml

- name: Copy some files
hosts: group1
tasks:
- name: Copy a file in tmp
ansible.builtin.copy:
src: /home/alexandre/fichier
dest: /tmp/fichier

Le module possède des options pour :

- indiquer le contenu du fichier à envoyer, directement dans le playbook (content) ;

- définir des droits, un propriétaire et un groupe particuliers (owner, group, mode) ;

- utiliser une somme de contrôle pour vérifier le succès de la copie (checksum) ;

- …

Par contre, de la sorte, il ne sera pas possible d’écrire dans un dossier d’un autre utilisateur, ou plus précisément, un dossier auquel seul le compte administrateur a accès, comme cela est majoritairement le cas pour les fichiers de configuration d’une machine.

IV-C-1-a - Obtenir les droits administrateur

Rapidement, il sera nécessaire d’exécuter des tâches en tant qu’administrateur (de la machine gérée). Pour cela, Ansible propose les commandes become, become_user, become_method et become_flags. Le playbook précédent deviendra :

copy-file-root.yml

- name: Copy some files
hosts: group1
tasks:
- name: Copy a file
ansible.builtin.copy:
src: /home/alexandre/stamp
dest: /etc/stamp
become: yes

Mais avec une telle solution (et en utilisant l’option --check pour jouer le playbook), Ansible indiquera qu’il ne sait pas comment devenir administrateur :

TASK [Copy a file in tmp] ******************************************************
fatal: [computer2]: FAILED! => {"msg": "Missing sudo password"}

Il est possible de donner le mot de passe, et ce, de plusieurs manières :

- directement dans le fichier YAML ;

- avec l’option -K de l’ansible-playbook, Ansible demandera le mot de passe dans le terminal ;

- de le mettre dans un fichier annexe ;

- de le renseigner au travers d’une variable d’environnement.

Bien que fonctionnelles, la sécurité de ces méthodes peut être discutée. Heureusement, Ansible propose une méthode pour stocker des informations de manière sécurisée : les coffres-forts.

IV-C-1-a-i - Coffre-fort

Ansible propose une solution au problème du stockage de mot de passe, d’information ou de fichier sensible sous la forme d’un coffre-fort (« vault » en anglais). Pour gérer le coffre-fort, Ansible propose une commande dédiée : ansible-vault.
La création d’un coffre-fort se fait ainsi :

ansible-vault create root_passwords.yaml

Une fois un mot de passe entré, la commande ouvre automatiquement le fichier en édition, dans vi. Dans ce fichier, vous pourrez renseigner une variable contenant le mot de passe. Pour l’exemple de copie d’un fichier, nécessitant une élévation de privilège demandé au travers du mot clef become, il suffit de définir le mot de passe dans la variable standard dédiée à cet usage :

ansible_become_password: MotDePasse

Le playbook peut alors être modifié pour charger le coffre-fort :

copy-file-root-2.yml

- name: Copy some files
hosts: group1
vars_files:
- root_passwords.yaml
tasks:
- name: Copy a file in tmp
ansible.builtin.copy:
src: /home/alexandre/stamp
dest: /etc/stamp
become: yes

Et si on lance le playbook, on obtient immédiatement la réponse suivante :

ERROR! Attempting to decrypt but no vault secrets found

Dorénavant, il faut utiliser l’option --ask-vault-pass lorsque l’on veut lancer le playbook :

ansible-playbook --ask-vault-pass -i ./inventory.yml ./tasks/copy_file_root-2.yml

Le mot de passe du coffre-fort peut être stocké dans un autre fichier ou encore, dans un gestionnaire de secrets.

La commande ansible-vault permet aussi de lire, éditer, déchiffrer et re-chiffrer un coffre-fort.

IV-C-2 - Récupérer un fichier depuis les machines gérées

Le module fetch permet d’effectuer l’opération inverse de la commande copy vue précédemment : grâce à fetch, vous pouvez récupérer des fichiers depuis les nœuds gérés.

fetch_file.yml

- name: Fetch file
hosts: group1
tasks:
- name: Fetch fstab
ansible.builtin.fetch:
src: /etc/fstab
dest: /tmp/out

Le fichier fstab sera alors stocké, sur la machine de contrôle, à l’emplacement /tmp/out/computer1/etc/fstab (pour la machine nommée computer1).

Le module fetch dispose d’une option flat, désactivée par défaut, permettant de stocker le fichier directement dans le fichier ou le dossier spécifié :

fetch_file_flat.yaml

- name: Fetch file
hosts: group1
tasks:
- name: Fetch fstab
ansible.builtin.fetch:
src: /etc/fstab
dest: /tmp/out/
flat: yes

Par conséquent, chaque fois qu’un fichier est récupéré d’une machine, le fichier est écrasé par le nouveau. Par conséquent, cette option n’est utile que lorsque vous souhaitez récupérer un fichier à partir d’une seule machine.

IV-C-3 - Télécharger un fichier

Pour télécharger un fichier, il faudra utiliser le module get_url (et win_get_url pour les machines Windows) :

get_file.yml

- name: Get file hosted online
hosts: group1
tasks:
- name: Get some good hosts
ansible.builtin.get_url:
url: https://sebsauvage.net/hosts/hosts
dest: /tmp/hosts

Entre autres, ce module dispose d’une option (cheksum) pour vérifier la somme de contrôle du fichier téléchargé.

IV-C-4 - Cloner un dépôt Git

Le module git permet récupérer des fichiers depuis Git. Il existe une commande équivalente pour hg et pour subversion.

clone_godot.yml

- name: Get godot
hosts: group1
tasks:
- name: Get latest godot sources
ansible.builtin.git:
repo: https://github.com/godotengine/godot.git
dest: /tmp/godot

IV-C-5 - Exécuter une commande quelconque

De manière plus générale, il est possible d’exécuter une commande quelconque avec le module command (win_command sous Windows) ou encore un script, avec le module script (le script est local et sera transféré sur le nœud). En plus du module command, il existe un module shell (et win_shell), reprenant le même principe que command mais disposant des spécificités d’un shell (c’est-à-dire, les variables telles que $HOME ou les redirections). Finalement, il existe le module raw, une variante bas niveau des commandes précédentes permettant d’exécuter une commande à travers SSH. L’avantage de cette dernière étant qu’il est possible de l’utiliser pour installer Python ou pour automatiser des tâches sur une machine sans Python. Toutefois, son utilisation doit être évitée, autant que possible.

curl_google.yml

- name: Fetch Google
hosts: group1
tasks:
- name: Make request
ansible.builtin.command: curl www.google.com --output /tmp/website.html

V - Variables

Ansible permet de créer des variables. Celles-ci peuvent être définies au travers d’un playbook, d’un inventaire, en les passant en option lors du lancement d’Ansible ou encore, dans un fichier à inclure (comme vu avec les coffre-forts). Une tâche peut aussi produire une nouvelle variable (en utilisant le mot clé register). La documentation indique dans la section « return values », les différentes variables produites par un module. Voici un exemple d’utilisation de register :

Register_vars.yml

- name: Récupération d’un fichier en ligne
hosts: group1
tasks:
- name: Récupération d’un fichier hosts
ansible.builtin.get_url:
url: https://sebsauvage.net/hosts/hosts
dest: /tmp/hosts
register: get_hosts

# Utilisation de la variable précédemment créée
- name: Debug
ansible.builtin.debug:
msg: "Taille : {{ get_hosts.size }}"

Le nom d’une variable est sujet à quelques restrictions : le nom ne peut contenir que des lettres, des chiffres et des soulignés. Plus précisément, le nom d’une variable ne peut pas commencer par un chiffre et ne peut pas être un mot clef du langage Python ou des playbooks.

Voici un autre exemple, où la variable est définie dans le playbook :

playbook_var.yml

- name: variable test in playbook
hosts: group1
vars:
my_var: "Developpez.com"
tasks:
- name: Simple variable test
ansible.builtin.debug:
msg: "Hello {{ my_var }}"

La même chose peut être réalisée, mais en définissant la variable dans l’inventaire :

inventory_var.yaml

group1:
hosts:
computer1:
ansible_host: 192.168.100.20
my_var: "Developpez.com"
computer2:
ansible_host: 192.168.100.21
my_var: "Developpez.net"

Ce qui permet d’avoir une variable spécifique à la machine l’exécutant.

Si la variable n’est définie que pour une machine et non pour l’autre, la tâche s’exécutera correctement pour la machine pour laquelle la variable est définie, mais échouera pour l’autre.

Finalement, la variable peut être définie lors de l’exécution de la commande avec l’option -e ou --extra-vars. Les différentes variables doivent être séparées par une espace :

ansible-playbook -e "my_var=World" -i ../inventory.yaml ./var-test.yaml

Par conséquent, il est possible que votre variable soit définie à deux endroits différents. La documentation officielle indique l’ordre de précédence.

Les variables peuvent être de différents types, comme des booléens, des listes et des dictionnaires. Avec la présence des listes ou des dictionnaires, il peut vite devenir utile d’itérer sur les différents éléments de ceux-ci, ce qu‘Ansible propose avec le mot clef loop. Ce dernier inscrit l’élément de l’itération en cours dans la variable item :

loop.yml

- name: Test de boucle
hosts: group1
vars:
a_list:
- "Developpez.com"
- "Developpez.net"
tasks:
- name: Say hi
ansible.builtin.debug:
msg: "Hello {{ item }}"
loop: "{{ a_list }}"

Ce qui affiche :

TASK [Say hi] ******************************************************************
ok: [computer1] => (item=Developpez.com) => {
"msg": "Hello Developpez.com"
}
ok: [computer1] => (item=Developpez.net) => {
"msg": "Hello Developpez.net"
}
ok: [computer2] => (item=Developpez.com) => {
"msg": "Hello Developpez.com"
}
ok: [computer2] => (item=Developpez.net) => {
"msg": "Hello Developpez.net"
}

Ansible fournit un ensemble de variables, appelées « faits » (facts), qu’il est possible de consulter de deux manières différentes :

- avec une ligne de commande :

ansible -m setup group1 -i ../inventory.yaml

- avec un playbook :

facts.yml

- name: Debug
hosts: group1
tasks:
- name: Afficher les variables
ansible.builtin.debug:
var: ansible_facts

Ansible propose aussi le mot clef when permettant d’ajouter une condition à l’exécution d’une tâche. La tâche suivante ne s’exécute que sur les machines non Windows :

condition.yml

- name: Test de condition
hosts: group1
tasks:
- name: Tâche pour Linux, mais pas Windows
ansible.builtin.debug:
msg: "Hello Linux"
when: ansible_facts['os_family'] != "Windows"

VI - Template

Les template permettent d’utiliser un fichier modèle pour générer un fichier sur les machines gérées. En effet, les variables utilisées dans le modèle seront remplacées par leur valeur telle que connue par Ansible. Ce mécanisme repose sur un langage spécifique : Jinja2. De plus, pour transformer un fichier modèle vers son état final, il est nécessaire d’utiliser le module Ansible template (et win_template pour Windows), qui, de manière globale, possède les mêmes fonctionnalités que le module copy.

Avec le fichier Jinja 2 suivant :

motd.j2

Welcome on {{ machine_name }}

Et ce playbook :

template_motd.yml

- name: Send customized motd
hosts: group1
tasks:
- name: Greetzs
ansible.builtin.template:
src: motd.j2
dest: /tmp/motd

Vous obtiendrez, sur les machines gérées, le fichier suivant :

Welcome on Ordinateur 2

lorsque la variable machine_name est définie à « Ordinateur 2 ». Dans le cas présent, une telle variable serait certainement définie pour chaque machine de l’inventaire.

Le module template dispose d’une option validate permettant de lancer une commande vérifiant la validité du fichier créé avant d’effectuer la copie sur la machine de destination. Cela permet d’éviter de bloquer la machine gérée lors de la mise à jour des fichiers de configuration de cette dernière (par exemple, le fichier sudoers).

La documentation officielle de Jinja2 permet d’en apprendre plus sur les possibilités de ce langage.

VII - Handler

Ansible propose un mécanisme, sous le nom de « handler », permettant d’effectuer une tâche uniquement si la machine gérée a été modifiée (état « changed »). Plus précisément, un « handler » est une tâche placée dans une section dédiée : handlers. Pour que le « handler » soit exécuté, une tâche classique doit en faire la demande avec le mot clef notify. Voici un exemple, reprenant le playbook pour récupérer un fichier en ligne :

get_file_with_handler.yml

- name: Get file hosted online
hosts: group1
tasks:
- name: Get some good hosts
ansible.builtin.get_url:
url: https://sebsauvage.net/hosts/hosts
dest: /tmp/hosts
notify:
- Debug handler

handlers:
- name: Debug handler
ansible.builtin.debug:
msg: "File successfully downloaded"

Une telle tâche pourra être à l’origine de la sortie suivante :

PLAY [Get file hosted online] **************************************************

TASK [Gathering Facts] *********************************************************
ok: [computer1]
ok: [computer2]

TASK [Get some good hosts] *****************************************************
ok: [computer2]
changed: [computer1]

RUNNING HANDLER [Debug handler] ************************************************
ok: [computer1] => {
"msg": "File successfully downloaded"
}

PLAY RECAP *********************************************************************
computer1                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
computer2                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Ici, seul « computer1 » a exécuté le « handler ». En effet, sur « computer2 », le fichier à récupérer était déjà présent, faisant qu’aucun changement n’a été effectué sur cette machine. Par conséquent, le « handler » n’a pas été exécuté.
Un cas classique d’utilisation des « handlers » est de redémarrer un service après la modification d’un fichier de configuration.

Ansible permet :

- de notifier plusieurs « handlers » en une fois ;

- d’écrire plusieurs « handlers » dans un même playbook ;

- d’exécuter un « handler » à des moments donnés et non après l’exécution de toutes les tâches du playbook ;

- de forcer une tâche à indiquer un état « changed » avec changed_when.

VIII - Rôles

Les rôles d‘Ansible sont des ensembles de fichiers pouvant définir des tâches, des « handlers », des variables, des templates ou des fichiers quelconques. L’objectif est de mettre en place un ensemble de fichiers pouvant être utilisés dans une ou plusieurs tâches.

Les rôles se définissent au travers de la hiérarchie de fichiers suivante :

roles/
common/
tasks/
main.yml
handlers/
main.yml
templates/
ntp.conf.j2
files/
bar.txt
foo.sh
vars/
main.yml
defaults/
main.yml
meta/
main.yml
library/
module_utils/
exemple_plugins/

Dans une telle hiérarchie, le nom du rôle est défini par le nom du dossier dans le dossier roles, ici :common. Ansible cherchera un fichier main.yml dans chacun des sous dossiers. Les sous dossiers d’un rôle sont les suivants :

- tasks : les tâches définies par ce rôle ;

- handlers : les « handlers » définis par ce rôle ;

- templates : les fichiers de modèles que ce rôle utilise ;

- files : les fichiers quelconques que ce rôle utilise ;

- vars : les variables définies par ce rôle ;

- defaults : d’autres variables, ayant une priorité faible, définies par ce rôle ;

- meta : permet d’appeler un autre rôle lors de l’inclusion de ce rôle (telle une dépendance). Peut aussi contenir des méta-données pour Galaxy ;

- library : des modules personnalisés ;

- module_utils : du code supplémentaire pour les modules ;

- exemple_plugins : pour stocker des données spécifiques aux extensions, ici appelées « exemple ».

Avec la particularité que tout dossier est optionnel et peut-être absent si vous n’en avez pas besoin. Un rôle est valide s’il contient au moins un de ces dossiers.

Ansible cherche un fichier main.yml, dans les sous-dossiers d’un rôle. Il est toujours possible d’inclure d’autres fichiers, par exemple, un deuxième fichier de tâches, à partir du fichier main.yml.

Les rôles doivent être placés dans le dossier du playbook, ou un chemin relatif à celui-ci ou dans le dossier défini par roles_path. Il est aussi possible, lors de l’utilisation d’un rôle de spécifier un chemin complet.

Ci-dessous, un exemple d’un rôle, appelé test et définissant un unique fichier tasks/main.yml :

tasks/main.yml

- name: Une tâche sans intérêt
ansible.builtin.debug:
msg: "Hello"

Et un playbook utilisant ce rôle :

test-role.yml

- name: Role test
hosts: group1
roles:
- role: test
tasks:
- name: Récupération d’un bon fichier hosts
ansible.builtin.get_url:
url: https://sebsauvage.net/hosts/hosts
dest: /tmp/hosts

La tâche définie par le rôle est exécutée avant la tâche du playbook. Il est à noter que le playbook aurait aussi pu uniquement contenir :

test-role-2.yml

- name: Role test
hosts: group1
roles:
- role: test

Exécutant uniquement la ou les tâches définies dans le rôle.

VIII-A - Galaxy

En plus des modules proposés avec Ansible, il est possible d’obtenir des collections et des rôles provenant de la communauté et disponible sur le site Galaxy. En complément de l’interface Web, il est possible d’explorer Galaxy avec la commande ansible-galaxy.
La commande est aussi utile pour l’installation d’un rôle trouvé sur Galaxy :

ansible-galaxy role install namespace.role_name

IX - Mise en pratique

Maintenant que les fonctionnalités principales d‘Ansible ont été expliquées, il est temps de mettre en place des tâches utiles.

La création d’une tâche suit généralement le processus suivant :

- expérimentation manuelle pour déterminer les étapes nécessaires de la tâche ;

- lecture de la documentation afin de trouver les modules adéquats pour réaliser les étapes ;

- écriture du playbook et des fichiers associés en se reposant sur la page de documentation dédiée à chaque module ;

- vérification du playbook ;

- test du playbook sur une machine virtuelle de test. Cela est d’autant plus facile sachant que la machine virtuelle peut figurer dans un groupe spécifique de l’inventaire ;

- déploiement.

IX-A - Configuration de git

La tâche suivante va créer un nouveau compte utilisateur et configurer Git de façon à ce que le nom de l’utilisateur et son adresse de courriel soient définis.
En bref, la tâche effectue les actions suivantes :

- création d’un nouvel utilisateur avec le module user ;

- mise en place d’une configuration de Git spécifique à l’utilisateur avec le module git_config ;

- clonage d’un dépôt Git avec le module git.

Pour que la tâche soit plus intéressante, elle repose sur une liste d’utilisateurs dans un fichier de configuration, tel que le suivant :

users.yml

utilisateurs:
- utilisateur:
login: "utilisateur1"
username: "Developpez Com"
email: "utilisateur1@example.com"
- utilisateur:
login: "utilisateur2"
username: "Developpez Net"
email: "utilisateur2@example.com"

Il est aussi possible d’utiliser un fichier CSV, grâce au module read_csv.

Par contre, dans un vrai environnement, il est étonnant de configurer deux développeurs sur la même machine. Une autre façon de faire serait de définir ces variables dans l’inventaire.

La tâche peut être rédigée ainsi :

setup-git-user.yml

- name: Configure un nouvel utilisateur et prépare sa configuration Git
hosts: group1
vars_files:
- users.yml
- root_passwords.yml
tasks:
- name: Création de l’utilisateur
ansible.builtin.user:
name: "{{ item.utilisateur.login }}"
# group: developers
# password: https://docs.ansible.com/ansible/latest/reference_appendices/faq.html#how-do-i-generate-encrypted-passwords-for-the-user-module
loop: "{{ utilisateurs }}"
become: yes

- name: Configuration du username Git
ansible.builtin.git_config:
name: utilisateur.name
scope: global
value: "{{ item.utilisateur.username }}"
loop: "{{ utilisateurs }}"
become: yes
become_user: "{{ item.utilisateur.login }}"

- name: Configuration de l’email Git
ansible.builtin.git_config:
name: utilisateur.email
scope: global
value: "{{ item.utilisateur.email }}"
loop: "{{ utilisateurs }}"
become: yes
become_user: "{{ item.utilisateur.login }}"

- name: Récupération des sources de Godot
ansible.builtin.git:
repo: https://github.com/godotengine/godot.git
dest: ~/godot
loop: "{{ utilisateurs }}"
become: yes
become_user: "{{ item.utilisateur.login }}"

IX-B - Installation et configuration d’un logiciel

La tâche ci-dessous installe et configure automatiquement le logiciel fzf. Le playbook utilise les conditions pour rendre le processus compatible avec différentes distributions Linux et différents shells. Cette fois, le mot de passe administrateur est demandé par Ansible au lancement du playbook (grâce à vars_prompt). Pour activer fzf, il est nécessaire d’ajouter une ligne dans le fichier de configuration du shell. La mise à jour de ce fichier de configuration est réalisée par le module lineinfile, qui permet de chercher si une ligne est déjà présente dans le fichier (option search_string) et de l’ajouter dans le cas contraire (option line).

install-fzf.yml

- name: Installation et configuration de fzf
hosts: group1
vars_prompt:
- name: "ansible_become_pass"
prompt: "Mot de passe su"
tasks:
- name: Update (pacman)
community.general.pacman:
update_cache: true
upgrade: true
when: ansible_facts['os_family'] == "Archlinux"
become: yes
become_user: root
become_method: su

- name: Installation (pacman)
community.general.pacman:
name: fzf
state: present
when: ansible_facts['os_family'] == "Archlinux"
become: yes
become_user: root
become_method: su

- name: Update (apt)
ansible.builtin.apt:
upgrade: dist
update_cache: yes
when: ansible_facts['os_family'] == "Debian"
become: yes
become_user: root
become_method: su

- name: Installation (apt)
ansible.builtin.apt:
name: fzf
state: present
when: ansible_facts['os_family'] == "Debian"
become: yes
become_user: root
become_method: su

- name: Active fzf dans Bash
ansible.builtin.lineinfile:
path: ~/.bashrc
search_string: eval "$(fzf --bash)"
line: eval "$(fzf --bash)"
when: ansible_facts['user_shell'] == "/bin/bash"

- name: Active fzf dans zsh
ansible.builtin.lineinfile:
path: ~/.zshrc
search_string: source <(fzf --zsh)
line: source <(fzf --zsh)
when: ansible_facts['user_shell'] == "/bin/zsh"

Il est possible d’améliorer le processus. Notamment, il serait judicieux d’avoir un rôle spécifique à la mise à jour du système et qui serait appelé au lancement du playbook ci-dessus. En effet, il n’est pas toujours nécessaire de faire la mise à jour du système à chaque lancement du playbook, ou inversement, il serait intéressant d’avoir un playbook pour uniquement mettre à jour le système.

IX-C - Déploiement de point de montage réseau

Le scénario suivant a pour but de mettre en place un point de montage réseau (par exemple, pour se connecter automatiquement à un NAS) grâce à systemd automount.

Pour cela, le module systemd sera utile, notamment pour charger la nouvelle configuration à travers un handler, ainsi que les commandes template et copy pour envoyer des fichiers sur les machines cibles.

Les fichiers de configuration à envoyer seront :

mnt-server.mount

[Install]
WantedBy=multi-user.target
[Unit]
Description=SMB Server

[Mount]
What=//mon-server.example.com/server
Where=/mnt/server
Options=_netdev,credentials=/root/smb.pass,x-systemd.automount,uid=1000,gid=1000
Type=cifs
TimeoutSec=300

mnt-server.automount

[Automount]
Where=/mnt/server

Ainsi qu’un troisième fichier devant être préparé par Ansible et donc au format Jinja 2.

smb.pass.j2

username={{ username }}
password={{ server_password }}
domain=WORKGROUP

Et la tâche en elle-même :

smb-server-automount.yml

- name: Configuration d’un automount systemd
hosts: group1
vars:
username: "developpez"
server_password: "super_mot_de_passe"
vars_files:
- root_passwords.yml
tasks:
- name: Copie des informations de connexion
ansible.builtin.template:
src: ./smb.pass
dest: /root/smb.pass
become: yes

- name: Copie des fichiers automount
ansible.builtin.copy:
src: ./mnt-server.mount
dest: /etc/systemd/system/mnt-server.mount
become: yes

- name:  Copie des fichiers automount
ansible.builtin.copy:
src: ./mnt-server.automount
dest: /etc/systemd/system/mnt-server.automount
become: yes

- name: Activation au démarrage de la machine
ansible.builtin.systemd:
daemon_reload: yes
name: mnt-server.automount
enabled: yes
become: yes

- name: Montage
ansible.builtin.systemd:
name: mnt-server.automount
state: started
become: yes

Dans cet exemple, Ansible configure un point de montage automatique sur les machines cibles. Le processus (avec les modules template et copy) est le même pour la configuration d’une multitude de logiciels ou même, du système. Généralement, la configuration de ceux-ci est stockée dans des fichiers.

X - Astuces

X-A - Vérification d’un playbook

La commande ansible-playbook dispose d’une option --check permettant de vérifier ce qui va être réalisé lors de l’exécution d’un playbook (sans pour autant effectuer les tâches en question) :

ansible-playbook --check -i ./inventory.yaml ./ping.yaml

Cette option est donc un bon outil pour vérifier que les tâches effectueront ce qui est demandé ou encore, pour déboguer un playbook ne fonctionnant pas comme attendu.

En plus de l’option --check, ansible-playbook propose aussi les options --diff, --list-hosts, --list-tasks et --syntax-check.

X-B - Linter

De plus, Ansible propose un outil, ansible-lint, vérifiant la syntaxe et des erreurs communes lors de l’écriture d’un playbook.
Celui-ci n’est pas installé par défaut avec Ansible. La commande suivante permet de l’installer :

pip install ansible-lint

L’outil s’exécute grâce à la commande suivante :

ansible-lint ./ping.yaml

Ce qui pourra donner la sortie suivante :

WARNING  Listing 1 violation(s) that are fatal
yaml[indentation]: Wrong indentation: expected 6 but found 8
ping.yaml:9

Read documentation for instructions on how to ignore specific rule violations.

# Rule Violation Summary

1 yaml profile:basic tags:formatting,yaml

Failed: 1 failure(s), 0 warning(s) on 1 files. Last profile that met the validation criteria was 'min'.

XI - Conclusion

Ansible est un outil puissant, utile pour gérer un parc informatique, mais pouvant aussi être pratique dans un environnement plus modeste, tel que celui à la maison.
Cet article a permis de découvrir les différentes fonctionnalités d’Ansible et détaille la mise en place de tâches simples. Libre à vous de réutiliser ces connaissances et d’étendre les différents playbooks afin de gérer la configuration de vos machines.

XII - Remerciements

Merci à Franck Talbart et Christophe pour leurs suggestions. Merci également à ALT pour sa relecture orthographique.