ANSIBLE : Installation et premiers pas

ANSIBLE : Installation et premiers pas

Introduction

Ansible est un moteur d’automatisation open-source qui simplifie de nombreux processus informatiques complexes, de la gestion de configurations à l’orchestration de tâches multi-niveaux.

Dans cet article, nous explorerons les bases d’Ansible, en commençant par son installation sur une distribution Linux. Ensuite, nous plongerons dans les fondamentaux de sa prise en main et avancerons pas à pas vers la création d’un premier playbook.

Architecture et Principes de Base

Ansible repose sur une architecture sans agent, signifiant qu’il n’est pas nécessaire d’installer un logiciel supplémentaire sur les nœuds gérés. Il utilise SSH (ou WinRM pour les machines Windows) pour communiquer avec les hôtes, garantissant ainsi sécurité et simplicité. Cette approche minimaliste réduit la courbe d’apprentissage et facilite la maintenance.

Playbooks et Automatisation

Au cœur d’Ansible se trouvent les « Playbooks », des fichiers de configuration écrits en YAML (Yet Another Markup Language). Ces Playbooks définissent les tâches à exécuter et dans quel ordre. Chaque Playbook peut contenir une ou plusieurs « plays », chaque play étant destiné à un groupe de machines cibles et peut être composé de plusieurs tâches.

L’inventaire

L’inventaire est un autre composant clé d’Ansible. Il s’agit d’un fichier qui liste toutes les machines à gérer, organisées en groupes. L’inventaire peut être simple (un fichier texte plat) ou complexe (une source dynamique), offrant une flexibilité pour gérer divers environnements.

Les modules

Ansible possède une riche bibliothèque de modules qui peuvent être appelés dans les Playbooks. Ces modules permettent d’effectuer une variété de tâches sur les machines distantes, allant de la gestion des fichiers à la configuration des services réseau.

Idempotence et Convergence

L’un des principes fondamentaux d’Ansible est l’idempotence, la capacité d’exécuter plusieurs fois une même commande sans changer l’état final du système après la première exécution réussie. Cela garantit la convergence, où Ansible effectue les modifications nécessaires pour atteindre l’état désiré défini dans le Playbook.

Installation d’Ansible

La plupart des distributions Linux proposent une installation d’Ansible dans les packages officiels. Ceci dit, ces packages ne sont que très rarement à jour.

Ansible étant développé en Python, la solution la plus universelle consiste à l’installer via pip ou pipx. Ce dernier assurant que les dépendances d’Ansible soient isolées et ne puissent pas entrer en conflit avec d’autres applications sur le système.

Installation des pré-requis

Vous devez bien évidemment disposer d’un interpréteur Python récent, comme c’est le cas sur la majorité des distributions linux, je vais considérer ce point comme acquis.

