Comprendre et utiliser Ansible pour l'automatisation de l'infrastructure

Principe de fonctionnement

Le processus central d'Ansible suit ces étapes :

  1. Le module est chargé en mémoire.
  2. Les arguments lui sont ajoutés.
  3. Les classes requises par le module sont instanciées.
  4. Le tout est compressé en archive ZIP puis encodé en Base64 dans un fichier temporaire.
  5. Une connexion SSH est établie avec l'hôte distant pour créer un répertoire temporaire, puis la connexion est fermée.
  6. Une seconde connexion SSH transfère le fichier temporaire via SFTP dans le répertoire créé.
  7. Une troisième connexion rend le fichier exécutable et lance son exécution.
  8. Une dernière connexion exécute le module et supprime le répertoire temporaire.
  9. Le résultat, sous forme de chaîne JSON, est extrait de la sortie standard.
  10. L'état de la tâche est vérifié périodiquement jusqu'à sa completion ou un dépassement de délai.

On peut optimiser le processus en utilisant le mode pipeline pour réduire le nombre de connexions. Pour l'activer :

# Dans /etc/ansible/ansible.cfg
[ssh_connection]
pipelining = True

Architecture

Pour lister les plugins de rappel (callback) activés :

ansible-doc -t callback -l

Installation

Ansible peut être installé via le gestionnaire de paquets ou les sources.

# Via le gestionnaire de paquets
yum install ansible

