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 commentaires

vous devez être inscrit pour poster un message !

0 commentaire

Autres articles

Sommaire