Ensuite nous allons utiliser pipx pour installer Ansible, que nous pouvons installer lui-même à l’aide de pip. (Si vous n’avez pas pip installé sur votre machine, vous trouverez les différentes méthode possible ici: https://pip.pypa.io/en/stable/installation/)

Bash
pip install pipx

Si vous voyez le message ci-dessous s’afficher, cela signifie que le chemin d’accès à pipx n’est pas dans votre $PATH et qu’il est nécessaire de l’y ajouter:

WARNING: The script pipx is installed in '/home/steve/.local/bin' which is not on PATH.
  Consider adding this directory to PATH or, if you prefer to suppress this warning, use --no-warn-script-location.

Pour ça il vous suffit de rajouter PATH="$HOME/.local/bin:$PATH" à la fin de votre .bashrc.

Bash
echo 'PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc

Installation d’Ansible avec pipx

Bash
pipx install --include-deps ansible

Ce qui donnera…

steve@jsi-ubuntu ~ $ pipx install --include-deps ansible
  installed package ansible 9.1.0, installed using Python 3.10.12
  These apps are now globally available
    - ansible
    - ansible-community
    - ansible-config
    - ansible-connection
    - ansible-console
    - ansible-doc
    - ansible-galaxy
    - ansible-inventory
    - ansible-playbook
    - ansible-pull
    - ansible-test
    - ansible-vault
done! ✨ 🌟 ✨
steve@jsi-ubuntu ~ $

Tester l’installation d’Ansible

Pour tester l’installation d’Ansible, nous pouvons afficher sa version: ansible --version

steve@jsi-ubuntu ~ $ ansible --version
ansible [core 2.16.2]
  config file = None
  configured module search path = ['/home/steve/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/steve/.local/share/pipx/venvs/ansible/lib/python3.10/site-packages/ansible
  ansible collection location = /home/steve/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/steve/.local/bin/ansible
  python version = 3.10.12 (main, Nov 20 2023, 15:14:05) [GCC 11.4.0] (/home/steve/.local/share/pipx/venvs/ansible/bin/python)
  jinja version = 3.1.3
  libyaml = True
steve@jsi-ubuntu ~ $

Ou encore tester une première commande ad-hoc (je reviens sur ce principe plus tard): ansible -m ping localhost

steve@jsi-ubuntu ~ $ ansible -m ping localhost
[WARNING]: No inventory was parsed, only implicit localhost is available
localhost | SUCCESS => {
    "changed": false,
    "ping": "pong"
}
steve@jsi-ubuntu ~ $

L’alerte concernant l’absence d’inventaire est normale à ce point. Nous y reviendrons plus tard.

Créer la configuration d’Ansible

Le configuration d’Ansible peut être définie à plusieurs endroits de telle sorte qu’elle soit chargée automatiquement. Par ordre de priorité, Ansible chargera sa configuration depuis:

  1. La variable d’environnement ANSIBLE_CONFIG
  2. Le fichier ansible.cfg dans le répertoire courant
  3. Le fichier ~/.ansible.cfg (donc relatif à l’utilisateur)
  4. Le fichier /etc/ansible/ansible.cfg

Dés que l’une de ces options est disponible, la configuration est chargée et les suivant ne sont pas utilisés. Ceci permet par exemple d’avoir une configuration générale et une plus spécifique pour chaque utilisateur ou même chaque projet.

Dans le cadre de cet article, nous utiliserons le fichier ~/.ansible.cfg.

Pour commencer simplement, nous allons partir d’un fichier vierge et y ajouter quelques options, tout ce qui n’est pas défini utilisera les valeurs par défaut d’Ansible.

~/.ansible.cfg
[defaults]
# Définit où se trouve le fichier d'inventaire par défaut
inventory = ~/ansible/hosts.yaml

# Définit combient de jobs peuvent être lancés en simultanés
forks = 20

# Supprime les alertes de dépréciation
deprecation_warnings = False

# Defines how tasks are handled
strategy = linear

# Désactive la vérification des clés SSH des hôtes
host_key_checking = False

# Désactive les alertes de découvertes d'interpréteurs
interpreter_python = auto_silent

# Affiche les hôtes ignorés lors d'une exécution de tâche
display_skipped_hosts = no

Ce sont généralement les paramètres généraux que j’utilise toujours, le plus important étant bien sûr la localisation de l’inventaire. Le nombre de forks permettra d’exécuter des tâches sur plusieurs hôtes en parallèle. La stratégie linéaire assure que chaque tâche sera complètement terminée avant de passer à la suivante. Et le host_key_checking désactivé nous épargnera des soucis avec les clés d’hôtes SSH enregistré dans ~/.ssh/known_hosts surtout dans le cadre d’un laboratoire de test.

Création de l’inventaire d’Ansible

L’inventaire est la pierre angulaire d’Ansible. Il s’agit d’un fichier contenant la définition des hôtes et groupes d’hôtes avec lesquels Ansible pourra interagir. Cet inventaire peut être écrit soit selon un format INI soit selon un format YAML.

Lister les hôtes dans l’inventaire

Nous allons commencer ici par un inventaire au format INI le plus basique possible, mais pour ce faire voici un tableau récapitulatif des hôtes que je vais utiliser dans les exemples suivants (à vous d’adapter):

HôteAdresse IPv4UsernamePasswordSudo Password
jsi-ubuntu192.168.242.10stevejsi_passwordjsi_sudo_password
jsi-debian192.168.242.11stevejsi_passwordjsi_sudo_password
jsi-rocky8192.168.242.12stevejsi_passwordjsi_sudo_password
jsi-arch192.168.242.13stevejsi_passwordjsi_sudo_password

Comme dit dans l’introduction Ansible se repose sur SSH pour les hôtes Linux, il est donc nécessaire d’avoir un services SSH fonctionnel sur les hôtes qu’on souhaite gérer. Toujours dans le but de rester aussi simple que possible, l’authentification SSH se fera par mot de passe, ce qui n’est pas idéal en soi, mais fonctionnera très bien pour de premiers essais.

~/ansible/hosts.yaml
jsi-ubuntu
jsi-debian
jsi-rocky8
jsi-arch

Difficile de faire plus basique que ça. Nous avons ici un inventaire avec quatre hôtes définis.

Un hôte peut être défini par son adresse, par son nom de domaine (donc qui peut être résolu par DNS) ou un nom fictif, ce qui est le cas ici, donc nous devons fournir à Ansible les informations nécessaires pour établir la connexion SSH (adresse, utilisateur, mot de passe, …). Ces informations sont des variables qui peuvent être définies en de multiples endroits, y compris directement dans l’inventaire, mais il existe une méthode simple et efficace pour que chaque information soit à sa place: les host_vars et group_vars.

Définition des host_vars

Le concept est simple: a l’exécution d’une commande ou d’un playbook, Ansible chargera automatiquement certains fichiers de variables si ils sont au bon endroit et nommés en fonction des groupes te des noms d’hôtes. Les fichiers « host_vars » doivent résider dans un répertoire host_vars situé au même endroit que l’inventaire. Donc dans notre cas ~/ansible/host_vars/ et il devra contenir un fichier pour chaque hôte qui contiendra les variables propre à ce même hôte. Ce fichier peut être de type INI ou YAML, ici, nous utiliserons le format YAML (c’est plus lisible quand ça devient complexe).

Voici donc les quatre fichiers de variables d’hôtes:

~/ansible/host_vars/jsi-ubuntu.yaml
ansible_host: 192.168.242.10
ansible_user: steve
ansible_password: jsi_password
ansible_become: true
ansible_become_method: sudo
ansible_become_password: jsi_sudo_password
~/ansible/host_vars/jsi-debian.yaml
ansible_host: 192.168.242.11
ansible_user: steve
ansible_password: jsi_password
ansible_become: true
ansible_become_method: sudo
ansible_become_password: jsi_sudo_password
~/ansible/host_vars/jsi-rocky8.yaml
ansible_host: 192.168.242.12
ansible_user: steve
ansible_password: jsi_password
ansible_become: true
ansible_become_method: sudo
ansible_become_password: jsi_sudo_password
~/ansible/host_vars/jsi-arch.yaml
ansible_host: 192.168.242.13
ansible_user: steve
ansible_password: jsi_password
ansible_become: true
ansible_become_method: sudo
ansible_become_password: jsi_sudo_password

Et voici la description des variables utilisées:

VariableDescription
ansible_hostl’adresse ou le nom DNS de l’hôte
ansible_userl’utilisateur utilisé pour établir la connexion SSH
ansible_passwordle mot de passe utilisé pour la connexion SSH
ansible_becomel’escalation e privilège doit elle être utilisée (true/false)
ansible_become_methodla méthode d’escalation de privilège (par défaut: sudo)
ansible_become_passwordle mot de passe pour l’escalation de privilège

A partir de là nous devrions pouvoir gérer ces quatre hôtes à l’aide d’Ansible.

Exécution de commandes ad-hoc avec Ansible

Bien que l’automatisation de tâche soit le principal objectif d’Ansible, il est possible d’exécuter des commandes ad-hoc. C’est à dire d’exécuter des commandes Ansible à directement depuis la ligne de commande sans passer par un playbook.

Tester la connectivité Ansible

Nous l’avons déjà utilisé tout au début sans l’expliquer, Ansible dispose d’un module « ping » qui permet de tester la connectivité Ansible de l’hôte. Concrètement, ce module établi une connexion SSH vers l’hôte et teste la présence d’un interpréteur Python et retourne le résultat.

Bash
ansible -m ping <hôte|groupe>

Exemple:

steve@jsi-ubuntu ~ $ ansible -m ping jsi-debian
jsi-debian | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
steve@jsi-ubuntu ~ $

N’importe quel hôte ou groupe peut être ciblé, y compris le groupe implicite « all » qui comme son nom l’indique regroupe tous les hôtes de l’inventaire.

steve@jsi-ubuntu ~ $ ansible -m ping all
jsi-debian | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
jsi-rocky8 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
jsi-arch | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.11"
    },
    "changed": false,
    "ping": "pong"
}
jsi-ubuntu | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "ping": "pong"
}
steve@jsi-ubuntu ~ $

