Présentation de Monkeyble: Framework de test bout en bout pour Ansible

Posté par  . Édité par palm123, devnewton 🍺, Xavier Teyssier et Benoît Sibaud. Modéré par devnewton 🍺. Licence CC By‑SA.
Étiquettes :
29
30
nov.
2022
Administration système

Présentation de Monkeyble: framework de test bout en bout pour Ansible

monkeyble_logo

Monkeyble est un petit framework qui permet de tester de bout en bout vos playbooks Ansible.

Il permet, au niveau des tâches des Playbooks, de:

  • 🐵 Vérifier qu'un module a été appelé avec les bons arguments ;
  • 🙊 Vérifier le résultat du retour d'un module ;
  • 🙈 Vérifier l'état d'exécution d'une tâche (changed, skipped, failed) ;
  • 🙉 Simuler (Mock) un module afin de retourner un résultat sans faire appel au vrai module.

Monkeyble est tout particulièrement conçu pour être placé dans une CI/CD afin de détecter les éventuels régressions lors des modifications sur une base de code Ansible 🚀.

Sommaire

Contexte

La stratégie de test décrite dans la documentation officielle de Ansible consiste a exécuter un playbook sur un environnement (un inventaire) de développement qui simule la production et de valider le fonctionnement de celui-ci pour l'exécuter ensuite sur l'environement de production. Cette stratégie fonctionne dans le cadre d'un playbook utilisé pour un déploiement d'un service à un instant T. Les tests sont effectués manuellement en quelque sorte. Une fois le service déployé, en cas de modification sur le code, une exécution devra à nouveau être réalisée sur l'environement de développement avant celui de production et cetera…

Cependant, si vos playbooks sont utilisés en tant que "services" en étant exposés par exemple dans un AWX/Tower ou derrière un catalogue de services comme Squest, en cas de modification sur le code il est préférable de vérifier que le service fonctionne toujours. Un test manuel n'est bien évidemment plus adapté. On préféra donner ce travail à une CI/CD.

Ansible met à disposition de façon native quelques "garde-fous" sous forme de modules à placer dans vos tâches, rôles ou playbook afin d'effectuer des étapes de validation de données tel que "fail", "assert" ou encore le "check mode". Cela n'est pas du test à proprement parler mais il permet d'apporter une forme de vérification avant l'exécution.

Des projets annexes se sont également ajoutés pour compléter l'offre. Le plus connu étant Molecule, ce dernier se base sur la librairie Python "testinfra" ou sur Ansible lui même afin de, non pas réellement de "tester" tâche par tâche, mais de "vérifier" le résultat suite à l'exécution du rôle ou playbook.

C'est l'une des choses qui peut déranger avec ce framework quand on a l'habitude de coder des tests avec des bibliothèques de tests natives (comme unittest en Python par exemple) qui permettent de tester le code lui-même.

Par ailleurs, comme précisé dans la documentation, les ressources Ansible sont des "modèles d'états souhaités". Il n'est donc pas nécessaire de tester qu'un service a bien été démarré, qu'un paquet a bien été installé, etc. C'est Ansible qui le garantit pour vous. En effet les modules utilisés dans nos tâches ont leur propres tests unitaires qui assurent que ces choses sont déclarativement vraies.

Plus encore, les modules derrière testinfra sont d'un nombre limité et ne peuvent couvrir à eux seuls toutes les possibilités d'exécutions d'un script Ansible.

Et puis finalement un Playbook c'est quoi ? C'est bien souvent une suite de manipulations de données avant d'exécuter un module particulier. Nous récupérons de la donnée depuis une API d'un coté, puis de l'autre le résultat de l'exécution d'un module. Pour transformer cette donnée à l'aide de "filtre" afin de merger des dictionnaires, de créer des listes, extraire des variables, nous concaténons le tout avec les informations fournies par l'opérateur au moment de l'exécution, etc… pour finalement faire un appel à un autre module dans une nouvelle tâche.

Comme pour un test unitaire, suivant une liste de variables connues en entrée nous voulons être certains que cette derrière tâche :

  • est bien exécutée (le script peut avoir échoué en amont) ;
  • a bien été appelée avec la bonne instanciation d'arguments (notre donnée transformée) ;
  • a bien retourné le résultat attendu ;
  • s'est bien terminée dans l'état souhaité ("changed", skipped,"failed").

Monkeyble permet ce type de tests avec une approche que l'on retrouve communément dans les langages de programmation.

Petit tour des fonctionnalités

Tester les entrées

Vous pouvez déclarer une vérification des valeurs instanciées passées en argument des modules

  - task: "my_task_name"
    test_input:
      - assert_equal:
          arg_name: module_argument
          expected: "my_value"

Monkeyble supporte des méthodes de tests comme celle disponibles dans la librairie unittest de Python: assert_equal, assert_not_equal, assert_in, assert_not_in, assert_true, assert_false, assert_is_none, assert_is_not_none, assert_list_equal, assert_dict_equal.

