Instrucciones: cómo probar roles de ansible y detectar 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 compartir nuestra experiencia probando roles de ansible.

En Ostrovok.ru, como gestores de configuración, utilizamos ansible. Recientemente llegamos a la necesidad de probar roles, pero, como resultó, no existen muchas herramientas para ello; el más popular, probablemente, es el marco Molecule, por lo que decidimos utilizarlo. Pero resultó que su documentación omite muchas dificultades. No pudimos encontrar una guía suficientemente detallada en ruso, así que decidimos escribir este artículo.

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

Molecule

Molécula — es un marco para ayudar en la prueba de roles de ansible.

Descripción simplificada: Molécula crea una instancia en la plataforma que usted indique (nube, virtual, contenedor; vea más en la sección Driver), ejecuta su rol en ella, luego ejecuta pruebas y elimina la instancia. Si ocurre un fallo en uno de los pasos, Molecule le informará sobre ello.

Ahora en más detalle.

Un poco de teoría

Consideremos dos entidades clave de Molecule: Scenario y Driver.

Scenario

El escenario contiene la descripción de qué, dónde, cómo y en qué secuencia se ejecutará. Un rol puede tener varios escenarios, y cada uno de ellos es un directorio en la ruta /molecule/, que contiene descripciones de las acciones necesarias para la prueba. Debe haber un escenario default, que será creado automáticamente si inicializa el rol con Molecule. Los nombres de los siguientes escenarios se eligen a su discreción.

La secuencia de acciones de prueba en el escenario se llama matrix, y por defecto es la siguiente:

(Los pasos marcados ?, se omiten por defecto si no son descritos por el usuario)

  • lint — ejecución de linters. Por defecto, se utilizan yamllint y flake8,
  • destroy — eliminación de instancias de la última ejecución de Molecule (si quedan),
  • dependency? — установка ansible-зависимости тестируемой роли,
  • syntax — verificación de la sintaxis del rol con ansible-playbook --syntax-check,
  • en kube api y el recurso se crea. La representación en YAML del manifiesto se puede almacenar en Git, y para crearla se puede usar el comando — creación de la instancia,
  • prepare? — подготовка инстанса; например, проверка / установка python2
  • converge — ejecución del playbook que se está probando,
  • idempotence — reejecución del playbook para la prueba de idempotencia,
  • side_effect? — действия, не относящиеся непосредственно к роли, но нужные для тестов,
  • verify — ejecución de pruebas de la configuración resultante con testinfra(por defecto) /goss/inspec,
  • cleanup? — (в новых версиях) — грубо говоря, «очистка» внешней инфраструктуры, задетой Молекулой,
  • destroy — eliminación de la instancia.

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

Cada uno de los pasos mencionados se puede ejecutar por separado usando molecule. Pero es importante entender que para cada uno de estos comandos de CLI puede existir una secuencia de acciones específica, que se puede conocer ejecutando molecule matrix. Por ejemplo, al ejecutar el comando converge (ejecutar el playbook en prueba) se llevarán a cabo las siguientes acciones:

$ molecule matrix converge
...
└── default         # nombre del escenario
    ├── dependency  # instalación de dependencias
    ├── create      # creación de instancia
    ├── prepare     # preparación de la instancia
    └── converge    # ejecución del playbook

La secuencia de estas acciones se puede editar. Si algo de la lista ya se ha ejecutado, se saltará. El estado actual, así como la configuración de las instancias, los almacena Molecule en el directorio $TMPDIR/molecule//.

Se pueden agregar pasos con ? describiendo las acciones deseadas en formato de playbook de ansible, y el nombre del archivo debe corresponder al paso: prepare.yml/side_effect.yml. Molecule esperará encontrar estos archivos en la carpeta del escenario.

Driver

El controlador es una entidad donde se crean instancias para las pruebas.
La lista de controladores estándar para los cuales Molecule tiene plantillas 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 y destroy.yml en la carpeta del escenario, que describen la creación y eliminación de instancias respectivamente.
Las excepciones son Docker y Vagrant, ya que la interacción con sus módulos puede ocurrir sin los archivos mencionados anteriormente.

Cabe destacar el controlador Delegated, ya que en su caso, los archivos de creación y eliminación de instancias solo describen el trabajo con la configuración de las instancias; el resto debe describirlo un ingeniero.

El controlador predeterminado es Docker.

Ahora pasemos a la práctica y examinaremos las características adicionales allí.

Inicio

Como un 'hello world', probaremos un simple rol de instalación de nginx. Como controlador elegiremos Docker; creo que la mayoría de ustedes lo tienen instalado (y recordemos que Docker es el controlador predeterminado).

Prepararemos virtualenv y lo instalaremos en molecule:

> pip install virtualenv
> virtualenv -p `which python2` venv
> source venv/bin/activate
> pip install molecule docker  # molecule instalará ansible como dependencia; docker como controlador

El siguiente paso es inicializar un nuevo rol.
La inicialización de un nuevo rol, al igual que la de un nuevo escenario, se realiza mediante el comando molecule init:

