Ansible pour debutants : automatiser sa config serveur

Credit : Logo officiel

Ansible pour debutants : automatiser sa config serveur

Dylan D. — Agent Support Technique Serveur DevOps 2050 mots 11 min de lecture

Reinstaller 3 serveurs identiques en 20 minutes au lieu de 3 jours

La semaine derniere j'ai du remonter trois VPS IONOS strictement identiques pour un client : meme distrib (Debian 12), meme stack (Nginx + PHP 8.2 + MariaDB + Redis), meme securisation (CrowdSec, Fail2ban, SSH durci, certificats SSL). Sans automatisation j'aurais facilement passe une journee complete a configurer chaque serveur a la main. Avec Ansible, j'ai branche les trois IPs dans mon inventaire, lance le playbook a 14h32, et a 14h53 les trois serveurs etaient prets a recevoir leur application. Cafe compris.

C'est ca le pouvoir d'Ansible. C'est un outil d'automatisation qui se connecte en SSH, execute des taches Python, et garantit que vos serveurs convergent vers l'etat decrit dans vos playbooks. Pas d'agent a installer cote serveur (juste Python qui est deja la sur toute distribution Linux moderne), pas d'infrastructure complexe a maintenir, juste un dossier git avec vos fichiers YAML et c'est parti.

Dans ce guide je couvre l'installation, l'inventaire, les premiers playbooks utiles, l'organisation en roles, et les modules que j'utilise au quotidien. C'est exactement la progression que je conseille a mes collegues qui demarrent.

Pourquoi Ansible plutot qu'autre chose

Il existe d'autres outils : Puppet, Chef, Salt, et plus recemment des trucs comme Pulumi pour l'infra cloud. Ansible a quelques avantages pratiques :

Le seul vrai concurrent moderne c'est Saltstack, mais Ansible reste largement plus repandu donc plus de tutos, plus d'exemples sur Stack Overflow, plus de roles pre-faits sur Ansible Galaxy.

Installation d'Ansible

Ansible s'installe sur votre machine locale (votre laptop ou un serveur de management dedie), PAS sur les serveurs cibles. Les serveurs cibles n'ont besoin de rien de special.

# Sur Debian/Ubuntu local
sudo apt update
sudo apt install pipx
pipx install ansible-core
pipx install ansible-lint

# Verifier la version
ansible --version
# ansible [core 2.16.3]
#   python version = 3.11.2

pipx isole Ansible dans son propre venv Python, ca evite les conflits de dependances avec d'autres outils Python systeme. C'est la methode recommandee depuis 2024.

Sur macOS :

brew install ansible

Sur les serveurs cibles, verifiez juste que Python 3 est installe :

ssh deploy@web1 "python3 --version"
# Python 3.11.2

L'inventaire : la liste de vos serveurs

L'inventaire c'est un fichier YAML qui liste vos serveurs et les groupe par role. Creez inventory.yml dans un dossier dedie :

all:
  children:
    webservers:
      hosts:
        web1:
          ansible_host: 203.0.113.10
          ansible_user: deploy
        web2:
          ansible_host: 203.0.113.11
          ansible_user: deploy
      vars:
        domain_name: monsite.fr
        php_version: "8.2"

    databases:
      hosts:
        db1:
          ansible_host: 203.0.113.20
          ansible_user: deploy
      vars:
        mariadb_buffer_pool: 2G

    production:
      children:
        webservers:
        databases:

  vars:
    ansible_python_interpreter: /usr/bin/python3
    ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'

Les groupes peuvent contenir d'autres groupes (production contient webservers et databases), ce qui permet de cibler des sous-ensembles dans vos playbooks.

Testez la connectivite avec le module ping :

ansible all -i inventory.yml -m ping

Resultat attendu :

web1 | SUCCESS => { "ping": "pong" }
web2 | SUCCESS => { "ping": "pong" }
db1  | SUCCESS => { "ping": "pong" }

Si vous avez du rouge, c'est presque toujours un probleme de cle SSH. Verifiez avec un ssh deploy@web1 direct, et confirmez que vous arrivez sans mot de passe (avec authentification par cle).

Le fichier ansible.cfg

A la racine du projet, creez ansible.cfg pour eviter de retaper les options a chaque commande :

[defaults]
inventory = ./inventory.yml
host_key_checking = False
stdout_callback = yaml
bin_ansible_callbacks = True
retry_files_enabled = False
forks = 10

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

pipelining = True accelere significativement Ansible (parfois 2x plus rapide). Pour que ca marche, il faut que requiretty soit desactive dans sudoers cote serveur, ce qui est le cas par defaut sur Debian/Ubuntu modernes.

Votre premier playbook

Un playbook decrit l'etat souhaite de vos serveurs. Creez setup-web.yml :

---
- name: Configuration de base des serveurs web
  hosts: webservers
  become: yes
  gather_facts: yes

  vars:
    packages:
      - nginx
      - php8.2-fpm
      - php8.2-mysql
      - php8.2-curl
      - php8.2-xml
      - php8.2-mbstring
      - php8.2-zip
      - php8.2-gd
      - php8.2-imagick
      - certbot
      - python3-certbot-nginx
      - fail2ban
      - unattended-upgrades

  tasks:
    - name: Mettre a jour le cache APT
      apt:
        update_cache: yes
        cache_valid_time: 3600

    - name: Mettre a jour tous les paquets
      apt:
        upgrade: dist

    - name: Installer les paquets requis
      apt:
        name: "{{ packages }}"
        state: present

    - name: Demarrer et activer Nginx
      systemd:
        name: nginx
        state: started
        enabled: yes

    - name: Demarrer et activer PHP-FPM
      systemd:
        name: "php{{ php_version }}-fpm"
        state: started
        enabled: yes

    - name: Creer le repertoire du site
      file:
        path: "/var/www/{{ domain_name }}/public"
        state: directory
        owner: deploy
        group: www-data
        mode: '0755'

    - name: Deployer la config Nginx du site
      template:
        src: templates/nginx-site.conf.j2
        dest: "/etc/nginx/sites-available/{{ domain_name }}.conf"
        owner: root
        group: root
        mode: '0644'
      notify: Recharger Nginx

    - name: Activer le site
      file:
        src: "/etc/nginx/sites-available/{{ domain_name }}.conf"
        dest: "/etc/nginx/sites-enabled/{{ domain_name }}.conf"
        state: link
      notify: Recharger Nginx

    - name: Tester la config Nginx
      command: nginx -t
      changed_when: false

  handlers:
    - name: Recharger Nginx
      systemd:
        name: nginx
        state: reloaded

Lancez :

ansible-playbook setup-web.yml

L'idempotence c'est la cle : vous pouvez relancer ce playbook 100 fois, Ansible ne reinstalle pas Nginx s'il est deja installe, ne recree pas le repertoire s'il existe deja, ne recharge Nginx que si la config a effectivement change. Ca rend les operations sures.

Le mode dry-run

Avant d'appliquer en prod, simulez :

# Dry-run : montre ce qui changerait sans rien faire
ansible-playbook setup-web.yml --check

# Avec diff sur les fichiers modifies
ansible-playbook setup-web.yml --check --diff

# Limiter a une machine seulement
ansible-playbook setup-web.yml --limit web1

Le --diff montre exactement les changements ligne par ligne sur les fichiers de config. Indispensable pour valider avant de pousser en prod.

Les templates Jinja2

Les templates permettent de generer des fichiers de config avec des variables. Creez templates/nginx-site.conf.j2 :