Tester le résultat des exécutions de module

Les modules Ansible, pour la majeure partie, retournent un dictionnaire qui peut être conservé à l'aide d'un "register" afin d'etre utilisé par les tâches suivantes.
Monkeyble permet de tester les valeurs retournées avec les mêmes méthodes que pour le test des entrées.

  - task: "my_task_name"
    test_output:
      - assert_dict_equal:
          dict_key: "result.key.name"
          expected: 
            key1: "my_value"
            key2: "my_other_value"

Tester les états des tâches

Une tâche Ansible a plusieurs états possibles suite à son exécution. Monkeyble peut vérifier qu'une tâche s'est bien terminée dans un état particulier.

  - task: "my_task_name"
    should_be_skipped: false
    should_be_changed: true
    should_failed: false

Simuler un module (Monkey patching)

Le "Monkey patching", est une technique permettant de modifier dynamiquement une méthode afin de renvoyer une ou des valeurs préalablement spécifiées dans le test.
Monkeyble permet de "mocker" une tâche et retourner un résultat défini.
Dans un scénario où vous travaillez avec des API de cloud, dans un contexte de tests, vous ne souhaitez pas forcément créer réellement une instance d'un objet à chaque exécution de votre CI/CD, cependant vous souhaitez que la tâche retourne ce que le vrai module aurait retourné afin d'exécuter le playbook dans son intégralité et que les tâches suivantes puissent se servir de cette sortie.

- task: "my_task_name"
  mock:
    config:
      monkeyble_module:
        consider_changed: true
        result_dict:
          my_key: "mock value"

Conclusion

Monkeyble est très simple à utiliser, son fonctionnement est entièrement basé sur de la configuration Yaml et reste donc dans le même état d'esprit que le projet Ansible.

Vous pouvez créer autant de scenarios que nécessaire afin de :

  • garantir que vos playbooks ou rôles fonctionnent correctement ;
  • protéger des régressions suite à une mise à jour de parties partagées ;
  • tester unitairement des tâches complexes qui manipulent de la donnée ;
  • passer dans chacune des conditions et blocs de code ;
  • valider que vos playbooks s'arrêtent bien (fail) ou ne s'exécutent pas (skip) quand il le faut ;
  • que les tâches critiques s'exécutent avec le bon jeu d'arguments.
monkeyble test

Playbook   | Scenario        | Test passed
-----------+-----------------+-------------
 play1.yml | validate_test_1 | ✅
 play1.yml | validate_test_2 | ✅
 play2.yml | validate_this   | ✅
 play2.yml | validate_that   | ✅

 🐵 Monkeyble test result - Tests passed: 4 of 4 tests

Happy testing à vous ! ✅