# Via les sources
yum install python-jinja2 PyYAML python-paramiko python-babel python-crypto
wget https://releases.ansible.com/ansible/ansible-latest.tar.gz
tar xzf ansible-latest.tar.gz
cd ansible-latest
python setup.py build
python setup.py install
mkdir -p /etc/ansible
cp -r examples/* /etc/ansible

Liste d'inventaire (Inventory)

L'inventaire permet de catégoriser les hôtes gérés. Il supporte plusieurs formats.

# Fichier hosts
# Hôte unique
192.168.1.10

# Groupe
[serveurs_web]
192.168.1.11
192.168.1.12

# Plage
[kubernetes]
192.168.1.[13:15]

# Hôte avec port non standard
192.168.1.16:2222

# Super-groupe
[infra]
serveurs_web
kubernetes

# Variables d'hôte pour la personnalisation
[serveurs_web:vars]
remote_user=admin
ansible_ssh_private_key_file=~/.ssh/id_ed25519
shell_type=bash
python_path=/usr/bin/python3

Outils en ligne de commande

ansible-doc

Cet outil fournit la documentation sur les modules et plugins.

# Lister tous les modules disponibles
ansible-doc -l

# Obtenir de l'aide sur un module spécifique
ansible-doc copy

# Afficher un exemple d'utilisation
ansible-doc -s yum

ansible-vault

Outil pour chiffrer et déchiffrer les fichiers de configuraton.

ansible-vault encrypt secrets.yml
ansible-vault decrypt secrets.yml
ansible-vault edit secrets.yml
ansible-vault create new_secrets.yml

ansible-playbook

Cet outil exécute les playbooks, qui orchestrent l'application de plusieurs modules.

ansible

L'outil d'exécution ad-hoc pour des commandes ponctuelles.

# Exécuter un ping sur tous les hôtes
ansible all -m ping

# Spécifier un fichier d'inventaire
ansible -i mon_inventaire.ini groupe_cible -m shell -a "uptime"

# Utiliser une authentification par mot de passe
ansible hote_cible -k -m setup

# Limiter la parallélisation
ansible all -m ping -f 3
Configuration pour l'authentification par mot de passe

Éditez /etc/ansible/ansible.cfg pour désactiver la vérification de clé :

[defaults]
host_key_checking = False

Dans le fichier d'inventaire :

192.168.1.10 ansible_ssh_pass="monMotDePasse"
Formater la cible

La cible (pattern) peut prendre pluseiurs formes :

# Tous les hôtes
ansible "*" -m ping

# Motif
ansible "192.168.1.*" -m ping

# Multiples hôtes
ansible "192.168.1.10 192.168.1.11" -m ping

# Union de groupes
ansible "serveurs_web:kubernetes" -m ping

# Intersection
ansible "serveurs_web:&kubernetes" -m ping

# Exclusion
ansible "serveurs_web:!kubernetes" -m ping

# Expression rationnelle
ansible "~(web|db).*" -m ping

ansible-galaxy

Gestionnaire pour les rôles partagés. Le dépôt est disponible sur galaxy.ansible.com.

# Initialiser un nouveau rôle
ansible-galaxy init mon_role

# Rechercher des rôles
ansible-galaxy search apache

# Installer un rôle
ansible-galaxy role install geerlingguy.apache

# Supprimer un rôle
ansible-galaxy role remove geerlingguy.apache

Fichier de configuration principal

Le fichier ansible.cfg peut être placé au niveau du projet pour une priorité maximale.

[defaults]
inventory = ./hosts.ini
forks = 10
remote_user = admin
log_path = ./ansible.log
module_name = shell
roles_path = ./roles

[privilege_escalation]
become = true
become_user = root
become_method = sudo

[stdout_callback]
# Pour un rendu JSON
stdout_callback = json
# Activer les callbacks personnalisés
callback_whitelist = timer, profile_tasks

Modules principaux

La liste complète est accessible via ansible-doc -l.

shell

Exécute une commande sur l'hôte distant via le shell par défaut.

script

Exécute un script local sur l'hôte distant. Ce module n'est pas idempotent.

copy

Copie un fichier ou un répertoire de la machine de contrôle vers l'hôte distant.

- name: Copier un fichier de configuration
  copy:
    src: ./configs/nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: www-data
    mode: '0644'

get_url

Télécharge un fichier depuis une URL sur l'hôte distant.

- name: Télécharger un binaire
  get_url:
    url: "https://example.com/app.tar.gz"
    dest: "/tmp/app.tar.gz"
    checksum: "sha256:a1b2c3..."
    timeout: 30

fetch

Récupère un fichier depuis l'hôte distant vers la machine de contrôle.

- name: Récupérer les journaux
  fetch:
    src: "/var/log/app.log"
    dest: "./collected_logs/{{ inventory_hostname }}/"
    flat: yes

file

Configure les attributs des fichiers et répertoires (existence, permissions, liens).

- name: Créer un répertoire d'application
  file:
    path: /opt/myapp/data
    state: directory
    owner: app_user
    group: app_group
    mode: '0755'

- name: Créer un lien symbolique
  file:
    src: /opt/myapp/current
    dest: /var/www/html/app
    state: link

stat

Vérifie les métadonnées d'un fichier ou système de fichiers.

unarchive

Décompresse une archive sur l'hôte distant. Elle peut provenir de la machine de contrôle ou d'une URL.

archive

Crée une archive à partir de fichiers sur l'hôte distant.

hostname

Modifie le nom d'hôte de la machine cible.

cron

Gère les tâches planifiées (crontab).

- name: Planifier une sauvegarde
  cron:
    name: "Backup de la base de données"
    minute: "0"
    hour: "2"
    job: "/usr/local/bin/backup_db.sh"
    user: admin

yum / apt

Gère les paquets logiciels.

# Exemple avec apt (Debian/Ubuntu)
- name: Installer des paquets
  apt:
    name:
      - nginx
      - postgresql-client
    state: present
    update_cache: yes

service

Gère l'état des services système.

- name: S'assurer que nginx est démarré
  service:
    name: nginx
    state: started
    enabled: yes

lineinfile / replace

Modifie le contenu des fichiers de manière ciblée.

- name: Ajouter une entrée dans /etc/hosts
  lineinfile:
    path: /etc/hosts
    line: "192.168.1.10 monserveur.local"
    state: present

- name: Modifier une configuration
  replace:
    path: /etc/app/config.yml
    regexp: '^timeout: .*$'
    replace: 'timeout: 30'

user / group

Gère les comptes utilisateurs et les groupes.

setup

Collecte les faits (facts) sur l'hôte distant. Peut être désactivé avec gather_facts: false au niveau du playbook.

debug

Affiche des messages dans la sortie standard pour le débogage.

Playbooks

Les playbooks sont des fichiers YAML qui décrivent l'état désiré de l'infrastructure.

Structure de base

---
# Exemple de playbook
- name: Configurer les serveurs web
  hosts: serveurs_web
  become: yes
  vars:
    http_port: 80
    app_version: "2.1.0"

  tasks:
    - name: Installer les dépendances
      apt:
        name: "{{ item }}"
        state: present
      loop:
        - nginx
        - python3-venv

    - name: Déployer l'application
      copy:
        src: "app-{{ app_version }}.tar.gz"
        dest: /opt/

    - name: Démarrer le service
      service:
        name: nginx
        state: restarted
      notify: "Redémarrer l'application"

Composants clés

hosts & remote_user

Définit les hôtes cibles et l'utilisateur de connexion.

tasks & handlers

tasks liste les actions à exécuter. handlers sont des tâches déclenchées par des notifications.

  handlers:
    - name: Redémarrer l'application
      service:
        name: myapp
        state: restarted

Conditions et boucles

when permet l'exécution conditionnelle. loop itère sur une liste.

    - name: Installer un paquet spécifique à CentOS
      yum:
        name: epel-release
        state: present
      when: ansible_distribution == "CentOS"

    - name: Créer plusieurs utilisateurs
      user:
        name: "{{ item }}"
        state: present
      loop:
        - alice
        - bob
        - charlie

Étiquettes (Tags)

Permettent d'exécuter un sous-ensemble de tâches.

    - name: Déployer le code
      copy:
        src: ./app
        dest: /opt/app
      tags: [déploiement, code]

# Exécution ciblée : ansible-playbook mon_playbook.yml --tags "déploiement"

Inclusion et rôles

  tasks:
    - name: Inclure des tâches spécifiques
      include_tasks: taches_supplementaires.yml
      vars:
        param: "valeur"

  roles:
    - common
    - role: nginx
      vars:
        nginx_port: 8080

Templates Jinja2

Les templates permettent de générer des fichiers de configuration dynamiques.

Structure du projet :

mon_projet/
├── playbook.yml
└── templates/
    └── nginx.conf.j2

Exemple de template (nginx.conf.j2) :

server {
    listen {{ http_port }};
    server_name {{ ansible_hostname }};
    location / {
        proxy_pass http://localhost:{{ app_internal_port }};
    }
}

Appel dans le playbook :

    - name: Déployer la configuration Nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/default
        validate: nginx -t -c %s

Rôles

Les rôles sont une manière de structurer les playbooks en composants réutilisables.

Structure d'un rôle :

roles/
└── mon_role/
    ├── defaults/   # Variables par défaut
    ├── handlers/   # Handlers
    ├── tasks/      # Tâches principales
    ├── templates/  # Fichiers template
    ├── files/      # Fichiers statiques
    └── vars/       # Variables du rôle

Utilisation dans un playbook :

- name: Appliquer la configuration de base
  hosts: all
  roles:
    - common
    - role: securite
      vars:
        niveau: strict

Développement de plugins

Plugin de rappel (Callback)

Les plugins de rappel personnalisent le traitement des événements d'exécution.

# Fichier: callback_plugins/custom_logger.py
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import json
from ansible.plugins.callback import CallbackBase

DOCUMENTATION = '''
    callback: custom_logger
    type: stdout
    short_description: Enregistre les événements dans un fichier
    version_added: "2.9"
    description:
        - Ce plugin écrit les sorties playbook dans un fichier journal par hôte.
    options:
        log_dir:
            description: Répertoire des fichiers journaux.
            default: /var/log/ansible_custom
            type: str
            env:
                - name: CUSTOM_LOG_DIR
            ini:
                - section: callback_custom_logger
                  key: log_dir
'''

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'stdout'
    CALLBACK_NAME = 'custom_logger'
    CALLBACK_NEEDS_WHITELIST = True

    def __init__(self):
        super(CallbackModule, self).__init__()
        self._log_enabled = False

    def set_options(self, task_keys=None, var_options=None, direct=None):
        super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
        self._log_dir = self.get_option('log_dir')
        # Initialisation du répertoire de log ici...

    def _write_to_log(self, host, status, result_data):
        # Logique pour écrire dans un fichier
        if isinstance(result_data, dict):
            log_entry = {
                'hote': host,
                'statut': status,
                'donnees': result_data
            }
            # Écriture log_entry dans un fichier sous self._log_dir
        pass

    def v2_runner_on_ok(self, result):
        host = result._host.get_name()
        self._write_to_log(host, 'SUCCES', result._result)

    def v2_runner_on_failed(self, result, ignore_errors=False):
        host = result._host.get_name()
        self._write_to_log(host, 'ECHEC', result._result)

    # ... Implémenter d'autres méthodes d'événements v2_...

Plugin pour sortie MySQL

Ce plugin enregistre les résultats dans une base de données MySQL.

# Fichier: callback_plugins/mysql_recorder.py
# ...
import pymysql
from ansible.module_utils._text import to_text

DOCUMENTATION = '''
    callback: mysql_recorder
    type: notification
    short_description: Enregistre les résultats dans MySQL
    # ... (documentation similaire avec options pour connexion DB)
'''

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'notification'
    CALLBACK_NAME = 'mysql_recorder'
    CALLBACK_NEEDS_WHITELIST = True

    def __init__(self):
        super(CallbackModule, self).__init__()
        self._connection = None

    def set_options(self, task_keys=None, var_options=None, direct=None):
        super(CallbackModule, self).set_options(task_keys=task_keys, var_options=var_options, direct=direct)
        self._db_host = self.get_option('db_host')
        self._db_user = self.get_option('db_user')
        self._db_pass = self.get_option('db_pass')
        self._db_name = self.get_option('db_name')
        self._table_name = self.get_option('table_name')
        self._init_connection()

    def _init_connection(self):
        try:
            self._connection = pymysql.connect(
                host=self._db_host,
                user=self._db_user,
                password=self._db_pass,
                database=self._db_name,
                charset='utf8mb4'
            )
        except pymysql.MySQLError as e:
            self._display.warning(f"Impossible de se connecter à MySQL: {e}")

    def _execute_query(self, host, status, data):
        if not self._connection:
            return
        try:
            with self._connection.cursor() as cursor:
                # Convertir data en chaîne si nécessaire
                result_str = to_text(json.dumps(data, ensure_ascii=False)) if isinstance(data, dict) else str(data)
                sql = f"INSERT INTO {self._table_name} (host, status, result, timestamp) VALUES (%s, %s, %s, NOW())"
                cursor.execute(sql, (host, status, result_str))
            self._connection.commit()
        except Exception as e:
            self._display.warning(f"Erreur d'insertion MySQL: {e}")

    def v2_playbook_on_task_start(self, task, is_conditional):
        # Exemple : logguer le début d'une tâche
        self._execute_query(task.get_name().get_name(), 'DEBUT_TACHE', task.get_path())

    def v2_runner_on_ok(self, result):
        self._execute_query(result._host.get_name(), 'OK', result._result)

    # ... Implémenter les autres méthodes nécessaires

Étiquettes: Ansible automatisation devops Infrastructure as Code configuration management

Publié le 10 juillet à 02h20