Ansible pour les sysadmins : automatiser sans tout casser

Guide pratique Ansible pour administrateurs système : inventaire, playbooks, modules essentiels, rôles et bonnes pratiques pour automatiser votre infrastructure Linux en toute confiance.

Soyons honnêtes : on a tous un dossier scripts/ rempli de fichiers bash écrits à la va-vite, avec des sed imbriqués, des if sans fi qui traînent, et des commentaires du type « ne pas toucher, ça marche ». Le jour où il faut déployer la même config sur 15 serveurs, on copie-colle en SSH comme des artisans du Moyen Âge numérique. Et quand ça casse, personne ne sait ce qui a changé, ni quand, ni pourquoi.

Ansible règle ce problème. Pas de client à installer sur les machines cibles, pas de base de données centrale, pas de langage ésotérique à apprendre. Du YAML, du SSH, et une logique d'idempotence qui garantit que relancer un playbook dix fois produit toujours le même résultat. C'est l'outil que tout sysadmin devrait maîtriser avant de prétendre faire du DevOps.

Ce guide est pragmatique. On part de zéro, on monte en puissance, et on termine avec une organisation de projet propre. Pas de théorie abstraite : du code, des exemples concrets, et les erreurs classiques à éviter.

L'inventaire, la base de tout

L'inventaire Ansible décrit votre infrastructure : quelles machines existent, comment les joindre, et comment les regrouper logiquement. Sans inventaire bien structuré, vous ne faites que du SSH en boucle avec des étapes supplémentaires.

Format INI : simple et lisible

Le format INI reste le plus courant pour les petites infrastructures. Chaque section entre crochets définit un groupe :

[webservers]
web01.example.com ansible_host=192.168.1.10
web02.example.com ansible_host=192.168.1.11

[databases]
db01.example.com ansible_host=192.168.1.20 ansible_port=2222

[production:children]
webservers
databases

[production:vars]
ansible_user=deploy
ansible_python_interpreter=/usr/bin/python3

La directive :children permet de créer des groupes de groupes. La directive :vars applique des variables à tous les membres d'un groupe. C'est la base de l'organisation hiérarchique.

Format YAML : pour les infrastructures complexes

Le format YAML offre plus de flexibilité quand l'inventaire grossit :

all:
  children:
    webservers:
      hosts:
        web01.example.com:
          ansible_host: 192.168.1.10
          http_port: 8080
        web02.example.com:
          ansible_host: 192.168.1.11
          http_port: 80
    databases:
      hosts:
        db01.example.com:
          ansible_host: 192.168.1.20
      vars:
        db_engine: postgresql
        db_port: 5432

Quel que soit le format choisi, testez votre inventaire avec ansible-inventory --list -i inventory.yml pour vérifier que la résolution des groupes et des variables est correcte.

Premier playbook : sécuriser un serveur

Un playbook Ansible est un fichier YAML qui décrit l'état souhaité de vos machines. Voici un premier playbook concret qui applique un durcissement de base sur un serveur Debian/Ubuntu fraîchement installé :

---
- name: Durcissement de base du serveur
  hosts: all
  become: true
  vars:
    admin_user: deploy
    ssh_port: 22
    allowed_ssh_keys:
      - "ssh-ed25519 AAAAC3... admin@workstation"

  tasks:
    - name: Mettre à jour le cache APT et upgrader les paquets
      ansible.builtin.apt:
        update_cache: true
        upgrade: safe
        cache_valid_time: 3600

    - name: Installer les paquets de base
      ansible.builtin.apt:
        name:
          - ufw
          - fail2ban
          - unattended-upgrades
          - curl
          - htop
        state: present

    - name: Créer l'utilisateur d'administration
      ansible.builtin.user:
        name: "{{ admin_user }}"
        groups: sudo
        shell: /bin/bash
        create_home: true
        state: present

    - name: Déployer les clés SSH autorisées
      ansible.posix.authorized_key:
        user: "{{ admin_user }}"
        key: "{{ item }}"
        state: present
      loop: "{{ allowed_ssh_keys }}"

    - name: Désactiver l'authentification par mot de passe SSH
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PasswordAuthentication"
        line: "PasswordAuthentication no"
        validate: "sshd -t -f %s"
      notify: restart sshd

    - name: Désactiver le login root par SSH
      ansible.builtin.lineinfile:
        path: /etc/ssh/sshd_config
        regexp: "^#?PermitRootLogin"
        line: "PermitRootLogin no"
        validate: "sshd -t -f %s"
      notify: restart sshd

    - name: Configurer UFW - politique par défaut
      community.general.ufw:
        state: enabled
        policy: deny
        direction: incoming

    - name: Autoriser SSH dans UFW
      community.general.ufw:
        rule: allow
        port: "{{ ssh_port }}"
        proto: tcp

  handlers:
    - name: restart sshd
      ansible.builtin.service:
        name: sshd
        state: restarted

Exécutez ce playbook avec la commande suivante :

ansible-playbook -i inventory.ini harden.yml --diff --check

L'option --check simule l'exécution sans rien modifier. L'option --diff affiche les changements qui seraient appliqués. Toujours tester en dry-run avant d'appliquer sur la production.