Aller plus loin

  • # C'est quoi la grande différence avec Molécule ?

    Posté par  . Évalué à 1.

    Je connaissais pas Monkeyble.

    Je sais qu'il y a Molecule pour tester les roles Ansible, mais de ce que tu indiques là, j'ai l'impression que Monkeyble test seulement le playbook.

    C'est quoi la grande différence entre les 2 soft et faut-il utiliser les 2 ?

    On est pas encore passé à ce niveau de qualité en interne, mais je suis preneur de tous les retours possible sur les 2 outils.

    • [^] # Re: C'est quoi la grande différence avec Molécule ?

      Posté par  (site web personnel, Mastodon) . Évalué à 3.

      De ce que j'ai compris, Monkeyble teste effectivement les playbooks selon un certain nombre de choix faits (comme écrire un test unitaire lorsqu'on développe)

      • Tester les valeurs passées aux modules avec son module test_input… Mais je n'ai pas bien compris où la tâche se place et en quoi c'est mieux que
        • la directive classique when dans la tâche,
        • ou le module assert avant.
      • Tester les valeurs retournées par les modules avec son module test_output… Pareil, je ne sais pas où se place cette tâche et pourquoi c'est mieux que d'utiliser
      • Tester les états des tâches avec ses directives should_be_skipped/should_be_changed/should_failed qui semblent être des synonymes des tests avec when sur les variables d'état changed/failed/skipped (avec des filtres de même nom sur results)
      • Simuler un module avec son module mock dont je n'ai pas compris l'exemple mais qui me fait penser au check_mode

      Molecule peut être utilisé aussi pour tester les playbooks (quand on teste un rôle, on teste un playbook qui ne traite que de ce rôle, mais rien n'empêche de faire plus large) mais le principe est d'automatiser ce test sur les environnements de qualification …en reproduisant tous les cas que l'on supporte (tous les os dans toutes les versions et toutes les combinaisons logicielles doivent être présentes.)

      “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: C'est quoi la grande différence avec Molécule ?

        Posté par  . Évalué à 3.

        J'aurais dû mettre le hello World de la doc.
        Monkeyble ne demande pas de modification du playbook ou rôle. Il va exécuter le playbook et vérifier tâche par tâche les entrée ou sortie.
        Le mock aussi présent dans la doc permet de valider par exemple les inputs sans pour autant vraiment exécuter le module.
        Par exemple vous avez un module pour créer une VM vous voulez juste vérifier que la tâche aurait été appelé avec les bons arguments.
        Faut faire un tour sur la doc pour mieux visualiser.

    • [^] # Re: C'est quoi la grande différence avec Molécule ?

      Posté par  . Évalué à 3.

      En réalité molécule ne test pas vraiment. Il exécute le playbook dans un env conteneurisé et vous demande de "vérifier" par vous même après coup que le résultat final est bien celui attendu.

      • [^] # Re: C'est quoi la grande différence avec Molécule ?

        Posté par  . Évalué à 3.

        Déjà savoir qu'un playbook qui run jusqu'au bout c'est un bon signe, car le playbook peut lui même contenir les tests que l'environnement déployé fonctionne (c'est mieux en général !) par exemple avec un check sur une url publique, ou une petite requête BDD, etc.

  • # Intéressant mais est-ce suffisant ?

    Posté par  . Évalué à 3.

    Bravo pour votre projet. Monkeyble est sûrement tout à fait utile pour améliorer les playbook, mais ça ne me semble pas suffisant.

    les ressources Ansible sont des "modèles d'états souhaités"

    Perso ce qui m'a éloigné de Ansible c'est justement que cette phrase est fausse ! Rare sont les modules Ansible vraiment écrits comme ça.

    (attention, mon expérience date un peu, si des choses ont changé c'est super)

    Un des manques criant, par exemple, est le nettoyage des tâches supprimées (si j'enlève une tâche qui ajoute une ligne dans un fichier de conf, cette ligne restera là où elle a été mise), il faudrait plutôt que Ansible gère un état on/off.

    Pour assurer sa maxime, Ansible devrait drastiquement limiter les modules à ceux capable de gérer ces cas là, mais ce n'est pas vrai de la plupart des modules de base même…

    J'ai beaucoup souffert à déverminer des playbook qui avait fait l'objet de maintenance, qui pouvait run sur la machine de prod, mais était incapable de ré-installer une machine à partir de zéro… ou le contraire !

    Bref perso je suis bien plus content de l'approche Docker pour le moment: rebâtir tout de zéro à chaque fois, sans arrêt ! (mais oui, ok, Docker ne va pas faire l'install du système sosu jacent, donc il faut un truc, mais je le limite au maximum)

    Si je devais faire du déploiement automatisé je chercherais autre chose que Ansible, peut être pyinfra pour éviter le cauchemar des yaml imbriqués les uns dans les autres (et qui s'appellent tous "main.yml" ;-) ).

    • [^] # Re: Intéressant mais est-ce suffisant ?

      Posté par  (site web personnel) . Évalué à 5. Dernière modification le 03 décembre 2022 à 10:57.

      Un des manques criant, par exemple, est le nettoyage des tâches supprimées (si j'enlève une tâche qui ajoute une ligne dans un fichier de conf, cette ligne restera là où elle a été mise), il faudrait plutôt que Ansible gère un état on/off.

      Oui tu es d'abord censé "retirer la ligne" (state absent au lieu de present) puis retirer la tâche. Il n'y a pas de magie non plus : si tu retires une machine de ton inventaire, ansible ne va pas la déconfigurer. Si tu retires de la gestion ansible un utilisateur ajouté sur une machine sans avoir fait son retrait, il va rester. Pareil pour une entrée DNS ajoutée, un stockage objet distant provisionné, un fichier créée sur disque, un paquet installé, etc. Etc. Ansible ne gère pas ce qu'on ne lui dit pas, Git ne gère pas ce qu'on ne lui dit pas, etc.

      • [^] # Re: Intéressant mais est-ce suffisant ?

        Posté par  (site web personnel, Mastodon) . Évalué à 2.

        +1 C'est que malheureusement l'outil n'est pas compris comme cela par certaines personne qui s'attendent à une sorte de magie vodou. Mais effectivement, ça gère les états que l'on déclare (et retire la gestion d'un état ne va pas automagiquement retirer l'état heureusement, mais juste ne plus la gérer.)

        “It is seldom that liberty of any kind is lost all at once.” ― David Hume

      • [^] # Re: Intéressant mais est-ce suffisant ?

        Posté par  . Évalué à 2.

        Oui tu es d'abord censé "retirer la ligne" (state absent au lieu de present) puis retirer la tâche.

        On est d'accord, et je n'ai jamais dit que ce devait être magique, mais vraiment il y a des commandes où cet état présent/absent n'est pas géré par la commande. (bon en tout cas à l'époque où je l'utilisais).
        C'est peut être aussi les rôles qui manquent parfois de maturité (et n'ont pas de vrai désinstallation).

        Au final je trouve en tout cas que la promesse de "déclarer un état" n'est que très peu tenue.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.