Augmenter les performances de votre Playbook

Dans ce chapitre, nous étudierons comment réduire le temps d'exécution de vos Playbooks.

Introduction

Ansible est un outil pratique qui ne nécessite pas de configuration complexe en raison de sa nature agentless (vous n'avez pas besoin de préinstaller de logiciel ou d'agent sur des hôtes gérés, plus d'informations ici). Dans la plupart des cas, vous utiliseriez une connexion avec le protocole ssh pour configurer vos serveurs distants. L'un des inconvénients de cette simplicité est la vitesse.

En effet, en fonction de votre environnement et du flux de travail de votre playbook, Ansible peut fonctionner lentement, puisqu'il fait toute la logique localement, génère l'exécution de tâches, l'envoie à l'hôte distant, l'exécute, attend les résultats, lit les résultats, les analyses et continue vers la tâche suivante. Dans cet article, nous décrivons plusieurs façons d'augmenter la vitesse de notre playbook.

Améliorer les performances de notre Playbook

Tout au long de cet article, nous prendrons comme exemple de test le playbook ci-dessous, avec le contenu suivant:

- hosts: test
  vars:
    - test_path: "/home/hatim/hatim.txt"
  tasks:
    - name: "Send {{ test_path }}"
      copy:
        content: "Ceci est une ligne de test"
        dest: '{{ test_path }}'

    - name: "Add text in {{ test_path }}"
      lineinfile:
        line: "Ceci est une autre ligne de test"
        dest: '{{ test_path }}'
        state: present

Mesurer le temps d'exécution de nos tâches Ansible

Avant d'optimiser quoi que ce soit, nous devrions au préalable être capable de mesurer les performances des tâches de notre playbook. Pour, ce faire nous modifierons la variable callback_whitelist dans le fichier ansible.cfg avec les valeurs suivantes :

[defaults]
callback_whitelist = timer, profile_tasks

Après avoir ajouté cette ligne, vous commencerez à voir le temps d'exécution de chaque tâche ainsi que le temps d'exécution de votre playbook :

ansible-playbook main.yml

Résultat :

TASK [Gathering Facts] ***************************
Tuesday 25 February 2020  15:50:02 +0100 (0:00:00.068)       0:00:00.068 ******
ok: [localhost]

TASK [Send /home/hatim/hatim.txt] *********************************************
Tuesday 25 February 2020  15:50:05 +0100 (0:00:03.158)       0:00:03.226 ******
changed: [localhost]

TASK [Add text in /home/hatim/hatim.txt] **************************************
Tuesday 25 February 2020  15:50:07 +0100 (0:00:01.595)       0:00:04.822 ******
changed: [localhost]

PLAY RECAP *******************************************************************
localhost : ok=3    changed=2    unreachable=0    failed=0

Tuesday 25 February 2020  15:50:07 +0100 (0:00:00.840)       0:00:05.663 *****
==============================================================================
Gathering Facts -------------------------------------------------------------- 3.16s
Send /home/hatim/hatim.txt --------------------------------------------------- 1.60s
Add text in /home/hatim/hatim.txt -------------------------------------------- 0.84s
Playbook run took 0 days, 0 hours, 0 minutes, 5 seconds

La collecte de Facts

Désactiver la collecte des Facts

Au début de l'exécution de votre playbook, Ansible collecte des informations sur chaque système distant (il s'agit du comportement par défaut de la commande ansible-playbook). Vous remarquerez que cette étape nous a pris 3.16s de temps d'exécution. Si vous n'avez pas besoin de récupérer les facts, alors cette étape peut être ignorée depuis votre playbook, voici comment ça se passe :

- hosts: test
  gather_facts: no
  # suite de votre playbook

De ce fait, nous gagnerons quelques secondes de performances :

ansible-playbook main.yml

Résultat :

=======================================================
Send /home/hatim/hatim.txt ---------------------- 1.62s
Add text in /home/hatim/hatim.txt --------------- 0.79s
Playbook run took 0 days, 0 hours, 0 minutes, 2 seconds

Filtrer la collecte des Facts

Dans le cas où vous souhaitez récupérer uniquement certains facts , et donc d'intercepter exactement juste ce dont vous avez besoin, il suffit alors d'affiner la collecte de facts à l'intérieur de votre playbook.

En effet, le module setup propose un paramètre intéressant qui se nomme : gather_subset qui permet de collecter et ignorer un sous-ensemble de facts. Dans l'exemple ci-dessous, nous l'utiliserons pour ne récupérer que des informations sur la partie réseau de nos systèmes distants :