Les modules essentiels

Ansible dispose de milliers de modules, mais une poignée couvre 90% des besoins quotidiens d'un sysadmin. Voici ceux que vous utiliserez le plus :

apt : gestion des paquets

Le module apt gère l'installation, la mise à jour et la suppression de paquets Debian/Ubuntu. L'option cache_valid_time évite de relancer un apt update à chaque exécution :

- name: Installer nginx en version précise
  ansible.builtin.apt:
    name: nginx=1.24.*
    state: present
    update_cache: true
    cache_valid_time: 3600

template : fichiers de configuration dynamiques

Le module template utilise Jinja2 pour générer des fichiers de configuration à partir de variables. C'est infiniment plus propre que des sed en cascade :

- name: Déployer la configuration nginx
  ansible.builtin.template:
    src: templates/nginx.conf.j2
    dest: /etc/nginx/sites-available/{{ domain }}.conf
    owner: root
    group: root
    mode: "0644"
    validate: "nginx -t -c %s"
  notify: reload nginx

copy, service, lineinfile

Le module copy transfère des fichiers statiques. Le module service gère l'état des services systemd. Le module lineinfile modifie une ligne précise dans un fichier existant, ce qui est idéal pour les ajustements ponctuels sans réécrire tout le fichier :

- name: Activer le forwarding IPv4
  ansible.builtin.lineinfile:
    path: /etc/sysctl.conf
    regexp: "^#?net.ipv4.ip_forward"
    line: "net.ipv4.ip_forward = 1"
  notify: reload sysctl

- name: S'assurer que nginx est démarré et activé
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

Variables et facts

La puissance d'Ansible réside dans sa gestion des variables. Elles permettent de factoriser la configuration et d'adapter le comportement en fonction de chaque machine ou groupe.

group_vars et host_vars

Ansible charge automatiquement les variables depuis des fichiers organisés par convention :

inventory/
├── hosts.yml
├── group_vars/
│   ├── all.yml          # Variables globales
│   ├── webservers.yml   # Variables du groupe webservers
│   └── databases.yml    # Variables du groupe databases
└── host_vars/
    └── db01.example.com.yml  # Variables spécifiques à un hôte

L'ordre de priorité est strict : les variables d'hôte écrasent celles de groupe, qui écrasent celles de all. Gardez ce mécanisme en tête pour éviter les surprises.

Facts et register

Ansible collecte automatiquement des informations sur chaque machine cible (les facts). Vous pouvez les exploiter dans vos conditions et templates :

- name: Installer le paquet selon la distribution
  ansible.builtin.apt:
    name: "{{ pkg_name }}"
    state: present
  when: ansible_facts['os_family'] == 'Debian'

- name: Vérifier l'espace disque disponible
  ansible.builtin.command: df -h /
  register: disk_usage
  changed_when: false

- name: Alerter si espace disque critique
  ansible.builtin.debug:
    msg: "Attention : espace disque critique sur {{ inventory_hostname }}"
  when: disk_usage.stdout is search('9[0-9]%|100%')

Le mot-clé register capture la sortie d'une tâche dans une variable réutilisable. Combiné avec when, il permet de créer des workflows conditionnels puissants. Notez l'usage de changed_when: false pour indiquer qu'une commande de lecture ne modifie rien.

Handlers et idempotence

L'idempotence est le principe fondamental d'Ansible : une tâche ne s'exécute que si l'état actuel diffère de l'état souhaité. C'est ce qui permet de relancer un playbook sans crainte.

Les handlers : réagir aux changements

Un handler est une tâche déclenchée uniquement quand une autre tâche signale un changement via notify. Cas typique : redémarrer nginx après modification de sa configuration, mais uniquement si la configuration a réellement changé :

tasks:
  - name: Déployer le virtualhost
    ansible.builtin.template:
      src: vhost.conf.j2
      dest: /etc/nginx/sites-available/monsite.conf
    notify:
      - validate nginx
      - reload nginx

handlers:
  - name: validate nginx
    ansible.builtin.command: nginx -t
    changed_when: false

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

Contrôle fin avec changed_when et failed_when

Certaines commandes retournent toujours « changed » même quand rien n'a bougé. Les directives changed_when et failed_when permettent d'affiner le comportement :

- name: Vérifier si le certificat SSL expire bientôt
  ansible.builtin.command: >
    openssl x509 -checkend 2592000
    -in /etc/ssl/certs/{{ domain }}.pem
  register: cert_check
  changed_when: false
  failed_when: false

- name: Renouveler le certificat si nécessaire
  ansible.builtin.command: certbot renew --cert-name {{ domain }}
  when: cert_check.rc != 0

Sans changed_when: false, chaque exécution du playbook afficherait un changement fictif sur la tâche de vérification. Ce genre de bruit parasite rend les rapports d'exécution illisibles et masque les vrais changements.

Rôles et organisation du projet

Quand un playbook dépasse 200 lignes, il est temps de le découper en rôles. Un rôle est un module réutilisable qui encapsule tâches, handlers, templates, variables et fichiers autour d'une responsabilité unique.

