ITSkillsCenter
Blog

Tester ses rôles Ansible avec Molecule et Docker pas à pas

13 دقائق للقراءة

📍 Article principal : Ansible 2026 : la stack pratique pour automatiser Linux et Windows

Vos rôles sont propres et durcis. On les teste maintenant — automatiquement, dans des conteneurs éphémères, à chaque pull request.

Ce que vous aurez à la fin

Une suite Molecule pour le rôle nginx (ou n’importe quel autre rôle) qui provisionne un conteneur Docker, applique le rôle, vérifie l’idempotence, lance un jeu d’assertions, puis détruit l’environnement. Le tout s’exécute en local en quelques minutes et tourne automatiquement dans GitHub Actions à chaque push. Vous pourrez ajouter de nouvelles assertions sans toucher à l’infrastructure de tests, et matricer le rôle sur Debian, Ubuntu et Rocky Linux dans le même run.

Prérequis

  • ansible-core 2.20 et un rôle existant à tester (par exemple celui produit dans le tutoriel sur la structuration en rôles).
  • Docker Engine installé localement (Docker Desktop sur macOS/Windows, paquet docker-ce sur Linux). Podman fonctionne aussi avec une configuration équivalente.
  • Python 3.10 minimum sur le poste, et pipx déjà disponible.
  • Niveau attendu : avoir écrit un rôle Ansible avec au moins trois tâches.
  • Temps : 90 minutes en lecture active.

Étape 1 — Pourquoi Molecule plutôt que des scripts maison

Tester un rôle Ansible signifie répondre à trois questions. Le rôle s’applique-t-il sans erreur sur l’OS cible ? Est-il idempotent (un second run ne fait rien) ? Le résultat correspond-il à ce qu’on attendait (services démarrés, fichiers en place, ports ouverts) ?

On peut bricoler une réponse avec des scripts shell : docker run, ansible-playbook, docker exec pour vérifier, docker rm. Au début ça marche. Au bout de cinq rôles, le ménage des conteneurs orphelins, la gestion des matrices d’OS et l’intégration CI deviennent un projet à part entière.

Molecule encapsule tout cela dans un outil dédié. Le cycle est figé (create → converge → idempotence → verify → destroy), les drivers (Docker, Podman, EC2, OpenStack) sont interchangeables, l’intégration CI est triviale, et l’écosystème publie depuis longtemps des images de cibles bien préparées.

Étape 2 — Installer Molecule et ses dépendances

Molecule est distribué comme un paquet Python. La méthode propre consiste à l’installer via pipx, dans un environnement isolé qui partage avec ansible-core. On installe également molecule-plugins qui contient les drivers (Docker, EC2, etc.) :

pipx install molecule
pipx inject molecule molecule-plugins[docker] ansible-lint pytest-testinfra

La commande pipx inject ajoute des paquets Python à l’environnement de molecule sans casser son isolation. ansible-lint sera utilisé par Molecule pour valider les rôles, et pytest-testinfra sert à écrire les assertions de vérification — c’est le framework standard pour interroger un système Linux depuis Python.

Vérification immédiate :

molecule --version

La sortie doit afficher Molecule en version 26.x ou supérieure (la 26.4.0 d’avril 2026 est la branche active). Si la version est plus ancienne, on force la mise à jour avec pipx upgrade molecule.

Une note importante avant de continuer. Molecule 26 introduit un modèle de configuration ansible-native qui remplace progressivement le modèle historique à drivers (docker, podman, ec2). Le modèle legacy, désormais appelé pre ansible-native, reste maintenu pour les rôles existants et la majorité des projets en 2026 — c’est ce que nous utilisons dans ce tutoriel. Une fois à l’aise avec ce modèle, la transition vers l’ansible-native consiste à remplacer le bloc driver par des playbooks create.yml/destroy.yml qui invoquent directement les modules de la collection containers.podman ou community.docker.

Étape 3 — Initialiser un scénario Molecule

On se place à la racine d’un rôle existant et on initialise un scénario par défaut :

cd roles/nginx
molecule init scenario --driver-name docker default

Cette commande crée le répertoire molecule/default/ avec quatre fichiers :

roles/nginx/molecule/default/
├── molecule.yml      # configuration du scénario : drivers, plateformes, étapes
├── converge.yml      # le playbook qui applique le rôle dans le conteneur
├── verify.yml        # le playbook ou les tests qui vérifient le résultat
└── create.yml        # rare à modifier — création du conteneur (livré par défaut)

