Maximiser les performances d'Ansible avec ControlPersist et Pipelining

Boostez significativement les performances de vos playbooks Ansible en activant la réutilisation des connexions SSH avec ControlPersist et en rationalisant l'exécution des modules via Pipelining. Ce guide fournit des informations essentielles et des configurations pratiques pour réduire les temps d'exécution, en particulier dans les environnements à grande échelle. Apprenez à paramétrer votre `ansible.cfg` pour une automatisation IT plus rapide et plus efficace.

Maximiser les performances d'Ansible avec ControlPersist et Pipelining

Les exécutions lentes d'Ansible semblent souvent mystérieuses jusqu'à ce que vous observiez ce qui se passe sur le réseau. Un playbook avec vingt petites tâches contre cent hôtes peut passer un temps surprenant à ouvrir des sessions SSH, copier des fichiers de modules temporaires, exécuter Python, collecter les résultats et fermer les connexions. Le travail sur l'hôte distant peut prendre quelques millisecondes, mais la surcharge de connexion se répète encore et encore.

Deux paramètres aident souvent : la réutilisation de connexion SSH via ControlPersist, et le pipelining Ansible. Ce ne sont pas des interrupteurs magiques, et ils ne corrigeront pas les miroirs de paquets lents, les bases de données surchargées ou les tâches qui effectuent un travail lourd. Ils réduisent la surcharge de communication évitable, ce qui est exactement là où de nombreux playbooks avec de petites tâches perdent du temps.

D'abord, mesurez la douleur actuelle

Avant de modifier la configuration, exécutez le playbook une fois avec le timing activé :

ANSIBLE_CALLBACKS_ENABLED=ansible.posix.profile_tasks ansible-playbook site.yml

Si ce callback n'est pas installé, utilisez la sortie de timing intégrée plus simple de votre système CI ou encapsulez la commande avec time. L'objectif n'est pas un benchmark parfait. Vous voulez une base de référence et une idée de si les tâches lentes sont un travail réel ou de petites tâches répétées sur de nombreux hôtes.

Un test de fumée utile est un ping ad-hoc sur un inventaire représentatif :

time ansible all -m ping

Exécutez-le deux fois. Si la deuxième exécution est beaucoup plus rapide après avoir configuré la réutilisation de connexion, vous avez confirmé que le coût de configuration SSH faisait partie du problème.

Ce que ControlPersist change

ControlPersist est une fonctionnalité d'OpenSSH. Elle maintient une connexion SSH maître ouverte pendant un certain temps afin que les commandes SSH ultérieures vers le même hôte, utilisateur et port puissent la réutiliser. Ansible utilise couramment SSH pour chaque tâche, donc le multiplexage de connexion supprime les négociations répétées.

Un ansible.cfg pratique au niveau du projet ressemble à ceci :

[defaults]
forks = 20

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r

Créez le répertoire de sockets avec des permissions privées :

mkdir -p ~/.ansible/cp
chmod 700 ~/.ansible ~/.ansible/cp

ControlMaster=auto indique à SSH d'utiliser une connexion maître existante lorsqu'elle est disponible et d'en créer une lorsqu'elle ne l'est pas. ControlPersist=600s maintient le maître pendant dix minutes après la fin de la dernière session. Cette valeur n'est pas sacrée. Pour les courts jobs CI, quelques minutes peuvent suffire. Pour un opérateur exécutant des playbooks de manière répétée pendant une fenêtre de maintenance, dix ou quinze minutes peuvent être confortables.

Le ControlPath est plus important que ce que les gens pensent. Les chemins de sockets Unix ont des limites de longueur sur de nombreux systèmes. Un nom d'hôte d'inventaire long dans un chemin d'espace de travail profond peut casser le multiplexage avec une erreur confuse. Garder le chemin court, par exemple sous ~/.ansible/cp, évite ce type de défaillance.

Les versions récentes d'Ansible activent déjà le multiplexage SSH par défaut dans de nombreuses configurations normales, mais des paramètres explicites dans un ansible.cfg de projet rendent le comportement plus facile à auditer. Vérifiez la configuration active avec :

