Instrucciones: cómo probar roles de ansible y descubrir problemas antes de la producción

Hola a todos!

Trabajo como ingeniero DevOps en un servicio de reservas de hoteles. ostrovok.ru. En este artículo, quiero hablar sobre nuestra experiencia en la prueba de roles de ansible.

En Ostrovok.ru, usamos ansible como administrador de configuración. Recientemente, tuvimos la necesidad de probar roles, pero resultó que no hay tantas herramientas para esto; la más popular, quizás, es el marco Molecule, por lo que decidimos usarlo. Pero resultó que su documentación guarda silencio sobre muchas trampas. No pudimos encontrar un manual suficientemente detallado en ruso, por lo que decidimos escribir este artículo.

Instrucciones: cómo probar roles de ansible y descubrir problemas antes de la producción

molécula

Molécula - un marco para ayudar a probar los roles de ansible.

Descripción simplificada: La molécula crea una instancia en la plataforma que especifiques (nube, máquina virtual, contenedor; para más detalles, consulta la sección Destornillador), ejecuta su rol en él, luego ejecuta pruebas y elimina la instancia. En caso de fallo en uno de los pasos, la Molécula te informará al respecto.

Ahora más.

Un poco de teoría

Considere dos entidades clave de la Molécula: Escenario y Conductor.

Guión

El guión contiene una descripción de qué, dónde, cómo y en qué secuencia se realizará. Un rol puede tener varios scripts, y cada uno es un directorio a lo largo de la ruta <role>/molecule/<scenario>, que contiene descripciones de las acciones requeridas para la prueba. Se debe incluir el guión default, que se creará automáticamente si inicializa el rol con una molécula. Los nombres de los siguientes scripts dependen de usted.

La secuencia de acciones de prueba en un script se llama matriz, y por defecto es:

(Pasos etiquetados ?, omitido por defecto si no lo especifica el usuario)

  • lint - linters corrientes. Por defecto se utilizan yamllint и flake8,
  • destroy - eliminar instancias del último lanzamiento de la Molécula (si corresponde),
  • dependency? — instalación de la dependencia ansible del rol probado,
  • syntax - comprobando la sintaxis del rol usando ansible-playbook --syntax-check,
  • create - crear una instancia,
  • prepare? — preparación de la instancia; por ejemplo, comprobar/instalar python2
  • converge — lanzamiento del libro de jugadas que se está probando,
  • idempotence - reiniciar el libro de jugadas para la prueba de idempotencia,
  • side_effect? - acciones no relacionadas directamente con el rol, pero necesarias para las pruebas,
  • verify - ejecutar pruebas de la configuración resultante usando testinfra(por defecto) /goss/inspec,
  • cleanup? - (en nuevas versiones) - en términos generales, "limpiar" la infraestructura externa afectada por la Molécula,
  • destroy - Eliminar una instancia.

Esta secuencia cubre la mayoría de los casos, pero se puede cambiar si es necesario.

Cada uno de los pasos anteriores se puede ejecutar por separado con molecule <command>. Pero debe entenderse que para cada comando cli puede haber su propia secuencia de acciones, que puede averiguar ejecutando molecule matrix <command>. Por ejemplo, al ejecutar el comando converge (ejecutando el libro de jugadas bajo prueba), se realizarán las siguientes acciones:

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

La secuencia de estas acciones se puede editar. Si algo de la lista ya está hecho, se omitirá. El estado actual, así como la configuración de las instancias, las tiendas Molecule en el directorio $TMPDIR/molecule/<role>/<scenario>.

Añadir pasos con ? puede, describiendo las acciones deseadas en el formato ansible-playbook, y hacer el nombre del archivo de acuerdo con el paso: prepare.yml/side_effect.yml. Espere estos archivos. La molécula estará en la carpeta del script.

Destornillador

Un controlador es una entidad donde se crean instancias de prueba.
La lista de controladores estándar para los que Molecule tiene plantillas preparadas es la siguiente: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.

En la mayoría de los casos, las plantillas son archivos create.yml и destroy.yml en la carpeta del script que describe la creación y eliminación de una instancia, respectivamente.
Las excepciones son Docker y Vagrant, ya que las interacciones con sus módulos pueden ocurrir sin los archivos antes mencionados.

Cabe destacar el controlador Delegado, ya que si se utiliza en los archivos de creación y eliminación de una instancia, solo se describe el trabajo con la configuración de instancias, el resto debe ser descrito por el ingeniero.

El controlador predeterminado es Docker.

Ahora pasemos a la práctica y consideremos otras características allí.

Primeros pasos

Como "hola mundo", probemos un rol de instalación simple de nginx. Elegiremos docker como controlador; creo que la mayoría de ustedes lo tiene instalado (y recuerde que docker es el controlador predeterminado).

