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:
-
Un serveur Ubuntu 18.04. Suivez les étapes du guideInitial Server Setup with Ubuntu 18.04 pour créer un utilisateur sudo non root et assurez-vous que vous pouvez vous connecter au serveur sans mot de passe.
-
Docker installé sur votre serveur. Suivez les étapes 1 et 2 deHow To Install and Use Docker on Ubuntu 18.04, y compris l'ajout de votre utilisateur non root au groupe
docker
. -
Python 3 et
venv
installés et configurés sur votre serveur. SuivezHow To Install Python 3 and Set Up a Programming Environment on an Ubuntu 18.04 Server pour obtenir des conseils. -
Familiarité avec les playbooks Ansible. Pour un examen, voirConfiguration Management 101: Writing Ansible Playbooks.
[[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_wheel
setuptools
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-systemd
available 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 packages
httpd
etfirewalld
sont installés. -
Que les services
httpd
etfirewalld
sont en cours d'exécution et activés. -
Que le service
http
est activé dans vos paramètres de pare-feu. -
Ce
index.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.