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

L'auteur a sélectionné lesMozilla Foundation pour recevoir un don dans le cadre du programmeWrite for DOnations.

introduction

Les tests unitaires enAnsible sont essentiels pour s'assurer que les rôles fonctionnent comme prévu. Molecule facilite ce processus en vous permettant de spécifier des scénarios qui testent les rôles dans différents environnements. En utilisant Ansible sous le capot, Molecule décharge les rôles vers un approvisionneur qui déploie le rôle dans un environnement configuré et appelle un vérificateur (tel queTestinfra) 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éploieApache sur un hôte et configurefirewalld sur CentOS 7. Pour tester que ce rôle fonctionne comme prévu, vous allez créer un test dans Molecule en utilisantDocker comme pilote et Testinfra, une bibliothèque Python pour 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:

[[step-1 -—- Preparing-the-Environment]] == Étape 1 - Préparation de l'environnement

Si vous avez respecté les conditions préalables, vous devez avoir Python 3,venv et Docker installés et correctement configurés. Commençons par créer un environnement virtuel pour tester Ansible avec Molecule.

Commencez par vous connecter en tant qu'utilisateur non root et en créant un nouvel environnement virtuel:

python3 -m venv my_env

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

source my_env/bin/activate

Ensuite, dans votre environnement activé, installez le packagewheel, qui fournit l'extensionbdist_wheelsetuptools quepip utilise pour installer Ansible:

python3 -m pip install wheel

Vous pouvez maintenant installermolecule etdocker avecpip. Ansible sera automatiquement installé en tant que dépendance pour Molecule:

python3 -m pip install molecule docker

Voici ce que chacun de ces paquets va faire:

  • molecule: Il s'agit du package principal Molecule que vous utiliserez pour tester les rôles. L'installation demolecule installe automatiquement Ansible, ainsi que d'autres dépendances, et permet l'utilisation de 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.

[[step-2 -—- creating-a-role-in-molecule]] == Étape 2 - Créer un rôle dans la molécule

Avec votre environnement configuré, vous pouvez utiliser Molecule pour créer un rôle de base que vous utiliserez pour 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 pour que Molecule les utilise dans les 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 répertoriant chacune des actions de test par défaut. Avant de démarrer le test, Molecule valide le fichier de configurationmolecule.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:

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
...

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 auxPLAY_RECAP pour chaque test, et assurez-vous qu'aucune des actions par défaut ne renvoie un statutfailed. Par exemple, lePLAY_RECAP pour l'action par défaut'create' 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.

[[step-3 -—- configuring-apache-and-firewalld]] == Étape 3 - Configuration d'Apache et de 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.

Toujours dans le répertoireansible-apache, créez un fichier de tâches pour le rôle en utilisantnano ou votre éditeur de texte préféré:

nano tasks/main.yml

Vous verrez que le fichier existe déjà. Supprimez ce qui existe et remplacez-le par le code suivant pour installer les packages requis et activer les services, les paramètres HTML par défaut et les 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:

  • "Ensure required packages are present": cette tâche installera les packages répertoriés dans le fichier de variables souspkg_list. Le fichier de variables sera situé à~/ansible-apache/vars/main.yml et vous le créerez à la fin de cette étape.

  • "Ensure latest index.html is present": cette tâche copiera une page modèle,index.html.j2, et la collera sur le fichier d'index par défaut,/var/www/html/index.html, généré par Apache. Vous créerez également le nouveau modèle à cette étape.

  • "Ensure httpd service is started and enabled": Cette tâche démarre et active les services répertoriés danssvc_list dans le fichier de variables.

  • "Whitelist http in firewalld": cette tâche mettra en liste blanche le servicehttp dansfirewalld. Firewalld est une solution de pare-feu complète présente par défaut sur les serveurs CentOS. Pour que le servicehttp fonctionne, vous devrez exposer les ports requis. Demander àfirewalld de mettre un service sur liste blanche garantit qu'il liste tous les ports dont le service a besoin.

Enregistrez et fermez le fichier lorsque vous avez terminé.

Ensuite, créons un répertoiretemplates pour la page de modèleindex.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

Managed by Ansible

Enregistrez et fermez le fichier.

La dernière étape de la finalisation du rôle est l'écriture du fichier de variables, qui fournit les noms des packages et des services à notre livre de rôle principal:

nano vars/main.yml

Collez le contenu par défaut avec le code suivant, qui spécifiepkg_list etsvc_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 installera:httpd etfirewalld.

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