- hosts: test
  gather_facts: no
  vars:
    - test_path: "/home/hatim/hatim.txt"
  pre_tasks:
    - name: Collect only facts returned by network
      setup:
        gather_subset:
          - '!all'
          - '!any'
          - 'network'

  tasks:
  - debug: 
      msg: 'Mon ip {{ ansible_default_ipv4.address }}'
    # suite du playbook

Au lancement de notre playbook, on arrive à économiser le temps d'exécution des facts :

Send /home/hatim/hatim.txt -------------- 1.46s
Collect only facts returned by network -------------------------------------- 1.16s
Add text in /home/hatim/hatim.txt ------------------------------------------- 0.75s
debug ----------------------------------------------------------------------- 0.05s

Voici les options disponible dans gather_subset :

  • all: rassembler tous les sous-ensembles (par défaut).
  • network: recueillir des facts sur le réseau.
  • hardware: recueillir des facts sur le matériel (facts les plus longs à récupérer).
  • virtual: recueillir des informations sur les machines virtuelles hébergées sur la machine.
  • ohai: recueillir des facts d'Ohai (facts de chef's).
  • facter: recueillir des facts de facter (facter de puppet's).

Vous pouvez également contrôler le processus de collecte des facts par configuration globale dans votre fichier ansible.cfg, comme suit :

[defaults]
gather_subset = !hardware,!ohai,!facte

Mise en cache des Facts

Vous pouvez aussi essayer d'utiliser la mise en cache des facts. Dans ce cas, nous demanderons à Ansible de conserver les facts pour un hôte donné qu'il recueille dans un fichier local. Vous pouvez également définir d'autre type de cache (memcache, redis etc ... plus d'informations ici). Pour ce faire, il suffit de rajouter l'option fact_caching_connection et fact_caching dans le fichier ansible.cfg, comme suit :

[defaults]
fact_caching_connection = /tmp/.ansible_fact_cache
fact_caching = jsonfile
fact_caching_timeout = 7200

Information

fact_caching_timeout = 7200 correspond à 2 heures de mise en cache.

Le protocole ssh

Laissez-moi d'abord vous expliquer de façon très simple, le workflow par défaut utilisé par Ansible pour exécuter les tâches de notre playbook :

  1. Génération d'un package d'exécution avec le module et ses paramètres pour une exécution à distance.
  2. Connexion via SSH pour envoyer notre fichier via SFTP.
  3. Vérification du code retour de votre module: si déclenchement d'erreur alors arrêt du playbook sinon il continue son exécution.
  4. Connexion via SSH pour détecter l'existence de notre fichier distant et rajouter la ligne adéquate.
  5. Vérification du code retour de votre module: si déclenchement d'erreur alors arrêt du playbook sinon il continue son exécution.
  6. Résumé de l'exécution.

Vous remarquerez alors qu'ansible utilise à chaque fois le protocole ssh pour exécuter ses tâches. Il est donc important de s'intéresser de plus près à ce protocole pour améliorer nos performances.

Dans cette section, je vais vous dévoiler quelques astuces qui vous permettront d'optimiser le temps d'exécution de vos tâches. Comment ? Le but est d'améliorer la façon dont le serveur maître utilise le protocole ssh pour communiquer avec vos serveurs distants. Ces méthodes pourront vous apporter des avantages considérables en matière de performances, surtout si vous exécutez un grand nombre de tâches ou que vous exécutez sur un grand nombre d'hôtes, ou les deux.

Le multiplexage SSH

L'idée derrière le multiplexage SSH est qu'une fois qu'une connexion ssh est établie avec un hôte distant, la connexion restera persistante en arrière-plan pendant une période de temps donnée. Donc, chaque fois que vous devez réexécuter vos tâches sur le même hôte, la même connexion sera réutilisée.

Nous allons dans un premier lieu, vérifier comment le protocole ssh est utilisé pour une simple exécution du module ping, pour cela exécuter la commande suivante avec un maximum de verbosité :

ansible test -m ping -vvvv | grep EXEC

Résultat filtré :

<localhost> SSH: EXEC ssh -vvv -C -o 
ControlMaster=auto -o ControlPersist=60s
-o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey
ControlPath=/home/hatim/.ansible/cp/9ab9540323