> inicializar rol de molécula -r nginx
--> Inicializando nuevo rol nginx...
Rol inicializado en /nginx con éxito.
> cd nginx
> tree -L 1
.
├── README.md
├── defaults
├── handlers
├── meta
├── molecule
├── tasks
└── vars

6 directorios, 1 archivo

Se creó un rol de ansible típico. A partir de aquí, todas las interacciones con la CLI de Molecule se realizan desde la raíz del rol.

Veamos qué hay en el directorio del rol:

> tree molecule/default/
molecule/default/
├── Dockerfile.j2  # Plantilla Jinja para Dockerfile
├── INSTALL.rst.   # Información sobre la instalación de dependencias del script
├── molecule.yml   # Archivo de configuración
├── playbook.yml   # Playbook para ejecutar el rol
└── tests          # Directorio con pruebas de la etapa de verificación
    └── test_default.py

1 directorio, 6 archivos

Desglosemos la configuración molecule/default/molecule.yml (solo cambiaremos la imagen de docker):

---
dependencia:
  nombre: galaxy
driver:
  nombre: docker
lint:
  nombre: yamllint
plataformas:
  - nombre: instancia
    imagen: centos:7
proveedor:
  nombre: ansible
  lint:
    nombre: ansible-lint
escenario:
  nombre: predeterminado
verificador:
  nombre: testinfra
  lint:
    nombre: flake8

dependency

Esta sección describe la fuente de las dependencias.

Opciones disponibles: galaxy, gilt, shell.

Shell es simplemente un shell de comandos que se utiliza si galaxy y gilt no satisfacen sus necesidades.

No me detendré mucho aquí, está suficientemente descrito en la documentación.

driver

Nombre del controlador. En nuestro caso, es docker.

lint

Se utiliza yamllint como linteador.

Las opciones útiles en esta parte de la configuración son la posibilidad de especificar un archivo de configuración para yamllint, pasar variables de entorno o desactivar el linteador:

lint:
  nombre: yamllint
  opciones:
    archivo-configuración: foo/bar
  env:
    FOO: bar
  habilitado: False

plataformas

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

En el caso de un controlador donde se requiere create.yml y destroy.yml, la sección está disponible en ellos como molecule_yml.plataformas, y las iteraciones sobre ella se describen ya en esos archivos.

Dado que Molecule proporciona gestión de instancias a los módulos de ansible, también se deben buscar allí las posibles configuraciones. Para docker, por ejemplo, se utiliza el módulo docker_container_module. Los módulos utilizados en los otros controladores se pueden encontrar en la documentación.

Y también se pueden encontrar ejemplos de uso de varios controladores en las pruebas de Molecule mismas.

Reemplacemos aquí centos:7 en ubuntu.

proveedor

"Proveedor" es la entidad que gestiona las instancias. En el caso de Molecule, es ansible; no se planea soporte para otros, por lo que esta sección se puede considerar informalmente como una configuración extendida de ansible.
Aquí se pueden mencionar muchas cosas, destacaré los aspectos principales, en mi opinión.

  • playbooks: se pueden especificar qué playbooks deben utilizarse en determinadas 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'"

  • options: parámetros de Ansible y variables de entorno

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

scenario

Nombre y descripción de las secuencias del escenario.
Se puede modificar la matriz de acciones predeterminada de un comando añadiendo la clave <command>_sequence y como valor para ella definir la lista de pasos que necesitamos.
Supongamos que queremos cambiar la secuencia de acciones al ejecutar el comando de correr un playbook: molecule converge

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

verifier

Configuración del marco para pruebas y su linter correspondiente. Por defecto, se utiliza como linter testinfra y flake8. Las opciones posibles son similares a las mencionadas anteriormente:

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 rol. Editaremos el archivo tasks\/main.yml a este formato:

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

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

Y añadiremos pruebas en 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")

Listo, solo queda ejecutar (desde la raíz del rol, recordemos):

> molecule test

Salida larga en el spoiler:

--> Validando el esquema /nginx/molecule/default/molecule.yml.
La validación se completó con éxito.
--> Matriz de pruebas

└── default
    ├── lint
    ├── destroy
    ├── dependency
    ├── syntax
    ├── create
    ├── prepare
    ├── converge
    ├── idempotence
    ├── side_effect
    ├── verify
    └── destroy

--> Escenario: 'default'
--> Acción: 'lint'
--> Ejecutando Yamllint en los archivos encontrados en /nginx/...
Lint se completó con éxito.
--> Ejecutando Flake8 en los archivos encontrados en /nginx/molecule/default/tests/...
Lint se completó con éxito.
--> Ejecutando Ansible Lint en /nginx/molecule/default/playbook.yml...
Lint se completó con éxito.
--> Escenario: 'default'
--> Acción: 'destroy'

    PLAY [Destruir] *****************************************************************

    TAREA [Destruir instancia(s) de molecule] *************************************
    cambiado: [localhost] => (item=None)
    cambiado: [localhost]

    TAREA [Esperar a que la(s) instancia(s) se eliminen] *************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TAREA [Eliminar red(es) de docker] ******************************************

    RESUMEN DE JUEGO ***************************************************************
    localhost                  : ok=2    cambiado=1    inalcanzable=0    fallido=0

