Meilleures pratiques pour optimiser les déploiements Ansible à grande échelle

Moyens pratiques d'accélérer les exécutions Ansible volumineuses avec forks, plugins de stratégie, mise en cache des faits, réutilisation SSH et une meilleure conception de playbook.

Meilleures pratiques pour optimiser les déploiements Ansible à grande échelle

Les déploiements Ansible à grande échelle deviennent généralement lents pour des raisons simples : trop de poignées de main SSH, trop de collecte de faits, un contrôleur avec trop peu de CPU, ou un playbook qui fait attendre chaque hôte pour le plus lent. La solution est rarement un seul paramètre. Vous obtenez le meilleur résultat en réduisant la surcharge de connexion, en ajustant la concurrence et en écrivant des plays qui font moins de travail par hôte.

Je ne définirais pas « grande échelle » par un nombre strict d'hôtes. Un inventaire de 300 hôtes peut sembler important si chaque tâche installe des paquets sur des liaisons lentes. Un inventaire de 3 000 hôtes peut être gérable si le contrôleur est bien dimensionné et les playbooks sont serrés. Traitez les chiffres ci-dessous comme des points de départ, puis mesurez avec votre propre inventaire et vos modules.


Ajustez le parallélisme avant de blâmer Ansible

Le parallélisme est généralement le premier levier à tester car Ansible passe beaucoup de temps à attendre les hôtes distants. L'objectif n'est pas « le plus grand nombre de forks gagne ». L'objectif est d'avoir suffisamment de concurrence pour maintenir le contrôleur occupé sans submerger SSH, l'élévation de privilèges, les dépôts de paquets ou les cibles elles-mêmes.

Contrôle de la concurrence avec forks

Le paramètre forks définit le nombre de processus de travail parallèles que le contrôleur Ansible peut lancer. Trouver le nombre optimal nécessite d'équilibrer les ressources du contrôleur (CPU et mémoire) avec les limites de connexion de l'environnement cible.

Définissez forks dans votre ansible.cfg ou via la ligne de commande (-f ou --forks).

[defaults]
forks = 100

Commencez plus bas que vous ne le pensez nécessaire. Exécutez le même playbook contre le même groupe d'hôtes avec 25, 50, 100 et 200 forks tout en surveillant le CPU, la mémoire, les échecs SSH et le temps d'exécution. Si le CPU reste principalement inactif et que les hôtes passent du temps à attendre, augmentez les forks. Si le contrôleur commence à swapper, les processus Python s'accumulent ou les cibles rejettent les connexions, réduisez.

Choisir le bon plugin de stratégie

La stratégie d'exécution par défaut d'Ansible est linear, ce qui signifie que les tâches doivent être terminées sur tous les hôtes ciblés avant de passer à la tâche suivante dans le playbook. Pour des milliers de nœuds, un seul hôte lent peut créer un goulot d'étranglement pour toute l'exécution.

Pour certains grands déploiements, utilisez la stratégie free.

Stratégie libre (strategy = free) : free permet aux hôtes de progresser indépendamment dans le playbook dès qu'ils terminent une tâche, sans attendre les hôtes plus lents. Cela peut améliorer le débit lorsque les tâches sont indépendantes. Ne l'utilisez pas aveuglément pour les déploiements progressifs, les migrations partagées ou les plays où l'ordre des tâches sur l'ensemble du parc est important.

# Exemple de définition de playbook
---
- hosts: all
  strategy: free
  tasks:
    - name: Assurez-vous que le service est en cours d'exécution
      ansible.builtin.service:
        name: httpd
        state: started

Mettez en cache les faits lorsque vous les réutilisez

La collecte de faits est utile, mais il est facile de payer pour cela à plusieurs reprises. Si vos playbooks utilisent des faits sur plusieurs exécutions, mettez-les en cache. Si un play n'a pas besoin de faits d'hôte du tout, désactivez la collecte pour ce play.

Utilisation de caches externes (Redis ou Memcached)

