Principe de fonctionnement
Le processus central d'Ansible suit ces étapes :
- Le module est chargé en mémoire.
- Les arguments lui sont ajoutés.
- Les classes requises par le module sont instanciées.
- Le tout est compressé en archive ZIP puis encodé en Base64 dans un fichier temporaire.
- Une connexion SSH est établie avec l'hôte distant pour créer un répertoire temporaire, puis la connexion est fermée.
- Une seconde connexion SSH transfère le fichier temporaire via SFTP dans le répertoire créé.
- Une troisième connexion rend le fichier exécutable et lance son exécution.
- Une dernière connexion exécute le module et supprime le répertoire temporaire.
- Le résultat, sous forme de chaîne JSON, est extrait de la sortie standard.
- 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