Ansible: Fondations et logique de base

Notions abordées

  • Introduction à Ansible
  • Installation et premiers pas
  • Structure YAML
  • Premier playbook

Création des machines virtuelles

Pour ce premier cours, nous allons utiliser des machines virtuelles locales. Voici les liens pour télécharger les .ova:

Importez ces fichiers .ova dans VMWare Workstation pour créer les machines virtuelles.

Connexion aux machines virtuelles

Une fois les machines créées, démarrez-les. Faites la commande ip a dans chaque machine pour obtenir leur adresse IP et prenez-en note.

Sur votre machine hôte, ouvrez un terminal et connectez-vous à chaque machine virtuelle en utilisant SSH. Par exemple, pour vous connecter à la machine "control", utilisez la commande suivante :

ssh ansible@192.168.56.10 #Adresse IP de la machine "control"
# Tapez "yes" si demandé (première connexion)
# Mot de passe : Ansible123!
exit

Répétez cette étape pour les machines "node1" et "node2" pour s'assurer que vous pouvez vous y connecter via SSH.

Le mot de passe par défaut est Ansible123!

Si certaines de vos machines ont la même adresse IP, vous pouvez réappliquer les configurations réseau en effectuant la commande suivante sur chaque machine virtuelle:

sudo netplan apply

Installation d'Ansible

La machine ansible-controller a déjà le package ansible installé dessus. Pour en avoir la confirmation, exécutez la commande suivante:

ansible --version

Si ansible n'est pas installé, vous pouvez le faire avec les commandes suivantes:

sudo apt update
sudo apt install -y ansible sshpass
ansible --version

Vous devriez voir la version d'Ansible installée affichée dans le terminal.

Test de l'authentification SSH

Ansible utilise SSH pour se connecter aux machines gérées. Pour ce cours, nous allons utiliser l'authentification par mot de passe, qui est plus simple pour débuter.

Note importante : En production, on utilise plutôt des clés SSH (plus sécurisées). Nous verrons cette méthode dans les cours avancés.

Pour vérifier que vous pouvez vous connecter manuellement aux nœuds, testez la connexion SSH depuis la machine "control" (remplacez les adresses IP par celles de vos nœuds) :

# Test de connexion manuelle à node1
ssh ansible@192.168.56.11
# Tapez "yes" si demandé (première connexion)
# Mot de passe : Ansible123!
# Vous êtes maintenant sur node1!
exit
# Test de connexion manuelle à node2
ssh ansible@192.168.56.12
# Mot de passe : Ansible123!
exit

Ce que vous venez de faire : Vous avez testé que SSH fonctionne. Ansible va faire exactement la même chose automatiquement pour vous!

Vérification de la connectivité Ansible

Pour vérifier que Ansible peut se connecter aux nœuds gérés, créez un fichier d'inventaire simple. Depuis la machine "control", créez un fichier nommé hosts.ini:

cd ~
mkdir ansible-semaine-10
cd ansible-semaine-10
nano hosts.ini

Ajoutez le contenu suivant au fichier hosts.ini en remplaçant les adresses IP par les adresses de vos noeuds gérés:

[web]
node1 ansible_host=192.168.56.11

[db]
node2 ansible_host=192.168.56.12

[all:vars]
ansible_user=ansible
ansible_ssh_pass=Ansible123!
ansible_become_pass=Ansible123!

Explications:

  • [web] et [db] sont des groupes d'hôtes. Vous pouvez organiser vos nœuds gérés en groupes selon leur rôle.
  • La ligne node1 ansible_host=192.168.56.11 associe le nom d'hôte node1 à son adresse IP.
  • La section sous [all:vars] définit des variables pour l'utilisateur SSH et les mots de passe pour tous les nœuds.

Ensuite, testez la connectivité avec la commande Ansible suivante :

ansible all -i hosts.ini -m ping

Vous devriez voir une sortie indiquant que chaque nœud a répondu avec "pong", ce qui signifie qu'Ansible peut communiquer avec succès avec les nœuds gérés.