Cette structure est ce qui rend Molecule prévisible. Tous les rôles Molecule de la planète ont la même organisation, et un développeur qui arrive comprend l’arborescence en trente secondes.

Étape 4 — Configurer molecule.yml

Le fichier molecule/default/molecule.yml est le cœur de la configuration. On le réécrit pour matricer le rôle sur trois OS :

---
dependency:
  name: galaxy
  options:
    requirements-file: requirements.yml

driver:
  name: docker

platforms:
  - name: nginx-debian-12
    image: geerlingguy/docker-debian12-ansible:latest
    pre_build_image: true
    privileged: true
    cgroupns_mode: host
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    command: /lib/systemd/systemd

  - name: nginx-ubuntu-2404
    image: geerlingguy/docker-ubuntu2404-ansible:latest
    pre_build_image: true
    privileged: true
    cgroupns_mode: host
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    command: /lib/systemd/systemd

  - name: nginx-rocky-9
    image: geerlingguy/docker-rockylinux9-ansible:latest
    pre_build_image: true
    privileged: true
    cgroupns_mode: host
    volumes:
      - /sys/fs/cgroup:/sys/fs/cgroup:rw
    command: /usr/lib/systemd/systemd

provisioner:
  name: ansible
  config_options:
    defaults:
      callback_whitelist: timer,profile_tasks
  inventory:
    host_vars:
      nginx-debian-12:
        ansible_python_interpreter: /usr/bin/python3
      nginx-ubuntu-2404:
        ansible_python_interpreter: /usr/bin/python3
      nginx-rocky-9:
        ansible_python_interpreter: /usr/bin/python3

verifier:
  name: ansible

Plusieurs détails comptent. Les images geerlingguy/docker-*-ansible sont préinstallées avec systemd opérationnel — ce qui permet aux rôles d’utiliser le module ansible.builtin.systemd_service sans bidouille. Les options privileged, cgroupns_mode: host et le volume /sys/fs/cgroup sont les directives qui rendent systemd fonctionnel dans un conteneur en 2026 — sans elles, systemctl tombe en erreur.

Le command: /lib/systemd/systemd remplace l’entrypoint par défaut. Sur Rocky Linux et autres dérivés RHEL, le chemin est /usr/lib/systemd/systemd ; on adapte plate-forme par plate-forme.

Les callbacks timer et profile_tasks activés sur le provisioner ajoutent à la sortie le temps total et le détail par tâche — précieux pour repérer les goulots quand le test devient lent.

Étape 5 — Le playbook converge

C’est ici qu’on applique le rôle dans les conteneurs. Le fichier molecule/default/converge.yml :

---
- name: Converge
  hosts: all
  become: true
  pre_tasks:
    - name: Mettre à jour le cache APT (Debian/Ubuntu)
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 600
      when: ansible_os_family == 'Debian'
      changed_when: false

    - name: Mettre à jour le cache DNF (Rocky)
      ansible.builtin.dnf:
        update_cache: true
      when: ansible_os_family == 'RedHat'
      changed_when: false

  roles:
    - role: nginx
      vars:
        nginx_default_site:
          server_name: test.local
          root: /var/www/html
          index: index.html

On expose explicitement les variables qu’un utilisateur du rôle pourrait surcharger — c’est aussi un moyen de documenter par l’exemple. Les pre_tasks mettent à jour le cache du gestionnaire de paquets selon la famille d’OS. Le drapeau changed_when: false évite que ces tâches comptent comme un changement et faussent le test d’idempotence.

Étape 6 — Le playbook verify

L’étape verify est ce qui transforme un déploiement en test. Elle s’exécute après converge et après le test d’idempotence ; on y vérifie que le système est dans l’état attendu. molecule/default/verify.yml :