--> Escenario: 'default'
--> Acción: 'dependency'
Saltando, falta el archivo de requisitos.
--> Escenario: 'default'
--> Acción: 'syntax'

    playbook: /nginx/molecule/default/playbook.yml

--> Escenario: 'default'
--> Acción: 'create'

    PLAY [Crear] ******************************************************************

    TAREA [Iniciar sesión en un registro de Docker] *******************************
    saltando: [localhost] => (item=None)

    TAREA [Crear Dockerfiles a partir de nombres de imágenes] ********************
    cambiado: [localhost] => (item=None)
    cambiado: [localhost]

    TAREA [Descubrir imágenes de Docker locales] *********************************
    ok: [localhost] => (item=None)
    ok: [localhost]

    TAREA [Construir una imagen compatible con Ansible] ****************************
    cambiado: [localhost] => (item=None)
    cambiado: [localhost]

    TAREA [Crear red(es) de docker] **********************************************

    TAREA [Crear instancia(s) de molecule] *****************************************
    cambiado: [localhost] => (item=None)
    cambiado: [localhost]

    TAREA [Esperar a que la(s) instancia(s) se creen] ****************************
    cambiado: [localhost] => (item=None)
    cambiado: [localhost]

    RESUMEN DE JUEGO ***************************************************************
    localhost                  : ok=5    cambiado=4    inalcanzable=0    fallido=0

--> Escenario: 'default'
--> Acción: 'prepare'
Saltando, el playbook de preparación no está configurado.
--> Escenario: 'default'
--> Acción: 'converge'

    PLAY [Convergir] **************************************************************

    TAREA [Reuniendo hechos] *****************************************************
    ok: [instance]

    TAREA [nginx : Instalar nginx] ***********************************************
    cambiado: [instance]

    TAREA [nginx : Iniciar nginx] ***********************************************
    cambiado: [instance]

    RESUMEN DE JUEGO ***************************************************************
    instance                   : ok=3    cambiado=2    inalcanzable=0    fallido=0

--> Escenario: 'default'
--> Acción: 'idempotence'
La idempotencia se completó con éxito.
--> Escenario: 'default'
--> Acción: 'side_effect'
Saltando, el playbook de efectos secundarios no está configurado.
--> Escenario: 'default'
--> Acción: 'verify'
--> Ejecutando las pruebas de Testinfra encontradas en /nginx/molecule/default/tests/...
    ============================= comienza la sesión de prueba ==============================
    plataforma darwin -- Python 2.7.15, pytest-4.3.0, py-1.8.0, pluggy-0.9.0
    rootdir: /nginx/molecule/default, inifile:
    plugins: testinfra-1.16.0
se recolectaron 4 artículos

    tests/test_default.py ....                                               [100%]

    ========================== 4 aprobados en 27.23 segundos ===========================
El verificador se completó con éxito.
--> Escenario: 'default'
--> Acción: 'destroy'

    PLAY [Destruir] *****************************************************************

    TAREA [Destruir instancia(s) de molecule] *************************************
    cambiado: [localhost] => (item=None)
    cambiado: [localhost]

    TAREA [Esperar a que la(s) instancia(s) se eliminen] *************************
    cambiado: [localhost] => (item=None)
    cambiado: [localhost]

    TAREA [Eliminar red(es) de docker] ******************************************

    RESUMEN DE JUEGO ***************************************************************
    localhost                  : ok=2    cambiado=2    inalcanzable=0    fallido=0

Nuestro sencillo papel se probó sin problemas.
Es importante recordar que si surgen problemas durante el trabajo prueba de molécula, entonces, si no ha cambiado la secuencia estándar, Molecule eliminará la instancia.

Para depuración, son útiles los siguientes comandos:

> molecule --debug  # información de depuración. En un inicio normal, Molecule oculta los registros.
> molecule converge          # Mantiene la instancia después de ejecutar el rol en prueba.
> molecule login             # Acceder a la instancia creada.
> molecule --help            # Lista completa de comandos.

Rol existente

Agregar un nuevo escenario a un rol existente se realiza desde el directorio del rol 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, se puede omitir el parámetro -s ya que se creará un escenario default.

Conclusión

Como pueden ver, Molecule no es muy complicado, y al utilizar sus propias plantillas, se puede reducir el despliegue de un nuevo escenario a la edición de variables en los playbooks de creación y eliminación de instancias. Molecule se integra sin problemas con sistemas CI, lo que permite aumentar la velocidad de desarrollo al reducir el tiempo dedicado a las pruebas manuales de los playbooks.

Gracias por su atención. Si tiene experiencia en la prueba de roles de Ansible que no está relacionada con Molecule, ¡compártala en los comentarios!

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster