Ansible — Gestion de configuration#

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.patches as patches
import numpy as np
import pandas as pd
import seaborn as sns
import re
from collections import defaultdict

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

Pourquoi la gestion de configuration#

Dérive de configuration#

Lorsqu’une infrastructure grandit, les serveurs administrés manuellement divergent inévitablement. Un administrateur applique un correctif sur trois machines mais en oublie une quatrième. Un collègue modifie un paramètre noyau en urgence sans le documenter. Six mois plus tard, personne ne sait pourquoi le serveur de production se comporte différemment des serveurs de staging.

Ce phénomène s’appelle la dérive de configuration (configuration drift). Il se manifeste par :

  • des différences subtiles de versions entre environnements,

  • des fichiers de configuration locaux non tracés,

  • des cron jobs ajoutés à la main et jamais supprimés,

  • des paquets installés manuellement qui n’apparaissent dans aucun registre.

Snowflake servers#

Un serveur devenu unique par accumulation de modifications manuelles est appelé un snowflake server (serveur flocon de neige). Chaque flocon est irremplaçable, fragile, et opaque. Personne n’ose le toucher. S’il tombe, la restauration prend des jours.

L’antidote est le phoenix server : une machine que l’on peut détruire et reconstruire en quelques minutes à partir de la définition en code.

Infrastructure as Code#

L”Infrastructure as Code (IaC) consiste à décrire l’état désiré de l’infrastructure dans des fichiers versionnable et rejouables. Les avantages sont nombreux :

  • Reproductibilité : le même code produit le même résultat sur n’importe quelle machine cible.

  • Traçabilité : chaque changement est un commit git avec auteur et message.

  • Idempotence : rejouer le code n’a pas d’effet de bord si l’état est déjà atteint.

  • Revue de code : les changements d’infrastructure passent par une pull request.

Ansible est l’un des outils IaC les plus populaires. Il se distingue par sa simplicité : pas d’agent à installer, pas de serveur maître complexe, un inventaire de fichiers texte et des playbooks YAML lisibles.

Architecture Ansible#

Control node et managed nodes#

L’architecture Ansible repose sur deux rôles :

  • Control node : la machine depuis laquelle Ansible est exécuté. Elle porte le code, les inventaires, les playbooks et les rôles. Ansible doit y être installé (Python ≥ 3.8).

  • Managed nodes : les machines cibles. Elles n’ont besoin que d’un accès SSH et d’un interpréteur Python (généralement préinstallé).

Agentless et SSH#

La particularité d’Ansible est d’être agentless : aucun démon ne tourne sur les managed nodes. Ansible se connecte en SSH, copie un module Python dans un répertoire temporaire, l’exécute, récupère le résultat JSON et supprime le fichier temporaire. Cette simplicité facilite l’adoption et élimine une surface d’attaque.

Prérequis SSH

Les managed nodes doivent être accessibles par SSH sans mot de passe interactif (clé publique déposée) ou via un bastion. L’utilisateur SSH doit disposer des droits sudo si les tâches nécessitent une élévation de privilèges (become: true).

Inventaire statique et dynamique#

L”inventaire liste les hôtes gérés. En mode statique, c’est un fichier INI ou YAML. En mode dynamique, Ansible interroge une API (AWS EC2, GCP, Azure, vSphere, Netbox…) pour construire l’inventaire à la volée.

Inventaire#

Format INI#

# inventaire/hosts.ini

[webservers]
web01.example.com
web02.example.com ansible_user=deploy

[dbservers]
db01.example.com ansible_port=2222
db02.example.com

[prod:children]
webservers
dbservers

[prod:vars]
ansible_user=ansible
ansible_become=true

Format YAML#

# inventaire/hosts.yml
all:
  children:
    webservers:
      hosts:
        web01.example.com:
        web02.example.com:
          ansible_user: deploy
    dbservers:
      hosts:
        db01.example.com:
          ansible_port: 2222
        db02.example.com:
      vars:
        ansible_user: ansible
        ansible_become: true

Variables de groupe et d’hôte#

Les variables peuvent être définies dans des répertoires dédiés :

inventaire/
├── hosts.yml
├── group_vars/
│   ├── all.yml          # variables pour tous les hôtes
│   ├── webservers.yml   # variables pour le groupe webservers
│   └── dbservers.yml
└── host_vars/
    ├── web01.example.com.yml
    └── db01.example.com.yml

