Contrôler l'exécution des Playbooks Ansible sur RHEL

Red Hat Enterprise LinuxBeginner
Pratiquer maintenant

Introduction

Dans ce laboratoire, vous apprendrez à contrôler le flux d'exécution des playbooks Ansible sur un système Red Hat Enterprise Linux (RHEL). Vous commencerez par écrire un playbook qui utilise des structures de contrôle fondamentales, notamment des boucles pour répéter efficacement les tâches et des conditions pour exécuter des tâches uniquement lorsque des critères spécifiques sont remplis. Vous implémenterez également des handlers pour déclencher des actions, telles que le redémarrage de services, uniquement lorsqu'un changement se produit, rendant ainsi votre automatisation plus intelligente et plus efficace.

En vous appuyant sur ces compétences fondamentales, vous explorerez des techniques plus avancées pour gérer l'exécution des playbooks. Cela inclut l'utilisation des instructions block et rescue pour gérer gracieusement les échecs de tâches et l'emploi de changed_when et failed_when pour obtenir un contrôle précis sur le statut des tâches. Pour conclure le laboratoire, vous appliquerez tous ces concepts dans un exercice pratique pour déployer un serveur web sécurisé, consolidant ainsi votre capacité à créer une automatisation Ansible robuste et fiable.

Écrire un Playbook avec des Boucles et des Conditions

Dans cette étape, vous apprendrez deux concepts fondamentaux d'Ansible pour contrôler l'exécution des tâches : les boucles et les conditions. Les boucles vous permettent de répéter une tâche plusieurs fois avec des valeurs différentes, ce qui est très efficace pour des tâches telles que l'installation de plusieurs paquets ou la création de plusieurs utilisateurs. Les conditions, utilisant le mot-clé when, vous permettent d'exécuter une tâche uniquement lorsque des critères spécifiques sont remplis, comme le fait que le système d'exploitation soit d'une version particulière ou qu'un fichier existe déjà.

Tout d'abord, assurons-nous qu'Ansible est installé sur votre VM LabEx. Nous utiliserons le gestionnaire de paquets DNF pour cela.

sudo dnf install -y ansible-core

Vous devriez voir une sortie indiquant que ansible-core et ses dépendances sont en cours d'installation.

...
Installed:
  ansible-core-2.x.x-1.el9.x86_64
  ...
Complete!

Maintenant, mettons en place notre répertoire de projet. Tout notre travail pour ce laboratoire se trouvera dans un répertoire dédié pour garder les choses organisées.

cd ~/project
mkdir control-flow-lab
cd control-flow-lab

Un projet Ansible a besoin d'un fichier d'inventaire, qui définit les hôtes que vous souhaitez gérer. Pour ce laboratoire, nous allons gérer la machine locale, localhost.

Créez un fichier d'inventaire nommé inventory en utilisant l'éditeur nano :

nano inventory

Ajoutez la ligne suivante au fichier. Cela indique à Ansible d'exécuter le playbook sur localhost et de s'y connecter directement au lieu d'utiliser SSH.

localhost ansible_connection=local

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

Ensuite, nous allons créer notre premier playbook, playbook.yml, pour démontrer une boucle. Ce playbook installera une liste d'outils en ligne de commande utiles.

nano playbook.yml

Entrez le contenu YAML suivant dans l'éditeur. Ce playbook définit une tâche qui utilise le module ansible.builtin.dnf pour installer des paquets. La directive become: yes indique à Ansible d'exécuter les tâches avec les privilèges sudo, ce qui est nécessaire pour installer des paquets. Le mot-clé loop fournit une liste de noms de paquets. Ansible exécutera cette tâche une fois pour chaque élément de la liste, en remplaçant le placeholder {{ item }} par le nom du paquet actuel.

---
- name: Install common tools
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

Enregistrez et quittez l'éditeur. Exécutez maintenant le playbook en utilisant la commande ansible-playbook, en spécifiant votre fichier d'inventaire avec le drapeau -i.

ansible-playbook -i inventory playbook.yml

La sortie montrera l'exécution du playbook. Ansible vérifiera chaque paquet et l'installera s'il n'est pas déjà présent. Le PLAY RECAP à la fin résume les résultats.

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
changed: [localhost] => (item=git)
changed: [localhost] => (item=tree)
changed: [localhost] => (item=wget)

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