Récupérer les « ansible_facts »

Ansible ne se contente pas d’exécuter des commandes, mais génère l’exécution de commandes pour obtenir un résultat décrit dans un playbook. Ce qui veut dire que la ré-éxécution d’un même playbook n’entraîne pas forcément de changements si ce n’est pas nécessaire.

Pour ce faire Ansible a besoin de connaître l’état du système et dispose d’un module dont c’est le rôle: « gather_facts ». Nous pouvons aussi l’exécuter en mode ad-hoc. Le résultat sera l’ensemble des informations collectées par Ansible depuis l’hôte concerné.

Bash
ansible -m gather_facts <hôte|groupe>

Exemple quelque peut raccourci tellement il y a d’informations à afficher:

steve@jsi-ubuntu ~ $ ansible -m gather_facts jsi-ubuntu
jsi-ubuntu | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.122.175",
            "192.168.242.10"
        ],
        "ansible_all_ipv6_addresses": [
            "fe80::5054:ff:fe90:5e78",
            "fe80::5054:ff:fe13:6cc7"
        ],
        "ansible_apparmor": {
            "status": "enabled"
        },
    },
    
    .... plusieurs dizaines de lignes coupées ....
    
    "changed": false,
    "deprecations": [],
    "warnings": []
}
steve@jsi-ubuntu ~ $