Visualiser l’inventaire#

ansible-inventory -i inventaire/hosts.yml --graph

Sortie :

@all:
  |--@prod:
  |  |--@webservers:
  |  |  |--web01.example.com
  |  |  |--web02.example.com
  |  |--@dbservers:
  |  |  |--db01.example.com
  |  |  |--db02.example.com
  |--@ungrouped:

Hide code cell source

# Visualisation d'un inventaire Ansible INI simulé → graphe de topologie

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(10, 6))
ax.set_xlim(0, 10)
ax.set_ylim(0, 8)
ax.axis("off")
ax.set_title("Topologie d'inventaire Ansible", fontsize=14, fontweight="bold", pad=15)

# Définition des noeuds
noeuds = {
    "all":         (5.0, 7.2),
    "prod":        (2.5, 5.8),
    "staging":     (7.5, 5.8),
    "webservers":  (1.2, 4.2),
    "dbservers":   (3.8, 4.2),
    "web01":       (0.4, 2.5),
    "web02":       (1.8, 2.5),
    "db01":        (3.2, 2.5),
    "db02":        (4.5, 2.5),
    "stg-web":     (6.8, 4.2),
    "stg-web01":   (6.4, 2.5),
    "stg-web02":   (7.8, 2.5),
}

couleurs = {
    "all": "#4C72B0",
    "prod": "#55A868", "staging": "#C44E52",
    "webservers": "#8172B2", "dbservers": "#8172B2", "stg-web": "#CCB974",
    "web01": "#64B5CD", "web02": "#64B5CD",
    "db01": "#64B5CD", "db02": "#64B5CD",
    "stg-web01": "#64B5CD", "stg-web02": "#64B5CD",
}

aretes = [
    ("all", "prod"), ("all", "staging"),
    ("prod", "webservers"), ("prod", "dbservers"),
    ("staging", "stg-web"),
    ("webservers", "web01"), ("webservers", "web02"),
    ("dbservers", "db01"), ("dbservers", "db02"),
    ("stg-web", "stg-web01"), ("stg-web", "stg-web02"),
]

for src, dst in aretes:
    x1, y1 = noeuds[src]
    x2, y2 = noeuds[dst]
    ax.plot([x1, x2], [y1, y2], color="#AAAAAA", linewidth=1.5, zorder=1)

for nom, (x, y) in noeuds.items():
    c = couleurs[nom]
    r = 0.35 if nom in ("all", "prod", "staging") else 0.28
    cercle = plt.Circle((x, y), r, color=c, zorder=2, ec="white", linewidth=1.5)
    ax.add_patch(cercle)
    ax.text(x, y, nom, ha="center", va="center", fontsize=7.5,
            color="white", fontweight="bold", zorder=3)

legende = [
    mpatches.Patch(color="#4C72B0", label="Groupe racine"),
    mpatches.Patch(color="#55A868", label="Groupe prod"),
    mpatches.Patch(color="#C44E52", label="Groupe staging"),
    mpatches.Patch(color="#8172B2", label="Sous-groupe"),
    mpatches.Patch(color="#64B5CD", label="Hôte"),
]
ax.legend(handles=legende, loc="lower right", fontsize=8)
plt.savefig("19_inventaire_topologie.png", dpi=100, bbox_inches="tight")
plt.show()
_images/e955f9f4e8456c274f84d75070f12bfef9f103e3ca14fefc240fdc2e527cb661.png

Modules essentiels#

Les modules sont les unités d’action d’Ansible. Chaque tâche appelle un module avec des paramètres. Ansible en propose plus de 3 000 ; voici les incontournables.

command et shell#

- name: Vérifier l'uptime
  ansible.builtin.command: uptime

- name: Lister les processus avec pipeline
  ansible.builtin.shell: ps aux | grep nginx | wc -l
  register: nb_nginx

command n’interprète pas le shell (pas de |, >, &&). shell exécute via /bin/sh et supporte les redirections. Préférer command quand possible pour la sécurité.

copy et template#

- name: Copier un fichier statique
  ansible.builtin.copy:
    src: files/nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: "0644"

- name: Déployer un template Jinja2
  ansible.builtin.template:
    src: templates/vhost.conf.j2
    dest: /etc/nginx/sites-available/{{ site_name }}.conf
    mode: "0644"
  notify: Recharger nginx

