Skip to main content

Playbook

image.png

Avant propos

Cette procédure aura pour exemple concret, la copie d'un script, ainsi que de la mise en place d'une tâche cron qui éxecute ce script.

Echange de clés SSH

Dans un premier temps, pour se simplifier la vie, nous allons faire un échange de clé ssh, pour ne pas avoir à entrer identifiant et mot de passe chaque fois.

Générer une clé

On va commencer par générer une clé SSH pour notre serveur Ansible.

ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -C "root@Ansible"
Copier la clé

On peut maintenant copier la clé sur les serveurs à gérer.

ssh-copy-id -i ~/.ssh/id_rsa.pub <user>@<IP>

Avec cette commande vient la demande d'acceptation de la fingerprint, et on tape ensuite le mot de passe pour valider la copie.

Arboréscence

Le plus simple pour utiliser Ansible est d'avoir une aboréscence simple :

/Ansible/ 
├── inventory
├── playbook.yml
└── files/
    └── docker_update.sh

Le répertoire Ansible est à la racine du système.
Le fichier inventory, permet de lister l'ensemble des machines gérées, ainsi que de les placer dans divers groupes.
Le fichier playbook.yml contient les tâches à effectuer.
Le répertoire files permet de contenir l'ensemble des ressources utiles : scripts etc

Fichiers

Inventory
[VM_Docker]
# Groupe d’hôtes appelé "VM_Docker" : liste des machines sur lesquelles Ansible va agir

Supervisor      ansible_host=<IP>
Vaultwarden     ansible_host=<IP>
RustDesk        ansible_host=<IP>
Servarr         ansible_host=<IP>
Immich          ansible_host=<IP>
NTFY            ansible_host=<IP>
Mealie          ansible_host=<IP>
Firefly         ansible_host=<IP>
FreshRSS        ansible_host=<IP>

[all:vars]
# Variables globales appliquées à tous les hôtes de tous les groupes

ansible_user=<user>
# Utilisateur SSH utilisé par Ansible pour se connecter aux machines (remplace <user> par le nom d’utilisateur réel)

ansible_become=true
# Active l’élévation de privilèges (exécution des commandes avec sudo par exemple)
# Utile si l’utilisateur SSH n’est pas root, mais doit exécuter des commandes administratives
  • [VM_Docker] : groupe contenant la liste des machines où déployer le script.

  • ansible_host : adresse IP de chaque machine.

  • [all:vars] : variables communes à tous les hôtes.

  • ansible_become : Demande à Ansible d’élever les privilèges, c’est-à-dire d’utiliser sudo.
playbook.yml
---
- name: "Copier le script docker_update.sh et planifier son exécution"
  # Nom global du playbook, indique l'objectif général : copier le script puis planifier son exécution automatique
  hosts: VM_Docker
  # Indique que les tâches seront exécutées sur tous les hôtes du groupe VM_Docker
  tasks:
  # Liste des tâches à exécuter dans l'ordre

    - name: "S' assurer que le dossier /Docker existe"
      # Tâche 1 : garantir la présence du dossier /Docker sur chaque machine cible
      file:
        path: /Docker
        # Chemin du dossier à vérifier ou créer
        state: directory
        # On veut un dossier (et non un fichier)
        owner: root
        # Propriétaire du dossier
        group: root
        # Groupe propriétaire du dossier
        mode: '0755'
        # Permissions : rwxr-xr-x (propriétaire lecture-écriture-exécution, groupe et autres lecture-exécution)

    - name: "Copier docker_update.sh uniquement s’il n’existe pas"
      # Tâche 2 : copier le script shell vers /Docker/docker_update.sh si ce fichier n'existe pas déjà
      copy:
        src: files/docker_update.sh
        # Chemin source local du script à copier (dans le dossier files de ton projet Ansible)
        dest: /Docker/docker_update.sh
        # Destination sur la machine distante
        owner: root
        group: root
        mode: '0755'
        # Le script sera exécutable
        force: no
        # Ne pas écraser le fichier s’il est déjà présent (évite d’écraser une version modifiée)

    - name: Ajouter un cronjob pour lancer le script chaque jour à 3h
      # Tâche 3 : ajouter une tâche cron qui lance le script tous les jours à 3h du matin
      cron:
        name: "Mise à jour Docker quotidienne"
        # Nom lisible de la tâche cron (pour repérer la tâche plus facilement)
        user: root
        # La tâche sera lancée avec l'utilisateur root
        minute: "0"
        # Minute de lancement (00)
        hour: "3"
        # Heure de lancement (3h du matin)
        job: "/Docker/docker_update.sh"
        # Commande exécutée : lancement du script docker_update.sh
docker_update.sh
#!/bin/bash
# Indique que le script doit être exécuté avec bash

BASE_DIR="/Docker"
# Répertoire racine où sont situés les dossiers des différents projets Docker

TOPIC="Docker_Updates"
# Nom du sujet (topic) pour la notification ntfy

