Les Rôles Ansible : Structurer et Réutiliser votre Automatisation

Structurez votre automatisation avec les rôles Ansible. Structure d'un rôle, ansible-galaxy init, tasks, handlers, templates, variables, dépendances et tests avec Molecule.

Introduction : Pourquoi les Rôles Ansible ?

Lorsque vous commencez à travailler avec Ansible, vos premiers playbooks sont souvent de simples fichiers YAML contenant quelques tâches. Mais à mesure que votre infrastructure grandit, ces playbooks deviennent rapidement volumineux, difficiles à maintenir et impossibles à réutiliser. C'est précisément là qu'interviennent les rôles Ansible.

Un rôle Ansible est une unité d'organisation qui permet de décomposer un playbook complexe en composants modulaires et réutilisables. Pensez aux rôles comme à des briques LEGO : chaque brique a une fonction précise, et vous les assemblez pour construire des structures complètes. Un rôle pour Nginx, un rôle pour PostgreSQL, un rôle pour la sécurité du système — chacun encapsule toute la logique nécessaire à une responsabilité donnée.

Architecture en un coup d'œil

Diagramme - Les Rôles Ansible : Structurer et Réutiliser votre Automatisation

Les avantages des rôles sont nombreux :

  • Réutilisabilité : un rôle bien conçu peut être utilisé dans plusieurs projets sans modification.
  • Lisibilité : la structure standardisée rend le code immédiatement compréhensible par toute l'équipe.
  • Maintenabilité : les modifications sont isolées dans le rôle concerné, sans effet de bord sur le reste.
  • Testabilité : chaque rôle peut être testé indépendamment avec des outils comme Molecule.
  • Partage : les rôles peuvent être publiés sur Ansible Galaxy pour la communauté.

Dans cet article, nous allons explorer en profondeur la structure d'un rôle Ansible, apprendre à en créer un de A à Z, et mettre en pratique avec un rôle complet pour le serveur web Nginx. Que vous soyez débutant ou praticien intermédiaire, ce guide vous donnera toutes les clés pour maîtriser les rôles Ansible.

La Structure d'un Rôle Ansible

Un rôle Ansible suit une convention de répertoires stricte. C'est cette convention qui permet à Ansible de savoir automatiquement où chercher les tâches, les variables, les templates et les autres composants. Voici la structure complète d'un rôle :

mon_role/
├── defaults/
│   └── main.yml        # Variables par défaut (priorité la plus basse)
├── vars/
│   └── main.yml        # Variables du rôle (priorité plus élevée)
├── tasks/
│   └── main.yml        # Liste principale des tâches
├── handlers/
│   └── main.yml        # Handlers (actions déclenchées par notify)
├── templates/
│   └── config.j2       # Templates Jinja2
├── files/
│   └── script.sh       # Fichiers statiques à copier
├── meta/
│   └── main.yml        # Métadonnées et dépendances du rôle
├── tests/
│   ├── inventory        # Inventaire de test
│   └── test.yml         # Playbook de test
└── README.md            # Documentation du rôle

Chaque répertoire a un rôle bien défini. Examinons-les un par un.

Le répertoire tasks/

C'est le cœur du rôle. Le fichier tasks/main.yml contient la liste des tâches à exécuter. Ansible charge automatiquement ce fichier lorsque le rôle est appelé. Vous pouvez également découper vos tâches en plusieurs fichiers et les inclure depuis main.yml :

# tasks/main.yml
---
- name: Inclure les tâches d'installation
  ansible.builtin.include_tasks: install.yml

- name: Inclure les tâches de configuration
  ansible.builtin.include_tasks: configure.yml

- name: Inclure les tâches de service
  ansible.builtin.include_tasks: service.yml

Ce découpage améliore considérablement la lisibilité, surtout pour les rôles complexes comprenant des dizaines de tâches.

Le répertoire handlers/

Les handlers sont des tâches spéciales qui ne s'exécutent que lorsqu'elles sont notifiées par une autre tâche via le mot-clé notify. Ils sont typiquement utilisés pour redémarrer ou recharger des services après un changement de configuration :

# handlers/main.yml
---
- name: Redémarrer nginx
  ansible.builtin.service:
    name: nginx
    state: restarted

- name: Recharger nginx
  ansible.builtin.service:
    name: nginx
    state: reloaded