Maintenant, modifions le playbook pour inclure une tâche conditionnelle. Nous allons ajouter une tâche qui affiche un message, mais uniquement si le système d'exploitation est Red Hat Enterprise Linux. C'est un cas d'utilisation courant pour adapter l'automatisation à des environnements spécifiques.

Ouvrez à nouveau le fichier playbook.yml :

nano playbook.yml

Ajoutez les tâches suivantes à la fin du fichier. Le mot-clé when évalue l'expression donnée. ansible_facts['distribution'] est une variable que Ansible découvre automatiquement sur l'hôte géré. La première tâche s'exécutera car notre environnement est RHEL, et la seconde tâche sera ignorée.

---
- name: Install tools and run conditional tasks
  hosts: localhost
  become: yes
  tasks:
    - name: Install specified packages
      ansible.builtin.dnf:
        name: "{{ item }}"
        state: present
      loop:
        - git
        - tree
        - wget

    - name: Show message on Red Hat systems
      ansible.builtin.debug:
        msg: "This system is a Red Hat family distribution."
      when: ansible_facts['distribution'] == "RedHat"

    - name: Show message on other systems
      ansible.builtin.debug:
        msg: "This system is NOT a Red Hat family distribution."
      when: ansible_facts['distribution'] != "RedHat"

Enregistrez et quittez l'éditeur. Exécutez le playbook mis à jour :

ansible-playbook -i inventory playbook.yml

Observez attentivement la sortie. La tâche d'installation des paquets indiquera probablement ok pour tous les éléments puisqu'ils sont déjà installés. Plus important encore, vous verrez le premier message de débogage s'afficher, tandis que le second sera marqué comme skipping.

PLAY [Install tools and run conditional tasks] *********************************

TASK [Gathering Facts] *********************************************************
ok: [localhost]

TASK [Install specified packages] **********************************************
ok: [localhost] => (item=git)
ok: [localhost] => (item=tree)
ok: [localhost] => (item=wget)

TASK [Show message on Red Hat systems] *****************************************
ok: [localhost] => {
    "msg": "This system is a Red Hat family distribution."
}

TASK [Show message on other systems] *******************************************
skipping: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0

Vous avez écrit et exécuté avec succès un playbook Ansible qui utilise à la fois des boucles pour effectuer des actions répétitives et des conditions pour contrôler l'exécution des tâches en fonction des faits du système.

Implémenter des Handlers pour Déclencher les Redémarrages de Service

Dans cette étape, vous découvrirez les handlers Ansible. Les handlers sont des tâches spéciales qui ne s'exécutent que lorsqu'elles sont "notifiées" par une autre tâche. Ils sont généralement utilisés pour des actions qui ne doivent se produire que lorsqu'un changement a été effectué, comme le redémarrage d'un service après la mise à jour de son fichier de configuration. Cette approche est plus efficace que le redémarrage d'un service à chaque exécution du playbook, car elle garantit que l'action n'est effectuée que lorsque cela est nécessaire.

Nous allons créer un playbook qui installe le serveur web Nginx, déploie une page d'accueil personnalisée et utilise un handler pour recharger Nginx uniquement lorsque le contenu de la page d'accueil change.

Tout d'abord, créons un nouveau répertoire pour cet exercice afin de garder notre projet organisé.

cd ~/project
mkdir control-handlers-lab
cd control-handlers-lab

Comme précédemment, nous avons besoin d'un fichier d'inventaire pour indiquer à Ansible où exécuter le playbook.

nano inventory

Ajoutez la ligne suivante pour spécifier la machine locale.

localhost ansible_connection=local

Enregistrez et quittez l'éditeur (Ctrl+X, Y, Entrée).

Ensuite, nous avons besoin d'un fichier pour servir de page d'accueil à notre serveur web. Nous allons créer un répertoire files pour le stocker.

mkdir files

Maintenant, créez un fichier simple index.html dans le répertoire files.

nano files/index.html

Ajoutez le contenu HTML suivant :

<h1>Welcome to the Ansible Handler Lab!</h1>

