Bonjour Ă tous!
Je travaille en tant qu'ingénieur DevOps dans le service de réservation d'hÎtel Ostrovok.ru . Dans cet article, je veux parler de notre expérience dans le test des rÎles ansibles.
Chez Ostrovok.ru, nous utilisons ansible comme gestionnaire de configuration. Nous avons rĂ©cemment trouvĂ© la nĂ©cessitĂ© de tester les rĂŽles, mais il s'est avĂ©rĂ© qu'il n'y avait pas beaucoup d'outils pour cela - le framework Molecule est peut-ĂȘtre le plus populaire, nous avons donc dĂ©cidĂ© de l'utiliser. Mais il s'est avĂ©rĂ© que sa documentation Ă©tait muette sur de nombreux piĂšges. Nous n'avons pas trouvĂ© de guide suffisamment dĂ©taillĂ© en russe, nous avons donc dĂ©cidĂ© d'Ă©crire cet article.

Molécule
Molécule - un cadre pour aider à tester les rÎles ansibles.
Description simplifiée: la molécule crée une instance sur la plateforme que vous spécifiez (cloud, machine virtuelle, conteneur; pour plus de détails, voir la section Pilote ), y exécute votre rÎle, puis exécute les tests et supprime l'instance. En cas d'échec à l'une des étapes, la Molécule vous en informera.
Maintenant plus en détail.
Un peu de théorie
Considérez les deux entités clés de la molécule: scénario et pilote.
Scénario
Le script contient une description de quoi, oĂč, comment et dans quelle sĂ©quence sera effectuĂ©e. Un rĂŽle peut avoir plusieurs scripts, et chacun est un rĂ©pertoire sur le chemin <role>/molecule/<scenario>
, qui contient des descriptions des actions nécessaires pour le test. Le script default
doit ĂȘtre prĂ©sent et sera automatiquement créé si vous initialisez le rĂŽle Ă l'aide de la molĂ©cule. Les noms des scĂ©narios suivants sont choisis Ă votre discrĂ©tion.
La séquence de tests dans le script est appelée matrice et, par défaut, elle est la suivante:
(Les étapes marquées d'un ?
ignorées par défaut si elles ne sont pas décrites par l'utilisateur)
lint
- run linter. Par défaut, yamllint
et flake8
,destroy
- supprime les instances du dernier lancement de Molecule (si laissé),dependency
? - mise en place d'une dépendance ansible du rÎle testé,syntax
- vérifier la syntaxe d'un rÎle en utilisant ansible-playbook --syntax-check
,create
- crée une instance,prepare
? - préparation de l'instance; par exemple vérifier / installer python2converge
- lancement du playbook testé,idempotence
- redémarrez le playbook pour le test d'idempotence,side_effect
? - actions non directement liées au rÎle, mais nécessaires aux tests,verify
- exécuter des tests de la configuration résultante en utilisant testinfra
(par défaut) / goss
/ inspec
,cleanup
? - (dans les nouvelles versions) - grosso modo, le «nettoyage» des infrastructures externes affectées par la Molécule,destroy
- supprime une instance.
Cette séquence couvre la plupart des cas, mais vous pouvez la modifier si nécessaire.
Chacune des Ă©tapes ci-dessus peut ĂȘtre exĂ©cutĂ©e sĂ©parĂ©ment en utilisant la molecule <command>
. Mais il vaut la peine de comprendre que pour chacune de ces commandes cli, il peut exister sa propre sĂ©quence dâactions, qui peut ĂȘtre reconnue en exĂ©cutant la molecule matrix <command>
. Par exemple, lorsque vous exécutez la commande de converge
(exécutez le playbook testé), les actions suivantes seront effectuées:
$ molecule matrix converge ... âââ default
La sĂ©quence de ces actions peut ĂȘtre modifiĂ©e. Si quelque chose de la liste est dĂ©jĂ terminĂ©, il sera ignorĂ©. L'Ă©tat actuel, ainsi que la configuration de l'instance, sont stockĂ©s dans le $TMPDIR/molecule/<role>/<scenario>
.
Ajouter des étapes avec ?
Vous pouvez, aprÚs avoir décrit les actions souhaitées au format ansible-playbook, et faire le nom du fichier selon l'étape: prepare.yml
/ side_effect.yml
. La molécule attendra ces fichiers dans le dossier de script.
Chauffeur
Un pilote est une entité dans laquelle des instances de test sont créées.
La liste des pilotes standard pour lesquels la molĂ©cule a des modĂšles prĂȘts est la suivante: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.
Dans la plupart des cas, les modĂšles sont les destroy.yml
create.yml
et destroy.yml
dans le dossier de script qui décrivent respectivement la création et la suppression de l'instance.
Les exceptions sont Docker et Vagrant, car les interactions avec leurs modules peuvent se produire sans les fichiers ci-dessus.
Il convient de souligner le pilote dĂ©lĂ©guĂ©, car s'il est utilisĂ© dans les fichiers pour crĂ©er et supprimer une instance, seul le travail avec la configuration des instances est dĂ©crit, le reste doit ĂȘtre dĂ©crit par l'ingĂ©nieur.
Le pilote par défaut est Docker.
Maintenant, nous nous tournons vers la pratique et considérons d'autres fonctionnalités.
Pour commencer
En tant que "bonjour le monde", nous testons le rÎle simple de l'installation de nginx. Nous choisirons docker comme pilote - je pense qu'il est installé sur la plupart d'entre vous (et rappelez-vous que docker est le pilote par défaut).
Préparez virtualenv
et installez-y la molecule
:
> pip install virtualenv > virtualenv -p `which python2` venv > source venv/bin/activate > pip install molecule docker
L'étape suivante consiste à initialiser un nouveau rÎle.
L'initialisation d'un nouveau rÎle, ainsi que d'un nouveau scénario, s'effectue à l'aide de la commande molecule init <params>
:
> molecule init role -r nginx --> Initializing new role nginx... Initialized role in <path>/nginx successfully. > cd nginx > tree -L 1 . âââ README.md âââ defaults âââ handlers âââ meta âââ molecule âââ tasks âââ vars 6 directories, 1 file
Le résultat fut un rÎle ansible typique. De plus, toutes les interactions avec les molécules CLI sont faites à partir de la racine du rÎle.
Voyons ce qu'il y a dans le répertoire des rÎles:
> tree molecule/default/ molecule/default/ âââ Dockerfile.j2
Analysons la configuration molecule/default/molecule.yml
(nous ne remplacerons que 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: flake8
dépendance
Cette section décrit la source des dépendances.
Options possibles: galaxie , dorure , coquille.
Shell est juste un shell de commande qui est utilisé si la galaxie et la dorure ne couvrent pas vos besoins.
Je ne m'arrĂȘterai pas ici longtemps, c'est assez dĂ©crit dans la documentation .
chauffeur
Le nom du conducteur. Nous avons ce docker.
peluche
Comme linter, le yamllint est utilisé.
Les options utiles dans cette partie de la configuration sont la possibilité de spécifier un fichier de configuration pour yamllint, de transmettre les variables d'environnement ou de désactiver le linter:
lint: name: yamllint options: config-file: foo/bar env: FOO: bar enabled: False
Décrit la configuration des instances.
Dans le cas du docker en tant que pilote, la molécule est itérée sur cette section et chaque élément de la liste est disponible dans Dockerfile.j2
tant que variable d' item
.
Dans le cas du pilote, dans lequel create.yml
et destroy.yml
, la section y est disponible en tant que molecule_yml.platforms
, et les itérations sur celui-ci sont déjà décrites dans ces fichiers.
Ătant donnĂ© que la molĂ©cule fournit un contrĂŽle d'instance pour les modules ansible, une liste des paramĂštres possibles doit y ĂȘtre recherchĂ©e. Pour docker, par exemple, le module docker_container_module est utilisĂ© . Les modules utilisĂ©s dans d'autres pilotes se trouvent dans la documentation .
Et des exemples d'utilisation de divers pilotes peuvent ĂȘtre trouvĂ©s dans les tests de la molĂ©cule elle-mĂȘme .
Remplacez centos: 7 sur ubuntu ici .
approvisionneur
"Fournisseur" est l'entitĂ© qui contrĂŽle les instances. Dans le cas de la MolĂ©cule, c'est ansible, le support des autres n'est pas prĂ©vu, donc cette section peut ĂȘtre appelĂ©e avec rĂ©serve la configuration Ă©tendue d'ansible.
Ici, vous pouvez spécifier beaucoup de choses, je vais souligner les principaux moments, à mon avis,:
- playbooks : vous pouvez spécifier les playbooks à utiliser à certaines étapes.
provisioner: name: ansible playbooks: create: create.yml destroy: ../default/destroy.yml converge: playbook.yml side_effect: side_effect.yml cleanup: cleanup.yml
provisioner: name: ansible config_options: defaults: fact_caching: jsonfile ssh_connection: scp_if_ssh: True
- connection_options : paramĂštres de connexion
provisioner: name: ansible connection_options: ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"
- options : options et variables d'environnement possibles
provisioner: name: ansible options: vvv: true diff: true env: FOO: BAR
scénario
Le nom et la description des séquences de script.
Vous pouvez modifier la matrice d'actions par défaut d'une <command>_sequence
ajoutant la <command>_sequence
et en déterminant la liste des étapes dont nous avons besoin comme valeur.
Supposons que nous voulons changer la séquence d'actions lors de l'exécution de la commande playbook run: molecule converge
# : # - dependency # - create # - prepare # - converge scenario: name: default converge_sequence: - create - converge
vérificateur
Mise en place du cadre pour les tests et le linter à lui. Par défaut, testinfra
et flake8
sont utilisés testinfra
flake8
. Les options possibles sont similaires Ă celles 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: bar
Revenons Ă notre rĂŽle. Modifiez le tasks/main.yml
pour qu'il ressemble Ă ceci:
--- - name: Install nginx apt: name: nginx state: present - name: Start nginx service: name: nginx state: started
Et ajoutez les tests Ă la 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")
Terminé, il ne reste plus qu'à courir (depuis la racine du rÎle, je vous le rappelle):
> molecule test
Ăchappement long sous le becquet: --> Validating schema <path>/nginx/molecule/default/molecule.yml. Validation completed successfully. --> Test matrix âââ default âââ lint âââ destroy âââ dependency âââ syntax âââ create âââ prepare âââ converge âââ idempotence âââ side_effect âââ verify âââ destroy --> Scenario: 'default' --> Action: 'lint' --> Executing Yamllint on files found in <path>/nginx/... Lint completed successfully. --> Executing Flake8 on files found in <path>/nginx/molecule/default/tests/... Lint completed successfully. --> Executing Ansible Lint on <path>/nginx/molecule/default/playbook.yml... Lint completed successfully. --> 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)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=1 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'dependency' Skipping, missing the requirements file. --> Scenario: 'default' --> Action: 'syntax' playbook: <path>/nginx/molecule/default/playbook.yml --> Scenario: 'default' --> Action: 'create' PLAY [Create] ****************************************************************** TASK [Log into a Docker registry] ********************************************** skipping: [localhost] => (item=None) 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)] ************************************************ 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 --> Scenario: 'default' --> Action: 'prepare' Skipping, prepare playbook not configured. --> Scenario: 'default' --> Action: 'converge' PLAY [Converge] **************************************************************** TASK [Gathering Facts] ********************************************************* ok: [instance] TASK [nginx : Install nginx] *************************************************** changed: [instance] TASK [nginx : Start nginx] ***************************************************** changed: [instance] PLAY RECAP ********************************************************************* instance : ok=3 changed=2 unreachable=0 failed=0 --> Scenario: 'default' --> Action: 'idempotence' Idempotence completed successfully. --> Scenario: 'default' --> Action: 'side_effect' Skipping, side effect playbook not configured. --> Scenario: 'default' --> Action: 'verify' --> Executing Testinfra tests found in <path>/nginx/molecule/default/tests/... ============================= test session starts ============================== platform darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0 rootdir: <path>/nginx/molecule/default, inifile: plugins: testinfra-1.16.0 collected 4 items tests/test_default.py .... [100%] ========================== 4 passed in 27.23 seconds =========================== Verifier completed successfully. --> 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)] ************************************************ PLAY RECAP ********************************************************************* localhost : ok=2 changed=2 unreachable=0 failed=0
Notre rÎle simple a été testé sans problÚme.
Il convient de rappeler que s'il y a eu des problĂšmes pendant le fonctionnement du molecule test
, alors si vous n'avez pas modifié la séquence standard, la molécule supprimera l'instance.
Les commandes suivantes sont utiles pour le débogage:
> molecule --debug <command>
RĂŽle existant
L'ajout d'un nouveau script à un rÎle existant s'effectue à partir du répertoire des rÎles avec les commandes suivantes:
Dans le cas oĂč il s'agit du premier script du rĂŽle, l' -s
peut ĂȘtre omise car le script default
sera créé.
Conclusion
Comme vous pouvez le voir, la Molécule n'est pas trÚs compliquée, et lorsque vous utilisez vos propres modÚles, vous pouvez réduire le déploiement d'un nouveau script pour éditer des variables dans les playbooks pour créer et supprimer des instances. La molécule s'intÚgre parfaitement aux systÚmes CI, ce qui vous permet d'augmenter la vitesse de développement en réduisant le temps nécessaire pour tester manuellement les playbooks.
Merci de votre attention. Si vous avez de l'expérience dans les tests de rÎles ansibles, et que cela n'est pas lié à la molécule - parlez-nous-en dans les commentaires!