Un handler ne s'exécute qu'une seule fois à la fin du play, même s'il est notifié plusieurs fois. C'est un mécanisme élégant pour éviter les redémarrages inutiles.

Le répertoire vars/

Ce répertoire contient les variables internes du rôle qui ne sont pas censées être modifiées par l'utilisateur. Elles ont une priorité élevée dans la hiérarchie des variables Ansible :

# vars/main.yml
---
nginx_user: www-data
nginx_pid_file: /run/nginx.pid
nginx_error_log: /var/log/nginx/error.log
nginx_access_log: /var/log/nginx/access.log

Le répertoire defaults/

Contrairement à vars/, le répertoire defaults/ contient les variables par défaut du rôle. Elles ont la priorité la plus basse dans la hiérarchie des variables Ansible, ce qui signifie qu'elles peuvent être facilement surchargées par l'utilisateur du rôle :

# defaults/main.yml
---
nginx_listen_port: 80
nginx_server_name: localhost
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_gzip_enabled: true
nginx_ssl_enabled: false
nginx_ssl_certificate: ""
nginx_ssl_certificate_key: ""

C'est ici que vous placez toutes les valeurs que l'utilisateur du rôle pourrait vouloir personnaliser. C'est une bonne pratique de fournir des valeurs par défaut sensées pour que le rôle fonctionne "out of the box".

Le répertoire templates/

