Instructions : comment tester les rôles ansibles et découvrir les problèmes avant la production

Bonjour à tous!

Je travaille comme ingénieur DevOps dans un service de réservation hôtelière. Ostrovok.ru. Dans cet article, je veux parler de notre expérience dans le test des rôles ansible.

Chez Ostrovok.ru, nous utilisons ansible comme gestionnaire de configuration. Récemment, nous sommes arrivés à la nécessité de tester les rôles, mais il s'est avéré qu'il n'y a pas tellement d'outils pour cela - le plus populaire, peut-être, est le framework Molecule, nous avons donc décidé de l'utiliser. Mais il s'est avéré que sa documentation est muette sur de nombreux pièges. Nous n'avons pas trouvé de manuel suffisamment détaillé en russe, nous avons donc décidé d'écrire cet article.

Instructions : comment tester les rôles ansibles et découvrir les problèmes avant la production

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, consultez la section Tournevis), exécute votre rôle dessus, puis exécute des tests et supprime l'instance. En cas d'échec sur l'une des étapes, la Molécule vous en informera.

Maintenant plus.

Un peu de théorie

Considérez deux entités clés de la molécule : le scénario et le pilote.

Scénario

Le script contient une description de quoi, où, comment et dans quel ordre sera exécuté. Un rôle peut avoir plusieurs scripts, et chacun est un répertoire le long du chemin <role>/molecule/<scenario>, qui contient des descriptions des actions requises pour le test. Le script doit être inclus default, qui sera automatiquement créé si vous initialisez le rôle avec une Molecule. Les noms des scripts suivants dépendent de vous.

La séquence d'actions de test dans un script est appelée matrice, et par défaut c'est :