Que s'est-il passé ici?

  • La commande commence par ansible all, ce qui signifie que nous ciblons tous les hôtes définis dans l'inventaire.
  • L'option -i hosts.ini spécifie le fichier d'inventaire à utiliser.
  • Le module -m ping est utilisé pour envoyer une requête ping simple à chaque nœud.

Chaque nœud qui répond avec "pong" confirme que la connexion SSH fonctionne correctement et qu'Ansible peut gérer ces nœuds.

Cette première commande montre que nous pouvons exécuter une action simple sur tous les nœuds gérés. Dans les prochains cours, nous explorerons des actions plus complexes et la création de playbooks Ansible pour automatiser la gestion d'infrastructures.

L'image suivante illustre l'architecture de base que nous venons de mettre en place :

Architecture Ansible de base

Tiré de: https://www.devopsschool.com/blog/understanding-ansible-architecture-using-diagram/

Explications:

  • Le noeud (ou la machine) de gestion (Management Node) exécute Ansible et envoie des commandes aux nœuds gérés (Host 1, Host 2, etc.). Dans notre cas, c'est la machine "control".
  • Le noeud (ou la machine) géré (Managed Node) utilise un fichier Playbook (que nous verrons plus tard) et un fichier d'inventaire (hosts.ini).
  • Le fichier d'inventaire (Inventory File) contient la liste des nœuds gérés et leurs adresses IP, et les regroupe en groupes logiques (comme "web" et "db" dans notre cas, ou "group A" et "group B" dans l'image).
  • Ansible utilise SSH pour se connecter aux nœuds gérés et exécuter les commandes.

Fichiers de configuration Ansible

Ansible utilise un fichier de configuration principal nommé ansible.cfg. Par défaut, Ansible cherche ce fichier dans plusieurs emplacements, mais pour ce cours, nous allons créer un fichier de configuration spécifique dans notre répertoire de travail. Depuis la machine "control", créez un fichier nommé ansible.cfg dans le répertoire ansible-semaine-10 (à côté de hosts.ini) :

nano ansible.cfg

Ajoutez le contenu suivant au fichier ansible.cfg :

[defaults]
inventory = ./hosts.ini
host_key_checking = False

Explications:

  • La section [defaults] contient les paramètres par défaut pour Ansible.
  • La ligne inventory = ./hosts.ini indique à Ansible d'utiliser le fichier hosts.ini comme inventaire par défaut.
  • La ligne host_key_checking = False désactive la vérification des clés d'hôte SSH, ce qui est utile dans un environnement de laboratoire où les hôtes peuvent être recréés fréquemment.

Avec cette configuration, vous n'aurez plus besoin de spécifier le fichier d'inventaire à chaque commande Ansible, car Ansible utilisera automatiquement hosts.ini dans le répertoire courant.

Vous pouvez maintenant exécuter à nouveau la commande ping sans l'option -i :

ansible all -m ping

Cela devrait fonctionner de la même manière qu'avant, confirmant que la configuration est correcte.

Le langage YAML