Pour un seul contrôleur, la mise en cache de fichiers JSON peut suffire. Pour plusieurs contrôleurs ou workers d'automatisation, utilisez un cache externe tel que Redis ou Memcached afin que chaque worker voie le même cache de faits.

Configuration exploitable dans ansible.cfg :

[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 7200 ; Mettre en cache les faits pendant 2 heures (en secondes)
fact_caching_prefix = ansible_facts

; Si vous utilisez Redis
fact_caching_connection = localhost:6379:0

Définissez gathering = smart lorsque les faits mis en cache font partie de votre flux de travail. Si vous n'avez besoin que d'une petite partie des données de l'hôte, utilisez gather_subset au lieu de tout collecter.

3. Optimisation de la connexion et du transport

La réduction de la surcharge associée à l'établissement de connexions est primordiale lorsqu'il s'agit de milliers de sessions SSH simultanées.

Pipelining SSH

Le pipelining réduit le nombre d'allers-retours SSH qu'Ansible utilise pour de nombreuses exécutions de modules. Cela vaut souvent la peine d'être activé, mais testez-le avec vos règles d'élévation de privilèges.

Réutilisation de la connexion SSH (ControlPersist)

Pour les cibles de type Unix, les paramètres ControlMaster et ControlPersist empêchent Ansible d'initier une toute nouvelle session SSH pour chaque tâche. Il maintient un socket de contrôle ouvert pendant une durée spécifiée, permettant aux tâches suivantes d'utiliser la connexion existante.

Configuration exploitable dans ansible.cfg :

[ssh_connection]
pipelining = True

; Utilisez une réutilisation agressive de la connexion (par exemple, 30 minutes)
ssh_args = -C -o ControlMaster=auto -o ControlPersist=30m -o ServerAliveInterval=15

Le pipelining peut entrer en conflit avec les configurations sudo qui nécessitent un TTY. Si vous avez toujours Defaults requiretty dans sudoers, supprimez-le pour l'utilisateur d'automatisation ou gardez le pipelining désactivé pour ces hôtes.

Optimisation Windows (WinRM)

Lorsque vous ciblez des nœuds Windows, ajustez WinRM séparément. Kerberos est généralement un meilleur choix de production que l'authentification de base, et les limites du service WinRM peuvent nécessiter une révision si de nombreux travaux se connectent en même temps.

4. Gestion de l'inventaire pour l'échelle

Les fichiers d'inventaire statiques deviennent pénibles lorsque les hôtes sont créés et détruits fréquemment. L'inventaire dynamique n'est pas obligatoire pour tous les grands environnements, mais c'est la valeur par défaut appropriée pour les parcs cloud, les groupes de mise à l'échelle automatique et l'infrastructure basée sur CMDB.

Sources d'inventaire dynamiques

Utilisez des plugins d'inventaire pour votre fournisseur cloud (AWS EC2, Azure, Google Cloud) ou votre système CMDB. L'inventaire dynamique garantit qu'Ansible ne cible que les hôtes actifs avec des données à jour.

# Exemple : Exécution contre un inventaire AWS filtré dynamiquement
ansible-playbook -i aws_ec2.yml site.yml --limit 'tag_Environment_production'

Ciblage et filtrage intelligents

Évitez d'exécuter des playbooks contre l'ensemble de l'inventaire (hosts: all) sauf si cela est absolument nécessaire. Utilisez des groupes granulaires, des limites (--limit) et des tags (--tags) pour garantir que l'ensemble des cibles d'exécution est minimisé.

5. Considérations architecturales et dimensionnement du contrôleur

Pour les déploiements à grande échelle, l'environnement dans lequel Ansible s'exécute doit être correctement provisionné.

Dimensionnement du contrôleur

Ansible est très dépendant des ressources du contrôleur, principalement le CPU et la RAM, en raison de la nécessité de forker des processus pour une exécution parallèle.

  • CPU : Plus de forks signifie généralement plus de travail Python sur le contrôleur. Surveillez la charge moyenne et la saturation par cœur lors des exécutions réelles de playbook.
  • RAM : Chaque fork consomme de la mémoire. Les grands modèles, les grandes variables et les plugins de callback bavards peuvent rapidement augmenter l'utilisation de la mémoire.
  • E/S de stockage : Un stockage local rapide aide lorsque le contrôleur écrit de nombreux fichiers temporaires, journaux, artefacts ou entrées de cache de faits basées sur des fichiers.

Utilisation des plateformes d'automatisation

Pour les équipes qui ont besoin de planification, de RBAC, de pistes d'audit et de plusieurs workers d'exécution, utilisez Ansible Automation Platform ou AWX plutôt qu'une seule session shell de longue durée sur un nœud de contrôle.

AAP fournit :

  • Planification et historique des travaux : Journalisation et audit centralisés.
  • Environnements d'exécution : Environnements d'exécution cohérents et reproductibles.
  • Clustering et mise à l'échelle : Distribuez l'exécution sur plusieurs nœuds de travail pour gérer des besoins de concurrence massifs sans surcharger un seul contrôleur.
  • Gestion des identifiants : Gestion sécurisée des secrets à grande échelle.

6. Conception de playbook pour l'efficacité

Même avec une infrastructure optimisée, des playbooks mal écrits peuvent annuler les gains de performance.

Minimisez la collecte de faits

Si vous utilisez des faits mis en cache (Section 2), désactivez activement la collecte redondante de faits lorsque cela est possible :

- hosts: web_servers
  gather_facts: no # Désactiver la collecte de faits pour ce play
  tasks:
    # ... exécutez uniquement les tâches qui ne reposent pas sur des faits système collectés

Utilisez run_once et delegate_to avec parcimonie

Les tâches qui doivent s'exécuter séquentiellement ou centralement (par exemple, lancer un déploiement progressif, mettre à jour un équilibreur de charge) doivent être gérées via run_once: true et delegate_to: management_node. Cela évite un parallélisme inutile lorsqu'un seul hôte doit effectuer l'action.

Préférez les opérations par lots

Dans la mesure du possible, utilisez des modules qui gèrent les opérations par lots de manière native (par exemple, les gestionnaires de paquets comme apt ou yum qui acceptent une liste de paquets) plutôt que d'itérer sur une grande liste en utilisant une loop ou with_items sur des tâches package séparées.

# Mieux : une tâche de paquet avec une liste
- name: Installer les dépendances nécessaires
  ansible.builtin.package:
    name:
      - nginx
      - python3-pip
      - firewall
    state: present

7. Mesurez le playbook, pas seulement le nombre d'hôtes

Lorsqu'une exécution Ansible est lente, ajoutez du chronométrage avant de modifier d'autres boutons. Le callback intégré profile_tasks est un bon premier passage :

[defaults]
callbacks_enabled = profile_tasks, timer

Exécutez le playbook une fois contre un groupe d'hôtes représentatif et examinez les tâches les plus lentes. Vous constaterez peut-être que la majeure partie du temps est consacrée à une installation de paquet, une étape de rendu de modèle ou une commande qui attend un service externe. Dans ce cas, augmenter forks crée simplement plus de pression sur le même goulot d'étranglement.

Pour un test reproductible, maintenez la tranche d'inventaire stable :

ansible-playbook -i inventory site.yml --limit 'web:&production' -f 50
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 100
ansible-playbook -i inventory site.yml --limit 'web:&production' -f 200

Enregistrez le temps d'exécution total, les hôtes en échec, le CPU du contrôleur, la mémoire du contrôleur et toute erreur SSH ou sudo côté cible. Surveillez également le dépôt de paquets ou le serveur d'artefacts pendant le test. Un playbook peut ressembler à un problème Ansible alors que le vrai problème est que chaque hôte télécharge le même paquet en même temps à partir d'un miroir interne surchargé.

8. Réduisez le travail avant d'augmenter la concurrence

Les exécutions Ansible à grande échelle s'améliorent souvent plus en faisant moins qu'en le faisant plus vite. Quelques exemples reviennent souvent :

  • Une tâche de modèle rend un grand fichier de configuration à chaque exécution même si seule une petite inclusion a changé.
  • Une tâche shell exécute une commande de découverte sur chaque hôte alors que la valeur est déjà dans l'inventaire.
  • Un rôle installe des paquets un par un dans une boucle.
  • Un gestionnaire redémarre un service après plusieurs modifications de modèle non liées alors qu'un seul rechargement suffirait.

Utilisez l'idempotence du module au lieu de shell lorsque cela est possible. Une commande shell qui signale toujours un changement peut déclencher des gestionnaires sur des centaines d'hôtes et transformer une vérification inoffensive en un redémarrage progressif. Si vous devez utiliser command ou shell, définissez soigneusement changed_when et creates ou removes.

- name: Initialiser le répertoire de l'application une fois
  ansible.builtin.command: /usr/local/bin/app-init /srv/app
  args:
    creates: /srv/app/.initialized

Cette petite garde empêche le travail répété et évite les faux rapports de modification.

9. Utilisez des lots pour le contrôle des risques

La performance n'est pas la seule préoccupation à grande échelle. Parfois, le playbook le plus rapide est dangereux sur le plan opérationnel. Pour les parcs de services, utilisez serial pour contrôler le rayon d'explosion :

- hosts: app_servers
  serial: 10%
  max_fail_percentage: 5
  tasks:
    - name: Déployer le paquet de l'application
      ansible.builtin.package:
        name: myapp
        state: latest

serial rendra l'exécution plus longue que de tirer sur tous les hôtes en même temps, mais cela donne aux équilibreurs de charge, à la surveillance et aux humains le temps de réagir. Cela protège également les dépendances partagées. Un miroir de paquets, un point de terminaison de migration de base de données ou un gestionnaire de secrets peuvent ne pas survivre à des milliers de requêtes simultanées.

Les déploiements Ansible à grande échelle mettent sous tension des systèmes faciles à oublier : les résolveurs DNS, les dépôts de paquets, les magasins de secrets, les pipelines de journalisation et les points de terminaison de surveillance. Si un playbook ralentit uniquement à des nombres de forks plus élevés, vérifiez ces services partagés avant de blâmer Ansible.

Gardez également la sortie du callback sous contrôle. Les journaux très verbeux sont utiles lors du débogage, mais ils peuvent ralentir les grandes exécutions et enterrer la véritable défaillance. Utilisez une verbosité élevée pour une tranche d'hôtes étroite, puis revenez à une sortie normale pour l'exécution sur l'ensemble du parc.

10. Divisez les plays par domaine de défaillance

Une astuce de mise à l'échelle souvent négligée consiste à cesser de traiter l'ensemble du parc comme une seule unité de déploiement. Si les hôtes de base de données, les hôtes Web, les files d'attente et les nœuds de cache vivent tous dans le même play géant, un groupe lent ou cassé peut retarder un travail non lié. Séparez les plays par domaine de défaillance et ordre de dépendance.

Par exemple, exécutez la configuration de base du système d'exploitation largement, mais déployez le code de l'application par niveau de service. Mettez à jour les nœuds de cache dans leur propre play. Vidangez et redémarrez les nœuds Web par lots. Appliquez la configuration de la base de données avec des vérifications supplémentaires et une concurrence plus faible. Cela rend les nouvelles tentatives plus sûres car vous pouvez réexécuter la partie défaillante sans répéter le travail sur chaque hôte.

Cela clarifie également la propriété. L'équipe responsable d'un service peut ajuster sa taille de lot, ses contrôles de santé et son comportement de restauration sans modifier les valeurs par défaut globales de l'automatisation. Ansible à grande échelle reste maintenable lorsque la structure du playbook correspond à la façon dont l'infrastructure échoue réellement lors d'incidents réels et de fenêtres de maintenance.

Le travail de performance Ansible le plus percutant est généralement simple : réutiliser les connexions SSH, éviter la collecte inutile de faits, dimensionner correctement forks et empêcher les playbooks d'effectuer des opérations minuscules répétées. Ensuite, regardez l'architecture. Si un seul contrôleur ne peut pas suivre proprement, répartissez le travail sur les nœuds d'exécution et rendez l'inventaire, les identifiants et la journalisation reproductibles.