(Étapes étiquetées ?, ignoré par défaut si non spécifié par l'utilisateur)

  • lint - linters en cours d'exécution. Par défaut sont utilisés yamllint и flake8,
  • destroy - supprimer les instances du dernier lancement de la Molécule (le cas échéant),
  • dependency? — installation de la dépendance ansible du rôle testé,
  • syntax - vérification de la syntaxe du rôle à l'aide ansible-playbook --syntax-check,
  • create - créer une instance,
  • prepare? — préparation de l'instance ; par exemple vérifier/installer python2
  • converge — lancement du playbook en test,
  • idempotence - relancer le playbook pour le test d'idempotence,
  • side_effect? - les 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(défaut) /goss/inspec,
  • cleanup? - (dans les nouvelles versions) - en gros, "nettoyer" l'infrastructure externe affectée par la Molécule,
  • destroy - Suppression d'une instance.

Cette séquence couvre la plupart des cas, mais peut être modifiée si nécessaire.

Chacune des étapes ci-dessus peut être exécutée séparément avec molecule <command>. Mais il faut comprendre que pour chacune de ces commandes cli, il peut y avoir sa propre séquence d'actions, que vous pouvez découvrir en exécutant molecule matrix <command>. Par exemple, lors de l'exécution de la commande converge (exécutant le playbook en cours de test), les actions suivantes seront effectuées :

$ molecule matrix converge
...
└── default         # название сценария
    ├── dependency  # установка зависимостей
    ├── create      # создание инстанса
    ├── prepare     # преднастройка инстанса
    └── converge    # прогон плейбука

La séquence de ces actions peut être modifiée. Si quelque chose de la liste est déjà fait, il sera sauté. L'état actuel, ainsi que la configuration des instances, la Molécule stocke dans le répertoire $TMPDIR/molecule/<role>/<scenario>.

Ajouter des étapes avec ? vous pouvez décrire les actions souhaitées au format ansible-playbook et créer le nom du fichier en fonction de l'étape : prepare.yml/side_effect.yml. Attendez-vous à ces fichiers La molécule sera dans le dossier du script.

Tournevis

Un pilote est une entité dans laquelle des instances de test sont créées.
La liste des pilotes standard pour lesquels Molecule 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 des fichiers create.yml и destroy.yml dans le dossier de script 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 produire sans les fichiers susmentionnés.

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.

Passons maintenant à la pratique et considérons d'autres fonctionnalités.

Mise en route

En tant que "hello world", testons un simple rôle d'installation nginx. Choisissons docker comme pilote - je pense que la plupart d'entre vous l'ont installé (et rappelez-vous que docker est le pilote par défaut).

Préparer virtualenv et installez-y molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule установит ansible как зависимость; docker для драйвера

L'étape suivante consiste à initialiser le nouveau rôle.
L'initialisation d'un nouveau rôle, ainsi qu'un nouveau script, 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

Il s'est avéré qu'il s'agissait d'un rôle ansible typique. De plus, toutes les interactions avec les molécules CLI sont effectuées à 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  # Jinja-шаблон для Dockerfile
├── INSTALL.rst.   # Немного информации об установке зависимостей сценария
├── molecule.yml   # Файл конфигурации
├── playbook.yml   # Плейбук запуска роли
└── tests          # Директория с тестами стадии verify
    └── test_default.py

1 directory, 6 files

Analysons la configuration molecule/default/molecule.yml (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: flake8

dépendance

Cette section décrit la source des dépendances.

Les options sont: galaxie, dorée, coquille.

Shell n'est qu'un shell de commande utilisé au cas où galaxy et gilt ne couvriraient pas vos besoins.

Je ne m'attarderai pas ici longtemps, c'est assez décrit dans documentation.

driver

Le nom du chauffeur. Le nôtre est docker.

peluche

Le linter est du yamllint.

Les options utiles dans cette partie de la configuration sont la possibilité de spécifier un fichier de configuration pour yamllint, de transférer des variables d'environnement ou de désactiver le linter :

lint:
  name: yamllint
  options:
    config-file: foo/bar
  env:
    FOO: bar
  enabled: False

plates-formes

Décrit la configuration des instances.
Dans le cas de docker en tant que pilote, la Molecule est itérée sur cette section, et chaque élément de la liste est disponible dans Dockerfile.j2 en tant que variable item.

Dans le cas d'un conducteur qui nécessite create.yml и destroy.yml, la section y est disponible en tant que molecule_yml.platforms, et les itérations dessus sont déjà décrites dans ces fichiers.

Étant donné que la molécule fournit le contrôle des instances aux modules ansibles, la liste des paramètres possibles doit également y être recherchée. Pour docker, par exemple, le module est utilisé docker_container_module. Les modules utilisés dans d'autres pilotes peuvent être trouvés dans documentation.

Ainsi que des exemples d'utilisation de divers pilotes peuvent être trouvés dans les tests de la Molécule elle-même.

Remplacer ici centos : 7 sur ubuntu.

fournisseur

"Fournisseur" - une entité qui gère les instances. Dans le cas de Molecule, c'est ansible, la prise en charge des autres n'est pas prévue, donc cette section peut être appelée configuration étendue ansible avec une mise en garde.
Ici, vous pouvez préciser beaucoup de choses, je vais souligner les points principaux, à mon avis:

  • playbooks: vous pouvez spécifier quels playbooks doivent être utilisés à 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

provisioner:
  name: ansible  
  connection_options:
    ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"

  • Options: Options Ansible et variables d'environnement

provisioner:
  name: ansible  
  options:
    vvv: true
    diff: true
  env:
    FOO: BAR

scénario

Nom et description des séquences de script.
Vous pouvez modifier la matrice d'action par défaut de n'importe quelle commande en ajoutant la clé <command>_sequence et comme valeur pour cela en définissant la liste des étapes dont nous avons besoin.
Disons 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érifieur

Mise en place d'un cadre de tests et d'un linter à celui-ci. Le linter par défaut est testinfra и flake8. Les options possibles sont les mêmes que 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. Modifions le fichier tasks/main.yml à ce genre :

---
- name: Install nginx
  apt:
    name: nginx
    state: present

- name: Start nginx
  service:
    name: nginx
    state: started

Et ajouter des tests à 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 vous 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 des problèmes pendant le travail molecule test, alors si vous n'avez pas changé la séquence par défaut, la Molécule supprimera l'instance.

Les commandes suivantes sont utiles pour le débogage :

> molecule --debug <command> # debug info. При обычном запуске Молекула скрывает логи.
> molecule converge          # Оставляет инстанс после прогона тестируемой роли.
> molecule login             # Зайти в созданный инстанс.
> molecule --help            # Полный список команд.

Rôle existant

L'ajout d'un nouveau script à un rôle existant est depuis le répertoire des rôles avec les commandes suivantes :

# полный список доступных параметров
> molecule init scenarion --help
# создание нового сценария
> molecule init scenario -r <role_name> -s <scenario_name>

S'il s'agit du premier scénario du rôle, le paramètre -s peut être omis car cela créera un script default.

Conclusion

Comme vous pouvez le voir, la Molécule n'est pas très complexe, et en utilisant vos propres modèles, le déploiement d'un nouveau script peut être réduit à l'édition de variables dans les playbooks de création et de suppression d'instance. 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 de test manuel des playbooks.

Merci pour votre attention. Si vous avez de l'expérience dans le test de rôles ansibles, et que ce n'est pas lié à la Molécule, parlez-en nous dans les commentaires !

Source: habr.com

Ajouter un commentaire