Créer un rôle avec ansible-galaxy

ansible-galaxy init roles/hardening
# Crée la structure suivante :
roles/hardening/
├── defaults/main.yml     # Variables par défaut (priorité basse)
├── files/                # Fichiers statiques à copier
├── handlers/main.yml     # Handlers du rôle
├── meta/main.yml         # Métadonnées et dépendances
├── tasks/main.yml        # Tâches principales
├── templates/            # Templates Jinja2
└── vars/main.yml         # Variables du rôle (priorité haute)

Structure d'un projet complet

Voici l'organisation recommandée pour un projet Ansible de taille moyenne :

ansible-project/
├── ansible.cfg
├── inventory/
│   ├── production/
│   │   ├── hosts.yml
│   │   ├── group_vars/
│   │   └── host_vars/
│   └── staging/
│       ├── hosts.yml
│       └── group_vars/
├── playbooks/
│   ├── site.yml            # Playbook principal
│   ├── webservers.yml
│   └── databases.yml
├── roles/
│   ├── common/
│   ├── hardening/
│   ├── nginx/
│   └── postgresql/
└── requirements.yml        # Rôles Galaxy externes

Le fichier ansible.cfg à la racine du projet centralise la configuration locale :

[defaults]
inventory = inventory/production/hosts.yml
roles_path = roles
retry_files_enabled = false
stdout_callback = yaml

[privilege_escalation]
become = true
become_method = sudo

[ssh_connection]
pipelining = true
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

L'activation du pipelining réduit significativement le temps d'exécution en diminuant le nombre de connexions SSH nécessaires.

Erreurs courantes et debugging

Même avec un outil idempotent, on fait des erreurs. Voici les pièges classiques et les outils pour les diagnostiquer.

Les niveaux de verbosité

# Verbosité croissante
ansible-playbook site.yml -v      # Affiche les résultats des tâches
ansible-playbook site.yml -vv     # Affiche les paramètres des modules
ansible-playbook site.yml -vvv    # Affiche les connexions SSH
ansible-playbook site.yml -vvvv   # Affiche tout, y compris les scripts injectés

Le mode check et diff

Toujours valider avant d'appliquer. Le duo --check --diff est votre filet de sécurité :

# Simulation avec affichage des différences
ansible-playbook site.yml --check --diff

# Limiter à un groupe ou un hôte
ansible-playbook site.yml --limit webservers --check --diff

# Limiter à une tâche spécifique par tag
ansible-playbook site.yml --tags "firewall" --check --diff

Les erreurs les plus fréquentes

Erreur : « Undefined variable » -- Vous utilisez une variable qui n'existe pas dans le contexte actuel. Vérifiez la hiérarchie group_vars / host_vars / defaults et utilisez le filtre | default('valeur') pour les variables optionnelles.
Erreur : « Permission denied » -- Oubli de become: true dans le playbook ou dans la tâche. Si le sudo nécessite un mot de passe, ajoutez --ask-become-pass à la commande.
Erreur : « Module not found » -- Le module appartient à une collection qui n'est pas installée. Installez-la avec ansible-galaxy collection install community.general.

Un dernier conseil de debugging : le module ansible.builtin.debug est votre meilleur allié. Utilisez-le pour inspecter les variables et les facts en cours d'exécution :

- name: Inspecter les variables pour debug
  ansible.builtin.debug:
    msg: |
      Hostname : {{ inventory_hostname }}
      OS : {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}
      IP : {{ ansible_facts['default_ipv4']['address'] }}
      RAM : {{ ansible_facts['memtotal_mb'] }} Mo

Conclusion

Ansible transforme l'administration système artisanale en ingénierie reproductible. On récapitule les points essentiels :

  • L'inventaire structure votre infrastructure en groupes logiques avec des variables hiérarchisées.
  • Les playbooks décrivent l'état souhaité de vos machines de manière déclarative et idempotente.
  • Les modules apt, template, service, lineinfile et copy couvrent la majorité des besoins quotidiens.
  • Les variables et facts permettent d'adapter le comportement à chaque machine sans dupliquer le code.
  • Les handlers garantissent que les services ne redémarrent que quand c'est nécessaire.
  • Les rôles découpent la complexité en modules réutilisables et testables.
  • Le mode check/diff est votre filet de sécurité avant toute modification en production.

Commencez petit : un playbook qui gère vos clés SSH et votre configuration de base. Puis étendez progressivement. Chaque tâche manuelle que vous convertissez en Ansible est une tâche que vous n'aurez plus jamais à refaire à la main, ni à expliquer à votre collègue un vendredi soir.

La documentation officielle Ansible reste la référence la plus complète et la plus à jour pour approfondir chaque module et chaque concept abordé dans cet article.

Cet article vous a plu ?

Commentaires

Morgann Riu
Morgann Riu

Expert en cybersécurité et administration Linux. J'aide les entreprises à sécuriser et optimiser leurs infrastructures critiques.

Retour au blog

Checklist Sécurité Linux

30 points essentiels pour sécuriser un serveur Linux. Recevez aussi les nouveaux tutoriels par email.

Pas de spam. Désabonnement en 1 clic.