---
- name: Verify
  hosts: all
  become: true
  tasks:
    - name: Vérifier que nginx est installé
      ansible.builtin.package_facts:
        manager: auto

    - name: Assertion — paquet nginx présent
      ansible.builtin.assert:
        that: "'nginx' in ansible_facts.packages"
        fail_msg: "Le paquet nginx n'est pas installé"

    - name: Vérifier que le service nginx tourne
      ansible.builtin.service_facts:

    - name: Assertion — service nginx actif
      ansible.builtin.assert:
        that:
          - "'nginx.service' in ansible_facts.services"
          - "ansible_facts.services['nginx.service'].state == 'running'"

    - name: Tester l'écoute sur le port 80
      ansible.builtin.wait_for:
        port: 80
        host: 127.0.0.1
        timeout: 5

    - name: Récupérer la page d'accueil
      ansible.builtin.uri:
        url: http://127.0.0.1/
        status_code: [200, 403]
        return_content: true
      register: home_response

    - name: Assertion — réponse contient bien du HTML
      ansible.builtin.assert:
        that: home_response.content is match('.*<[hH][tT][mM][lL].*')
        fail_msg: "La page servie par nginx ne contient pas de HTML"

Le module ansible.builtin.assert est l’outil de prédilection pour les vérifications. Il échoue le play si la condition est fausse, avec un message clair. Combiné à package_facts et service_facts, on couvre la majorité des assertions courantes sans dépendance externe.

Étape 7 — Lancer le cycle complet

On lance le cycle Molecule complet :

cd roles/nginx
molecule test

La commande exécute la séquence : dependency (installe collections déclarées), lint (passe ansible-lint), cleanup (purge les conteneurs résiduels), destroy (au cas où), syntax (valide le playbook), create (lance les conteneurs), prepare (étape optionnelle), converge (applique le rôle), idempotence (relance le converge et exige changed=0), side_effect (optionnel), verify (lance les assertions), cleanup, destroy.

Sur la matrice à trois OS, comptez sept à dix minutes la première fois — les images Docker sont téléchargées (350 Mo chacune environ). Les exécutions suivantes prennent deux à trois minutes grâce au cache Docker.

Pendant le développement, la séquence test est trop lente pour être pratique à chaque modification. On utilise alors les sous-commandes :

molecule create        # lance les conteneurs et s'arrête
molecule converge      # applique le rôle (rapide)
molecule verify        # lance les assertions (rapide)
molecule destroy       # nettoie

Le pattern itératif consiste à create une fois, puis converge + verify à chaque modification, et destroy à la fin de la session.

Étape 8 — Plusieurs scénarios pour plusieurs cas d’usage

Un rôle complexe peut avoir plusieurs configurations à tester. Le rôle nginx par exemple doit fonctionner avec et sans HTTPS, avec et sans modules supplémentaires, avec différents nombres de vhosts. On crée des scénarios complémentaires :

molecule init scenario --driver-name docker https
molecule init scenario --driver-name docker multi-vhost

Chaque scénario vit sous molecule/<nom>/ avec sa propre molecule.yml, son propre converge.yml, son propre verify.yml. On les lance individuellement :

molecule test --scenario-name https
molecule test --scenario-name multi-vhost

Pour exécuter tous les scénarios en cascade : molecule test --all. La séparation par scénarios évite les configurations switch-case imbriquées dans un unique molecule.yml.

Étape 9 — Intégration GitHub Actions

Le bénéfice complet de Molecule arrive quand chaque pull request lance la suite automatiquement. Le workflow .github/workflows/molecule.yml :

name: Molecule

on:
  push:
    branches: [main]
  pull_request:

jobs:
  molecule:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        scenario: [default]
        platform:
          - debian-12
          - ubuntu-2404
          - rocky-9

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Installer Molecule et ansible-core
        run: |
          pip install ansible-core==2.20.* molecule molecule-plugins[docker] \
                      ansible-lint pytest-testinfra

      - name: Installer les collections du rôle
        run: ansible-galaxy collection install -r requirements.yml

      - name: Lancer Molecule
        env:
          PY_COLORS: '1'
          ANSIBLE_FORCE_COLOR: '1'
        run: molecule test --scenario-name ${{ matrix.scenario }}

La matrice GitHub Actions multiplie les jobs : un par OS, en parallèle. Une PR qui casse Rocky Linux mais pas Debian apparaît immédiatement avec un échec ciblé. Le job dure cinq à huit minutes selon la taille du rôle ; sur les runners GitHub gratuits, ça reste largement dans les budgets.

Étape 10 — Tests d’infrastructure avec testinfra (option avancée)