NTFY_URL="https://ntfy.rakouns.bzh"
# URL du serveur ntfy pour envoyer les notifications

timestamp=$(date +"%Y-%m-%d %H:%M:%S")
# Date et heure actuelles, formatées pour le rapport

report="Rapport de mise à jour Docker - $timestamp\n\n"
# Initialisation de la variable rapport avec un titre et un saut de ligne

for dir in "$BASE_DIR"/*/; do
    # Boucle sur chaque sous-dossier du répertoire /Docker (un dossier par projet Docker)

    container_name=$(basename "$dir")
    # Extraction du nom du dossier (nom du conteneur/service Docker)

    # Trouver le bon fichier docker-compose
    if [[ -f "$dir/docker-compose.yaml" ]]; then
        compose_file="$dir/docker-compose.yaml"
    elif [[ -f "$dir/docker-compose.yml" ]]; then
        compose_file="$dir/docker-compose.yml"
    else
        # Ignorer les répertoires sans fichier docker-compose (pas un projet Docker valide)
        continue
    fi

    # Pull update : récupération des dernières images Docker du fichier compose
    pull_output=$(docker compose -f "$compose_file" pull 2>&1)
    pull_exit=$?
    # Capture la sortie et le code de retour de la commande "docker compose pull"

    # Up update : relance des conteneurs en mode détaché avec les nouvelles images
    up_output=$(docker compose -f "$compose_file" up -d 2>&1)
    up_exit=$?
    # Capture la sortie et le code de retour de la commande "docker compose up -d"

    # Résumé avec ou sans erreurs
    if [[ $pull_exit -eq 0 && $up_exit -eq 0 ]]; then
        # Si les deux commandes ont réussi, ajouter une coche verte au rapport
        report+="$container_name : ✅\n"
    else
        # Sinon, coche rouge et détails des erreurs
        report+="$container_name : ❌\n"
        if [[ $pull_exit -ne 0 ]]; then
            # Ajouter les 5 dernières lignes de la sortie pull en cas d’erreur
            report+="  Erreur pull :\n$(echo "$pull_output" | tail -n 5)\n"
        fi
        if [[ $up_exit -ne 0 ]]; then
            # Ajouter les 5 dernières lignes de la sortie up en cas d’erreur
            report+="  Erreur up :\n$(echo "$up_output" | tail -n 5)\n"
        fi
    fi
done

# Envoi de la notification condensée
printf "$report" | curl -s -X POST \
    -H "Title: Rapport de mise à jour Docker" \
    -H "Priority: 5" \
    --data-binary @- \
    "$NTFY_URL/$TOPIC"
# Envoie le rapport via curl en POST à ntfy, avec un titre et une priorité,
# la donnée est envoyée depuis la variable report

Execution

On va pouvoir maintenant lancer le playbook, sur le groupe souhaité.
Pour cela, rien de plus simple, on tape la commande suivante :

ansible-playbook playbook.yml -i inventory

On à alors un retour qui ressemble à ça :

PLAY ["Copier le script docker_update.sh et planifier son exécution"] *****************************************************************************************************************************************

TASK [Gathering Facts] *****************************************************************************************************************************************
ok: [RustDesk]
ok: [Immich]
ok: [Servarr]
ok: [Supervisor]
ok: [NTFY]
ok: [Mealie]
ok: [FreshRSS]
ok: [Firefly]
ok: [Vaultwarden]

TASK ["S'assurer que le dossier /Docker existe"] *****************************************************************************************************************************************
ok: [Servarr]
ok: [Vaultwarden]
ok: [Immich]
ok: [RustDesk]
ok: [Supervisor]
ok: [Mealie]
ok: [NTFY]
ok: [Firefly]
ok: [FreshRSS]

TASK ["Copier docker_update.sh uniquement s’il n’existe pas"] *****************************************************************************************************************************************
ok: [Servarr]
ok: [RustDesk]
ok: [Immich]
ok: [Supervisor]
ok: [NTFY]
ok: [Mealie]
ok: [FreshRSS]
ok: [Firefly]
changed: [Vaultwarden]

TASK ["Ajouter un cronjob pour lancer le script chaque jour à 3h"] *****************************************************************************************************************************************
ok: [RustDesk]
ok: [Servarr]
ok: [Immich]
changed: [Vaultwarden]
ok: [Supervisor]
ok: [NTFY]
ok: [Mealie]
ok: [Firefly]
ok: [FreshRSS]

PLAY RECAP *****************************************************************************************************************************************
Firefly                    : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
FreshRSS                   : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Immich                     : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Mealie                     : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
NTFY                       : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
RustDesk                   : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Servarr                    : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Supervisor                 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
Vaultwarden                : ok=4    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

On peut vérifier que le script est lancé en se rendant sur une machine cible, et vérifier la présence de la tâche cron :

crontab -l

Ainsi que de la présence du script :

ls -la /Docker

Normalement, Ansible ne se trompe pas lorsqu'il affiche un status ok, autrement, le score de failed n'aurait ps été de 0.