Ce répertoire contient les templates Jinja2 (fichiers avec l'extension .j2). Ansible utilise le moteur de template Jinja2 pour générer des fichiers de configuration dynamiques en injectant les valeurs des variables :

# templates/nginx.conf.j2
worker_processes {{ nginx_worker_processes }};
pid {{ nginx_pid_file }};

events {
    worker_connections {{ nginx_worker_connections }};
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    access_log {{ nginx_access_log }};
    error_log {{ nginx_error_log }};

    keepalive_timeout {{ nginx_keepalive_timeout }};

{% if nginx_gzip_enabled %}
    gzip on;
    gzip_types text/plain text/css application/json application/javascript;
{% endif %}

    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Lorsque vous utilisez le module template dans vos tâches, Ansible cherche automatiquement les templates dans le répertoire templates/ du rôle. Vous n'avez pas besoin de spécifier le chemin complet.

Le répertoire files/

Ce répertoire contient les fichiers statiques qui doivent être copiés tels quels sur les hôtes distants. Contrairement aux templates, ces fichiers ne subissent aucune transformation. Exemples typiques : scripts shell, certificats, clés SSH.

# files/index.html
<!DOCTYPE html>
<html>
<head><title>Bienvenue</title></head>
<body>
    <h1>Serveur configuré par Ansible</h1>
</body>
</html>

Le répertoire meta/

Le fichier meta/main.yml contient les métadonnées du rôle ainsi que ses dépendances. Ces informations sont cruciales pour Ansible Galaxy et pour la résolution automatique des dépendances :

# meta/main.yml
---
galaxy_info:
  author: votre_nom
  description: Rôle pour installer et configurer Nginx
  company: CodeClan
  license: MIT
  min_ansible_version: "2.14"
  platforms:
    - name: Ubuntu
      versions:
        - focal
        - jammy
    - name: Debian
      versions:
        - bullseye
        - bookworm
  galaxy_tags:
    - nginx
    - web
    - reverse_proxy

dependencies:
  - role: common
  - role: firewall
    vars:
      firewall_allowed_ports:
        - "80/tcp"
        - "443/tcp"

Les dépendances listées ici seront automatiquement exécutées avant le rôle lui-même. C'est un moyen puissant de chaîner les rôles.

Créer un Rôle avec ansible-galaxy init

Plutôt que de créer manuellement toute cette arborescence, Ansible fournit une commande dédiée qui génère le squelette d'un rôle en un instant :

# Créer un nouveau rôle
ansible-galaxy init mon_role_nginx

# Résultat affiché
# - Role mon_role_nginx was created successfully

Cette commande crée automatiquement tous les répertoires et fichiers main.yml avec un contenu de base. Vous pouvez également spécifier un chemin de destination :

# Créer le rôle dans un répertoire spécifique
ansible-galaxy init --init-path ./roles mon_role_nginx

# Vérifier la structure créée
tree roles/mon_role_nginx/

Par convention, les rôles sont placés dans un répertoire roles/ à la racine de votre projet Ansible. Vous pouvez aussi configurer d'autres chemins via le fichier ansible.cfg :

# ansible.cfg
[defaults]
roles_path = ./roles:~/.ansible/roles:/etc/ansible/roles

Variables par défaut vs Variables de rôle

La distinction entre defaults/main.yml et vars/main.yml est fondamentale et souvent source de confusion pour les débutants. Voici la règle d'or :

defaults/ contient les variables que l'utilisateur du rôle est censé pouvoir modifier. vars/ contient les variables internes au rôle qui ne devraient pas être changées.

Dans la hiérarchie de priorité des variables Ansible (de la plus basse à la plus haute), voici où se situent ces deux types :

  1. defaults/main.yml — priorité la plus basse (facilement surchargeable)
  2. Variables d'inventaire (group_vars, host_vars)
  3. Variables de play (vars: dans le playbook)
  4. vars/main.yml — priorité élevée (difficile à surcharger)
  5. Extra vars (-e sur la ligne de commande) — priorité maximale

En pratique, la grande majorité de vos variables devraient aller dans defaults/main.yml. Réservez vars/main.yml pour les constantes techniques qui ne doivent pas varier (chemins système, noms de paquets spécifiques à l'OS, etc.).

Voici un exemple concret illustrant cette distinction :

# defaults/main.yml — Configurable par l'utilisateur
---
nginx_listen_port: 80
nginx_server_name: localhost
nginx_worker_connections: 1024
nginx_client_max_body_size: "10m"
nginx_custom_locations: []

# vars/main.yml — Constantes internes du rôle
---
nginx_package_name: nginx
nginx_service_name: nginx
nginx_conf_dir: /etc/nginx
nginx_sites_available: /etc/nginx/sites-available
nginx_sites_enabled: /etc/nginx/sites-enabled

Vous pouvez même utiliser des variables conditionnelles basées sur le système d'exploitation, en chargeant dynamiquement des fichiers de variables :

# tasks/main.yml
- name: Charger les variables spécifiques à l'OS
  ansible.builtin.include_vars: "{{ ansible_os_family | lower }}.yml"
# vars/debian.yml
nginx_package_name: nginx
nginx_user: www-data

# vars/redhat.yml
nginx_package_name: nginx
nginx_user: nginx

Dépendances entre Rôles

Les rôles Ansible peuvent déclarer des dépendances vers d'autres rôles via le fichier meta/main.yml. Ces dépendances sont résolues et exécutées automatiquement avant le rôle principal.

# meta/main.yml
---
dependencies:
  # Dépendance simple
  - role: base_system

  # Dépendance avec des variables
  - role: firewall
    vars:
      firewall_allowed_ports:
        - "80/tcp"
        - "443/tcp"

  # Dépendance depuis Galaxy
  - role: geerlingguy.certbot
    when: nginx_ssl_enabled | bool

  # Dépendance avec tag
  - role: monitoring
    tags:
      - monitoring

Quelques points importants à retenir sur les dépendances :

  • Par défaut, un rôle dépendant n'est exécuté qu'une seule fois, même s'il apparaît dans les dépendances de plusieurs rôles. Vous pouvez changer ce comportement avec allow_duplicates: true dans le meta/main.yml du rôle dépendant.
  • Les dépendances sont exécutées avant les tâches du rôle qui les déclare.
  • Les dépendances peuvent elles-mêmes avoir des dépendances, formant une chaîne récursive.
  • Il est possible d'utiliser des conditions (when) pour rendre une dépendance optionnelle.

Appeler un Rôle dans un Playbook

Il existe trois façons d'utiliser un rôle dans un playbook Ansible. Chacune a ses particularités et ses cas d'usage.

Méthode 1 : Le mot-clé roles (classique)

C'est la méthode historique et la plus courante. Les rôles listés sous roles: sont exécutés avant les tâches du play :

# playbook.yml
---
- name: Configurer les serveurs web
  hosts: webservers
  become: true

  roles:
    - role: common
    - role: nginx
      vars:
        nginx_listen_port: 8080
        nginx_server_name: monsite.fr
    - role: monitoring
      tags:
        - monitoring

Méthode 2 : import_role (importation statique)

L'importation statique avec import_role permet d'insérer un rôle au milieu des tâches d'un play. L'importation est résolue au moment du parsing du playbook, avant l'exécution :

# playbook.yml
---
- name: Déploiement complet
  hosts: webservers
  become: true

  tasks:
    - name: Préparer le système
      ansible.builtin.apt:
        update_cache: true

    - name: Importer le rôle nginx
      ansible.builtin.import_role:
        name: nginx
      vars:
        nginx_listen_port: 80

    - name: Vérifier que le service est actif
      ansible.builtin.uri:
        url: "http://localhost"
        status_code: 200

Méthode 3 : include_role (inclusion dynamique)

L'inclusion dynamique avec include_role est évaluée au moment de l'exécution. C'est utile lorsque le rôle à inclure dépend d'une condition ou d'une variable calculée pendant l'exécution :

# playbook.yml
---
- name: Déploiement conditionnel
  hosts: all
  become: true

  tasks:
    - name: Inclure le rôle web si nécessaire
      ansible.builtin.include_role:
        name: nginx
      when: "'webservers' in group_names"

    - name: Inclure un rôle dynamiquement
      ansible.builtin.include_role:
        name: "{{ item }}"
      loop:
        - common
        - security
        - monitoring
Quelle méthode choisir ? Utilisez roles: pour les cas simples et classiques. Préférez import_role quand vous avez besoin de mélanger rôles et tâches dans un ordre précis. Réservez include_role pour les inclusions conditionnelles ou dynamiques.

Exemple Complet : Un Rôle Nginx de Production

Mettons tout cela en pratique avec un rôle Nginx complet et prêt pour la production. Ce rôle gère l'installation, la configuration, les virtual hosts et le SSL.

Initialisation du rôle

ansible-galaxy init roles/nginx

defaults/main.yml

# roles/nginx/defaults/main.yml
---
# Configuration générale
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: "64m"
nginx_gzip_enabled: true

# Virtual hosts
nginx_vhosts:
  - server_name: localhost
    listen_port: 80
    root: /var/www/html
    index: "index.html index.htm"
    locations:
      - path: /
        options: |
          try_files $uri $uri/ =404;

# SSL (désactivé par défaut)
nginx_ssl_enabled: false
nginx_ssl_certificate: ""
nginx_ssl_certificate_key: ""
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"
nginx_ssl_ciphers: "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256"

# Sécurité headers
nginx_security_headers_enabled: true
nginx_hsts_max_age: 31536000

# Gestion du service
nginx_service_enabled: true
nginx_service_state: started

# Page par défaut
nginx_default_page_enabled: true
nginx_default_page_title: "Bienvenue"

vars/main.yml

# roles/nginx/vars/main.yml
---
nginx_package_name: nginx
nginx_service_name: nginx
nginx_conf_dir: /etc/nginx
nginx_conf_file: /etc/nginx/nginx.conf
nginx_sites_available: /etc/nginx/sites-available
nginx_sites_enabled: /etc/nginx/sites-enabled
nginx_log_dir: /var/log/nginx
nginx_pid_file: /run/nginx.pid

tasks/main.yml

# roles/nginx/tasks/main.yml
---
- name: Inclure les tâches d'installation
  ansible.builtin.include_tasks: install.yml

- name: Inclure les tâches de configuration
  ansible.builtin.include_tasks: configure.yml

- name: Inclure les tâches de virtual hosts
  ansible.builtin.include_tasks: vhosts.yml

- name: Inclure les tâches SSL
  ansible.builtin.include_tasks: ssl.yml
  when: nginx_ssl_enabled | bool

- name: Inclure les tâches de service
  ansible.builtin.include_tasks: service.yml

tasks/install.yml

# roles/nginx/tasks/install.yml
---
- name: Mettre à jour le cache APT
  ansible.builtin.apt:
    update_cache: true
    cache_valid_time: 3600

- name: Installer Nginx
  ansible.builtin.apt:
    name: "{{ nginx_package_name }}"
    state: present
  notify: Redémarrer nginx

- name: Créer les répertoires nécessaires
  ansible.builtin.file:
    path: "{{ item }}"
    state: directory
    owner: root
    group: root
    mode: "0755"
  loop:
    - "{{ nginx_sites_available }}"
    - "{{ nginx_sites_enabled }}"
    - "{{ nginx_log_dir }}"
    - /var/www/html

tasks/configure.yml

# roles/nginx/tasks/configure.yml
---
- name: Déployer la configuration principale Nginx
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: "{{ nginx_conf_file }}"
    owner: root
    group: root
    mode: "0644"
    validate: "nginx -t -c %s"
  notify: Recharger nginx

- name: Supprimer la configuration par défaut
  ansible.builtin.file:
    path: "{{ nginx_sites_enabled }}/default"
    state: absent
  notify: Recharger nginx

- name: Déployer la page par défaut
  ansible.builtin.template:
    src: index.html.j2
    dest: /var/www/html/index.html
    owner: www-data
    group: www-data
    mode: "0644"
  when: nginx_default_page_enabled | bool

tasks/vhosts.yml

# roles/nginx/tasks/vhosts.yml
---
- name: Déployer les virtual hosts
  ansible.builtin.template:
    src: vhost.conf.j2
    dest: "{{ nginx_sites_available }}/{{ item.server_name }}.conf"
    owner: root
    group: root
    mode: "0644"
  loop: "{{ nginx_vhosts }}"
  notify: Recharger nginx

- name: Activer les virtual hosts
  ansible.builtin.file:
    src: "{{ nginx_sites_available }}/{{ item.server_name }}.conf"
    dest: "{{ nginx_sites_enabled }}/{{ item.server_name }}.conf"
    state: link
  loop: "{{ nginx_vhosts }}"
  notify: Recharger nginx

tasks/ssl.yml

# roles/nginx/tasks/ssl.yml
---
- name: Créer le répertoire SSL
  ansible.builtin.file:
    path: /etc/nginx/ssl
    state: directory
    owner: root
    group: root
    mode: "0700"

- name: Copier le certificat SSL
  ansible.builtin.copy:
    src: "{{ nginx_ssl_certificate }}"
    dest: /etc/nginx/ssl/server.crt
    owner: root
    group: root
    mode: "0644"
  when: nginx_ssl_certificate | length > 0
  notify: Recharger nginx

- name: Copier la clé privée SSL
  ansible.builtin.copy:
    src: "{{ nginx_ssl_certificate_key }}"
    dest: /etc/nginx/ssl/server.key
    owner: root
    group: root
    mode: "0600"
  when: nginx_ssl_certificate_key | length > 0
  notify: Recharger nginx

- name: Générer les paramètres Diffie-Hellman
  ansible.builtin.command:
    cmd: openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
    creates: /etc/nginx/ssl/dhparam.pem

tasks/service.yml

# roles/nginx/tasks/service.yml
---
- name: Vérifier la configuration Nginx
  ansible.builtin.command:
    cmd: nginx -t
  changed_when: false

- name: Gérer le service Nginx
  ansible.builtin.service:
    name: "{{ nginx_service_name }}"
    state: "{{ nginx_service_state }}"
    enabled: "{{ nginx_service_enabled }}"

handlers/main.yml

# roles/nginx/handlers/main.yml
---
- name: Redémarrer nginx
  ansible.builtin.service:
    name: "{{ nginx_service_name }}"
    state: restarted

- name: Recharger nginx
  ansible.builtin.service:
    name: "{{ nginx_service_name }}"
    state: reloaded

templates/nginx.conf.j2

# roles/nginx/templates/nginx.conf.j2
user www-data;
worker_processes {{ nginx_worker_processes }};
pid {{ nginx_pid_file }};

events {
    worker_connections {{ nginx_worker_connections }};
    multi_accept on;
}

http {
    # Configuration de base
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout {{ nginx_keepalive_timeout }};
    types_hash_max_size 2048;
    client_max_body_size {{ nginx_client_max_body_size }};

    # Types MIME
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    access_log {{ nginx_log_dir }}/access.log;
    error_log {{ nginx_log_dir }}/error.log;

{% if nginx_gzip_enabled %}
    # Compression Gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types
        text/plain
        text/css
        text/xml
        application/json
        application/javascript
        application/xml
        application/rss+xml
        image/svg+xml;
{% endif %}

{% if nginx_security_headers_enabled %}
    # En-têtes de sécurité
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
{% if nginx_ssl_enabled %}
    add_header Strict-Transport-Security "max-age={{ nginx_hsts_max_age }}; includeSubDomains" always;
{% endif %}
{% endif %}

    # Virtual hosts
    include {{ nginx_sites_enabled }}/*;
}

templates/vhost.conf.j2

# roles/nginx/templates/vhost.conf.j2
server {
    listen {{ item.listen_port }};
    server_name {{ item.server_name }};
    root {{ item.root }};
    index {{ item.index | default('index.html index.htm') }};

{% if nginx_ssl_enabled and item.listen_port | int == 443 %}
    listen 443 ssl;
    ssl_certificate /etc/nginx/ssl/server.crt;
    ssl_certificate_key /etc/nginx/ssl/server.key;
    ssl_protocols {{ nginx_ssl_protocols }};
    ssl_ciphers {{ nginx_ssl_ciphers }};
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
{% endif %}

    # Logging par virtual host
    access_log {{ nginx_log_dir }}/{{ item.server_name }}_access.log;
    error_log {{ nginx_log_dir }}/{{ item.server_name }}_error.log;

{% for location in item.locations | default([]) %}
    location {{ location.path }} {
        {{ location.options | indent(8) }}
    }

{% endfor %}
    # Interdire l'accès aux fichiers cachés
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

templates/index.html.j2

# roles/nginx/templates/index.html.j2
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ nginx_default_page_title }}</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; padding: 50px; }
        h1 { color: #333; }
    </style>
</head>
<body>
    <h1>{{ nginx_default_page_title }}</h1>
    <p>Serveur {{ inventory_hostname }} configuré avec Ansible.</p>
</body>
</html>

Utilisation du rôle dans un playbook

# site.yml
---
- name: Déployer les serveurs web
  hosts: webservers
  become: true

  roles:
    - role: nginx
      vars:
        nginx_listen_port: 80
        nginx_server_name: codeclan.fr
        nginx_worker_connections: 2048
        nginx_gzip_enabled: true
        nginx_ssl_enabled: true
        nginx_ssl_certificate: files/certs/codeclan.crt
        nginx_ssl_certificate_key: files/certs/codeclan.key
        nginx_vhosts:
          - server_name: codeclan.fr
            listen_port: 80
            root: /var/www/codeclan
            index: "index.html"
            locations:
              - path: /
                options: |
                  try_files $uri $uri/ =404;
              - path: /api
                options: |
                  proxy_pass http://127.0.0.1:3000;
                  proxy_set_header Host $host;
                  proxy_set_header X-Real-IP $remote_addr;

Bonnes Pratiques pour les Rôles Ansible

Conventions de nommage

Adoptez des conventions cohérentes pour vos rôles :

  • Utilisez des noms en minuscules avec des underscores : nginx_reverse_proxy, pas Nginx-Reverse-Proxy.
  • Préfixez les variables du rôle avec le nom du rôle : nginx_listen_port, pas simplement listen_port. Cela évite les collisions de noms entre rôles.
  • Nommez explicitement chaque tâche avec un verbe d'action : "Installer Nginx", "Déployer la configuration", "Activer le service".

Documentation

Chaque rôle devrait inclure un README.md documentant :

  • La description du rôle et son objectif.
  • Les prérequis et les plateformes supportées.
  • La liste complète des variables avec leurs valeurs par défaut.
  • Des exemples d'utilisation dans un playbook.
  • Les dépendances requises.
  • La licence et les informations de l'auteur.

Idempotence

Vos tâches doivent être idempotentes : exécuter le rôle plusieurs fois doit produire le même résultat sans effet de bord. Évitez les modules command et shell quand un module dédié existe. Si vous devez les utiliser, pensez aux paramètres creates, removes et changed_when.

Tests avec Molecule

Molecule est l'outil de test standard pour les rôles Ansible. Il permet de tester vos rôles dans des conteneurs Docker ou des machines virtuelles de manière automatisée :

# Installer Molecule avec le driver Docker
pip install molecule molecule-docker

# Initialiser les tests Molecule pour un rôle existant
cd roles/nginx
molecule init scenario --driver-name docker

# Lancer les tests
molecule test

# Cycle de test complet :
# 1. molecule create    — crée l'instance de test
# 2. molecule converge  — applique le rôle
# 3. molecule idempotence — vérifie l'idempotence
# 4. molecule verify    — lance les vérifications
# 5. molecule destroy   — détruit l'instance

Le fichier molecule/default/molecule.yml définit l'environnement de test :

# molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: instance-ubuntu
    image: ubuntu:22.04
    pre_build_image: true
    privileged: true
    command: /sbin/init
  - name: instance-debian
    image: debian:12
    pre_build_image: true
    privileged: true
    command: /sbin/init
provisioner:
  name: ansible
verifier:
  name: ansible

Et le fichier molecule/default/verify.yml contient les assertions de vérification :

# molecule/default/verify.yml
---
- name: Vérifier le rôle nginx
  hosts: all
  gather_facts: false

  tasks:
    - name: Vérifier que Nginx est installé
      ansible.builtin.command: nginx -v
      changed_when: false

    - name: Vérifier que le service Nginx est actif
      ansible.builtin.service_facts:

    - name: Asserter que Nginx est en cours d'exécution
      ansible.builtin.assert:
        that:
          - "'nginx.service' in services"
          - "services['nginx.service'].state == 'running'"

    - name: Vérifier que le port 80 est en écoute
      ansible.builtin.wait_for:
        port: 80
        timeout: 5

Organisation d'un Projet avec des Rôles

Pour un projet Ansible bien structuré utilisant des rôles, voici l'arborescence recommandée :

mon_projet_ansible/
├── ansible.cfg              # Configuration Ansible
├── inventory/
│   ├── production/
│   │   ├── hosts.yml        # Inventaire de production
│   │   ├── group_vars/
│   │   │   ├── all.yml
│   │   │   └── webservers.yml
│   │   └── host_vars/
│   │       └── web01.yml
│   └── staging/
│       ├── hosts.yml
│       └── group_vars/
│           └── all.yml
├── playbooks/
│   ├── site.yml             # Playbook principal
│   ├── webservers.yml       # Playbook serveurs web
│   └── databases.yml        # Playbook bases de données
├── roles/
│   ├── common/              # Rôle de base commun
│   ├── nginx/               # Rôle Nginx
│   ├── postgresql/          # Rôle PostgreSQL
│   ├── security/            # Rôle durcissement sécurité
│   └── monitoring/          # Rôle surveillance
└── requirements.yml         # Dépendances Galaxy

Le playbook principal site.yml orchestre l'ensemble :

# playbooks/site.yml
---
- name: Appliquer la configuration commune
  hosts: all
  become: true
  roles:
    - common
    - security

- name: Configurer les serveurs web
  hosts: webservers
  become: true
  roles:
    - nginx
    - monitoring

- name: Configurer les bases de données
  hosts: databases
  become: true
  roles:
    - postgresql
    - monitoring

Conclusion

Les rôles Ansible sont un pilier fondamental de toute automatisation professionnelle. Ils transforment des playbooks monolithiques en composants modulaires, réutilisables et testables. En suivant les conventions de structure, en séparant clairement les variables par défaut des variables internes, et en documentant chaque rôle, vous construisez une base d'automatisation solide et maintenable.

Le rôle Nginx que nous avons construit ensemble illustre les bonnes pratiques : séparation des tâches en fichiers logiques, utilisation de templates Jinja2, variables par défaut configurables, handlers pour les rechargements, et tests avec Molecule. Ce même patron peut être appliqué à n'importe quel logiciel ou composant d'infrastructure.

Dans le prochain article, nous explorerons Ansible Galaxy, la plateforme communautaire qui vous permet de partager vos rôles et de réutiliser ceux créés par la communauté. Vous découvrirez comment tirer parti de milliers de rôles et collections prêts à l'emploi pour accélérer considérablement vos projets d'automatisation.

Vous vous êtes abonné avec succès à CodeClan
Parfait ! Ensuite, complétez le paiement pour obtenir un accès complet à tout le contenu premium.
Erreur ! Impossible de s'inscrire. Lien invalide.
Bienvenue ! Vous vous êtes connecté avec succès.
Erreur ! Impossible de se connecter. Veuillez réessayer.
Succès ! Votre compte est entièrement activé, vous avez maintenant accès à tout le contenu.
Erreur ! Le paiement Stripe a échoué.
Succès ! Vos informations de facturation sont mises à jour.
Erreur ! La mise à jour des informations de facturation a échoué.