Le verify.yml en YAML est suffisant pour 90 % des cas. Pour des assertions plus riches — manipulation de fichiers binaires, tests réseaux complexes, validations métier — Molecule supporte aussi testinfra, un framework Python qui permet d’écrire des tests pytest interrogeant l’infrastructure.

On bascule le verifier dans molecule.yml :

verifier:
  name: testinfra
  options:
    verbose: true

Et on écrit les tests dans molecule/default/tests/test_default.py :

import pytest

@pytest.mark.parametrize('pkg', ['nginx'])
def test_packages_installed(host, pkg):
    assert host.package(pkg).is_installed

def test_nginx_running(host):
    nginx = host.service('nginx')
    assert nginx.is_running
    assert nginx.is_enabled

def test_nginx_listens(host):
    assert host.socket('tcp://0.0.0.0:80').is_listening

def test_index_html_served(host):
    cmd = host.run('curl -s http://127.0.0.1/')
    assert cmd.rc == 0
    assert '<html' in cmd.stdout.lower()

Testinfra apporte la lisibilité de pytest et la composition par fixtures. Sur les rôles partagés inter-équipes, le retour sur investissement se voit rapidement — chaque équipe ajoute ses propres assertions sans toucher au reste.

Erreurs fréquentes

Erreur Cause Solution
systemctl échoue dans le conteneur Image sans systemd ou sans privileged Utiliser une image geerlingguy/docker-*-ansible et activer privileged: true
Test d’idempotence rouge Tâche shell ou command sans garde Ajouter changed_when: conditionnel, ou utiliser un module dédié
Conteneurs orphelins après Ctrl+C destroy non appelé molecule destroy ou docker rm -f $(docker ps -aq -f label=molecule)
Failed to find required executable apt-get Image sans gestionnaire de paquet attendu Adapter les pre_tasks à la famille OS via when: ansible_os_family
CI tombe sur Docker daemon not running Job runner sans Docker activé Sur GitHub Actions, ubuntu-latest a Docker préinstallé ; sur self-hosted, vérifier systemctl status docker
Tests Molecule très lents Téléchargement des images à chaque run Activer le cache Docker côté CI, ou utiliser actions/cache sur ~/.cache/molecule

Tutoriels frères

Pour étoffer le tableau

FAQ

Docker ou Podman pour Molecule ?
Les deux fonctionnent. Docker reste plus simple à mettre en place sur GitHub Actions et offre la documentation la plus riche. Podman a l’avantage de tourner sans démon root, plus aligné avec les pratiques de sécurité modernes ; on l’utilise via driver: podman dans molecule.yml. Pour un rôle qui utilise systemd, vérifier que la version de Podman supporte les conteneurs avec --systemd=true.

Faut-il un scénario par OS ou un scénario qui matrice les OS ?
Un seul scénario qui matrice les OS dans platforms est plus simple à maintenir et garantit des assertions identiques sur toutes les cibles. On crée un scénario distinct uniquement quand la configuration testée diffère vraiment (HTTP vs HTTPS, mode standalone vs cluster), pas quand seul l’OS change.

Que faire d’un test d’idempotence qui échoue après un upgrade APT légitime ?
On exclut la tâche du compte d’idempotence en lui ajoutant changed_when: false si elle peut légitimement faire un changement à chaque run (mise à jour de cache APT par exemple). Pour les tâches qui doivent réellement changer la première fois et pas la suivante, on cherche le module idempotent natif au lieu de shell.

Combien de temps un test Molecule prend-il sur un rôle moyen ?
Trois à cinq minutes pour un seul OS, sept à dix minutes pour trois OS en parallèle local. En CI sur runner partagé, comptez le double — c’est encore acceptable pour un déclenchement par PR.

Molecule peut-il tester un rôle qui modifie des modules kernel ?
Pas dans un conteneur. Les modules kernel ne se chargent que sur la machine hôte ou dans une vraie VM. On utilise alors le driver delegated qui pointe sur une instance EC2 ou un VPS éphémère, ou on bascule sur vagrant en local pour des VM complètes. Les performances en pâtissent, mais c’est le seul moyen.

Doit-on versionner les fichiers Molecule dans le rôle ?
Oui, absolument. Le scénario par défaut fait partie de la documentation exécutable du rôle. Un développeur qui clone le rôle doit pouvoir lancer molecule test immédiatement. C’est aussi ce qui permet à Galaxy d’afficher un badge « tested with Molecule ».

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité