Structurer des Playbooks Ansible Complexes sur RHEL

AnsibleBeginner
Pratiquer maintenant

Introduction

Dans ce laboratoire, vous apprendrez des techniques essentielles pour structurer des playbooks Ansible complexes sur RHEL afin de créer une automatisation plus gérable, évolutive et réutilisable. Vous progresserez des concepts fondamentaux aux stratégies d'organisation avancées, en vous concentrant sur la manière de contrôler précisément sur quels hôtes votre automatisation s'exécute et comment décomposer de grands playbooks en composants logiques et modulaires.

Vous commencerez par maîtriser la sélection des hôtes, en utilisant des noms de groupes de base, des jokers (wildcards), des exclusions et des opérateurs logiques pour cibler des nœuds spécifiques dans votre inventaire. Ensuite, vous explorerez la modularisation en refactorisant des tâches dans des fichiers séparés à l'aide de include_tasks et import_tasks. Enfin, vous apprendrez à composer un flux de travail complet et multi-playbook avec import_playbook, pour aboutir à l'exécution et à la vérification de votre projet Ansible entièrement structuré et modularisé.

Sélectionner des Hôtes avec des Motifs de Base et des Jokers (Wildcards)

Dans cette étape, vous apprendrez les bases de la sélection d'hôtes spécifiques dans votre automatisation Ansible. Le cœur de cette opération réside dans le fichier d'inventaire Ansible, qui liste les serveurs que vous gérez, et dans la directive hosts d'un playbook, qui spécifie sur quels serveurs un ensemble de tâches doit s'exécuter. Nous commencerons par installer Ansible, créer un inventaire et un playbook de base, puis nous explorerons comment sélectionner des hôtes en utilisant des noms de groupes et des motifs de jokers (wildcards).

Tout d'abord, assurons-nous qu'Ansible est installé dans votre environnement. Ansible n'est pas installé par défaut, vous devez donc l'installer en utilisant le gestionnaire de paquets DNF. Le paquet ansible-core fournit les outils essentiels en ligne de commande d'Ansible, y compris ansible-playbook. Exécutez la commande suivante :

sudo dnf install -y ansible-core

Vous devriez voir une sortie similaire à celle-ci :

...
Installed:
  ansible-core-2.16.x-x.el9.x86_64
  ...
Complete!

Ensuite, créons un répertoire dédié pour cet exercice afin de garder nos fichiers organisés. Toutes les actions ultérieures dans cette étape se dérouleront dans ce nouveau répertoire :

mkdir -p ~/project/ansible_patterns
cd ~/project/ansible_patterns

Maintenant, créez un fichier d'inventaire. Un inventaire est un fichier texte qui définit les hôtes et les groupes d'hôtes sur lesquels les commandes, modules et tâches Ansible opèrent. Nous utiliserons le format INI pour sa simplicité.

Utilisez l'éditeur nano pour créer un fichier nommé inventory :

nano inventory

Ajoutez le contenu suivant au fichier inventory. Cela définit deux groupes, webservers et dbservers, chacun contenant deux hôtes :

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.lab.net
db2.lab.net

Enregistrez le fichier et quittez nano en appuyant sur Ctrl+X, puis Y, et Entrée.

Avec notre inventaire prêt, créons un playbook simple. Ce playbook utilisera le module ansible.builtin.debug pour afficher un message, confirmant sur quel hôte la tâche s'exécute. C'est un excellent moyen de tester les motifs d'hôtes sans apporter de modifications réelles au système.

Créez un nouveau fichier nommé playbook.yml :

nano playbook.yml

Ajoutez le contenu YAML suivant. Initialement, il cible tous les hôtes du groupe webservers :

---
- name: Test Host Patterns
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Enregistrez et quittez l'éditeur. Maintenant, exécutez le playbook en utilisant ansible-playbook. Le drapeau -i spécifie notre fichier d'inventaire personnalisé :

ansible-playbook playbook.yml -i inventory

La sortie devrait ressembler à ceci :

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Comme vous pouvez le constater, seuls les hôtes du groupe webservers ont été ciblés. Maintenant, modifions le playbook pour utiliser un joker (*). Les jokers permettent une correspondance de motifs plus flexible.

Modifiez playbook.yml et changez la ligne hosts en hosts: "*.lab.net". N'oubliez pas d'encadrer les motifs contenant des jokers entre guillemets :

