
Par défaut, Ansible exécute chaque tâche sur 5 hôtes en parallèle, simultanément, et attend que tous finissent avant de passer à la tâche suivante. Ce comportement marche bien pour 5-20 hôtes, mais devient problématique sur grande échelle (paralléliser plus pour aller vite) ou en production sensible (serializer les hôtes pour faire un rolling update). Cette page couvre les 5 leviers de parallélisme d'Ansible : forks, serial, throttle, strategy, max_fail_percentage, et explique quand utiliser lequel.
C'est un terrain où la performance et la fiabilité entrent souvent en conflit. Bien tuner ces paramètres permet de descendre un déploiement de 30 minutes à 5 minutes sur 200 hôtes, ou de garantir un rolling update sans coupure sur 50 webservers.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »forks: combien de connexions SSH parallèles ouvre Ansible ;serial: traiter les hôtes par lots séquentiels (rolling update) ;throttle: limiter une tâche spécifique à N hôtes simultanés ;strategy: comment Ansible synchronise entre hôtes :linear,free,host_pinned,debug;max_fail_percentage: circuit breaker sur un déploiement.
forks, le nombre de connexions parallèles
Section intitulée « forks, le nombre de connexions parallèles »forks détermine combien de hôtes Ansible traite en parallèle pour chaque tâche. Configuration au choix :
# ansible.cfg[defaults]forks = 20# CLIansible-playbook site.yml -f 20# Variable d'environnementANSIBLE_FORKS=20 ansible-playbook site.ymlValeur par défaut : 5. Pour aller vite sur grande fleet, on monte à 20-50 :
| Taille de fleet | forks recommandé |
|---|---|
| < 20 hôtes | 5 (défaut, OK) |
| 20-100 hôtes | 20-30 |
| 100-500 hôtes | 50 |
| > 500 hôtes | 100+ (avec mitogen pour aller plus loin) |
serial, rolling updates
Section intitulée « serial, rolling updates »serial impose à Ansible de traiter les hôtes par lots séquentiels. Indispensable pour un rolling update qui ne doit pas tout casser d'un coup :
- name: Déployer nginx en rolling hosts: webservers serial: 1 # un hôte à la fois tasks: - name: Installer la version cible de nginx ansible.builtin.dnf: name: "nginx-{{ nginx_version }}" state: presentVariantes :
serial: 1 # lot de 1 hôteserial: "20%" # lot = 20% du groupe (arrondi)serial: [1, 5, 20] # lot 1 : 1 hôte, lot 2 : 5, lots suivants : 20serial: - 1 # idem mais en YAML multiligne - "10%" - "100%"La stratégie de canary classique : [1, 10, 100]. Le premier lot teste sur 1 hôte (canary), le deuxième sur 10 (validation), puis 100% si tout va bien.
serial: + pre_tasks/post_tasks = rolling zero-downtime
Section intitulée « serial: + pre_tasks/post_tasks = rolling zero-downtime »Le pattern complet pour un rolling update sans coupure de service :
- name: Déployer en rolling hosts: webservers serial: 1 become: true
pre_tasks: - name: Drainer du load-balancer community.general.consul_kv: key: "lb/weight/{{ inventory_hostname }}" value: "0"
tasks: - name: Déployer la version cible de nginx ansible.builtin.dnf: name: "nginx-{{ nginx_version }}" state: present
- name: Vérifier que nginx redémarre OK ansible.builtin.wait_for: port: 80 timeout: 30
post_tasks: - name: Réintégrer dans le LB community.general.consul_kv: key: "lb/weight/{{ inventory_hostname }}" value: "100"Sur 5 webservers, le déploiement prend 5x plus de temps mais aucun client ne voit de coupure.
throttle, limiter une tâche spécifique
Section intitulée « throttle, limiter une tâche spécifique »throttle: limite une tâche à N hôtes simultanés, sans toucher au reste du play :
tasks: - name: Tâche normale (parallélisée selon forks) ansible.builtin.dnf: name: nginx state: present
- name: Tâche qui martèle un service externe (limiter à 2 simultanés) ansible.builtin.uri: url: "https://api.externe.com/register/{{ inventory_hostname }}" method: POST throttle: 2
- name: Tâche normale (à nouveau parallélisée) ansible.builtin.systemd: name: nginx state: startedCas typique : une API externe avec rate limiting, une base de données avec écritures coûteuses, une registry Docker privée.
strategy, comment Ansible synchronise
Section intitulée « strategy, comment Ansible synchronise »Quatre stratégies disponibles :
strategy: linear (défaut)
Section intitulée « strategy: linear (défaut) »Tous les hôtes exécutent la même tâche en parallèle. Ansible attend que tous finissent avant de passer à la tâche suivante :
Tâche 1 : web1 → web2 → web3 (en parallèle, attend tous)Tâche 2 : web1 → web2 → web3 (en parallèle, attend tous)Tâche 3 : web1 → web2 → web3 (en parallèle, attend tous)Comportement prévisible : à chaque instant, tous les hôtes sont sur la même tâche. C'est ce que vous voulez 99% du temps.
strategy: free
Section intitulée « strategy: free »Chaque hôte avance à son rythme, sans synchro entre tâches :
- name: Compiler sans synchroniser les hôtes entre eux hosts: webservers strategy: free tasks: - name: Compiler une grosse lib (long) ansible.builtin.command: make -j4 changed_when: trueSi web1 finit en 2 min et web2 en 5 min, web1 passe à la tâche 2 sans attendre web2. Gain de temps important quand les tâches sont longues et indépendantes (build, compilation, tests).
Inconvénient : la sortie devient désordonnée (output de tâches différentes intercalées) et le diagnostic plus complexe.
strategy: host_pinned
Section intitulée « strategy: host_pinned »Variante de free : chaque hôte est pinné à un worker dédié. Les hôtes avancent à leur rythme mais on garantit qu'un même hôte n'est pas pris par plusieurs workers concurrents. Cas marginal, à utiliser uniquement si vous avez un problème spécifique.
strategy: debug
Section intitulée « strategy: debug »Mode interactif : Ansible exécute une tâche à la fois et attend votre Enter avant de passer à la suivante :
ANSIBLE_STRATEGY=debug ansible-playbook site.ymlQuand une tâche échoue, vous tombez dans un mini-shell Ansible qui permet de :
- Réexécuter la tâche (
r) ; - Continuer au suivant (
c) ; - Quitter (
q) ; - Inspecter une variable (
p var_name).
Indispensable pour développer un playbook complexe en mode pas-à-pas.
max_fail_percentage, circuit breaker
Section intitulée « max_fail_percentage, circuit breaker »Sans max_fail_percentage:, le play continue tant qu'au moins un hôte réussit. Avec :
- name: Déployer hosts: webservers serial: 1 max_fail_percentage: 0 # arrêter dès la 1ère erreurmax_fail_percentage: 30 # arrêter si > 30% des hôtes ont échouéCombiné avec serial:, c'est le circuit breaker d'un déploiement : vous n'écrivez pas une bug sur l'ensemble de la fleet. Si web1 plante, web2 ne reçoit pas le déploiement.
Tuning combiné, exemple production
Section intitulée « Tuning combiné, exemple production »Un playbook prod typique sur 100 hôtes :
- name: Déploiement applicatif hosts: webservers # 100 hôtes serial: "10%" # lots de 10 hôtes max_fail_percentage: 5 # arrêter si > 5 hôtes du lot ont échoué strategy: linear # synchronisation par tâche
tasks: - name: Drainer du LB community.general.consul_kv: ...
- name: Déployer ansible.builtin.dnf: name: "app-{{ app_version }}" state: present
- name: API call qui rate-limite ansible.builtin.uri: url: ... throttle: 3 # max 3 calls API simultanésLancement avec forks adapté :
ansible-playbook site.yml -f 2010 hôtes en parallèle par lot, 10 lots, soit 100 hôtes traités. Avec max_fail_percentage: 5, si plus de 5 hôtes du lot courant échouent, le déploiement s'arrête net, on n'envoie pas la même erreur sur les 90 hôtes suivants.
Pièges fréquents
Section intitulée « Pièges fréquents »| Symptôme | Cause | Fix |
|---|---|---|
forks = 100 ralentit le poste | Saturation mémoire / FD côté control node | Réduire à 20-50 ou utiliser un Execution Environment dédié |
serial: ne change rien | Vous avez un seul hôte cible (ou --limit web1) | serial: ne s'applique qu'à un groupe avec plusieurs hôtes |
strategy: free rend les logs illisibles | C'est par design (hôtes désynchronisés) | Utiliser linear sauf gain de temps massif |
throttle: ignoré | throttle: 0 ou non défini = pas de limite | Vérifier la valeur, doit être ≥ 1 |
max_fail_percentage: 100 ne s'arrête jamais | 100% = jamais d'arrêt | Pour arrêter à la 1ère erreur : max_fail_percentage: 0 |
| Un play qui suit ne tourne pas après échec | C'est attendu : le play d'après ne démarre que si le précédent a au moins un hôte OK | Vérifier la convention multi-plays |
À retenir
Section intitulée « À retenir »forks(CLI-fouansible.cfg) contrôle le nombre de connexions SSH parallèles. Défaut 5, montez à 20-50 pour grande fleet.serial:réalise un rolling update par lots (1 hôte, 20%, ou patterns[1, 10, 100]).throttle:limite une tâche spécifique à N hôtes simultanés (API externe, DB, registry).strategy: linear(défaut) synchronise par tâche ;freedésynchronise pour aller plus vite ;debugactive le pas-à-pas interactif.max_fail_percentage:est le circuit breaker, combiné avecserial:, vous évitez de propager un bug à toute la fleet.
Mettre en pratique
Section intitulée « Mettre en pratique »La différence entre forks et serial se mesure sur des horodatages, pas sur une définition. Le lab vous fait jouer un rolling update en serial: 1 et vérifier que les marqueurs se posent l'un après l'autre, comparer les stratégies linear et free, puis brider une tâche gourmande avec throttle. Les timestamps écrits sur chaque hôte constituent la preuve de l'ordre d'exécution réel.
Pour aller plus loin
Section intitulée « Pour aller plus loin »- Délégation : sortir un hôte du load balancer avant de le mettre à jour, le geste qui accompagne
serial:. - Facts et magic vars :
ansible_play_hostsetgroupspour savoir quels hôtes composent le lot courant. - Contrôle de flux et gestion d'erreurs : ce qui prolonge
max_fail_percentage, deblock/rescueàany_errors_fatal.