server {
    listen 80;
    listen [::]:80;
    server_name {{ domain_name }} www.{{ domain_name }};

    root /var/www/{{ domain_name }}/public;
    index index.php index.html;

    access_log /var/log/nginx/{{ domain_name }}.access.log;
    error_log /var/log/nginx/{{ domain_name }}.error.log;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php{{ php_version }}-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.ht {
        deny all;
    }

    {% if enable_gzip | default(true) %}
    gzip on;
    gzip_types text/plain text/css application/javascript application/json image/svg+xml;
    gzip_vary on;
    {% endif %}
}

Les {{ variable }} sont remplacees par leur valeur depuis l'inventaire ou les vars du playbook. Les {% if %} permettent de la logique conditionnelle. Tres puissant pour generer des configs adaptees au context.

Organiser le code en roles

Quand vos playbooks grossissent, regroupez la logique en roles reutilisables. Structure standard :

project/
  ansible.cfg
  inventory.yml
  site.yml                 # Playbook principal
  group_vars/
    all.yml
    webservers.yml
  roles/
    common/
      tasks/main.yml
      handlers/main.yml
      defaults/main.yml
    nginx/
      tasks/main.yml
      templates/
        nginx.conf.j2
        site.conf.j2
      handlers/main.yml
      defaults/main.yml
    php/
      tasks/main.yml
      defaults/main.yml
    mariadb/
      tasks/main.yml
      defaults/main.yml

Un role minimal roles/nginx/tasks/main.yml :

---
- name: Installer Nginx
  apt:
    name: nginx
    state: present

- name: Deployer la config principale
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    backup: yes
  notify: Tester et recharger Nginx

- name: S'assurer que Nginx tourne
  systemd:
    name: nginx
    state: started
    enabled: yes

roles/nginx/handlers/main.yml :

---
- name: Tester et recharger Nginx
  block:
    - name: Test config
      command: nginx -t
      changed_when: false
    - name: Reload
      systemd:
        name: nginx
        state: reloaded

roles/nginx/defaults/main.yml :

---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_client_max_body_size: 64m

Utiliser le role dans site.yml :

---
- hosts: webservers
  become: yes
  roles:
    - common
    - nginx
    - php
    - certbot
    - fail2ban

Les defaults peuvent etre surcharges depuis group_vars/webservers.yml ou directement dans le playbook. C'est la flexibilite du systeme.

Modules essentiels au quotidien

Voici les modules que j'utilise quasi tous les jours, avec un exemple pour chacun :

# Gestion de paquets
- name: Installer plusieurs paquets
  apt:
    name: [git, vim, htop, ncdu]
    state: present
    update_cache: yes

# Fichiers et repertoires
- name: Creer un repertoire
  file:
    path: /var/www/monsite
    state: directory
    owner: deploy
    group: www-data
    mode: '0755'

- name: Copier un fichier
  copy:
    src: files/script.sh
    dest: /usr/local/bin/script.sh
    mode: '0755'

- name: Modifier une ligne dans un fichier (ULTRA UTILE)
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^#?PermitRootLogin'
    line: 'PermitRootLogin no'
  notify: Restart sshd

- name: Modifier un bloc de lignes
  blockinfile:
    path: /etc/sysctl.conf
    block: |
      net.ipv4.tcp_syncookies = 1
      net.ipv4.conf.all.rp_filter = 1
      net.ipv6.conf.all.disable_ipv6 = 1

# Utilisateurs
- name: Creer un user de deploiement
  user:
    name: deploy
    shell: /bin/bash
    groups: sudo,www-data
    append: yes
    create_home: yes

- name: Ajouter une cle SSH
  authorized_key:
    user: deploy
    state: present
    key: "{{ lookup('file', '~/.ssh/id_ed25519.pub') }}"

# Cron
- name: Backup quotidien
  cron:
    name: 'Backup MySQL quotidien'
    hour: '3'
    minute: '0'
    job: '/usr/local/bin/mysql-backup.sh'
    user: root

# Services
- name: S'assurer qu'un service tourne
  systemd:
    name: redis-server
    state: started
    enabled: yes

