Bonjour à tous !
Je travaille en tant qu'ingénieur DevOps dans un service de réservation d'hôtels . Dans cet article, je souhaite partager notre expérience en matière de tests des rôles ansible.
Chez Ostrovok.ru, nous utilisons ansible comme gestionnaire de configurations. Récemment, nous avons constaté la nécessité de tester les rôles, mais il existe peu d'outils pour cela — le plus populaire est probablement le framework Molecule, donc nous avons décidé de l'utiliser. Cependant, il s'est avéré que sa documentation omet de nombreux pièges. Nous n'avons pas pu trouver un guide suffisamment détaillé en russe, c'est pourquoi nous avons décidé d'écrire cet article.

Molecule
— est un framework visant à faciliter les tests des rôles ansible.
Description simplifiée : Molecule crée une instance sur la plateforme que vous avez spécifiée (cloud, machine virtuelle, conteneur ; voir la section ), exécute votre rôle, puis lance les tests et supprime l'instance. En cas d'échec à l'une des étapes, Molecule vous en informera.
Maintenant, plus de détails.
Un peu de théorie
Examinons deux entités clés de Molecule : Scenario et Driver.
Scenario
Le scénario contient la description de ce qui sera exécuté, où, comment et dans quel ordre. Un rôle peut avoir plusieurs scénarios, chacun étant un répertoire situé à l'emplacement /molecule/, contenant des descriptions des actions nécessaires pour le test. Un scénario default, qui sera automatiquement créé si vous initialisez le rôle avec Molecule. Les noms des scénarios suivants sont à votre choix.
La séquence des actions de test dans le scénario est appelée matrix, et par défaut elle est la suivante :
(Les étapes marquées ?, sont ignorées par défaut si non décrites par l'utilisateur)
lint— exécution des linters. Par défaut, sont utilisésyamllintetflake8,destroy— suppression des instances de la dernière exécution de Molecule (si elles existent),dependency? — установка ansible-зависимости тестируемой роли,syntax— vérification de la syntaxe du rôle avecansible-playbook --syntax-check,create— création d'une instance,prepare? — подготовка инстанса; например, проверка / установка python2converge— exécution du playbook à tester,idempotence— exécution répétée du playbook pour tester l'idempotence,side_effect? — действия, не относящиеся непосредственно к роли, но нужные для тестов,verify— exécution des tests de la configuration obtenue à l'aide detestinfra(par défaut) /goss/inspec,cleanup? — (в новых версиях) — грубо говоря, «очистка» внешней инфраструктуры, задетой Молекулой,destroy— suppression de l'instance.
Cette séquence couvre la plupart des cas, mais peut être modifiée si nécessaire.
Chaque étape mentionnée ci-dessus peut être exécutée séparément à l'aide de molecule. Cependant, il convient de comprendre qu'il peut exister une séquence d'actions propre à chaque commande CLI, que l'on peut découvrir en exécutant molecule matrix. Par exemple, lors de l'exécution de la commande converge (exécution du playbook testé), les actions suivantes seront effectuées :
$ molecule matrix converge
...
└── default # nom du scénario
├── dependency # installation des dépendances
├── create # création de l'instance
├── prepare # préparation de l'instance
└── converge # exécution du playbookLa séquence de ces actions peut être modifiée. Si quelque chose de la liste a déjà été exécuté, cela sera ignoré. L'état actuel, ainsi que la configuration des instances, est conservé par Molecule dans le répertoire $TMPDIR/molecule//.
Des étapes avec ? peuvent être ajoutées en décrivant les actions souhaitées au format d'un playbook Ansible, et le nom du fichier doit correspondre à l'étape : prepare.yml/side_effect.yml. Molecule attendra ces fichiers dans le dossier du scénario.
Driver
Un driver est une entité où des instances sont créées pour les tests.
La liste des drivers standard pour lesquels Molecule a des modèles est la suivante : Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.
Dans la plupart des cas, les modèles sont des fichiers create.yml et destroy.yml dans le dossier du scénario, qui décrivent respectivement la création et la suppression d'une instance.
Les exceptions sont Docker et Vagrant, car les interactions avec leurs modules peuvent se faire sans les fichiers mentionnés ci-dessus.
Il convient de souligner le driver Delegated, car en cas de son utilisation, les fichiers de création et de suppression d'instance ne décrivent que le travail avec la configuration des instances, le reste devant être décrit par l'ingénieur.
Le driver par défaut est Docker.
Passons maintenant à la pratique et examinons les aspects suivants là-bas.
Commencer
Comme « hello world », nous allons tester un simple rôle d'installation de nginx. Comme driver, nous allons choisir Docker - je pense qu'il est installé pour la plupart d'entre vous (et rappelons que Docker est le driver par défaut).
Préparons virtualenv et installons-y molecule:
> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker # molecule installera ansible comme dépendance ; docker pour le driverLa prochaine étape consiste à initialiser un nouveau rôle.
L'initialisation d'un nouveau rôle, tout comme d'un nouveau scénario, se fait à l'aide de la commande molecule init:
> molecule init role -r nginx
--> Initialisation du nouveau rôle nginx...
Rôle initialisé dans /nginx avec succès.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars
6 répertoires, 1 fichierNous avons obtenu un rôle ansible typique. Ensuite, toutes les interactions avec le CLI de Molecule se font à partir de la racine du rôle.
Voyons ce qui se trouve dans le répertoire du rôle :
> tree molecule/default/
molecule/default/
├── Dockerfile.j2 # Modèle Jinja pour Dockerfile
├── INSTALL.rst. # Quelques informations sur l'installation des dépendances du script
├── molecule.yml # Fichier de configuration
├── playbook.yml # Playbook pour exécuter le rôle
└── tests # Répertoire contenant des tests pour l'étape de vérification
└── test_default.py
1 répertoire, 6 fichiersAnalysons la configuration molecule/default/molecule.yml (nous allons remplacer uniquement l'image docker):
---
dependency:
name: galaxy
driver:
name: docker
lint:
name: yamllint
platforms:
- name: instance
image: centos:7
provisioner:
name: ansible
lint:
name: ansible-lint
scenario:
name: default
verifier:
name: testinfra
lint:
name: flake8dependency
Cette section décrit la source des dépendances.
Options possibles : , , shell.
Shell – est simplement un interpréteur de commandes utilisé dans le cas où galaxy et gilt ne couvrent pas vos besoins.
Je ne vais pas m'attarder ici, cela est suffisamment décrit dans .
driver
Nom du pilote. Pour nous, c'est docker.
lint
Le linter utilisé est yamllint.
Les options utiles dans cette partie de la config sont la possibilité d'indiquer un fichier de configuration pour yamllint, de passer des variables d'environnement ou de désactiver le linter :
lint:
name: yamllint
options:
config-file: foo/bar
env:
FOO: bar
enabled: Falseplatforms
Décrit la configuration des instances.
Dans le cas de Docker comme pilote, Molecule itère sur cette section et chaque élément de la liste est accessible dans Dockerfile.j2 comme variable item.
Dans le cas d'un pilote qui exige create.yml et destroy.yml, la section est accessible en tant que molecule_yml.platforms, et les itérations à travers celle-ci sont décrites dans ces fichiers.
Étant donné que Molecule fournit la gestion des instances pour les modules ansible, il faut également chercher la liste des configurations possibles là-bas. Pour Docker, par exemple, le module utilisé est . Quels modules sont utilisés dans les autres pilotes peut être trouvé dans .
Et des exemples d'utilisation de divers pilotes peuvent être trouvés .
Remplaçons ici centos:7 sur ubuntu.
provisioner
«Fournisseur» est une entité qui gère les instances. Dans le cas de Molecule, il s'agit d'ansible, le support d'autres n'est pas prévu, donc cette section peut être considérée comme une configuration avancée d'ansible.
Ici, il est possible d'indiquer beaucoup de choses, je vais souligner les principaux points selon moi :
- playbooks: il est possible d'indiquer quels playbooks doivent être utilisés à des stades spécifiques.
provisioner:
name: ansible
playbooks:
create: create.yml
destroy: ..\/default\/destroy.yml
converge: playbook.yml
side_effect: side_effect.yml
cleanup: cleanup.yml- config_options:
provisioner:
name: ansible
config_options:
defaults:
fact_caching: jsonfile
ssh_connection:
scp_if_ssh: True- connection_options: paramètres
provisioner:
name: ansible
connection_options:
ansible_ssh_common_args: "-o 'UserKnownHostsFile=\/dev\/null' -o 'ForwardAgent=yes'"- options: paramètres Ansible et variables d'environnement
provisioner:
name: ansible
options:
vvv: true
diff: true
env:
FOO: BARscenario
Nom et description des séquences de scénario.
Il est possible de modifier la matrice d'actions par défaut d'une commande en ajoutant la clé _sequence et en définissant comme valeur la liste d'étapes souhaitée.
Supposons que nous voulions modifier l'ordre des actions lors de l'exécution de la commande pour dérouler le playbook : molecule converge
# изначально:
# - dependency
# - create
# - prepare
# - converge
scenario:
name: default
converge_sequence:
- create
- convergeverificateur
Configuration du framework pour les tests et le linter associé. Par défaut, le linter utilisé est testinfra et flake8. Les options possibles sont similaires à celles décrites ci-dessus :
verifier:
name: testinfra
additional_files_or_dirs:
- ..\/path\/to\/test_1.py
- ..\/path\/to\/test_2.py
- ..\/path\/to\/directory\/*
options:
n: 1
enabled: False
env:
FOO: bar
lint:
name: flake8
options:
benchmark: True
enabled: False
env:
FOO: barRevenons à notre rôle. Modifions le fichier tasks\/main.yml de sorte à obtenir la forme suivante :
---
- name: Installer nginx
apt:
name: nginx
state: present
- name: Démarrer nginx
service:
name: nginx
state: started
Et ajoutons des tests dans molecule\/default\/tests\/test_default.py
def test_nginx_is_installed(host):
nginx = host.package("nginx")
assert nginx.is_installed
def test_nginx_running_and_enabled(host):
nginx = host.service("nginx")
assert nginx.is_running
assert nginx.is_enabled
def test_nginx_config(host):
host.run("nginx -t")
C'est fait, il ne reste plus qu'à lancer (depuis la racine du rôle, je le rappelle) :
> molecule testSortie longue sous spoiler :
--> Validation du schéma /nginx/molecule/default/molecule.yml.
Validation terminée avec succès.
--> Matrice de test
└── défaut
├── lint
├── détruire
├── dépendance
├── syntaxe
├── créer
├── préparer
├── converger
├── idempotence
├── effet secondaire
├── vérifier
└── détruire
--> Scénario : 'défaut'
--> Action : 'lint'
--> Exécution de Yamllint sur les fichiers trouvés dans /nginx/...
Lint terminé avec succès.
--> Exécution de Flake8 sur les fichiers trouvés dans /nginx/molecule/default/tests/...
Lint terminé avec succès.
--> Exécution de Ansible Lint sur /nginx/molecule/default/playbook.yml...
Lint terminé avec succès.
--> Scénario : 'défaut'
--> Action : 'détruire'
JEU [Détruire] *****************************************************************
TÂCHE [Détruire l'instance(s) de molécule] ************************************
changé : [localhost] => (item=None)
changé : [localhost]
TÂCHE [Attendre que la(s) instance(s) suppression soit terminé(e)] **********
ok : [localhost] => (item=None)
ok : [localhost]
TÂCHE [Supprimer le(s) réseau(x) docker] *************************************
RÉCAPITULATIF DE JEU ***********************************************************
localhost : ok=2 changé=1 injoignable=0 échoué=0
--> Scénario : 'défaut'
--> Action : 'dépendance'
Sauter, fichier des exigences manquant.
--> Scénario : 'défaut'
--> Action : 'syntaxe'
playbook: /nginx/molecule/default/playbook.yml
--> Scénario : 'défaut'
--> Action : 'créer'
JEU [Créer] ********************************************************************
TÂCHE [Se connecter à un registre Docker] *************************************
saut : [localhost] => (item=None)
TÂCHE [Créer des Dockerfiles à partir des noms d'images] ***********************
changé : [localhost] => (item=None)
changé : [localhost]
TÂCHE [Découvrir les images Docker locales] ************************************
ok : [localhost] => (item=None)
ok : [localhost]
TÂCHE [Construire une image compatible Ansible] *******************************
changé : [localhost] => (item=None)
changé : [localhost]
TÂCHE [Créer le(s) réseau(x) docker] ****************************************
TÂCHE [Créer l'instance(s) de molécule] ***************************************
changé : [localhost] => (item=None)
changé : [localhost]
TÂCHE [Attendre que la(s) instance(s) création soit terminée] *****************
changé : [localhost] => (item=None)
changé : [localhost]
RÉCAPITULATIF DE JEU ***********************************************************
localhost : ok=5 changé=4 injoignable=0 échoué=0
--> Scénario : 'défaut'
--> Action : 'préparer'
Sauter, playbook de préparation non configuré.
--> Scénario : 'défaut'
--> Action : 'converger'
JEU [Converger] ***************************************************************
TÂCHE [Collecte de faits] *****************************************************
ok : [instance]
TÂCHE [nginx : Installer nginx] ***********************************************
changé : [instance]
TÂCHE [nginx : Démarrer nginx] ************************************************
changé : [instance]
RÉCAPITULATIF DE JEU ***********************************************************
instance : ok=3 changé=2 injoignable=0 échoué=0
--> Scénario : 'défaut'
--> Action : 'idempotence'
Idempotence terminée avec succès.
--> Scénario : 'défaut'
--> Action : 'effet secondaire'
Sauter, playbook d'effet secondaire non configuré.
--> Scénario : 'défaut'
--> Action : 'vérifier'
--> Exécution des tests Testinfra trouvés dans /nginx/molecule/default/tests/...
============================= Début de la session de test ==============================
plate-forme darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
répertoire racine : /nginx/molecule/default, inifile :
plugins : testinfra-1.16.0
collecté 4 éléments
tests/test_default.py .... [100%]
========================= 4 réussis en 27.23 secondes ===========================
Vérificateur terminé avec succès.
--> Scénario : 'défaut'
--> Action : 'détruire'
JEU [Détruire] *****************************************************************
TÂCHE [Détruire l'instance(s) de molécule] ************************************
changé : [localhost] => (item=None)
changé : [localhost]
TÂCHE [Attendre que la(s) instance(s) suppression soit terminée] ************
changé : [localhost] => (item=None)
changé : [localhost]
TÂCHE [Supprimer le(s) réseau(x) docker] *************************************
RÉCAPITULATIF DE JEU ***********************************************************
localhost : ok=2 changé=2 injoignable=0 échoué=0
Notre rôle simple a été testé sans problème.
Il est important de se rappeler que si des problèmes surviennent lors de l'exécution test de molécule, alors, si vous n'avez pas changé la séquence standard, la molécule supprimera l'instance.
Les commandes suivantes sont utiles pour le débogage :
> molecule --debug # informations de débogage. Lors d'une exécution normale, la molécule masque les journaux.
> molecule converge # Conserve l'instance après l'exécution du rôle en cours de test.
> molecule login # Se connecter à l'instance créée.
> molecule --help # Liste complète des commandes.Rôle existant
Ajouter un nouveau scénario à un rôle existant se fait depuis le répertoire du rôle avec les commandes suivantes :
# полный список доступных параметров
> molecule init scenarion --help
# создание нового сценария
> molecule init scenario -r <role_name> -s <scenario_name>Dans le cas où il s'agit du premier scénario dans le rôle, le paramètre -s peut être omis, car un scénario sera créé. default.
Conclusion
Comme vous pouvez le voir, la molécule n'est pas très complexe, et en utilisant vos propres modèles, la mise en place d'un nouveau scénario peut se réduire à modifier les variables dans les playbooks de création et de suppression d'instances. La molécule s'intègre sans problème aux systèmes CI, ce qui permet d'accélérer le développement en réduisant le temps de test manuel des playbooks.
Merci pour votre attention. Si vous avez de l'expérience dans le test des rôles Ansible, qui n'est pas lié à la molécule, racontez-la dans les commentaires !
Source : habr.com