---
- name: Test Host Patterns
  hosts: "*.lab.net"
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Exécutez à nouveau le playbook :

ansible-playbook playbook.yml -i inventory

Vous devriez voir une sortie similaire à celle-ci :

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [db1.lab.net] => {
    "msg": "This task is running on db1.lab.net"
}
ok: [db2.lab.net] => {
    "msg": "This task is running on db2.lab.net"
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Cette fois, le jeu s'est exécuté uniquement sur les hôtes dont le nom se termine par .lab.net. Enfin, utilisons le mot-clé spécial all pour cibler tous les hôtes définis dans l'inventaire.

Modifiez playbook.yml une dernière fois et changez la ligne hosts en hosts: all :

---
- name: Test Host Patterns
  hosts: all
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Exécutez le playbook pour voir le résultat :

ansible-playbook playbook.yml -i inventory

La sortie montrera que tous les hôtes sont ciblés :

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}
ok: [db1.lab.net] => {
    "msg": "This task is running on db1.lab.net"
}
ok: [db2.lab.net] => {
    "msg": "This task is running on db2.lab.net"
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Le playbook s'exécute maintenant sur les quatre hôtes de votre inventaire, démontrant la puissance du motif all.

Affiner la Sélection d'Hôtes avec des Exclusions et des Opérateurs Logiques

Dans cette étape, vous perfectionnerez vos compétences en sélection d'hôtes en apprenant à utiliser les exclusions et les opérateurs logiques. Ces fonctionnalités permettent un ciblage très précis, ce qui est essentiel lors de la gestion d'environnements complexes. Vous apprendrez à exclure des hôtes en utilisant l'opérateur ! (NOT) et à combiner des groupes en utilisant l'opérateur & (AND). Nous continuerons à travailler avec les fichiers inventory et playbook.yml de l'étape précédente.

Tout d'abord, assurez-vous d'être dans le bon répertoire de travail :

cd ~/project/ansible_patterns

Pour démontrer efficacement les opérateurs logiques, nous devons créer un certain chevauchement dans nos groupes d'hôtes. Modifions le fichier inventory pour ajouter un nouveau groupe appelé production qui contient un serveur web et un serveur de base de données :

nano inventory

Ajoutez le groupe [production] et ses membres à la fin du fichier :

[webservers]
web1.example.com
web2.example.com

[dbservers]
db1.lab.net
db2.lab.net

[production]
web1.example.com
db1.lab.net

Enregistrez le fichier et quittez nano en appuyant sur Ctrl+X, puis Y, et Entrée.

Maintenant, pratiquons l'exclusion. L'opérateur ! (qui signifie NOT) vous permet d'exclure un hôte ou un groupe d'une sélection. Modifiez votre playbook.yml pour cibler tous les hôtes sauf ceux du groupe dbservers :

nano playbook.yml

Mettez à jour la ligne hosts comme indiqué ci-dessous. Le motif all,!dbservers sélectionne tous les hôtes puis supprime ceux qui appartiennent au groupe dbservers :

---
- name: Test Host Patterns
  hosts: all,!dbservers
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Enregistrez et quittez l'éditeur, puis exécutez le playbook :

ansible-playbook playbook.yml -i inventory

Vous ne devriez voir que les serveurs web ciblés :

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "This task is running on web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Comme prévu, seuls les hôtes du groupe webservers ont été ciblés.

Ensuite, explorons l'opérateur logique ET. L'opérateur & sélectionne uniquement les hôtes qui existent dans les deux groupes spécifiés (une intersection). Modifions le playbook pour cibler les hôtes qui sont dans le groupe webservers ET également dans le groupe production :

nano playbook.yml

Changez la ligne hosts en webservers,&production :

---
- name: Test Host Patterns
  hosts: webservers,&production
  gather_facts: false
  tasks:
    - name: Display the inventory hostname
      ansible.builtin.debug:
        msg: "This task is running on {{ inventory_hostname }}"

Enregistrez et exécutez le playbook :

ansible-playbook playbook.yml -i inventory

Cette fois, seule l'intersection des deux groupes sera ciblée :

PLAY [Test Host Patterns] ******************************************************

TASK [Display the inventory hostname] ******************************************
ok: [web1.example.com] => {
    "msg": "This task is running on web1.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

La sortie montre correctement que seul web1.example.com a été ciblé, car c'est le seul hôte qui est membre à la fois des groupes webservers et production. Ces opérateurs vous donnent un contrôle précis sur les hôtes affectés par votre automatisation.

Modulariser un Play avec include_tasks et import_tasks

Dans cette étape, vous apprendrez à structurer des projets Ansible plus importants en les décomposant en fichiers plus petits et réutilisables. À mesure que les playbooks s'étoffent, il devient difficile de gérer toutes les tâches dans un seul fichier. Ansible propose deux directives principales pour cela : import_tasks et include_tasks. Les deux permettent d'intégrer des tâches provenant d'un autre fichier.

  • import_tasks est statique. Il est traité lors de l'analyse initiale du playbook par Ansible. C'est idéal pour les parties structurelles et inconditionnelles de votre play.
  • include_tasks est dynamique. Il est traité pendant l'exécution du play. Cela le rend adapté à une utilisation avec des boucles et des conditions.

Nous allons maintenant refactoriser notre playbook pour utiliser les deux. Tout d'abord, assurez-vous d'être dans le répertoire du projet :

cd ~/project/ansible_patterns

Avant de continuer, mettons à jour le fichier d'inventaire pour que les hôtes pointent vers localhost pour cet environnement de laboratoire. Cela permettra au playbook de s'exécuter avec succès :

nano inventory

Remplacez le contenu par la configuration suivante qui mappe les hôtes d'exemple à localhost :

[webservers]
web1.example.com ansible_host=localhost ansible_connection=local
web2.example.com ansible_host=localhost ansible_connection=local

[dbservers]
db1.lab.net ansible_host=localhost ansible_connection=local
db2.lab.net ansible_host=localhost ansible_connection=local

Enregistrez et quittez l'éditeur. Cette configuration utilise ansible_host=localhost pour rediriger les connexions vers la machine locale et ansible_connection=local pour éviter les tentatives de connexion SSH.

Une pratique courante consiste à stocker les fichiers de tâches réutilisables dans un sous-répertoire dédié. Créons-en un nommé tasks :

mkdir tasks

Maintenant, créons un fichier pour les tâches de configuration communes qui pourraient s'appliquer à de nombreux serveurs. Nous y placerons une tâche pour installer le paquet du serveur web httpd :

nano tasks/web_setup.yml

Ajoutez le contenu suivant. Notez que ce fichier n'est qu'une liste de tâches ; il ne contient pas de structure de play complète (comme hosts: ou name:):

- name: Install the httpd package
  ansible.builtin.dnf:
    name: httpd
    state: present
  become: true

Enregistrez et quittez nano. Ensuite, créez un deuxième fichier de tâches pour une simple étape de vérification :

nano tasks/verify_config.yml

Ajoutez cette tâche de débogage à ce fichier :

- name: Display a verification message
  ansible.builtin.debug:
    msg: "Configuration tasks applied to {{ inventory_hostname }}"

Enregistrez et quittez l'éditeur. Maintenant, modifions le playbook.yml principal pour utiliser ces nouveaux fichiers de tâches. Nous utiliserons import_tasks pour la configuration statique et include_tasks pour le message de vérification dynamique :

nano playbook.yml

Remplacez tout le contenu de playbook.yml par le suivant. Ce playbook cible maintenant le groupe webservers et utilise les fichiers de tâches modulaires :

---
- name: Configure Web Servers
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Import web server setup tasks
      import_tasks: tasks/web_setup.yml

    - name: Include verification tasks
      include_tasks: tasks/verify_config.yml

Enregistrez le fichier et exécutez le playbook :

ansible-playbook playbook.yml -i inventory

Vous devriez voir les tâches modulaires en cours d'exécution :

PLAY [Configure Web Servers] ***************************************************

TASK [Import web server setup tasks] *******************************************
imported: /home/labex/project/ansible_patterns/tasks/web_setup.yml

TASK [Install the httpd package] ***********************************************
changed: [web1.example.com]
changed: [web2.example.com]

TASK [Include verification tasks] **********************************************
included: /home/labex/project/ansible_patterns/tasks/verify_config.yml for web1.example.com, web2.example.com

TASK [Display a verification message] ******************************************
ok: [web1.example.com] => {
    "msg": "Configuration tasks applied to web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "Configuration tasks applied to web2.example.com"
}

PLAY RECAP *********************************************************************
web1.example.com           : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Observez comment la sortie indique clairement quand les tâches sont importées et incluses à partir de leurs fichiers respectifs. Cette approche modulaire rend votre automatisation plus propre et plus facile à maintenir.

Composer un Workflow avec import_playbook

Dans cette étape, vous apprendrez à orchestrer des playbooks entiers pour former un flux de travail complexe en utilisant import_playbook. Alors que import_tasks et include_tasks servent à réutiliser des listes de tâches au sein d'un même play, import_playbook opère à un niveau supérieur. Il vous permet de créer un playbook maître qui exécute d'autres playbooks autonomes dans un ordre spécifique. C'est la méthode standard pour gérer l'automatisation à grande échelle, telle que le provisionnement d'une pile d'applications complète.

Tout d'abord, assurons-nous d'être dans le bon répertoire et organisons notre projet pour cette nouvelle structure :

cd ~/project/ansible_patterns

Il est recommandé de stocker les playbooks individuels et composants dans un sous-répertoire dédié. Créons un répertoire nommé playbooks :

mkdir playbooks

Maintenant, déplaçons le playbook que nous avons créé à l'étape précédente, qui configure les serveurs web, dans ce nouveau répertoire. Le renommer pour qu'il soit plus descriptif est également une bonne idée :

mv playbook.yml playbooks/web_configure.yml

Cependant, puisque nous avons déplacé le playbook dans un sous-répertoire, nous devons mettre à jour les chemins relatifs vers les fichiers de tâches. Les fichiers de tâches sont toujours dans le répertoire tasks/ par rapport au répertoire principal du projet, nous devons donc ajuster les chemins :

nano playbooks/web_configure.yml

Mettez à jour les chemins dans le playbook pour utiliser ../tasks/ au lieu de tasks/ :

---
- name: Configure Web Servers
  hosts: webservers
  gather_facts: false
  tasks:
    - name: Import web server setup tasks
      import_tasks: ../tasks/web_setup.yml

    - name: Include verification tasks
      include_tasks: ../tasks/verify_config.yml

Enregistrez et quittez l'éditeur.

Testons le playbook corrigé pour nous assurer que les chemins fonctionnent correctement :

ansible-playbook playbooks/web_configure.yml -i inventory

Vous devriez voir le playbook s'exécuter avec succès avec les chemins corrigés.

Ensuite, créez un nouveau playbook distinct pour configurer vos serveurs de base de données. Ce playbook ciblera le groupe dbservers et installera le paquet mariadb :

nano playbooks/db_setup.yml

Ajoutez le contenu suivant au fichier. Il s'agit d'un play complet et autonome :

---
- name: Configure Database Servers
  hosts: dbservers
  gather_facts: false
  tasks:
    - name: Install mariadb package
      ansible.builtin.dnf:
        name: mariadb
        state: present
      become: true

    - name: Display a confirmation message
      ansible.builtin.debug:
        msg: "Database server {{ inventory_hostname }} configured."

Enregistrez et quittez l'éditeur. Vous avez maintenant deux playbooks composants : un pour les serveurs web et un pour les serveurs de base de données.

Enfin, créez un playbook "principal" de niveau supérieur. Ce fichier ne contiendra aucune tâche ni aucun hôte en soi. Sa seule fonction est d'importer les autres playbooks dans le bon ordre pour définir le flux de travail global :

nano main.yml

Ajoutez le contenu suivant. Cela crée un flux de travail qui configure d'abord les serveurs web, puis configure les serveurs de base de données :

---
- name: Import the web server configuration play
  import_playbook: playbooks/web_configure.yml

- name: Import the database server configuration play
  import_playbook: playbooks/db_setup.yml

Enregistrez et quittez nano. Vous êtes maintenant prêt à exécuter votre flux de travail complet en lançant le playbook main.yml :

ansible-playbook main.yml -i inventory

La sortie montrera les deux playbooks exécutés séquentiellement :

PLAY [Configure Web Servers] ***************************************************

TASK [Import web server setup tasks] *******************************************
imported: /home/labex/project/ansible_patterns/playbooks/../tasks/web_setup.yml

TASK [Install the httpd package] ***********************************************
ok: [web1.example.com]
ok: [web2.example.com]

TASK [Include verification tasks] **********************************************
included: /home/labex/project/ansible_patterns/playbooks/../tasks/verify_config.yml for web1.example.com, web2.example.com

TASK [Display a verification message] ******************************************
ok: [web1.example.com] => {
    "msg": "Configuration tasks applied to web1.example.com"
}
ok: [web2.example.com] => {
    "msg": "Configuration tasks applied to web2.example.com"
}

PLAY [Configure Database Servers] **********************************************

TASK [Install mariadb package] *************************************************
changed: [db1.lab.net]
changed: [db2.lab.net]

TASK [Display a confirmation message] ******************************************
ok: [db1.lab.net] => {
    "msg": "Database server db1.lab.net configured."
}
ok: [db2.lab.net] => {
    "msg": "Database server db2.lab.net configured."
}

PLAY RECAP *********************************************************************
db1.lab.net                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
db2.lab.net                : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web1.example.com           : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
web2.example.com           : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

La sortie montre clairement deux plays distincts exécutés séquentiellement, démontrant comment import_playbook compose efficacement un flux de travail plus large à partir de parties plus petites et gérables.

Exécuter et Vérifier le Playbook Modulaire Complet

Dans cette dernière étape, vous exécuterez le flux de travail modulaire complet que vous avez construit et, plus important encore, vous apprendrez à vérifier que l'automatisation a atteint l'état souhaité sur les systèmes cibles. Une exécution réussie du playbook est une bonne chose, mais confirmer le résultat est essentiel pour une automatisation fiable.

Tout d'abord, assurez-vous d'être dans le répertoire principal du projet :

cd ~/project/ansible_patterns

Avant d'exécuter le playbook final, visualisons la structure complète du projet que vous avez créée. La commande tree est excellente pour cela. Si elle n'est pas installée, vous pouvez l'ajouter avec dnf :

sudo dnf install -y tree
tree .

Vous devriez voir une structure comme celle-ci :

.
├── inventory
├── main.yml
├── playbooks
│   ├── db_setup.yml
│   └── web_configure.yml
└── tasks
    ├── verify_config.yml
    └── web_setup.yml

2 directories, 6 files

Cette structure, avec un point d'entrée principal (main.yml), des fichiers de playbook séparés et des fichiers de tâches réutilisables, est une manière évolutive et maintenable de gérer les projets Ansible.

Maintenant, exécutez l'ensemble du flux de travail en lançant votre playbook de niveau supérieur main.yml :

ansible-playbook main.yml -i inventory

Une fois le playbook terminé avec succès, l'étape cruciale suivante est la vérification. Vous devez confirmer que le système est dans l'état que vous aviez prévu. Notre playbook a été conçu pour installer le paquet httpd sur les serveurs web et le paquet mariadb sur les serveurs de base de données. Comme toutes les tâches de ce laboratoire s'exécutent sur votre machine locale, nous pouvons vérifier leur installation directement à l'aide de la commande rpm.

Tout d'abord, vérifiez si le paquet httpd a été installé dans le cadre de la configuration du serveur web :

rpm -q httpd

Vous devriez voir une sortie confirmant que le paquet est installé :

httpd-2.4.xx-x.el9.x86_64

Ensuite, vérifiez l'installation du paquet mariadb à partir de la configuration du serveur de base de données :

rpm -q mariadb

De même, vous devriez voir une confirmation que mariadb est installé :

mariadb-10.5.xx-x.el9.x86_64

Voir les noms des paquets dans la sortie confirme que votre playbook Ansible a correctement configuré le système comme prévu. Vous avez maintenant construit, exécuté et vérifié avec succès un projet Ansible modulaire du début à la fin.

Résumé

Dans ce laboratoire, vous avez appris des techniques essentielles pour structurer des playbooks Ansible complexes sur RHEL. Vous avez commencé par les bases de la sélection d'hôtes, en utilisant des noms de groupes de base, des jokers (wildcards), des exclusions et des opérateurs logiques pour cibler précisément les nœuds définis dans un fichier d'inventaire. L'accent a ensuite été mis sur la modularisation, où vous avez pratiqué la décomposition de grands plays en composants plus gérables et réutilisables en utilisant à la fois include_tasks pour l'inclusion dynamique et import_tasks pour l'inclusion statique.

En vous appuyant sur ces compétences, vous avez appris à composer un flux de travail complet en plusieurs étapes en reliant des playbooks individuels avec import_playbook. Le processus pratique a impliqué l'installation d'Ansible, la création d'une structure de projet et le refactoring progressif d'un playbook simple en une structure sophistiquée à plusieurs fichiers. Le laboratoire s'est terminé par l'exécution du playbook composite final et la vérification que l'ensemble du flux de travail automatisé s'est déroulé avec succès contre les hôtes correctement ciblés, démontrant une approche organisée et évolutive de l'automatisation.