Comment tester des rôles Ansible avec Molecule sur Ubuntu 16.04

L’auteur a sélectionné le Mozilla Foundation pour recevoir un don dans le cadre du programme Write for DOnations .

introduction

Le test des unités dans Ansible est essentiel pour garantir que les rôles fonctionnent comme prévu. Molecule facilite ce processus en vous permettant de spécifier des scénarios qui testent des rôles dans différents environnements. En utilisant Ansible sous le capot, Molecule délègue les rôles à un fournisseur qui déploie le rôle dans un environnement configuré et appelle un vérificateur (tel que Testinfra) pour vérifier la dérive de configuration. Cela garantit que votre rôle a apporté toutes les modifications attendues à l’environnement dans ce scénario particulier.

Dans ce guide, vous allez créer un rôle Ansible qui déploie Apache sur un hôte et configure Firewalld sur CentOS 7. Pour vérifier que ce rôle fonctionne comme prévu, vous créerez un test dans Molecule en utilisant Docker en tant que pilote et Testinfra, une bibliothèque Python permettant de tester l’état des serveurs. Molecule fournira les conteneurs Docker pour tester le rôle et Testinfra vérifiera que le serveur a été configuré comme prévu. Une fois que vous avez terminé, vous pourrez créer plusieurs scénarios de test pour des générations dans plusieurs environnements et exécuter ces tests à l’aide de Molecule.

Conditions préalables

Avant de commencer ce guide, vous aurez besoin des éléments suivants:

Étape 1 - Préparation de l’environnement

Pour créer notre rôle et nos tests, commençons par créer un environnement virtuel et installer Molecule. L’installation de Molecule installera également Ansible, ce qui permettra d’utiliser des playbooks pour créer des rôles et exécuter des tests.

Commencez par vous connecter en tant qu’utilisateur non root et en vous assurant que vos référentiels sont à jour:

sudo apt-get update

Cela garantira que votre référentiel de paquet inclut la dernière version du paquet + python-pip +, qui installera + pip + et Python 2.7. Nous allons utiliser + pip + pour créer un environnement virtuel et installer des paquets supplémentaires. Pour installer + pip +, exécutez:

sudo apt-get install -y python-pip

Utilisez + pip + pour installer le module + virtualenv + Python:

python -m pip install virtualenv

Ensuite, créons et activons l’environnement virtuel:

python -m virtualenv

Activez-le pour vous assurer que vos actions sont limitées à cet environnement:

source my_env/bin/activate

Installez + molécule et` + docker` en utilisant + pip:

python -m pip install molecule docker

Voici ce que chacun de ces paquets va faire:

  • + molecule +: Ceci est le paquetage principal de la molécule que vous utiliserez pour tester les rôles. L’installation de + molecule + installe automatiquement Ansible, ainsi que d’autres dépendances, et permet d’utiliser les playbooks Ansible pour exécuter des rôles et des tests.

  • + docker +: Cette bibliothèque Python est utilisée par Molecule pour s’interfacer avec Docker. Vous en aurez besoin puisque vous utilisez Docker en tant que pilote.

Ensuite, créons un rôle dans Molecule.

Étape 2 - Création d’un rôle dans la molécule

Avec votre environnement configuré, vous pouvez utiliser Molecule pour créer un rôle de base afin de tester une installation d’Apache. Ce rôle créera la structure de répertoires et certains tests initiaux, et spécifiera Docker en tant que pilote afin que Molecule utilise Docker pour exécuter ses tests.

Créez un nouveau rôle appelé + ansible-apache:

molecule init role -r ansible-apache -d docker

L’indicateur + -r + spécifie le nom du rôle, tandis que + -d + spécifie le pilote qui fournit les hôtes à Molecule à utiliser lors des tests.

Accédez au répertoire du rôle nouvellement créé:

cd ansible-apache

Testez le rôle par défaut pour vérifier si Molecule a été correctement configuré:

