Hola a todos!
Trabajo como ingeniero DevOps en el servicio de reservas de hotel Ostrovok.ru . En este artículo quiero hablar sobre nuestra experiencia en la prueba de roles ansibles.
En Ostrovok.ru, utilizamos ansible como administrador de configuración. Recientemente surgió la necesidad de probar roles, pero resultó que no hay muchas herramientas para esto: el marco Molecule es quizás el más popular, por lo que decidimos usarlo. Pero resultó que su documentación guardaba silencio sobre muchas trampas. No pudimos encontrar una guía suficientemente detallada en ruso, por lo que decidimos escribir este artículo.

Molécula
Molécula : un marco para ayudar a probar roles ansibles.
Descripción simplificada: la molécula crea una instancia en la plataforma que especifique (nube, máquina virtual, contenedor; para obtener más detalles, consulte la sección Controlador ), ejecuta su función en ella, luego ejecuta las pruebas y elimina la instancia. En caso de falla en uno de los pasos, la Molécula le informará de esto.
Ahora con más detalle.
Poco de teoría
Considere las dos entidades clave de la molécula: escenario y controlador.
Escenario
El script 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 necesarias para la prueba. La secuencia de comandos default
debe estar presente, que se creará automáticamente si inicializa el rol utilizando la Molécula. Los nombres de los siguientes escenarios se eligen a su discreción.
La secuencia de prueba en el script se llama matriz , y por defecto es la siguiente:
(¿Los pasos marcados con ?
omiten de forma predeterminada si el usuario no lo describe)
lint
: ejecute linter. Por defecto, se yamllint
y flake8
,destroy
: eliminar instancias del último lanzamiento de Molecule (si se dejó),dependency
? - instalación de dependencia ansible del rol probado,syntax
: verifique la sintaxis de un rol usando ansible-playbook --syntax-check
,create
: crea una instancia,prepare
- preparación de la instancia; por ejemplo, verificar / instalar python2converge
- lanzamiento del libro de jugadas probado,idempotence
: reinicie el libro de jugadas para la prueba de idempotencia,side_effect
? - acciones no directamente relacionadas con el rol, pero necesarias para las pruebas,verify
: ejecute pruebas de la configuración resultante usando testinfra
(predeterminado) / goss
/ goss
,cleanup
? - (en nuevas versiones) - en términos generales, la "limpieza" de la infraestructura externa afectada por la Molécula,destroy
: eliminar una instancia.
Esta secuencia cubre la mayoría de los casos, pero puede cambiarla si es necesario.
Cada uno de los pasos anteriores se puede ejecutar por separado utilizando la molecule <command>
. Pero vale la pena entender que para cada uno de estos comandos cli puede existir su propia secuencia de acciones, que puede reconocerse ejecutando la molecule matrix <command>
la molecule matrix <command>
. Por ejemplo, cuando ejecuta el comando de converge
(ejecute el libro de jugadas probado), se realizarán las siguientes acciones:
$ molecule matrix converge ... └── default
La secuencia de estas acciones se puede editar. Si algo de la lista ya se ha completado, se omitirá. El estado actual, así como la configuración de la instancia, se almacenan en el directorio $TMPDIR/molecule/<role>/<scenario>
.
Añadir pasos con ?
Puede, después de haber descrito las acciones deseadas en el formato de ansible-playbook, y crear el nombre del archivo de acuerdo con el paso: prepare.yml
/ side_effect.yml
. La molécula esperará estos archivos en la carpeta del script.
Conductor
Un controlador es una entidad donde se crean instancias de prueba.
La lista de controladores estándar para los que la Molécula tiene plantillas listas es la siguiente: Azure, Docker, EC2, GCE, LXC, LXD, OpenStack, Vagrant, Delegated.
En la mayoría de los casos, las plantillas son los destroy.yml
create.yml
y destroy.yml
en la carpeta del script que describen la creación y eliminación de la instancia, respectivamente.
Las excepciones son Docker y Vagrant, ya que las interacciones con sus módulos pueden ocurrir sin los archivos anteriores.
Vale la pena resaltar el controlador Delegado, ya que si se usa en los archivos para crear y eliminar 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 pasamos a la práctica y consideramos otras características allí.
Empezando
Como "hola mundo", probamos el papel simple de instalar nginx. Elegiremos docker como controlador. Creo que está instalado en la mayoría de ustedes (y recuerden que docker es el controlador predeterminado).
Prepare virtualenv
e instale la molecule
en él:
> pip install virtualenv > virtualenv -p `which python2` venv > source venv/bin/activate > pip install molecule docker
El siguiente paso es inicializar un nuevo rol.
La inicialización de un nuevo rol, así como un nuevo escenario, se realiza utilizando 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
El resultado fue un típico papel ansible. Además, todas las interacciones con las moléculas CLI se hacen desde la raíz del rol.
Veamos qué hay en el directorio de roles:
> tree molecule/default/ molecule/default/ ├── Dockerfile.j2
Analicemos la molecule/default/molecule.yml
(reemplazaremos solo la imagen del acoplador):
--- 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
Esta sección describe la fuente de las dependencias.
Posibles opciones: galaxia , dorado , concha.
Shell es solo un shell de comando que se usa si galaxy y gilt no cubren sus necesidades.
No me detendré aquí por mucho tiempo, se describe lo suficiente en la documentación .
chofer
El nombre del conductor. Tenemos esta ventana acoplable.
pelusa
Como linter, se usa 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 la interfaz:
lint: name: yamllint options: config-file: foo/bar env: FOO: bar enabled: False
Describe la configuración de instancias.
En el caso de la ventana acoplable como controlador, la molécula se repite en esta sección y cada elemento de la lista está disponible en Dockerfile.j2
como una variable de item
.
En el caso del controlador, en el que destroy.yml
create.yml
y destroy.yml
, la sección está disponible en ellos como molecule_yml.platforms
, y las iteraciones en él ya se describen en estos archivos.
Dado que Molecule proporciona control de instancia para módulos ansibles, se debe buscar una lista de configuraciones posibles allí. Para docker, por ejemplo, se utiliza el módulo docker_container_module . Los módulos que se utilizan en otros controladores se pueden encontrar en la documentación .
Y también se pueden encontrar ejemplos del uso de varios controladores en las pruebas de la propia Molécula .
Reemplace centos: 7 en ubuntu aquí .
aprovisionador
"Proveedor" es la entidad que controla las instancias. En el caso de la Molécula, esto es ansible, el soporte para otros no está planificado, por lo que esta sección se puede llamar con reserva a la configuración extendida de ansible.
Aquí puede especificar muchas cosas, destacaré los momentos principales, en mi opinión:
- libros de jugadas : puede especificar qué libros de jugadas deben usarse 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
- connection_options : parámetros de conexión
provisioner: name: ansible connection_options: ansible_ssh_common_args: "-o 'UserKnownHostsFile=/dev/null' -o 'ForwardAgent=yes'"
- opciones : opciones de respuesta y variables de entorno
provisioner: name: ansible options: vvv: true diff: true env: FOO: BAR
escenario
El nombre y la descripción de las secuencias de comandos.
Puede cambiar la matriz de acción predeterminada de un <command>_sequence
agregando la <command>_sequence
y determinando la lista de pasos que necesitamos como valor para ello.
Supongamos 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
Configurar el marco para las pruebas y la interfaz para ello. Por defecto, testinfra
y flake8
se usan testinfra
flake8
. Las opciones posibles son similares a 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. Edite el tasks/main.yml
para que se vea así:
--- - name: Install nginx apt: name: nginx state: present - name: Start nginx service: name: nginx state: started
Y agregue las pruebas a 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")
Hecho, solo queda ejecutar (desde la raíz del rol, te recuerdo):
> 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 papel simple fue probado sin problemas.
Vale la pena recordar que si hubo problemas durante la operación de la molecule test
de la molecule test
, entonces si no cambió la secuencia estándar, la Molécula eliminará la instancia.
Los siguientes comandos son útiles para la depuración:
> molecule --debug <command>
Rol existente
Agregar un nuevo script a un rol existente ocurre desde el directorio de roles con los siguientes comandos:
En caso de que este sea el primer script en el rol, se puede omitir la -s
ya que se creará el script default
.
Conclusión
Como puede ver, la Molécula no es muy complicada, y cuando usa sus propias plantillas, puede reducir la implementación de un nuevo script para editar variables en libros de jugadas para crear y eliminar 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 que lleva probar manualmente los libros de jugadas.
Gracias por su atencion Si tienes experiencia probando roles ansibles, y no está relacionado con la Molécula, ¡cuéntanoslo en los comentarios!