Preparar virtualenv e instalar en ella molecule:

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

El siguiente paso es inicializar el nuevo rol.
La inicialización de un nuevo rol, así como un nuevo script, se realiza mediante el comando 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

Resultó un típico papel ansible. Además, todas las interacciones con CLI Molecules se realizan desde la raíz del rol.

Veamos qué hay en el directorio de roles:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Jinja-шаблон для Dockerfile
├── INSTALL.rst.   # Немного информации об установке зависимостей сценария
├── molecule.yml   # Файл конфигурации
├── playbook.yml   # Плейбук запуска роли
└── tests          # Директория с тестами стадии verify
    └── test_default.py

1 directory, 6 files

Analicemos la configuración. molecule/default/molecule.yml (reemplace solo la imagen de la ventana acoplable):

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

dependencia

En esta sección se describe el origen de las dependencias.

Posibles opciones: galaxia, Dorado, caparazón.

Shell es solo un shell de comando que se usa en caso de que galaxy y gilt no cubran sus necesidades.

No me detendré aquí por mucho tiempo, es suficiente descrito en documentación.

conductor

El nombre del conductor. El nuestro es Docker.

hilas

El linter es yamllint.

Las opciones útiles en esta parte de la configuración son la capacidad de especificar un archivo de configuración para yamllint, reenviar variables de entorno o deshabilitar el linter:

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

plataformas

Describe la configuración de las instancias.
En el caso de docker como controlador, la molécula se itera sobre esta sección y cada elemento de la lista está disponible en Dockerfile.j2 como variable item.

En el caso de un conductor que requiera create.yml и destroy.yml, la sección está disponible en ellos como molecule_yml.platforms, y las iteraciones sobre él ya se describen en estos archivos.

Dado que Molecule proporciona control de instancias a los módulos ansible, la lista de configuraciones posibles también debe buscarse allí. Para docker, por ejemplo, se usa el módulo docker_container_module. Los módulos que se utilizan en otros controladores se pueden encontrar en documentación.

Además de ejemplos del uso de varios controladores, se pueden encontrar en las pruebas de la Molécula misma.

Reemplazar aquí centos:7 en Ubuntu.

proveedor

"Proveedor" - una entidad que administra instancias. En el caso de Molecule, esto es ansible, el soporte para otros no está planeado, por lo que esta sección puede llamarse configuración extendida de ansible con una advertencia.
Aquí puede especificar muchas cosas, resaltaré los puntos principales, en mi opinión:

  • libros de jugadas: puede especificar qué libros de jugadas se deben usar en ciertas etapas.

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'"

  • opciones: opciones de Ansible y variables de entorno

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

guión

Nombre y descripción de secuencias de guiones.
Puede cambiar la matriz de acciones predeterminada de cualquier comando agregando la tecla <command>_sequence y como valor para ello definiendo la lista de pasos que necesitamos.
Digamos que queremos cambiar la secuencia de acciones al ejecutar el comando de ejecución del libro de jugadas: molecule converge

# изначально:
# - dependency
# - create
# - prepare
# - converge
scenario:
  name: default
  converge_sequence:
    - create
    - converge

verificador

Configuración de un marco para las pruebas y un linter. El filtro predeterminado es testinfra и flake8. Las opciones posibles son las mismas que las anteriores:

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

Volvamos a nuestro papel. Editemos el archivo tasks/main.yml a este tipo:

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

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

Y agregar pruebas a 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")

Hecho, solo queda ejecutar (desde la raíz del rol, déjame recordarte):

> molecule test

Escape largo debajo del alerón:

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

Nuestro rol simple fue probado sin problemas.
Vale la pena recordar que si hay problemas durante el trabajo molecule test, luego, si no cambió la secuencia predeterminada, la molécula eliminará la instancia.

Los siguientes comandos son útiles para la depuración:

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

Rol existente

Agregar un nuevo script a un rol existente es del directorio de funciones con los siguientes comandos:

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

En caso de que este sea el primer escenario en el rol, entonces el parámetro -s se puede omitir ya que creará un script default.

Conclusión

Como puede ver, Molecule no es muy complejo y, al usar sus propias plantillas, la implementación de un nuevo script puede reducirse a editar variables en los libros de jugadas de creación y eliminación de instancias. La molécula se integra a la perfección con los sistemas de CI, lo que le permite aumentar la velocidad de desarrollo al reducir el tiempo de prueba manual de los libros de jugadas.

Gracias por su atención. Si tiene experiencia en probar roles de ansible y no está relacionado con la molécula, ¡cuéntenoslo en los comentarios!

Fuente: habr.com

Añadir un comentario