[.note] #Note: Assurez-vous que votre fichier de variables ne contient pas de lignes vides, sinon votre test échouera pendant le lintage.
#

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

[[step-4 -—- modifying-the-role-for-running-tests]] == Étape 4 - Modification du rôle pour l'exécution des tests

Dans notre cas, la configuration de Molecule implique de modifier le fichier de configuration de Moleculemolecule.yml pour ajouter des spécifications de plateforme. Étant donné que vous testez un rôle qui configure et démarre le service systemd dehttpd, vous devrez utiliser une image avec systemd configuré et mode privilégié activé. Pour ce tutoriel, vous utiliserez l'imagemilcom/centos7-systemdavailable on Docker Hub. Le mode privilégié permet aux conteneurs de s'exécuter avec presque toutes les fonctionnalités de leur ordinateur hôte.

Modifionsmolecule.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:
  - name: centos7
    image: milcom/centos7-systemd
    privileged: true
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.

[[step-5 -—- writing-test-cases]] == Étape 5 - Ecrire des cas de test

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

  • Que les packageshttpd etfirewalld sont installés.

  • Que les serviceshttpd etfirewalld sont en cours d'exécution et activés.

  • Que le servicehttp est activé dans vos paramètres de pare-feu.

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

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

Pour écrire les cas de test pour ces conditions, modifions les tests par défaut en~/ansible-apache/molecule/default/tests/test_default.py. En utilisant Testinfra, nous allons écrire les cas de test sous forme de fonctions Python utilisant des classes Molecule.

Ouvrirtest_default.py:

nano molecule/default/tests/test_default.py

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

[.note] #Note: Lorsque vous écrivez vos tests, assurez-vous qu'ils sont séparés par deux nouvelles lignes, sinon ils échoueront.
#

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 les fonctionnalités dépendant du système d'exploitation, permettant à Python de s'interfacer avec le système d'exploitation sous-jacent.

  • pytest: le modulepytest active l'écriture de test.

  • testinfra.utils.ansible_runner: ce module Testinfra utiliseAnsible as the backend pour l'exécution de la commande.

Sous les importations du module, ajoutez 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 garantira quehttpd etfirewalld 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 lespytest.mark.parametrize decorator, ce qui nous permet de paramétrer les arguments du test. Ce premier test prendratest_pkg comme paramètre pour tester la présence des packageshttpd etfirewalld.

Le test suivant vérifie si leshttpd etfirewalld sont en cours d'exécution et activés. Il prendtest_svc comme 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 le contenu transmis àparametrize() existent. Si le fichier n’est pas créé par votre rôle et que le contenu n’est pas défini correctement,assert renverraFalse:

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

...

@pytest.mark.parametrize('file, content', [
  ("/etc/firewalld/zones/public.xml", ""),
  ("/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 renverraTrue ouFalse 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", ""),
  ("/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.

[[step-6 -—- testing-the-role-with-molecule]] == É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. Exécutons à nouveau le scénariomolecule par défaut, en exécutant les actions de la séquence de test par défaut tout en examinant chacun 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'actionlinting exécuteyamllint,flake8 etansible-lint:

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

  • flake8: ce linter de 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 fichierdestroy.yml. Ceci est fait pour tester notre 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 deansible-galaxy si votre rôle les requiert. Dans ce cas, notre rôle ne consiste pas à:

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

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

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

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

Ensuite, le test passe à l'actioncreate. Cela utilise le fichiercreate.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'actionprepare. Cette action exécute le livre de préparation, qui place l'hôte dans un état spécifique avant d'exécuter converger. Cela 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'actionconverge exécute votre rôle sur le conteneur en exécutant le playbook deplaybook.yml. Si plusieurs plates-formes sont configurées dans le fichiermolecule.yml, Molecule convergera vers toutes celles-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'actionside-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 alors l'actionverifier en utilisant le vérificateur par défaut, Testinfra. Cette action exécute les tests que vous avez écrits précédemment danstest_default.py. Si tous les tests réussissent, vous verrez un message de succès 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 linux -- Python 3.6.5, pytest-3.7.3, py-1.5.4, 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 41.05 seconds ===========================
Verifier completed successfully.

Enfin, Moleculedestroys les instances terminées pendant le 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. Vous pouvez intégrer des tests continus dans votre flux de travail à l'aide de Molecule et Travis CI avec le didacticielHow To Implement Continuous Testing of Ansible Roles Using Molecule and Travis CI on Ubuntu 18.04.

LeMolecule documentation officiel est la meilleure ressource pour apprendre à utiliser Molecule.