file#

- name: Créer un répertoire
  ansible.builtin.file:
    path: /var/www/{{ site_name }}
    state: directory
    owner: www-data
    group: www-data
    mode: "0755"

- name: Supprimer un fichier
  ansible.builtin.file:
    path: /tmp/ancien_fichier
    state: absent

user#

- name: Créer un utilisateur applicatif
  ansible.builtin.user:
    name: appuser
    shell: /bin/bash
    groups: sudo
    append: true
    create_home: true

service et package#

- name: Installer nginx
  ansible.builtin.package:
    name: nginx
    state: present

- name: Démarrer et activer nginx
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

package est un module générique qui délègue à apt, yum, dnf selon la distribution détectée. On peut aussi appeler ansible.builtin.apt directement pour bénéficier de paramètres spécifiques comme update_cache: true.

État vs action

Les modules Ansible expriment un état désiré, pas une action. state: started signifie « assure-toi que le service est démarré », pas « démarre-le ». Si le service tourne déjà, Ansible ne fait rien et rapporte ok au lieu de changed.

Playbooks#

Structure YAML#

---
- name: Configurer les serveurs web
  hosts: webservers
  become: true
  vars:
    site_name: monapp
    http_port: 80

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

  tasks:
    - name: Installer nginx
      ansible.builtin.apt:
        name: nginx
        state: present
        update_cache: true

    - name: Déployer la configuration
      ansible.builtin.template:
        src: templates/nginx.conf.j2
        dest: /etc/nginx/sites-available/{{ site_name }}.conf
      notify: Recharger nginx

    - name: Activer le site
      ansible.builtin.file:
        src: /etc/nginx/sites-available/{{ site_name }}.conf
        dest: /etc/nginx/sites-enabled/{{ site_name }}.conf
        state: link
      notify: Recharger nginx

Conditions et boucles#

- name: Installer des paquets supplémentaires (Debian uniquement)
  ansible.builtin.apt:
    name: "{{ item }}"
    state: present
  loop:
    - vim
    - htop
    - curl
  when: ansible_os_family == "Debian"

- name: Créer plusieurs utilisateurs
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
  loop:
    - { name: alice, groups: sudo }
    - { name: bob,   groups: dev  }

register et debug#

- name: Lire la version de Python
  ansible.builtin.command: python3 --version
  register: py_version

- name: Afficher la version
  ansible.builtin.debug:
    msg: "Python détecté : {{ py_version.stdout }}"

Exécuter un playbook#

ansible-playbook -i inventaire/hosts.yml site.yml
ansible-playbook -i inventaire/hosts.yml site.yml --limit webservers
ansible-playbook -i inventaire/hosts.yml site.yml --tags configuration

Hide code cell source

# Heatmap d'un run Ansible simulé : changed/ok/failed par hôte

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

hotes = ["web01", "web02", "web03", "db01", "db02", "cache01"]
etats = ["ok", "changed", "unreachable", "failed", "skipped"]

np.random.seed(42)
data = np.array([
    [12, 4, 0, 0, 2],
    [12, 3, 0, 0, 2],
    [12, 5, 0, 0, 2],
    [8,  2, 0, 0, 1],
    [8,  2, 0, 1, 1],
    [5,  0, 1, 0, 3],
])

df = pd.DataFrame(data, index=hotes, columns=etats)

cmap = sns.diverging_palette(10, 130, s=75, l=50, n=9, as_cmap=True)

fig, ax = plt.subplots(figsize=(9, 5))
sns.heatmap(
    df, annot=True, fmt="d", cmap="YlOrRd_r",
    linewidths=0.5, linecolor="white",
    cbar_kws={"label": "Nombre de tâches"},
    ax=ax
)
ax.set_title("Résultat d'un run Ansible par hôte", fontsize=13, fontweight="bold", pad=12)
ax.set_xlabel("État des tâches", fontsize=11)
ax.set_ylabel("Hôte", fontsize=11)
ax.tick_params(axis="x", rotation=0)
ax.tick_params(axis="y", rotation=0)
plt.savefig("19_run_heatmap.png", dpi=100, bbox_inches="tight")
plt.show()
_images/1acb0a4645490a8e83eeba39ecd5194d85220b1bc583de76d8c4091473da537f.png

Variables et templates Jinja2#