molecule test

Vous verrez une sortie qui répertorie chacune des actions de test par défaut:

Output--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix

└── default
   ├── lint
   ├── destroy
   ├── dependency
   ├── syntax
   ├── create
   ├── prepare
   ├── converge
   ├── idempotence
   ├── side_effect
   ├── verify
   └── destroy
...

Avant de commencer le test, Molecule valide le fichier de configuration + molecule.yml + pour s’assurer que tout est en ordre. Il imprime également cette matrice de test, qui spécifie l’ordre des actions de test.

Nous discuterons de chaque action de test en détail une fois que vous avez créé votre rôle et personnalisé vos tests. Pour l’instant, faites attention à + ​​PLAY_RECAP + pour chaque test et assurez-vous qu’aucune des actions par défaut ne renvoie un statut + échec +. Par exemple, l’action "+ PLAY_RECAP " pour l'action "" create '+ `par défaut devrait ressembler à ceci:

Output...
PLAY RECAP *********************************************************************
localhost                  : ok=5    changed=4    unreachable=0    failed=0

Passons maintenant à la modification du rôle pour configurer Apache et Firewalld.

Étape 3 - Configuration d’Apache et Firewalld

Pour configurer Apache et Firewalld, vous allez créer un fichier de tâches pour le rôle, en spécifiant les packages à installer et les services à activer. Ces détails seront extraits d’un fichier de variables et d’un modèle que vous utiliserez pour remplacer la page d’index Apache par défaut.

Créez un fichier de tâches pour le rôle en utilisant + nano + ou votre éditeur de texte préféré:

nano tasks/main.yml

Vous verrez que le fichier existe déjà. Supprimez ce qui est là et collez le code suivant pour installer les packages requis et activer les services, valeurs par défaut HTML et paramètres de pare-feu appropriés:

~ / ansible-apache / tasks / main.yml

---
- name: "Ensure required packages are present"
 yum:
   name: "{{ pkg_list }}"
   state: present

- name: "Ensure latest index.html is present"
 template:
   src: index.html.j2
   dest: /var/www/html/index.html

- name: "Ensure httpd service is started and enabled"
 service:
   name: "{{ item }}"
   state: started
   enabled: true
 with_items: "{{ svc_list }}"

- name: "Whitelist http in firewalld"
 firewalld:
   service: http
   state: enabled
   permanent: true
   immediate: true

Ce cahier comprend 4 tâches:

  • " Assurez-vous que les paquetages requis sont présents ": Cette tâche installera les paquetages listés dans le fichier de variables sous + pkg_list +. Le fichier de variables sera situé dans + ~ / ansible-apache / vars / main.yml + et vous le créerez à la fin de cette étape.

  • " S'assurer que le dernier index.html est présent ": cette tâche copiera une page de modèle + index.html.j2 + et le collera sur le fichier d’index par défaut, `+ / var / www / html / index.html + `, généré par Apache. Vous créerez également le modèle à cette étape.

  • " S'assurer que le service httpd est démarré et activé ": Cette tâche va démarrer et activer les services listés dans + svc_list + dans le fichier de variables.

  • " Liste blanche http dans firewalld ": cette tâche ajoutera la liste blanche du service + http + dans + firewalld +. Firewalld est une solution de pare-feu complète présente par défaut sur les serveurs CentOS. Pour que le service + http + fonctionne, vous devez exposer les ports requis. En ordonnant à + ​​firewalld + de mettre un service en liste blanche, il s’assure qu’il enregistre en liste blanche tous les ports requis par le service.

Enregistrez et fermez le fichier lorsque vous avez terminé.

Ensuite, créons un répertoire + templates + pour la page de modèle + index.html.j2 +:

mkdir templates

Créez la page elle-même:

nano templates/index.html.j2

Collez le code standard suivant:

~ / ansible-apache / templates / index.html.j2

<div style="text-align: center">
   <h2>Managed by Ansible</h2>
</div>

Enregistrez et fermez le fichier.

La dernière étape de la réalisation du rôle consiste à écrire le fichier de variables, qui fournit les noms des packages et des services à votre livre de rôle principal:

nano vars/main.yml

Collez le contenu par défaut avec le code suivant, qui spécifie + pkg_list + et + svc_list +:

~ / ansible-apache / vars / main.yml

---
pkg_list:
 - httpd
 - firewalld
svc_list:
 - httpd
 - firewalld

Ces listes contiennent les informations suivantes:

  • + pkg_list +: contient les noms des packages que le rôle va installer: + httpd + et + firewalld +.

  • + svc_list +: contient les noms des services que le rôle va démarrer et activer: + httpd + et + firewalld +.

Maintenant que vous avez terminé de créer le rôle, configurons Molecule pour vérifier s’il fonctionne comme prévu.

Étape 4 - Modification du rôle pour l’exécution de tests

Dans notre cas, la configuration de Molecule implique la modification du fichier de configuration de la molécule + molecule.yml + pour ajouter les spécifications de la plate-forme. Comme vous testez un rôle qui configure et lance le service systemd + httpd +, vous devrez utiliser une image avec le mode configuré et privilégié systemd activé. Pour ce tutoriel, vous utiliserez l’image + milcom / centos7-systemd + available sur le Docker Hub. Le mode privilégié permet aux conteneurs de s’exécuter avec presque toutes les fonctionnalités de leur ordinateur hôte.

Éditons + molecule.yml + pour refléter ces changements:

nano molecule/default/molecule.yml

Ajoutez les informations de la plateforme en surbrillance:

~ / ansible-apache / molecule / default / molecule.yml

---
dependency:
 name: galaxy
driver:
 name: docker
lint:
 name: yamllint
platforms:



provisioner:
 name: ansible
 lint:
   name: ansible-lint
scenario:
 name: default
verifier:
 name: testinfra
 lint:
   name: flake8

Enregistrez et fermez le fichier lorsque vous avez terminé.

Maintenant que vous avez correctement configuré l’environnement de test, passons à l’écriture des scénarios de test que Molecule exécutera sur votre conteneur après l’exécution du rôle.

Étape 5 - Écrire des cas de test

Lors du test de ce rôle, vous vérifierez les conditions suivantes:

  • Les packages + httpd + et + firewalld + sont installés.

  • Que les services + httpd + et + firewalld + sont en cours d’exécution et activés.

  • Que le service + http + est activé dans les paramètres de votre pare-feu.

  • Ce + index.html + contient les mêmes données que celles spécifiées dans votre fichier de modèle.

Si tous ces tests réussissent, le rôle fonctionne comme prévu.

Pour écrire les scénarios de test pour ces conditions, éditons les tests par défaut dans + ~ / ansible-apache / molecule / default / tests / test_default.py +. À l’aide de Testinfra, vous écrivez les scénarios de test sous forme de fonctions Python utilisant des classes Molecule.

Ouvrez + test_default.py +:

nano molecule/default/tests/test_default.py

Supprimez le contenu du fichier pour pouvoir écrire les tests à partir de zéro.

Commencez par importer les modules Python requis:

~ / ansible-apache / molecule / default / tests / test_default.py

import os
import pytest

import testinfra.utils.ansible_runner

Ces modules comprennent:

  • + os +: ce module Python intégré active la fonctionnalité dépendant du système d’exploitation, permettant à Python de se connecter au système d’exploitation sous-jacent.

  • + pytest +: le module https://docs.pytest.org/en/latest/ [+ pytest +]] permet l’écriture de test.

  • + testinfra.utils.ansible_runner +: ce module Testinfra utilise Ansible comme serveur principal pour l’exécution de la commande.

Sous les importations du module, collez le code suivant, qui utilise le moteur Ansible pour renvoyer l’instance d’hôte actuelle:

~ / ansible-apache / molecule / default / tests / test_default.py

...
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
   os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')

Votre fichier de test étant configuré pour utiliser le backend Ansible, laissez-nous écrire des tests unitaires pour tester l’état de l’hôte.

Le premier test s’assurera que + httpd + et + firewalld + sont installés:

~ / ansible-apache / molecule / default / tests / test_default.py

...

@pytest.mark.parametrize('pkg', [
 'httpd',
 'firewalld'
])
def test_pkg(host, pkg):
   package = host.package(pkg)

   assert package.is_installed

Le test commence par les https://docs.pytest.org/fr/latest/parametrize.html#pytest-mark-parametrize-parametrizing-test-functions [+ pytest.mark.parametrize + décorateur], qui nous permet de paramétrer les arguments pour le test. Ce premier test utilisera + test_pkg + comme paramètre pour tester la présence des paquets + httpd + et + firewalld +.

Le test suivant vérifie si + httpd + et + firewalld + sont en cours d’exécution et activés. Il prend + test_svc + en paramètre:

~ / ansible-apache / molecule / default / tests / test_default.py

...

@pytest.mark.parametrize('svc', [
 'httpd',
 'firewalld'
])
def test_svc(host, svc):
   service = host.service(svc)

   assert service.is_running
   assert service.is_enabled

Le dernier test vérifie que les fichiers et contenus passés à + ​​parametrize () + existent. Si le fichier n’est pas créé par votre rôle et que le contenu n’est pas défini correctement, + assert + retournera + False +:

~ / ansible-apache / molecule / default / tests / test_default.py

...

@pytest.mark.parametrize('file, content', [
 ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"),
 ("/var/www/html/index.html", "Managed by Ansible")
])
def test_files(host, file, content):
   file = host.file(file)

   assert file.exists
   assert file.contains(content)

Dans chaque test, + assert renverra` + True` ou + False en fonction du résultat du test.

Le fichier fini ressemble à ceci:

~ / ansible-apache / molecule / default / tests / test_default.py

import os
import pytest

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
   os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')


@pytest.mark.parametrize('pkg', [
 'httpd',
 'firewalld'
])
def test_pkg(host, pkg):
   package = host.package(pkg)

   assert package.is_installed


@pytest.mark.parametrize('svc', [
 'httpd',
 'firewalld'
])
def test_svc(host, svc):
   service = host.service(svc)

   assert service.is_running
   assert service.is_enabled


@pytest.mark.parametrize('file, content', [
 ("/etc/firewalld/zones/public.xml", "<service name=\"http\"/>"),
 ("/var/www/html/index.html", "Managed by Ansible")
])
def test_files(host, file, content):
   file = host.file(file)

   assert file.exists
   assert file.contains(content)

Maintenant que vous avez spécifié vos scénarios de test, testons le rôle.

Étape 6 - Test du rôle avec la molécule

Une fois le test lancé, Molecule exécutera les actions que vous avez définies dans votre scénario. Lançons à nouveau le scénario + molécule + par défaut, en exécutant les actions de la séquence de test par défaut tout en les examinant de plus près.

Exécutez à nouveau le test pour le scénario par défaut:

molecule test

Cela lancera le test. La sortie initiale imprime la matrice de test par défaut:

Output--> Validating schema /home/sammy/ansible-apache/molecule/default/molecule.yml.
Validation completed successfully.
--> Test matrix

└── default
   ├── lint
   ├── destroy
   ├── dependency
   ├── syntax
   ├── create
   ├── prepare
   ├── converge
   ├── idempotence
   ├── side_effect
   ├── verify
   └── destroy

Passons en revue chaque action de test et le résultat attendu, en commençant par le peluchage.

L’action linting a exécuté + yamllint,` + flake 8` et + ansible-lint:

  • + yamllint +: Ce linter est exécuté sur tous les fichiers YAML présents dans le répertoire du rôle.

  • + flake8 +: Ce code Python vérifie les tests créés pour Testinfra.

  • + ansible-lint +: Ce linter pour les playbooks Ansible est exécuté dans tous les scénarios.

Output...
--> Scenario: 'default'
--> Action: 'lint'
--> Executing Yamllint on files found in /home/sammy/ansible-apache/...
Lint completed successfully.
--> Executing Flake8 on files found in /home/sammy/ansible-apache/molecule/default/tests/...
Lint completed successfully.
--> Executing Ansible Lint on /home/sammy/ansible-apache/molecule/default/playbook.yml...
Lint completed successfully.

L’action suivante, destroy, est exécutée à l’aide du fichier + destroy.yml +. Ceci est fait pour tester votre rôle sur un conteneur nouvellement créé.

Par défaut, destroy est appelé deux fois: au début du test, pour supprimer tout conteneur préexistant, et à la fin, pour supprimer le conteneur nouvellement créé:

Output...
--> Scenario: 'default'
--> Action: 'destroy'

   PLAY [Destroy] *****************************************************************

   TASK [Destroy molecule instance(s)] ********************************************
   changed: [localhost] => (item=None)
   changed: [localhost]

   TASK [Wait for instance(s) deletion to complete] *******************************
   ok: [localhost] => (item=None)
   ok: [localhost]

   TASK [Delete docker network(s)] ************************************************
   skipping: [localhost]

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

Une fois l’action de destruction terminée, le test passera à dependency. Cette action vous permet d’extraire des dépendances à partir de https://galaxy.ansible.com/ [+ ansible-galaxy +] si votre rôle le requiert. Dans ce cas, le rôle ne:

Output...
--> Scenario: 'default'
--> Action: 'dependency'
Skipping, missing the requirements file.

L’action de test suivante est une vérification syntax, qui est exécutée sur le playbook par défaut + playbook.yml +. Cela fonctionne de la même manière que l’indicateur + - syntax-check + dans la commande + ansible-playbook --syntax-check playbook.yml +:

Output...
--> Scenario: 'default'
--> Action: 'syntax'

   playbook: /home/sammy/ansible-apache/molecule/default/playbook.yml

Ensuite, le test passe à l’action create. Ceci utilise le fichier + create.yml + dans le répertoire Molecule de votre rôle pour créer un conteneur Docker avec vos spécifications:

Output...

--> Scenario: 'default'
--> Action: 'create'

   PLAY [Create] ******************************************************************

   TASK [Log into a Docker registry] **********************************************
   skipping: [localhost] => (item=None)
   skipping: [localhost]

   TASK [Create Dockerfiles from image names] *************************************
   changed: [localhost] => (item=None)
   changed: [localhost]

   TASK [Discover local Docker images] ********************************************
   ok: [localhost] => (item=None)
   ok: [localhost]

   TASK [Build an Ansible compatible image] ***************************************
   changed: [localhost] => (item=None)
   changed: [localhost]

   TASK [Create docker network(s)] ************************************************
   skipping: [localhost]

   TASK [Create molecule instance(s)] *********************************************
   changed: [localhost] => (item=None)
   changed: [localhost]

   TASK [Wait for instance(s) creation to complete] *******************************
   changed: [localhost] => (item=None)
   changed: [localhost]

   PLAY RECAP *********************************************************************
   localhost                  : ok=5    changed=4    unreachable=0    failed=0

Après la création, le test passe à l’action prepare. Cette action exécute le livre de préparation, ce qui place l’hôte dans un état spécifique avant de lancer la convergence. Ceci est utile si votre rôle nécessite une pré-configuration du système avant son exécution. Encore une fois, cela ne s’applique pas à notre rôle:

Output...
--> Scenario: 'default'
--> Action: 'prepare'
Skipping, prepare playbook not configured.

Après la préparation, l’action converge exécute votre rôle sur le conteneur en exécutant le playbook + playbook.yml +. Si plusieurs plates-formes sont configurées dans le fichier + molecule.yml +, Molecule convergera vers tous ceux-ci:

Output...
--> Scenario: 'default'
--> Action: 'converge'

   PLAY [Converge] ****************************************************************

   TASK [Gathering Facts] *********************************************************
   ok: [centos7]

   TASK [ansible-apache : Ensure required packages are present] *******************
   changed: [centos7]

   TASK [ansible-apache : Ensure latest index.html is present] ********************
   changed: [centos7]

   TASK [ansible-apache : Ensure httpd service is started and enabled] ************
   changed: [centos7] => (item=httpd)
   changed: [centos7] => (item=firewalld)

   TASK [ansible-apache : Whitelist http in firewalld] ****************************
   changed: [centos7]

   PLAY RECAP *********************************************************************
   centos7                    : ok=5    changed=4    unreachable=0    failed=0

Après la couverture, le test passe à idempotence. Cette action teste le livret pour idempotence afin de s’assurer qu’aucune modification inattendue n’est apportée dans plusieurs exécutions:

Output...
--> Scenario: 'default'
--> Action: 'idempotence'
Idempotence completed successfully.

L’action de test suivante est l’action side-effect. Cela vous permet de créer des situations dans lesquelles vous pourrez tester plus de choses, comme le basculement haute disponibilité. Par défaut, Molecule ne configure pas de livre de lecture d’effets secondaires et la tâche est ignorée:

Output...
--> Scenario: 'default'
--> Action: 'side_effect'
Skipping, side effect playbook not configured.

Molecule exécutera ensuite l’action verifier à l’aide du vérificateur par défaut, Testinfra. Cette action exécute les tests que vous avez écrits précédemment dans + test_default.py +. Si tous les tests réussissent, vous verrez un message de réussite et Molecule passera à l’étape suivante:

Output...
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /home/sammy/ansible-apache/molecule/default/tests/...
   ============================= test session starts ==============================
   platform linux2 -- Python 2.7.12, pytest-3.8.0, py-1.6.0, pluggy-0.7.1
   rootdir: /home/sammy/ansible-apache/molecule/default, inifile:
   plugins: testinfra-1.14.1
collected 6 items

   tests/test_default.py ......                                             [100%]

   ========================== 6 passed in 56.73 seconds ===========================

Enfin, Molecule destroys les instances terminées lors du test et supprime le réseau affecté à ces instances:

Output...
--> Scenario: 'default'
--> Action: 'destroy'

   PLAY [Destroy] *****************************************************************

   TASK [Destroy molecule instance(s)] ********************************************
   changed: [localhost] => (item=None)
   changed: [localhost]

   TASK [Wait for instance(s) deletion to complete] *******************************
   changed: [localhost] => (item=None)
   changed: [localhost]

   TASK [Delete docker network(s)] ************************************************
   skipping: [localhost]

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

Les actions de test sont maintenant terminées et vérifient que votre rôle a fonctionné comme prévu.

Conclusion

Dans cet article, vous avez créé un rôle Ansible pour installer et configurer Apache et Firewalld. Vous avez ensuite écrit avec Testinfra des tests unitaires que Molecule utilisait pour affirmer que le rôle avait été exécuté avec succès.

Vous pouvez utiliser la même méthode de base pour des rôles très complexes et automatiser les tests à l’aide d’un pipeline de CI également. Molecule est un outil hautement configurable qui peut être utilisé pour tester des rôles avec n’importe quel fournisseur pris en charge par Ansible, pas seulement Docker. Il est également possible d’automatiser les tests sur votre propre infrastructure, en veillant à ce que vos rôles soient toujours à jour et fonctionnels. La documentation officielle Molecule est la meilleure ressource pour apprendre à utiliser Molecule.