ansible-config dump --only-changed

Ce que le pipelining change

Sans pipelining, Ansible copie souvent un module dans un répertoire temporaire sur l'hôte distant, l'exécute, puis nettoie. Avec le pipelining activé, Ansible peut passer le code du module via la connexion SSH au lieu d'écrire autant de fichiers temporaires. Cela économise des allers-retours et du travail sur le système de fichiers distant.

Activez-le dans le même fichier :

[ssh_connection]
pipelining = True

Ou testez-le pour une exécution :

ANSIBLE_PIPELINING=True ansible-playbook site.yml

Le paramètre est le plus notable lorsque le playbook a de nombreux petits modules : file, lineinfile, template, user, service, et des tâches de commande courtes. Il importe moins lorsqu'une tâche passe la plupart de son temps à installer des paquets, construire des logiciels, transférer de gros artefacts ou attendre un service externe.

Le piège sudo requiretty

Le problème classique du pipelining est requiretty dans sudoers. Certaines anciennes configurations Linux d'entreprise exigeaient un TTY pour sudo. Le pipelining ne fonctionne pas bien avec cette exigence car Ansible essaie de diffuser le travail via SSH de manière non interactive.

Vérifiez sudoers attentivement. Ne modifiez pas /etc/sudoers avec un éditeur normal ; utilisez visudo :

sudo visudo

Si vous voyez une ligne globale comme celle-ci :

Defaults requiretty

Vous devrez peut-être la supprimer ou la remplacer pour l'utilisateur d'automatisation Ansible :

Defaults:ansible !requiretty

Ne faites cette modification que si elle correspond à la politique de sécurité de votre organisation. Sur de nombreuses distributions modernes, requiretty n'est pas activé par défaut.

Une configuration combinée plus sûre

Pour de nombreuses équipes, c'est un point de départ raisonnable :

[defaults]
forks = 20
gathering = smart
fact_caching = jsonfile
fact_caching_connection = .ansible_facts
fact_caching_timeout = 86400

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=600s -o ControlPath=~/.ansible/cp/%%h-%%p-%%r
pipelining = True

forks contrôle le parallélisme. C'est lié à la performance, mais ce n'est pas la même optimisation. Augmenter forks de 5 à 20 peut aider si votre nœud de contrôle et votre réseau peuvent le supporter. L'augmenter trop peut surcharger les bastions, les dépôts de paquets ou les nœuds gérés eux-mêmes.

La mise en cache des faits aide un autre type de lenteur. Si vos playbooks rassemblent des faits à chaque exécution et que les faits n'ont pas besoin d'être frais chaque minute, la mise en cache peut supprimer le travail de configuration répété. Utilisez-la délibérément ; des faits obsolètes peuvent être déroutants si vous comptez sur des données actuelles de mémoire, de disque ou d'interface.

Comment tester sans se tromper soi-même

Testez sur un sous-ensemble qui ressemble à la production. Cinq VM de test inactives sur le même sous-réseau ne vous apprendront pas grand-chose sur cent hôtes mixtes derrière un bastion.

Exécutez le même playbook avant et après le changement. Supprimez les sockets de contrôle existants entre les tests si vous avez besoin d'une comparaison à froid :