Hiérarchie de priorité des variables#

Ansible applique une hiérarchie stricte de priorité (du plus faible au plus fort) : defaultsgroup_vars/allgroup_vars/<groupe>host_vars/<hôte>vars dans le play → set_fact → options -e en ligne de commande.

group_vars et host_vars#

# group_vars/webservers.yml
nginx_worker_processes: 4
nginx_keepalive_timeout: 65
site_root: /var/www/html

# host_vars/web01.example.com.yml
nginx_worker_processes: 8   # override pour cette machine plus puissante

Templates Jinja2#

Les templates .j2 utilisent la syntaxe Jinja2 : {{ variable }} pour l’interpolation, {% if %} / {% for %} pour la logique.

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

events {
    worker_connections 1024;
}

http {
    keepalive_timeout {{ nginx_keepalive_timeout }};

    {% for vhost in virtual_hosts %}
    server {
        listen 80;
        server_name {{ vhost.name }};
        root {{ vhost.root | default(site_root) }};
    }
    {% endfor %}
}

Filtres Jinja2 courants#

- name: Mettre en majuscule
  debug:
    msg: "{{ 'hello' | upper }}"          # HELLO

- name: Valeur par défaut
  debug:
    msg: "{{ ma_var | default('none') }}"

- name: Joindre une liste
  debug:
    msg: "{{ ['a','b','c'] | join(',') }}" # a,b,c

Rôles#

Structure d’un rôle#

Un rôle est une unité de réutilisation qui regroupe tâches, handlers, templates, fichiers et variables dans une structure conventionnelle.

roles/nginx/
├── defaults/
│   └── main.yml        # variables par défaut (priorité minimale)
├── handlers/
│   └── main.yml        # handlers déclenchés par notify
├── tasks/
│   └── main.yml        # tâches principales
├── templates/
│   └── nginx.conf.j2   # templates Jinja2
├── files/
│   └── index.html      # fichiers statiques
├── vars/
│   └── main.yml        # variables internes (priorité haute)
└── meta/
    └── main.yml        # dépendances, metadata galaxy

Utiliser un rôle dans un playbook#

---
- name: Configurer les serveurs web
  hosts: webservers
  become: true
  roles:
    - nginx
    - { role: certbot, site_name: monapp.fr }

ansible-galaxy#

# Installer un rôle depuis Ansible Galaxy
ansible-galaxy install geerlingguy.nginx

# Initialiser la structure d'un nouveau rôle
ansible-galaxy role init mon_role

# Installer depuis un fichier requirements.yml
ansible-galaxy install -r requirements.yml
# requirements.yml
roles:
  - name: geerlingguy.nginx
    version: "3.2.0"
  - name: geerlingguy.postgresql
collections:
  - name: community.postgresql
    version: ">=2.0.0"

Idempotence#

Principe#

L’idempotence est la propriété fondamentale d’un playbook Ansible : rejouer le playbook plusieurs fois produit exactement le même résultat que de l’exécuter une seule fois. Si l’état désiré est déjà atteint, Ansible ne modifie rien et rapporte ok.

Cette propriété est garantie par les modules bien écrits. Le module apt vérifie si le paquet est installé avant de l’installer. Le module file vérifie les permissions avant de les modifier.

Idempotence et commandes brutes

Les modules command et shell ne sont pas idempotents par nature. Il faut les combiner avec creates: ou when: pour éviter les exécutions répétées.

# Non idempotent — crée le répertoire même s'il existe
- ansible.builtin.command: mkdir /opt/app

# Idempotent — crée uniquement si absent
- ansible.builtin.command:
    cmd: mkdir /opt/app
    creates: /opt/app

Mode –check et –diff#

# Simulation sans modification (dry-run)
ansible-playbook site.yml --check

# Afficher les différences de fichiers modifiés
ansible-playbook site.yml --check --diff

La sortie --diff montre les deltas de fichiers comme un diff -u :

--- before: /etc/nginx/nginx.conf
+++ after: /etc/nginx/nginx.conf
@@ -1,3 +1,3 @@
 worker_processes 4;
-keepalive_timeout 65;
+keepalive_timeout 75;

Vault#

Chiffrement de secrets#

Ansible Vault permet de chiffrer des fichiers de variables ou des chaînes individuelles contenant des mots de passe, clés API, certificats, etc.