Les options affichées sont des options de la commande ssh, ci-dessous leur explication :

  • ControlMaster auto : ici on laisse SSH se charger de la détection de l'existence d'un socket (connexion) pour la connexion à l'host demandée.
  • ControlPersist=60s : chaque connexion restera active (en arrière-plan) pendant 60 secondes au maximum.
  • PreferredAuthentications : spécifie l'ordre dans lequel le serveur de contrôle doit essayer les méthodes d'authentification.
  • ControlPath : chemin où sera créé le fichier socket.

Une fois que nous avons fait le tour sur l'explication des différentes options utilisées par défaut par ansible, on peut alors s'imaginer les modifications suivantes pour un gain de performances :

  • Si nous avons plusieurs tâches à exécuter ou que nous exécutons les mêmes tâches sur le même serveur distant, alors il est préférable d'augmenter la valeur de ControlPersist.
  • Si vous utilisez uniquement des clés publiques pour la connexion ssh avec les hôtes souhaités, alors vous pouvez supprimer les autres méthodes d'authentification dans l'option PreferredAuthentications.

Toutes ces modifications combinées nous donnerons le fichier ansible.cfg suivant :

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=2h -o PreferredAuthentications=publickey
control_path = %(directory)s/ansible-ssh-%%h-%%p-%%r

L'option control_path n'est pas obligatoire mais je l'ai rajoutée pour sauvegarder de manière plus agréable notre fichier socket (%%h => host, %%p => port, %%r => utilisateur). Si nous relançons notre commande de debug alors nous obtiendrons le résultat avec nos nouvelles options :

<localhost> SSH: EXEC ssh -vvv -C -o 
ControlMaster=auto -o ControlPersist=2h
-o PreferredAuthentications=publickey
ControlPath=/home/hatim/.ansible/cp/ansible-ssh-%h-%p-%r

Vérifions si notre fichier socket existe bel et bien :

ls -l /home/hatim/.ansible/cp/

Résultat :

srw------- 1 hatim hatim 0 Feb 26 10:44 ansible-ssh-localhost-22-root

MISE EN GARDE

Lorsque vous utilisez le multiplexage SSH avec un temps ControlPersist plus long, il peut y avoir un problème potentiel de non connectivité. Voici par exemple un scénario qui m'est déjà arrivé : Mon pc s'est mis en veille et au moment de l'allumer, les connexions se sont interrompues, mais sont restées toujours persistantes, ce qui a interrompu la connectivité ssh aux hôtes multiplexés. Si jamais vous êtes dans ce cas de problème alors tuez les processus contenant le mot 'ssh' et '[mux]', cela pourrait être fait avec la commande suivante :

ps faux  | grep ssh | grep "\[mux\]"  | awk '{print $2}' | xargs kill

Le Pipelining ssh

La pipeline shh est la méthode Ansible moderne pour accélérer vos connexions ssh sur le réseau vers les hôtes gérés. C'est une fonctionnalité qui permet de réduire le nombre de connexions ssh à un hôte. Lorsqu'elle est activée, l'exécution d'un module se fait en passant les instructions à l'hôte via SSH, les instructions sont alors écrites directement sur le canal STDIN depuis l'interpréteur python, cela conduirait alors à de meilleures performances.

Pour mieux comprendre le but de cette fonctionnalité, le mieux reste de vérifier la pipeline utilisée sans et avec cette option. Pour ce faire, nous lancerons le module ping avec un niveau de verbosité élevé :

ansible-playbook main.yml

Résultat résumé :

SSH : EXEC ssh ...
SSH : EXEC ssh ...
SSH : EXEC sftp ...
SSH : EXEC ssh ... python ... ping.py

Avec l'option pipeline ssh activé voici le nouveau résultat obtenu :

SSH : EXEC ssh ... python && sleep 0

Voici ci-dessous le workflow avec le mode pipeline activé afin de mieux ce qui se passe en arrière-plan :

  1. Génération d'un fichier python avec le module et ses paramètres pour une exécution à distance.
  2. Connexion via SSH pour exécuter l'interpréteur Python.
  3. Envoie du contenu de fichier Python à l'entrée standard de l'interpréteur.
  4. Vérification du code retour de votre exécution: si déclenchement d'erreur alors arrêt du playbook sinon il continue l'exécution du fichier python.
  5. Résumé de l'exécution.

Nous passons alors d'une seule connexion SSH au lieu de quatre ! Multipliez maintenant cela en un certain nombre de tâches et imaginez le nombre de connexions ssh économisé. L'amélioration de la vitesse est importante, en particulier sur les connexions WAN.