Création et exécution d’un premier playbook Ansible

Les commandes ad-hoc c’est bien, mais la puissance d’Ansible réside dans la possibilité d’exécuter des chaînes de tâches par l’intermédiaire d’un playbook.

Un playbook est un document YAML qui contient au moins un « play » qui décrit une ou plusieurs tâches à effecter sur une série d’hôtes.

Pour l’exemple, nous allons ici créer un playbook qui contiendra un seul play qui lui même ne décrira qu’une seule tâche: installer le package « htop ».

Un premier playbook

~/ansible/playbooks/install_htop.yaml
---
- name: Installation de HTOP
  hosts:
    - jsi-ubuntu
    - jsi-debian
    - jsi-arch
    - jsi-rocky8
  
  tasks:
    - name: Utilisation du module package pour installer HTOP
      ansible.builtin.package:
        name: htop
        state: present

Comme vous pouvez le voir, un playbook est relativement lisible. Notez que les « — » du début de fichier sont obligatoires, c’est la syntaxe de base d’un document YAML.

Autre remarque importante, dans un fichier YAML l’indentation est extrêmement importante, et chaque niveau d’indentation doit être faire avec deux espaces (ou du moins toujours le même nombre d’espaces).

Le module « package » permet d’installer un package indépendamment du gestionnaire de package utilisé (apt, dnf ou encore pacman). Pour plus d’information sur ce module, jetez un oeil à la documentatio d’Ansible: https://docs.ansible.com/ansible/latest/collections/ansible/builtin/package_module.html

Exécution du playbook

Une fois le playbook rédigé, il ne nous reste plus qu’à l’exécuter:

Bash
ansible-playbook <chemin/vers/le/playbook>

Par exemple:

steve@jsi-ubuntu playbooks $ ansible-playbook ./install_htop.yaml 

PLAY [Installation de HTOP] ************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************
ok: [jsi-arch]
ok: [jsi-debian]
ok: [jsi-ubuntu]
ok: [jsi-rocky8]

TASK [Utilisation du module package pour installer HTOP] *******************************************************************************************************
ok: [jsi-ubuntu]
changed: [jsi-debian]
changed: [jsi-arch]
changed: [jsi-rocky8]

PLAY RECAP *****************************************************************************************************************************************************
jsi-arch                   : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
jsi-debian                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
jsi-rocky8                 : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
jsi-ubuntu                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

steve@jsi-ubuntu playbooks $

Vous noterez q’ici, sur ma VM ubuntu, la tâche n’a pas été exécutée (statut: OK), tout simplement parce que HTOP y était déjà installé. Les tâches exécutées sont indiquées par le statut « changed ». LEs autres statuts étant plus relatifs à des soucis d’exécution.

Comme dit précédemment, Ansible tient compte de l’état du système et n’exécute les commande que si c’est nécessaire. Si je réexécute le même playbook, je ne devrais plus avoir aucun changement:

steve@jsi-ubuntu playbooks $ ansible-playbook ./install_htop.yaml 

PLAY [Installation de HTOP] ************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************
ok: [jsi-arch]
ok: [jsi-debian]
ok: [jsi-ubuntu]
ok: [jsi-rocky8]

TASK [Utilisation du module package pour installer HTOP] *******************************************************************************************************
ok: [jsi-debian]
ok: [jsi-ubuntu]
ok: [jsi-rocky8]
ok: [jsi-arch]

PLAY RECAP *****************************************************************************************************************************************************
jsi-arch                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
jsi-debian                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
jsi-rocky8                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
jsi-ubuntu                 : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

steve@jsi-ubuntu playbooks $

Nous voyons bien ici que tous les statuts sont OK, ce qui signifie qu’aucun changement n’a dû être effectué.

Conclusion

Nous avons maintenant une base Ansible fonctionnelle permettant de gérer une série d’hôtes et un premier playbook gérant le déploient du package « htop » sur l’ensemble de ces hôtes par un simple exécution.

Dans les prochains articles nous verrons comment organiser notre inventaire, comment utiliser l’authentification par clé pour SSH ainsi que la création de playbooks plus complexes.

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.