# Chiffrer un fichier entier
ansible-vault encrypt group_vars/all/secrets.yml

# Déchiffrer pour édition
ansible-vault edit group_vars/all/secrets.yml

# Chiffrer une valeur inline
ansible-vault encrypt_string 'MonMotDePasse!' --name 'db_password'

La commande encrypt_string produit un bloc YAML utilisable directement :

db_password: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  62613833623333366565393431356166303366636662313564373630613531
  ...

Utilisation dans les playbooks#

# Fournir le mot de passe vault interactivement
ansible-playbook site.yml --ask-vault-pass

# Fournir via un fichier (pour CI/CD)
ansible-playbook site.yml --vault-password-file ~/.vault_pass

# Fichier .vault_pass sécurisé
chmod 600 ~/.vault_pass
echo 'MotDePasseVault' > ~/.vault_pass

Vault en production

Ne jamais stocker ~/.vault_pass dans git. Dans un pipeline CI/CD, injecter le mot de passe vault comme variable secrète (GitLab CI, GitHub Actions Secrets, Jenkins credentials). Ansible Vault n’est pas un gestionnaire de secrets complet ; pour des besoins avancés, intégrer HashiCorp Vault via le plugin community.hashi_vault.

Hide code cell source

# Diagramme de flux d'un playbook Ansible (flowchart matplotlib)

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

fig, ax = plt.subplots(figsize=(8, 11))
ax.set_xlim(0, 8)
ax.set_ylim(0, 12)
ax.axis("off")
ax.set_title("Flux d'exécution d'un playbook Ansible", fontsize=13,
             fontweight="bold", pad=12)

def boite(ax, x, y, w, h, texte, couleur="#4C72B0", fontsize=9):
    rect = patches.FancyBboxPatch(
        (x - w/2, y - h/2), w, h,
        boxstyle="round,pad=0.08", linewidth=1.2,
        edgecolor="#333333", facecolor=couleur
    )
    ax.add_patch(rect)
    ax.text(x, y, texte, ha="center", va="center", fontsize=fontsize,
            color="white", fontweight="bold", wrap=True,
            multialignment="center")

def losange(ax, x, y, w, h, texte, couleur="#C44E52"):
    dx, dy = w/2, h/2
    poly = plt.Polygon(
        [(x, y+dy), (x+dx, y), (x, y-dy), (x-dx, y)],
        closed=True, facecolor=couleur, edgecolor="#333333", linewidth=1.2
    )
    ax.add_patch(poly)
    ax.text(x, y, texte, ha="center", va="center", fontsize=8.5,
            color="white", fontweight="bold", multialignment="center")

def fleche(ax, x1, y1, x2, y2):
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle="->", color="#555555", lw=1.5))

# Noeuds
boite(ax, 4, 11.2, 3.2, 0.7, "ansible-playbook site.yml", couleur="#2d6a9f")
fleche(ax, 4, 10.85, 4, 10.35)
boite(ax, 4, 10.0, 3.2, 0.6, "Lecture de l'inventaire", couleur="#4C72B0")
fleche(ax, 4, 9.7, 4, 9.2)
boite(ax, 4, 8.9, 3.2, 0.6, "Collecte des facts (setup)", couleur="#4C72B0")
fleche(ax, 4, 8.6, 4, 8.1)
losange(ax, 4, 7.7, 3.2, 0.7, "when: condition ?")
# Branche non
ax.annotate("", xy=(6.2, 7.7), xytext=(5.6, 7.7),
            arrowprops=dict(arrowstyle="->", color="#555555", lw=1.5))
ax.text(5.9, 7.85, "Non", fontsize=8, color="#C44E52")
boite(ax, 6.9, 7.7, 1.4, 0.55, "Skipped", couleur="#8d8d8d")
# Branche oui
fleche(ax, 4, 7.35, 4, 6.85)
ax.text(4.12, 7.1, "Oui", fontsize=8, color="#55A868")
boite(ax, 4, 6.5, 3.2, 0.6, "Exécuter le module", couleur="#55A868")
fleche(ax, 4, 6.2, 4, 5.65)
losange(ax, 4, 5.25, 3.0, 0.7, "Changement\neffectué ?")
# Branche changed
ax.annotate("", xy=(6.2, 5.25), xytext=(5.5, 5.25),
            arrowprops=dict(arrowstyle="->", color="#555555", lw=1.5))