rm -f ~/.ansible/cp/*
time ansible-playbook site.yml --limit web

Ensuite, exécutez une comparaison à chaud :

time ansible-playbook site.yml --limit web

Recherchez moins de secondes passées dans de petites tâches répétées. Surveillez également les modes de défaillance. Si les tâches qui utilisent become commencent à échouer, enquêtez sur la configuration sudo avant de blâmer le pipelining lui-même.

Quand ces paramètres n'aideront pas beaucoup

ControlPersist et le pipelining ne rendent pas le travail lent à distance rapide. Si apt update attend un miroir, la réutilisation de connexion ne vous sauvera pas. Si un redémarrage de service attend trente secondes parce que l'application vide les connexions, le pipelining est sans importance. Si votre playbook copie un artefact de 2 Go sur chaque hôte, concentrez-vous sur la distribution d'artefacts, la mise en cache ou les dépôts de paquets locaux.

Ils ne remplacent pas non plus une conception de playbook idempotente. Un playbook qui exécute des commandes shell inconditionnellement gaspillera toujours du temps. Utilisez des modules qui peuvent détecter l'état actuel, ajoutez creates ou removes aux tâches de commande lorsque c'est approprié, et évitez de rassembler des faits lorsque le play ne les utilise pas.

L'approche pratique est simple : activez ControlPersist avec un chemin de contrôle court et privé ; testez le pipelining avec votre politique sudo ; ajustez forks progressivement ; et mesurez avec le même inventaire et la même charge de travail à chaque fois. Dans de nombreux environnements Ansible réels, ces changements transforment une exécution lente en une exécution tolérable car ils suppriment la surcharge répétée plutôt que de la cacher.

Bastions et hôtes de rebond

De nombreux inventaires ne se connectent pas directement aux nœuds gérés. Ils passent par un bastion. ControlPersist aide toujours, mais vous devez penser aux deux connexions : nœud de contrôle vers bastion, et bastion vers cible.

Une variable d'inventaire courante ressemble à ceci :

[private]
app01 ansible_host=10.0.10.11
app02 ansible_host=10.0.10.12

[private:vars]
ansible_user=ansible
ansible_ssh_common_args='-o ProxyJump=bastion.example.com'

Si chaque tâche construit de manière répétée une connexion de rebond, le bastion fait partie de la surcharge. Gardez le chemin de contrôle court et privé, et surveillez les limites de connexion SSH du bastion. Un playbook qui fonctionne bien contre dix hôtes peut surcharger un petit bastion lorsque forks est augmenté à cinquante.

Pour les clients SSH plus anciens, vous pouvez voir ProxyCommand au lieu de ProxyJump :

ansible_ssh_common_args='-o ProxyCommand="ssh -W %h:%p bastion.example.com"'

Testez une connexion SSH simple avant de blâmer Ansible :

ssh -o ProxyJump=bastion.example.com [email protected] hostname

Si c'est lent, Ansible sera lent aussi.

Pipelining et become dans les playbooks réels

Le vieux conseil selon lequel le pipelining ne fonctionne pas avec l'élévation de privilèges est trop large. Le pipelining peut fonctionner avec become ; le bloqueur courant est sudo exigeant un TTY ou une politique qui interfère avec l'exécution non interactive. La seule réponse fiable est de tester avec les mêmes paramètres become que vos playbooks utilisent.

Un petit play de test suffit :

- hosts: all
  become: true
  gather_facts: false
  tasks:
    - name: Confirmer que la commande privilégiée fonctionne
      ansible.builtin.command: id
      changed_when: false

Exécutez-le avec le pipelining activé. S'il échoue, vérifiez sudoers, les invites d'authentification, et si l'utilisateur d'automatisation peut exécuter les commandes requises sans invite de mot de passe. Les invites de mot de passe dans les grandes exécutions d'automatisation sont fragiles ; elles rendent également les mesures de timing bruyantes.

Ajustez forks en respectant les dépendances

Augmenter forks est tentant car cela produit un changement visible immédiat. Cela peut aussi créer un nouveau goulot d'étranglement. Si un play met à jour les caches de paquets sur tous les hôtes à la fois, un nombre élevé de forks peut punir le miroir de paquets. Si chaque hôte redémarre et se reconnecte à la même base de données à la fois, la base de données voit l'explosion.

Une approche mesurée est meilleure :

[defaults]
forks = 10

Exécutez le play. Essayez 20. Puis 30. Surveillez le CPU du nœud de contrôle, le nombre de processus SSH, la charge du bastion, la saturation du réseau, le dépôt de paquets et le service en cours de modification. Le paramètre le plus rapide n'est pas toujours celui avec le parallélisme le plus élevé. Pour les déploiements d'applications en continu, vous pouvez également vouloir serial dans le playbook pour protéger la disponibilité :

- hosts: web
  serial: 10

forks contrôle combien Ansible peut faire à la fois. serial contrôle combien d'hôtes le play doit traiter à la fois. Ils résolvent des problèmes différents.

Nettoyez les sockets de contrôle obsolètes

Les sockets de contrôle se nettoient généralement d'eux-mêmes, mais les ordinateurs portables se mettent en veille, les jobs CI sont tués et les chemins réseau changent. Si SSH commence à signaler des erreurs de multiplexage, supprimez les sockets obsolètes :

rm -f ~/.ansible/cp/*

C'est sûr lorsqu'aucune exécution Ansible n'est active pour ces sockets. Dans les exécuteurs d'automatisation partagés, évitez de placer les sockets de contrôle dans un répertoire partagé accessible en écriture. Chaque utilisateur d'automatisation doit avoir son propre chemin privé.

La conception de l'inventaire peut annuler les gains de performance

Le réglage de la connexion aide, mais la conception de l'inventaire peut encore tout ralentir. Les scripts d'inventaire dynamique qui appellent les API cloud lentement, les variables de groupe qui exécutent des recherches coûteuses, et les playbooks qui rassemblent des faits pour des hôtes qu'ils ne touchent jamais peuvent ajouter du délai avant même que SSH ne démarre.

Si une exécution fait une pause avant la première tâche, profilez le chargement de l'inventaire. Mettez en cache l'inventaire dynamique là où le plugin le supporte. Évitez les recherches de variables coûteuses au moment de l'analyse. Gardez les motifs d'hôtes précis :

ansible-playbook site.yml --limit web:&prod

Cette commande cible les hôtes à la fois dans web et prod. Exécuter un play large contre all puis sauter la plupart des tâches avec des conditions when gaspille du temps et rend la sortie plus difficile à lire.

Préférez des tâches moins nombreuses et plus claires lorsque l'état est lié

La lisibilité d'Ansible compte, mais des tâches excessivement petites peuvent rendre la surcharge de connexion plus visible. Si vous définissez dix lignes liées dans un fichier de configuration avec dix tâches lineinfile séparées, vous payez la surcharge de tâche dix fois et rendez le raisonnement de rollback plus difficile. Un modèle peut être plus clair et plus rapide :

- name: Rendre la configuration de l'application
  ansible.builtin.template:
    src: app.conf.j2
    dest: /etc/app/app.conf
    mode: '0644'
  notify: redémarrer app

Ce n'est pas un argument pour des scripts shell géants dans Ansible. C'est un rappel pour modéliser l'état souhaité au bon niveau. Un modèle pour un fichier de configuration est souvent meilleur que de nombreuses micro-modifications.

Évitez les changements globaux prématurés

Mettez les paramètres de performance dans le ansible.cfg du projet lorsque c'est possible. Modifier /etc/ansible/ansible.cfg sur un nœud de contrôle partagé peut surprendre d'autres équipes. Un fichier au niveau du projet fait voyager le comportement avec le dépôt et maintient CI, ordinateurs portables et exécuteurs d'automatisation plus proches de la même configuration.

Confirmez quel fichier de configuration Ansible utilise :

ansible --version

La sortie inclut le chemin de configuration actif. Cela attrape une erreur courante : modifier un ansible.cfg pendant qu'Ansible en lit un autre.

Sachez quand arrêter de régler Ansible

Si la surcharge de connexion n'est plus une partie majeure du temps d'exécution, arrêtez de régler SSH et regardez le travail. Les mises à jour de cache de paquets, les pulls de conteneurs, les migrations de base de données, les vérifications de santé de service et les appels API cloud dominent souvent les playbooks matures. À ce stade, la meilleure optimisation peut être un miroir de paquets local, la mise en cache d'artefacts, des lots de déploiement plus petits, ou le déplacement de l'approvisionnement ponctuel hors de chaque exécution de déploiement.