Enregistrez et quittez l'éditeur.

Maintenant, vous allez créer le playbook deploy_nginx.yml. Ce playbook effectuera trois actions principales : installer Nginx, copier le fichier index.html, et définir un handler pour recharger Nginx.

nano deploy_nginx.yml

Entrez le contenu suivant. Portez une attention particulière au mot-clé notify dans la tâche "Copy homepage" et à la section handlers correspondante à la fin. La directive become: yes indique à Ansible d'exécuter les tâches avec les privilèges sudo, ce qui est nécessaire pour installer des paquets et gérer des services.

---
- name: Deploy Nginx with a handler
  hosts: localhost
  become: yes
  tasks:
    - name: Ensure Nginx is installed
      ansible.builtin.dnf:
        name: nginx
        state: present

    - name: Start and enable Nginx service
      ansible.builtin.systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Copy homepage
      ansible.builtin.copy:
        src: files/index.html
        dest: /usr/share/nginx/html/index.html
      notify: reload nginx

  handlers:
    - name: reload nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded

Enregistrez et quittez l'éditeur.

Exécutez maintenant le playbook pour la première fois.

ansible-playbook -i inventory deploy_nginx.yml

Vous verrez une sortie indiquant que Nginx a été installé (ou était déjà présent), que le service Nginx a été démarré et activé, que le fichier index.html a été copié (statut changed), et surtout, que le handler a été notifié et exécuté à la fin du play.

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Vous pouvez vérifier que le serveur web est en cours d'exécution et qu'il sert votre page personnalisée en utilisant curl.

curl http://localhost

La sortie devrait être le contenu de votre fichier index.html.

<h1>Welcome to the Ansible Handler Lab!</h1>

Maintenant, exécutez exactement le même playbook à nouveau sans apporter de modifications.

ansible-playbook -i inventory deploy_nginx.yml

Cette fois, observez la sortie. La tâche "Copy homepage" indiquera ok au lieu de changed car le fichier de destination correspond déjà à la source. La tâche "Start and enable Nginx service" indiquera également ok puisque le service est déjà en cours d'exécution et activé. Comme aucune tâche n'a notifié le handler, le handler n'a pas été exécuté.

...
TASK [Copy homepage] ***********************************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Pour voir le handler à nouveau en action, modifions le fichier source index.html.

nano files/index.html

Modifiez le contenu comme suit :

<h1>The Handler Ran Again!</h1>

Enregistrez et quittez. Exécutez maintenant le playbook une dernière fois.

ansible-playbook -i inventory deploy_nginx.yml

Étant donné que le fichier source a changé, la tâche "Copy homepage" indiquera à nouveau changed, ce qui à son tour notifiera et exécutera le handler reload nginx.

...
TASK [Copy homepage] ***********************************************************
changed: [localhost]

RUNNING HANDLER [reload nginx] *************************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Vérifiez la modification avec curl une dernière fois.

curl http://localhost

Vous devriez voir le message mis à jour.

<h1>The Handler Ran Again!</h1>

Cet exercice démontre la puissance et l'efficacité des handlers pour gérer l'état des services en réponse aux changements de configuration.

Gérer les Échecs de Tâches avec Block et Rescue

Dans cette étape, vous apprendrez à gérer gracieusement les erreurs dans vos playbooks Ansible. Par défaut, si une tâche échoue, Ansible arrête l'exécution de l'intégralité du playbook sur cet hôte. Bien que ce soit un comportement par défaut sûr, vous avez parfois besoin de plus de contrôle. Vous explorerez deux méthodes de gestion des erreurs : la directive simple ignore_errors et la structure plus puissante block, rescue, et always, qui offre un moyen de tenter des tâches et de définir des actions de récupération si elles échouent.

Tout d'abord, créons un nouveau répertoire pour cet exercice.

cd ~/project
mkdir control-errors-lab
cd control-errors-lab

Créez le fichier inventory standard pour localhost.

nano inventory

Ajoutez le contenu suivant :

localhost ansible_connection=local

Enregistrez et quittez l'éditeur (Ctrl+X, Y, Entrée).

Maintenant, créons un playbook nommé playbook.yml conçu pour échouer. La première tâche tentera d'installer un paquet qui n'existe pas.

nano playbook.yml

Entrez le contenu suivant. Ce playbook essaie d'installer un faux paquet httpd-fake puis un vrai paquet, mariadb-server.

---
- name: Demonstrate Task Failure
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt to install a non-existent package
      ansible.builtin.dnf:
        name: httpd-fake
        state: present

    - name: Install MariaDB server
      ansible.builtin.dnf:
        name: mariadb-server
        state: present

Enregistrez et quittez l'éditeur. Exécutez maintenant le playbook.

ansible-playbook -i inventory playbook.yml

Vous verrez la première tâche échouer avec un message d'erreur car le paquet httpd-fake ne peut pas être trouvé. De manière cruciale, Ansible s'arrêtera et la deuxième tâche, "Install MariaDB server", ne sera pas exécutée.

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => {"changed": false, "msg": "No match for argument: httpd-fake", "rc": 1, "results": []}

PLAY RECAP *********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Maintenant, utilisons block et rescue pour gérer cet échec de manière plus élégante. Le mot-clé block regroupe un ensemble de tâches. Si une tâche dans le block échoue, Ansible ignore les tâches restantes dans le block et exécute les tâches dans la section rescue. La section always s'exécutera, que les sections block ou rescue aient réussi ou échoué.

Modifiez playbook.yml pour utiliser cette structure.

nano playbook.yml

Remplacez tout le contenu par le suivant. Ici, nous essayons d'installer le faux paquet dans le block. Lorsqu'il échoue, la section rescue s'exécute, installant mariadb-server comme étape de récupération. La section always affichera un message à la fin.

---
- name: Handle Task Failure with Block and Rescue
  hosts: localhost
  become: yes
  tasks:
    - name: Attempt primary task, with recovery
      block:
        - name: Attempt to install a non-existent package
          ansible.builtin.dnf:
            name: httpd-fake
            state: present
        - name: This task will be skipped
          ansible.builtin.debug:
            msg: "This message will not appear because the previous task fails."
      rescue:
        - name: Install MariaDB server on failure
          ansible.builtin.dnf:
            name: mariadb-server
            state: present
      always:
        - name: This always runs
          ansible.builtin.debug:
            msg: "The block has completed, either by success or rescue."

Enregistrez et quittez. Exécutez à nouveau le playbook.

ansible-playbook -i inventory playbook.yml

Observez la sortie. La première tâche du block échoue comme prévu. La deuxième tâche du block est ignorée. Ansible passe ensuite à la section rescue et installe avec succès mariadb-server. Enfin, la section always s'exécute.

...
TASK [Attempt to install a non-existent package] *******************************
fatal: [localhost]: FAILED! => ...

TASK [This task will be skipped] ***********************************************
skipping: [localhost]

RESCUE START *******************************************************************

TASK [Install MariaDB server on failure] ***************************************
changed: [localhost]

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

PLAY RECAP *********************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=1    rescued=1    ignored=0

Voyons maintenant ce qui se passe lorsque le block réussit. Modifiez le playbook et corrigez le nom du paquet.

nano playbook.yml

Changez httpd-fake en un paquet réel, httpd.

## ... (reste du playbook)
block:
  - name: Attempt to install a valid package
    ansible.builtin.dnf:
      name: httpd ## Corrected from httpd-fake
      state: present
  - name: This task will now run
    ansible.builtin.debug:
      msg: "This message will now appear because the previous task succeeds."
## ... (reste du playbook)

Enregistrez et quittez. Exécutez le playbook une dernière fois.

ansible-playbook -i inventory playbook.yml

Cette fois, les deux tâches du block réussissent. Comme le block s'est terminé sans erreur, la section rescue est entièrement ignorée. La section always s'exécute toujours, comme son nom l'indique.

...
TASK [Attempt to install a valid package] **************************************
changed: [localhost]

TASK [This task will now run] **************************************************
ok: [localhost] => {
    "msg": "This message will now appear because the previous task succeeds."
}

RESCUE START *******************************************************************
skipping rescue

ALWAYS START *******************************************************************

TASK [This always runs] ********************************************************
ok: [localhost] => {
    "msg": "The block has completed, either by success or rescue."
}