Ansible utilise le langage YAML (YAML Ain't Markup Language) pour définir les playbooks et les fichiers de configuration. YAML est un format de sérialisation de données lisible par l'humain, souvent utilisé pour les fichiers de configuration.

Anecdote: Le nom YAML est un acronyme récursif signifiant "YAML Ain't Markup Language", soulignant que YAML est conçu pour être un format de données plutôt qu'un langage de balisage (markup).

Voici quelques concepts de base de YAML que vous devez connaître pour travailler avec Ansible :

  • Début et fin d'un document: Un document YAML commence par --- et peut se terminer par ... (optionnel).

  • Indentation: YAML utilise l'indentation pour définir la hiérarchie des données. Assurez-vous d'utiliser des espaces (et non des tabulations) pour l'indentation.

  • Listes: Les listes sont définies en utilisant des tirets (-). Par exemple :

    fruits:
      - pomme
      - banane
      - cerise
    
  • Dictionnaires: Les dictionnaires sont définis en utilisant des paires clé-valeur. Par exemple :

      personne:
        nom: Alex
        age: 30
        ville: Montreal
    
  • Listes de dictionnaires: Vous pouvez combiner les listes et les dictionnaires. Par exemple :

    personnes:
      - nom: Alex
        age: 30
      - nom: Marie
        age: 25
    

Ici, personnes est une liste contenant deux dictionnaires, chacun représentant une personne avec des attributs nom et age. Ainsi, le premier élément de la liste personnes est un dictionnaire avec les clés nom et age (soit Alex, 30), et le second élément est un autre dictionnaire avec les clés nom et age (soit Marie, 25).

  • Commentaires: Les commentaires commencent par le symbole # et s'étendent jusqu'à la fin de la ligne. Par exemple :
    # Ceci est un commentaire
    nom: Alex  # Nom de la personne
    

Facultatif mais recommandé: Éditeur de texte avec support YAML

Pour faciliter l'écriture et la lecture des fichiers YAML, il est recommandé d'utiliser un éditeur de texte qui prend en charge la coloration syntaxique et la validation YAML.

Sur Visual Studio Code, vous pouvez installer l'extension "YAML" de Red Hat, qui offre une excellente prise en charge de YAML, y compris la validation et l'autocomplétion. Pour l'installer :

  1. Ouvrez Visual Studio Code.
  2. Allez dans l'onglet des extensions (icône de carré avec quatre carrés) ou utilisez le raccourci Ctrl+Shift+X.
  3. Recherchez "YAML" et installez l'extension développée par Red Hat.
  4. Redémarrez Visual Studio Code si nécessaire. Cela vous aidera à éviter les erreurs courantes liées à la syntaxe YAML lors de la création de vos playbooks Ansible.

Un premier playbook Ansible

Un playbook Ansible est un fichier YAML qui décrit une série de tâches à exécuter sur un ou plusieurs hôtes. Voici un exemple simple de playbook qui pinge tous les nœuds gérés :

---  # début du fichier YAML
  - name: Mon premier playbook  # description de ce qu'on fait
    hosts: all  # sur quelles machines?
    tasks:  # liste des actions
      - name: Première tâche  # toujours nommer!
        ping:  # module à utiliser

Explications:

  • La ligne --- indique le début d'un document YAML.
  • La clé name fournit une description lisible du playbook.
  • La clé hosts spécifie les hôtes cibles pour le playbook (ici, tous les hôtes).
  • La clé tasks contient une liste de tâches à exécuter.
  • Chaque tâche a également une clé name pour la décrire et spécifie le module Ansible à utiliser (ici, le module ping).

Vous remarquerez l'importance de l'indentation en YAML. Chaque niveau d'indentation représente une hiérarchie dans les données.

Pour exécuter ce playbook, enregistrez-le dans un fichier nommé mon_premier_playbook.yml dans le répertoire ansible-semaine-10, puis exécutez la commande suivante depuis la machine "control" :

ansible-playbook mon_premier_playbook.yml

Vous devriez voir une sortie indiquant que chaque nœud a répondu avec "pong", confirmant que le playbook a été exécuté avec succès:

PLAY [Mon premier playbook] ********************************************************

TASK [Gathering Facts] *************************************************************
ok: [node2]
ok: [node1]

TASK [Première tâche] **************************************************************
ok: [node2]
ok: [node1]

PLAY RECAP *************************************************************************
node1                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
node2                      : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0  

Analyse de la sortie:

  • La section PLAY [Mon premier playbook] indique le début de l'exécution du playbook.
  • La tâche Gathering Facts est une tâche automatique qu'Ansible exécute pour collecter des informations sur les nœuds gérés.
  • La tâche Première tâche correspond à notre tâche définie dans le playbook.
  • La section PLAY RECAP résume les résultats de l'exécution pour chaque nœud, indiquant le nombre de tâches réussies (ok), modifiées (changed), inaccessibles (unreachable), échouées (failed), etc.

Ici, nous voyons que les deux nœuds (node1 et node2) ont répondu avec succès à la tâche de ping, ce qui signifie qu'Ansible a pu se connecter et exécuter la tâche sur ces nœuds sans problème. À noter que nous ne voyons pas la sortie du module ping lui-même (le "pong") dans cette sortie, mais le fait que la tâche soit marquée comme ok indique que le ping a réussi.

Vous remarquerez que le nombre de tâches ok est de 2 pour chaque nœud (une pour Gathering Facts et une pour Première tâche), et le nombre de tâches changed est de 0, ce qui signifie qu'aucune modification n'a été apportée aux nœuds, ce qui est attendu pour une tâche de ping.

Explorons d'autres playbooks simples pour bien comprendre la structure et les fonctionnalités d'Ansible.

Affichage de noms d'hôtes

Voici un playbook qui affiche le nom d'hôte de chaque nœud géré en utilisant le module command pour exécuter la commande hostname :

---  # début du fichier YAML
  - name: Afficher les noms d'hôtes des nœuds gérés
    hosts: all  # cibler tous les hôtes
    tasks:
      - name: Afficher le nom d'hôte
        command: hostname  # exécuter la commande 'hostname'

Ici, à la différence du playbook précédent, nous utilisons le module command pour exécuter une commande shell sur les nœuds gérés. Nous fournissons à ce module la commande hostname, qui renvoie le nom d'hôte de la machine. Lors de l'exemple précédent, le module ping était un module Ansible spécifique conçu pour tester la connectivité, tandis que le module command permet d'exécuter n'importe quelle commande shell.

Enregistrez ce playbook dans un fichier nommé afficher_hostname.yml et exécutez-le avec la commande suivante :

ansible-playbook afficher_hostname.yml

Vous devriez voir la sortie affichant le nom d'hôte de chaque nœud géré:

PLAY [Afficher les noms d'hôtes des nœuds gérés] ***********************************

TASK [Gathering Facts] *************************************************************
ok: [node1]
ok: [node2]

TASK [Afficher le nom d'hôte] ******************************************************
changed: [node1]
changed: [node2]

PLAY RECAP *************************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
node2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

Analyse de la sortie:

  • Le nombre de tâches ok est de 2 pour chaque nœud (une pour Gathering Facts et une pour Afficher le nom d'hôte).
  • Le nombre de tâches changed est de 1 pour chaque nœud, ce qui indique que la tâche Afficher le nom d'hôte a été exécutée avec succès. Pourquoi changed? Parce que le module command considère qu'une exécution de commande modifie l'état du système, même si la commande elle-même ne change rien. Contrairement au module ping, qui est idempotent et ne modifie pas l'état du système, le module command exécute la commande à chaque fois, ce qui est interprété comme un changement.
  • La sortie de la commande hostname n'est pas affichée directement dans la sortie standard d'Ansible. Pour voir la sortie réelle de la commande, vous pouvez utiliser le module debug pour afficher la sortie.

Modifiez le playbook pour capturer et afficher la sortie de la commande hostname :

---  # début du fichier YAML
  - name: Afficher les noms d'hôtes des nœuds gérés
    hosts: all  # cibler tous les hôtes
    tasks:
      - name: Exécuter la commande hostname
        command: hostname
        register: hostname_output  # enregistrer la sortie dans une variable

      - name: Afficher le nom d'hôte
        debug:
          msg: "Le nom d'hôte est {{ hostname_output.stdout }}"  # afficher la sortie

Changements apportés:

  • La tâche Exécuter la commande hostname utilise le module register pour capturer la sortie de la commande hostname dans une variable nommée hostname_output.
  • La tâche Afficher le nom d'hôte utilise le module debug pour afficher un message contenant la sortie capturée, en accédant à hostname_output.stdout, qui contient la sortie standard de la commande exécutée.

Enregistrez ce playbook modifié dans un fichier nommé afficher_hostname_debug.yml et exécutez-le avec la commande suivante :

ansible-playbook afficher_hostname_debug.yml

Vous devriez voir une sortie affichant le nom d'hôte de chaque nœud. Nous avons maintenant 3 tâches: Gathering Facts, Exécuter la commande hostname, et Afficher le nom d'hôte. Nous avons cependant encore qu'un seul "play" (le playbook entier). Nous verrons plus tard comment structurer des playbooks avec plusieurs plays.

Création d'un répertoire

Voici un playbook qui crée un répertoire nommé /tmp/ansible_test sur chaque nœud géré en utilisant le module file :

---  # début du fichier YAML
  - name: Créer un répertoire sur les nœuds gérés
    hosts: all  # cibler tous les hôtes
    tasks:
      - name: Créer le répertoire /tmp/ansible_test
        file:
          path: /tmp/ansible_test
          state: directory
          mode: '0755'

Explications:

  • Le module file est utilisé pour gérer les fichiers et les répertoires.
  • La clé path spécifie le chemin du répertoire à créer.
  • La clé state: directory indique que nous voulons créer un répertoire.
  • La clé mode définit les permissions du répertoire en notation octale. 0755 signifie que le propriétaire a tous les droits (lecture, écriture, exécution), tandis que les autres utilisateurs ont des droits de lecture et d'exécution (allez revoir vos notes du cours 2C2 si besoin!). Enregistrez ce playbook dans un fichier nommé creer_repertoire.yml et exécutez-le avec la commande suivante :
ansible-playbook creer_repertoire.yml

Analyse de la sortie:

PLAY [Créer un répertoire sur les nœuds gérés] *************************************

TASK [Gathering Facts] *************************************************************
ok: [node1]
ok: [node2]

TASK [Créer le répertoire /tmp/ansible_test] ***************************************
changed: [node2]
changed: [node1]

PLAY RECAP *************************************************************************
node1                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
node2                      : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
  • Le nombre de tâches ok est de 2 pour chaque nœud (une pour Gathering Facts et une pour Créer le répertoire /tmp/ansible_test).
  • Le nombre de tâches changed est de 1 pour chaque nœud, ce qui indique que le répertoire /tmp/ansible_test a été créé avec succès sur chaque nœud géré.

Pour vérifier que le répertoire a bien été créé, vous pouvez vous connecter à chaque nœud géré et lister le contenu du répertoire /tmp :

ssh vagrant@192.168.56.11
ls -ld /tmp/ansible_test
exit
ssh vagrant@192.168.56.12
ls -ld /tmp/ansible_test
exit

Installation d'un package

Voici un playbook qui installe le package curl sur chaque nœud géré en utilisant le module apt (puisque nos nœuds sont basés sur Ubuntu) :

---  # début du fichier YAML
  - name: Installer curl sur les nœuds gérés
    hosts: all  # cibler tous les hôtes
    tasks:
      - name: Installer le package curl
        apt:
          name: curl
          state: present
          update_cache: yes

Explications:

  • Le module apt est utilisé pour gérer les packages sur les systèmes basés sur Debian/Ubuntu.
  • La clé name spécifie le nom du package à installer.
  • La clé state: present indique que nous voulons que le package soit installé.
  • La clé update_cache: yes met à jour le cache des packages avant l'installation. Enregistrez ce playbook dans un fichier nommé installer_curl.yml et exécutez-le avec la commande suivante :
ansible-playbook installer_curl.yml

Cette commande devrait échouer! Vous devrez voir un message d'erreur :

fatal: [node1]: FAILED! => {"changed": false, "msg": "Failed to lock apt for exclusive operation"}
fatal: [node2]: FAILED! => {"changed": false, "msg": "Failed to lock apt for exclusive operation"}

Cela se produit parce que l'installation de packages nécessite des privilèges élevés (sudo). Pour résoudre ce problème, nous devons exécuter la tâche avec des privilèges élevés en ajoutant la directive become: yes au playbook. Ajoutons cette directive dans le playbook.

Enregistrez le playbook modifié dans un fichier nommé installer_curl_sudo.yml :

  - name: Installer curl sur les nœuds gérés avec sudo
    hosts: all  # cibler tous les hôtes
    become: yes  # exécuter les tâches avec des privilèges élevés
    tasks:
      - name: Installer le package curl
        apt:
          name: curl
          state: present
          update_cache: yes

Cela indique à Ansible d'exécuter les tâches avec des privilèges élevés (sudo). Maintenant, exécutez le playbook modifié avec la commande suivante :

ansible-playbook installer_curl_sudo.yml

Vous devriez voir une sortie indiquant que le package curl a été installé avec succès sur chaque nœud géré. Vous remarquerez que le nombre de tâches changed est à 0. Cela signifie que curl était déjà installé sur les nœuds gérés, donc aucune modification n'a été nécessaire.

Nous pouvons essayer de modifier notre playbook pour installer un package qui n'est pas encore installé, comme cowsay. Voici le playbook modifié :

---  # début du fichier YAML
  - name: Installer htop sur les nœuds gérés avec sudo
    hosts: all  # cibler tous les hôtes
    become: yes  # exécuter les tâches avec des privilèges élevés
    tasks:
      - name: Installer le package cowsay
        apt:
          name: cowsay
          state: present
          update_cache: yes

Enregistrez ce playbook dans un fichier nommé installer_cowsay.yml et exécutez-le avec la commande suivante :

ansible-playbook installer_cowsay.yml

Vous devriez voir une sortie indiquant que le package cowsay a été installé avec succès sur chaque nœud géré, et cette fois, le nombre de tâches changed sera de 1, indiquant qu'une modification a été apportée (l'installation de cowsay).

Playbooks plus complexes

Ansible propose de nombreux modules pour effectuer diverses tâches, telles que l'installation de packages, la gestion des services, la copie de fichiers, et bien plus encore. Voici un exemple de playbook plus complexe qui installe et démarre un serveur web Apache sur le nœud node1 :

---  # début du fichier YAML
  - name: Installer et démarrer Apache sur le serveur web
    hosts: web  # cibler le groupe 'web' défini dans l'inventaire
    become: yes  # exécuter les tâches avec des privilèges élevés
    tasks:
      - name: Installer Apache
        apt:
          name: apache2
          state: present
          update_cache: yes
      - name: Démarrer Apache
        service:
          name: apache2
          state: started
          enabled: yes

Explications:

  • La clé hosts: web cible uniquement le groupe web défini dans le fichier d'inventaire, qui contient node1.
  • La clé become: yes indique qu'Ansible doit exécuter les tâches avec des privilèges élevés (sudo).
  • Le module apt est utilisé pour gérer les packages sur les systèmes basés sur Debian/Ubuntu.
  • Le module service est utilisé pour gérer les services système.

Vous pouvez enregistrer ce playbook dans un fichier nommé install_apache.yml et l'exécuter de la même manière que précédemment :

ansible-playbook install_apache.yml

Cela installera et démarrera Apache sur le nœud node1. Vous pouvez vérifier que le serveur web fonctionne en accédant à l'adresse IP de node1 avec la commande curl depuis la machine "control" :

curl http://192.168.56.11/index.html

Vous devriez voir la page par défaut d'Apache s'afficher dans le terminal... même si elle est difficile à lire dans ce format!

Pour une meilleure expérience, vous pouvez utiliser le navigateur web de votre machine hôte et accéder à l'adresse http://192.168.56.11/index.html.

Le concept de idempotence

L'idempotence est un concept clé dans Ansible et l'automatisation en général. Cela signifie que l'exécution répétée d'une opération produit le même résultat que son exécution une seule fois. En d'autres termes, si vous exécutez un playbook plusieurs fois, le système restera dans le même état après la première exécution. Par exemple, si vous exécutez le playbook install_apache.yml plusieurs fois, Apache sera installé et démarré la première fois, et les exécutions suivantes ne feront rien car le système est déjà dans l'état désiré (Apache est installé et en cours d'exécution). Cela permet d'éviter les modifications inutiles et garantit que les systèmes restent cohérents. L'idempotence est essentielle pour la gestion de la configuration, car elle permet aux administrateurs système de s'assurer que les systèmes sont toujours dans l'état souhaité, sans craindre que des exécutions répétées de scripts ou de playbooks n'introduisent des erreurs ou des incohérences.