# Telechargement
- name: Telecharger WP-CLI
  get_url:
    url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
    dest: /usr/local/bin/wp
    mode: '0755'

# Git
- name: Cloner un repo
  git:
    repo: 'https://github.com/user/projet.git'
    dest: /var/www/projet
    version: main
    force: yes

Le module lineinfile est mon prefere. Il permet de modifier UNE ligne dans un fichier de config sans tout reecrire. Parfait pour durcir SSH, modifier php.ini, ajouter des entries dans /etc/hosts, etc.

Variables, vault et secrets

Ne mettez JAMAIS de mot de passe en clair dans vos playbooks. Utilisez Ansible Vault :

# Creer un fichier de secrets chiffre
ansible-vault create group_vars/all/vault.yml
# (entrer un mot de passe maitre)

# Editer
ansible-vault edit group_vars/all/vault.yml

# Le contenu (chiffre sur disque)
vault_db_password: "MotDePasseTresSolide2026!"
vault_admin_email: "admin@monsite.fr"

Utilisation dans les playbooks :

vars:
  mariadb_root_password: "{{ vault_db_password }}"

Lancement avec le mot de passe vault :

ansible-playbook site.yml --ask-vault-pass
# ou pointer vers un fichier de mot de passe
ansible-playbook site.yml --vault-password-file ~/.ansible-vault-pass

Le fichier vault peut etre commit dans git en toute securite, le mot de passe maitre reste hors du depot.

Erreurs courantes et leur fix

"UNREACHABLE" lors du ansible all -m ping. C'est presque toujours SSH. Verifiez avec ssh deploy@web1 directement. Si ca demande un mot de passe, copiez votre cle avec ssh-copy-id. Si l'host n'est pas connu, ajoutez -o StrictHostKeyChecking=accept-new dans ansible.cfg.

"sudo: a password is required". Le user de deploiement n'a pas sudo NOPASSWD. Soit configurez /etc/sudoers.d/deploy avec deploy ALL=(ALL) NOPASSWD: ALL, soit lancez avec --ask-become-pass (-K) pour entrer le mot de passe sudo.

Module Python manquant sur le serveur cible. Certains modules Ansible necessitent des bibliotheques Python sur la cible (ex: mysql_user necessite python3-pymysql). Installez-les avec une task apt avant.

"failed - retrying: ...". Reseau instable ou serveur surcharge. Augmentez les timeouts dans ansible.cfg :

[defaults]
timeout = 60
[ssh_connection]
ssh_args = -o ConnectTimeout=30 -o ServerAliveInterval=30

Vault refuse de dechiffrer. Mauvais mot de passe ou fichier corrompu. Verifiez avec ansible-vault view puis re-chiffrez si necessaire.

Pour aller plus loin

Conclusion : 30 minutes investies, 30 heures economisees

Ansible parait overkill quand on a un seul serveur a configurer. Effectivement, pour une seule machine que vous touchez deux fois par an, taper les commandes a la main reste plus rapide. Mais des que vous avez deux serveurs a maintenir (ce qui est tres vite le cas), ou que vous voulez pouvoir reconstruire rapidement en cas de probleme, Ansible devient indispensable.

Le vrai declic, c'est quand vous realisez que votre playbook devient une documentation vivante de votre infrastructure. Quelqu'un peut reprendre la gestion de vos serveurs juste en lisant vos fichiers YAML. Ca change la donne pour la perennite des projets, surtout en agence ou les equipes tournent.

Commencez petit : un playbook qui installe Nginx et PHP. Ajoutez progressivement. Six mois plus tard vous aurez 15 roles, vous deploierez une nouvelle infrastructure complete en 20 minutes, et vous vous demanderez comment vous faisiez avant. Promesse de quelqu'un qui a fait le chemin.

# Articles similaires

Sur les memes sujets et plus loin