ax.text(5.75, 5.42, "Oui", fontsize=8, color="#C44E52")
boite(ax, 6.9, 5.25, 1.4, 0.55, "notify\nhandler", couleur="#C44E52", fontsize=8)
# Branche ok
fleche(ax, 4, 4.9, 4, 4.35)
ax.text(4.12, 4.6, "Non", fontsize=8, color="#55A868")
losange(ax, 4, 3.95, 3.0, 0.7, "Tâches\nsuivantes ?")
fleche(ax, 4, 3.6, 4, 3.05)
ax.text(4.12, 3.3, "Non", fontsize=8)
boite(ax, 4, 2.7, 3.2, 0.6, "Exécuter les handlers notifiés", couleur="#8172B2")
fleche(ax, 4, 2.4, 4, 1.85)
boite(ax, 4, 1.5, 3.2, 0.65, "Résumé du run\n(ok / changed / failed)", couleur="#2d6a9f", fontsize=8.5)

# Boucle "tâches suivantes"
ax.annotate("", xy=(1.8, 6.5), xytext=(2.5, 3.95),
            arrowprops=dict(arrowstyle="->", color="#aaaaaa", lw=1.2,
                            connectionstyle="arc3,rad=-0.4"))
ax.text(1.1, 5.1, "Tâche\nsuivante", fontsize=7.5, color="#888888")

plt.savefig("19_playbook_flowchart.png", dpi=100, bbox_inches="tight")
plt.show()
_images/aa1b664efc888e8dc309a069688021f10cc982543a9affa96b4ee6c5d3cfb4a5.png

Hide code cell source

# Comparaison Ansible vs Chef vs Puppet vs Salt

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

criteres = ["Courbe\nd'apprentissage", "Agentless", "Langage\nconfig",
            "Performance\n(nb hosts)", "Idempotence\nnative", "Communauté"]

# Scores de 1 à 5
donnees = {
    "Ansible": [5, 5, 4, 3, 4, 5],
    "Chef":    [2, 1, 3, 4, 4, 4],
    "Puppet":  [2, 1, 4, 4, 5, 4],
    "Salt":    [3, 3, 3, 5, 4, 3],
}

x = np.arange(len(criteres))
width = 0.2
fig, ax = plt.subplots(figsize=(11, 5))

palette = sns.color_palette("muted", 4)
for i, (outil, scores) in enumerate(donnees.items()):
    ax.bar(x + i * width, scores, width, label=outil, color=palette[i],
           edgecolor="white", linewidth=0.8)

ax.set_xticks(x + width * 1.5)
ax.set_xticklabels(criteres, fontsize=9.5)
ax.set_yticks(range(1, 6))
ax.set_yticklabels(["1\n(faible)", "2", "3", "4", "5\n(élevé)"], fontsize=9)
ax.set_ylabel("Score (1 = faible, 5 = élevé)", fontsize=10)
ax.set_title("Comparaison des outils de gestion de configuration", fontsize=13,
             fontweight="bold", pad=12)
ax.legend(fontsize=10)
ax.set_ylim(0, 6)

plt.savefig("19_comparaison_outils.png", dpi=100, bbox_inches="tight")
plt.show()
_images/ca249d1db788d906aeacff064dda1403ca0f4153c79b5bb3ef9fa105461c844d.png

Résumé#

Ansible rend l’administration de parcs hétérogènes reproductible et auditable. Les concepts clés à retenir :

Concept

Rôle

Inventaire

Liste structurée des hôtes et de leurs variables

Module

Unité d’action idempotente (apt, copy, template…)

Playbook

Orchestration de tâches sur un ou plusieurs groupes

Rôle

Unité de réutilisation packagée

Handler

Action déclenchée une seule fois sur notification

Vault

Chiffrement symétrique des secrets

–check

Simulation sans effet de bord (dry-run)

La philosophie d’Ansible peut se résumer ainsi : décrire l”état désiré du système, pas la séquence d’opérations pour y parvenir. Cette distinction entre approche déclarative et impérative est fondamentale en IaC.

Prochaine étape

Le chapitre suivant aborde la virtualisation et les conteneurs : KVM/libvirt pour les VMs complètes, LXC/LXD pour les conteneurs système, et la base technologique (namespaces, cgroups) sur laquelle repose Docker.