L'activation du pipeline se produit en ajoutant l'option suivante dans la section [ssh_connection] du fichier ansible.cfg :

[ssh_connection]
pipelining = True

MISE EN GARDE

Cette option ne peut fonctionner seulement si :

  • Désactiver le requireetty dans le fichier /etc/sudoers sur tous les hôtes gérés.
  • Aucun transfert de fichiers dans vos tâches.

Forks

Les forks sont le nombre de processus parallèles à générer lors de la communication avec des hôtes distants. Par défaut, Ansible règle la valeur fork sur 5, ce qui signifie qu'à tout moment donné, Ansible peut exécuter jusqu'à 5 exécutions parallèles.

Si vous exécutez un playbook sur plus de 5 hôtes, il est préférable d'augmenter cette valeur. Vous pouvez augmenter ce nombre dans le fichier ansible.cfg:

[defaults]
forks = 10

Ou vous pouvez également utiliser l'option -f de la commande ansible-playbook.

Attention

Notez que ce paramètre dépend de vos capacités matérielles et logicielles, telles que la bande passante réseau et la mémoire disponible sur la machine de contrôle. Limitez vos forks en fonction de vos ressources disponibles.

Free Strategy

Il existe deux types de stratégies dans ansible. Celle qui est utilisée par défaut est la stratégie serial (linéaire) dans laquelle chaque tâche attend la fin de chaque hôte avant de passer à la tâche suivante. Cependant, pour de nombreuses situations, nous voulons simplement que chaque hôte termine son jeu de tâches aussi rapidement que possible sans attendre les autres hôtes.

Voici un exemple de scénario qui m'est déjà arrivé aussi : Mon but était d'installer un serveur NGINX sur cinq serveurs différents et l'un de ces serveurs avait un verrou Yum, les quatre autres serveurs n'ont pas pu donc continuer leurs tâches. Pour éviter ce type de problème, nous utiliserons la stratégie free (libre), pour ce faire, il suffit d'utiliser l'instruction strategy dans votre playbook, comme suit :

- hosts: test 
  strategy: free 
  tasks:
    # suite de votre playbook

Attention

Pour les playbooks avec des dépendances entre les nœuds, cela pourrait être dangereux, mais pour la plupart des cas d'utilisation, une stratégie libre est idéale.

Conclusion

Ansible est un outil logiciel d'automatisation fantastique, mais l'automatisation est encore meilleure lorsqu'elle fonctionne aussi vite qu'elle le peut :).

J'espère que ces conseils vous aideront à accélérer considérablement les exécutions de vos playbooks Ansible. Sachez juste que ces astuces d'optimisations s'adaptent à des besoins précis, et certains d'entre eux nécessitent soit au préalable quelques prérequis à respecter, soit quelques mesures de précautions à l'utilisation. N'oubliez pas aussi que d'autres gains de vitesse sont possibles en optimisant le code de votre playbook ;).

Espace commentaire

Écrire un commentaire

Rejoignez la discussion

Vous devez être connecté pour poster un message.

24 commentaires

ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Content que ça aide. C'est souvent la première étape oubliée : on tente d'optimiser à l'aveugle alors qu'un simple profile_tasks montre directement le coupable.

03/03/2020 à 20:08
henri65
Membre Actif
Avatar de henri65
henri65
Membre Actif

Top article. J'ai ajouté le callback_whitelist, c'est radical pour voir quelle tâche plombe le temps total.

03/03/2020 à 15:45
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, mais il faut impérativement désactiver requiretty dans ton /etc/sudoers sur les cibles, sinon la session SSH échouera à l'élévation de privilèges.

03/03/2020 à 11:08
celine-bonneau
Membre Actif
Avatar de celine-bonneau
celine-bonneau
Membre Actif

Est-ce que le pipelining fonctionne avec become: yes ?

03/03/2020 à 05:26
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est une excellente remarque. Le multiplexage peut garder des connexions mortes si ton IP change. Dans ce cas, une valeur de 10m est souvent un bon compromis.

03/03/2020 à 00:25
ithierry
Membre Actif
Avatar de ithierry
ithierry
Membre Actif

J'ai testé ControlPersist, c'est bien, mais attention à ne pas mettre une valeur trop longue si vous changez souvent de réseau (VPN, Wi-Fi).

02/03/2020 à 16:31
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Oui, ça fonctionne bien. Il faut juste installer le paquet python redis sur ta machine de contrôle et configurer fact_caching = redis dans le ansible.cfg.