PLAY RECAP *********************************************************************
localhost                  : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Vous avez maintenant utilisé avec succès la structure block/rescue/always pour créer un playbook robuste capable de gérer les échecs et d'effectuer des actions de récupération.

Contrôler l'État des Tâches avec changed_when et failed_when

Dans cette étape, vous obtiendrez un contrôle plus fin sur la manière dont Ansible interprète le résultat de vos tâches. Vous découvrirez deux directives puissantes : changed_when et failed_when.

  • changed_when : Par défaut, les modules comme ansible.builtin.command ou ansible.builtin.shell rapportent presque toujours un état "changed" (modifié), même si la commande qu'ils ont exécutée n'a pas modifié le système. changed_when vous permet de définir une condition personnalisée qui détermine si une tâche doit être signalée comme "changed". Ceci est crucial pour écrire des playbooks idempotents et pour déclencher correctement les handlers.
  • failed_when : Parfois, une commande peut se terminer avec un code de retour non nul (ce qu'Ansible considère comme un échec) même lorsque le résultat est acceptable. failed_when vous permet de remplacer les conditions d'échec par défaut, permettant à votre playbook de continuer en fonction de critères plus intelligents, tels que la sortie de la commande ou un code de sortie spécifique.

Commençons par configurer un nouveau répertoire de projet.

cd ~/project
mkdir control-state-lab
cd control-state-lab

Créez le fichier inventory standard pour localhost.

nano inventory

Ajoutez le contenu suivant :

localhost ansible_connection=local

Enregistrez et quittez l'éditeur (Ctrl+X, Y, Entrée).

Utilisation de changed_when

Tout d'abord, voyons comment se comporte une tâche de commande par défaut. Nous allons créer un playbook qui exécute la commande date. Cette commande affiche simplement la date et ne modifie pas le système, mais le module command la signalera comme un changement.

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

nano playbook.yml

Entrez le contenu suivant :

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (default behavior)
      ansible.builtin.command: date

Enregistrez et quittez. Exécutez maintenant le playbook.

ansible-playbook -i inventory playbook.yml

Remarquez dans la sortie que la tâche est signalée comme changed=1, même si rien sur le système n'a été modifié.

...
TASK [Check local time (default behavior)] *************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    ...

Maintenant, utilisons changed_when pour indiquer à Ansible que cette commande ne modifie jamais le système. Modifiez playbook.yml.

nano playbook.yml

Ajoutez changed_when: false à la tâche.

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check local time (with changed_when)
      ansible.builtin.command: date
      changed_when: false

Enregistrez et quittez. Exécutez à nouveau le playbook.

ansible-playbook -i inventory playbook.yml

Cette fois, la tâche signale ok et le récapitulatif final montre changed=0. Vous avez réussi à outrepasser le comportement par défaut.

...
TASK [Check local time (with changed_when)] ************************************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

Utilisation de failed_when

Ensuite, explorons failed_when. Nous allons créer une tâche qui vérifie l'existence d'un fichier qui n'est pas là. La commande "échouera" par défaut.

Tout d'abord, créez un fichier factice dans lequel rechercher.

echo "System is running" > status.txt

Maintenant, modifiez playbook.yml pour rechercher le mot "ERROR" dans ce fichier. La commande grep se terminera avec un code de retour de 1 car le mot n'est pas trouvé, ce qu'Ansible interprète comme un échec.

nano playbook.yml

Remplacez le contenu par le suivant :

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (will fail)
      ansible.builtin.command: grep ERROR status.txt

Enregistrez et quittez. Exécutez le playbook.

ansible-playbook -i inventory playbook.yml

Comme prévu, l'exécution du playbook s'arrête avec un message FAILED!.

...
TASK [Check for ERROR in status file (will fail)] ******************************
fatal: [localhost]: FAILED! => {"changed": true, "cmd": ["grep", "ERROR", "status.txt"], "delta": "...", "end": "...", "msg": "non-zero return code", "rc": 1, ...}
...

Ce n'est pas ce que nous voulons. L'absence de "ERROR" est une condition de succès pour nous. Nous pouvons utiliser failed_when pour redéfinir ce qui constitue un échec. Nous indiquerons à Ansible de ne échouer que si le code de retour de la commande est supérieur à 1. Un code de retour de 1 (motif non trouvé) sera désormais considéré comme un succès. Nous devons également register le résultat de la tâche pour inspecter son code de retour (rc).

Modifiez playbook.yml une dernière fois.

nano playbook.yml

Mettez à jour le playbook avec register et failed_when.

---
- name: Control Task State
  hosts: localhost
  tasks:
    - name: Check for ERROR in status file (with failed_when)
      ansible.builtin.command: grep ERROR status.txt
      register: grep_result
      failed_when: grep_result.rc > 1
      changed_when: false

Nous avons également ajouté changed_when: false car grep est une opération en lecture seule et ne modifie pas le système.

Enregistrez et quittez. Exécutez le playbook final.

ansible-playbook -i inventory playbook.yml

Succès ! La tâche signale maintenant ok car son code de retour était 1, ce qui ne satisfait pas notre nouvelle condition d'échec (rc > 1). Le playbook se termine avec succès.

...
TASK [Check for ERROR in status file (with failed_when)] ***********************
ok: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    ...

Vous avez maintenant appris à utiliser changed_when et failed_when pour définir précisément les états de succès, de changement et d'échec de vos tâches, ce qui conduit à une automatisation plus robuste et plus intelligente.

Déployer un Serveur Web Sécurisé avec le Contrôle de Tâches

Dans cette dernière étape, vous combinerez tous les concepts que vous avez appris — boucles, conditions, handlers et gestion des erreurs — pour construire un playbook unique et robuste. L'objectif est de déployer le serveur Web Apache (httpd), de le sécuriser avec mod_ssl, de générer un certificat SSL auto-signé et de déployer une page d'accueil personnalisée. Cet exercice pratique simule une tâche d'automatisation réelle.

Tout d'abord, configurons le répertoire du projet pour cet exercice final.

cd ~/project
mkdir control-review-lab
cd control-review-lab

Comme toujours, créez un fichier inventory pour définir votre hôte cible.

nano inventory

Ajoutez l'entrée localhost :

localhost ansible_connection=local

Enregistrez et quittez l'éditeur (Ctrl+X, Y, Entrée).

Ensuite, nous avons besoin d'un répertoire pour stocker les fichiers que notre playbook déploiera.

mkdir files

Maintenant, créez une page d'accueil personnalisée, index.html, dans le répertoire files.

nano files/index.html

Ajoutez le contenu HTML suivant. Ce sera la page servie par notre serveur Web sécurisé.

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

Enregistrez et quittez l'éditeur.

Il est maintenant temps de construire le playbook principal, deploy_secure_web.yml. Ce playbook sera plus complexe que les précédents, intégrant plusieurs concepts.

nano deploy_secure_web.yml

Entrez le playbook complet suivant. Lisez les commentaires dans le code pour comprendre comment chaque partie contribue à l'objectif global.

---
- name: Deploy a Secure Apache Web Server
  hosts: localhost
  become: yes
  vars:
    packages_to_install:
      - httpd
      - mod_ssl
    ssl_cert_path: /etc/pki/tls/certs/localhost.crt
    ssl_key_path: /etc/pki/tls/private/localhost.key

  tasks:
    - name: Stop nginx to free port 80
      ansible.builtin.systemd:
        name: nginx
        state: stopped
      ignore_errors: yes

    - name: Install httpd and mod_ssl packages
      ansible.builtin.dnf:
        name: "{{ packages_to_install }}"
        state: present

    - name: Generate self-signed SSL certificate if it does not exist
      ansible.builtin.command: >
        openssl req -new -nodes -x509
        -subj "/C=US/ST=None/L=None/O=LabEx/CN=localhost"
        -keyout {{ ssl_key_path }}
        -out {{ ssl_cert_path }}
      args:
        creates: "{{ ssl_cert_path }}"

    - name: Deploy custom index.html
      ansible.builtin.copy:
        src: files/index.html
        dest: /var/www/html/index.html
      notify: restart httpd

    - name: Start and enable httpd service
      ansible.builtin.systemd:
        name: httpd
        state: started
        enabled: yes

  handlers:
    - name: restart httpd
      ansible.builtin.systemd:
        name: httpd
        state: restarted

Décomposons ce que fait ce playbook :

  • vars: Définit des variables pour les paquets à installer et les chemins du certificat et de la clé SSL, rendant le playbook plus facile à lire et à maintenir.
  • Tâche d'arrêt de Nginx: Arrête le service nginx de la dernière étape du laboratoire pour libérer le port 80 pour Apache. Utilise ignore_errors: yes au cas où nginx ne serait pas en cours d'exécution.
  • Tâche d'installation: Utilise la variable packages_to_install pour installer httpd et mod_ssl.
  • Tâche de génération de certificat: C'est une tâche clé. Elle utilise la commande openssl pour créer un certificat auto-signé. La directive args: { creates: ... } rend cette tâche idempotente. La commande ne s'exécutera que si le fichier certificat (/etc/pki/tls/certs/localhost.crt) n'existe pas déjà.
  • Tâche de déploiement de la page d'accueil: Copie votre index.html personnalisé. De manière cruciale, elle utilise notify: restart httpd pour déclencher le handler si le fichier est modifié.
  • Tâche de démarrage du service: Utilise le module systemd pour démarrer et activer le service httpd une fois toute la configuration en place, garantissant qu'il démarre au démarrage.
  • Handler: Le handler restart httpd effectue un redémarrage d'Apache en utilisant systemd, qui n'est déclenché que lorsqu'un fichier de configuration ou de contenu change.

Enregistrez et quittez l'éditeur. Exécutez maintenant votre playbook complet.

ansible-playbook -i inventory deploy_secure_web.yml

Lors de la première exécution, vous devriez voir plusieurs tâches signaler changed, y compris l'arrêt de nginx, l'installation des paquets, la génération du certificat, la copie du fichier et le démarrage du service.

...
TASK [Start and enable httpd service] ******************************************
changed: [localhost]

PLAY RECAP *********************************************************************
localhost                  : ok=6    changed=5    unreachable=0    failed=0    ...

Enfin, vérifiez que votre serveur Web sécurisé fonctionne. Testez d'abord la version HTTP, puis la version HTTPS avec l'option -k pour ignorer les avertissements concernant le certificat auto-signé.

curl http://localhost

Vous devriez voir le contenu de votre page d'accueil personnalisée.

<h1>Secure Web Server Deployed by Ansible!</h1>
<p>This page is served over HTTPS.</p>

Vous pouvez également tester la version HTTPS :

curl -k https://localhost

Si vous exécutez à nouveau le playbook, vous verrez qu'aucune tâche ne signale changed, et le handler n'est pas exécuté, prouvant ainsi que votre playbook est idempotent.

Félicitations ! Vous avez réussi à créer un playbook Ansible pratique et robuste qui combine boucles, variables, exécution de commandes idempotentes et handlers pour déployer une application sécurisée.

Résumé

Dans ce laboratoire, vous avez appris à contrôler l'exécution des playbooks Ansible sur un système RHEL. Vous avez commencé par configurer un environnement de projet de base, y compris l'installation d'Ansible et la création d'un fichier d'inventaire. Vous avez ensuite exploré les structures fondamentales de flux de contrôle, en utilisant des boucles pour répéter efficacement des tâches avec différentes entrées et des conditions avec l'instruction when pour exécuter des tâches uniquement dans des circonstances spécifiques. En vous basant sur cela, vous avez implémenté des handlers pour créer des automatisations réactives, telles que le déclenchement d'un redémarrage de service uniquement lorsque son fichier de configuration a été modifié.

Le laboratoire a également couvert des techniques avancées pour gérer l'exécution des playbooks. Vous avez appris à créer des playbooks plus robustes en utilisant les clauses block et rescue pour gérer gracieusement les échecs de tâches. De plus, vous avez obtenu un contrôle précis sur les résultats des tâches en utilisant changed_when et failed_when pour définir des conditions de succès et d'échec personnalisées. Enfin, vous avez consolidé toutes ces compétences en les appliquant à un scénario pratique : le déploiement d'un serveur Web sécurisé, démontrant comment combiner efficacement les boucles, les conditions, les handlers et la gestion des erreurs dans un flux de travail d'automatisation réel.