02/03/2020 à 10:39
adrien-perez
Membre Actif
Avatar de adrien-perez
adrien-perez
Membre Actif

Quelqu'un a déjà tenté de mettre le cache sur un redis distant pour partager les facts entre plusieurs serveurs de contrôle ?

02/03/2020 à 02:55
dalexandre
Membre Actif
Avatar de dalexandre
dalexandre
Membre Actif

Merci pour l'astuce du gather_subset: '!all', j'ai gagné 4 secondes sur mon déploiement.

01/03/2020 à 19:18
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie les permissions du répertoire. Assure-toi que ton utilisateur a bien les droits en écriture sur le chemin défini dans fact_caching_connection.

01/03/2020 à 12:00
ugillet
Membre Actif
Avatar de ugillet
ugillet
Membre Actif

J'ai un problème de permission avec /tmp/.ansible_fact_cache. Le user qui lance le playbook n'a pas les droits d'écriture.

01/03/2020 à 05:48
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Utilise le module setup avec gather_subset pour ne cibler que ce dont tu as besoin, comme network ou hardware. Ça évite de parser tout l'inventaire.

29/02/2020 à 23:33
josephine72
Membre
Avatar de josephine72
josephine72
Membre

Pour la collecte des facts, j'ai désactivé gather_facts: no mais j'ai besoin de certaines variables systèmes. Comment je fais sans tout récupérer ?

29/02/2020 à 17:09
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Exact. La stratégie free est puissante mais elle rend le debug plus pénible. À réserver aux tâches idempotentes et indépendantes.

29/02/2020 à 11:40
colin-michele
Membre Actif
Avatar de colin-michele
colin-michele
Membre Actif

Pareil ici. C'est le comportement attendu, chaque hôte avance à son rythme donc les logs s'entremêlent. Faut s'y habituer.

29/02/2020 à 06:27
philippe-rolland
Membre Actif
Avatar de philippe-rolland
philippe-rolland
Membre Actif

J'ai essayé la strategy: free mais je perds la visibilité sur l'ordre de mes tâches dans la console. C'est normal ?

29/02/2020 à 02:00
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Clairement. Si tu n'as plus de RAM, tu vas swap et tes perfs vont s'effondrer. Augmente les forks progressivement et surveille avec htop.

28/02/2020 à 19:15
ldupre
Membre Secouriste
Avatar de ldupre
ldupre
Membre Secouriste

Est-ce que passer forks = 20 sur une petite machine de contrôle ne va pas juste saturer la RAM ?

28/02/2020 à 12:51
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Si ça s'accumule, tue les processus zombies avec cette commande :

ps faux | grep ssh | grep "\[mux\]" | awk '{print $2}' | xargs kill
28/02/2020 à 05:06
eroy
Membre Actif
Avatar de eroy
eroy
Membre Actif

Le multiplexage SSH avec ControlPersist=2h me fout la zone. Mes connexions restent ouvertes même après le crash d'un playbook. Comment je nettoie ça proprement ?

27/02/2020 à 22:48
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

Vérifie bien que ton ansible.cfg est lu par la commande. Fais un ansible --version pour voir quel fichier de conf est utilisé par défaut. Parfois il est priorisé dans /etc/ansible/.

27/02/2020 à 17:57
benard-pierre
Membre Actif
Avatar de benard-pierre
benard-pierre
Membre Actif

J'ai testé callback_whitelist = timer, profile_tasks pour mesurer mes perfs mais rien ne s'affiche. J'ai bien modifié ansible.cfg.

27/02/2020 à 12:51
ajdaini-hatim
Auteur Rédacteur Secouriste Actif
Avatar de ajdaini-hatim
ajdaini-hatim
Auteur Rédacteur Secouriste Actif

C'est normal. Comme indiqué dans la mise en garde, le pipelining ne supporte pas le transfert de fichiers. Si tu as besoin de copier des fichiers, il faut désactiver cette option pour ces rôles spécifiques ou repenser ton workflow.

27/02/2020 à 08:20
david-mathilde
Membre Rédacteur
Avatar de david-mathilde
david-mathilde
Membre Rédacteur

Super article. Par contre j'ai un souci avec le pipelining. Dès que je l'active, mes tâches qui utilisent copy échouent lamentablement.

27/02/2020 à 04:13

Rejoindre la communauté

Recevoir les derniers articles gratuitement en créant un compte !

S'inscrire