Testing Continuo de Roles Ansible

Publicado por Ignacio Sánchez Ginés el

DevOpsAnsibleInfraestructura como Código

Ansible se está convirtiendo en un estándar para la automatización en IT y esta automatización es un componente esencial en la Transformación Digital de las empresas.

Con Ansible podemos automatizar fácilmente la provisión, el plataformado y la configuración de, prácticamente, cualquier aspecto en nuestra infraestructura.

Pero probar los desarrollos en Ansible no es tan sencillo. Normalmente, se usan roles que nos permiten desarrollar funcionalidad genérica y así favorecer la reutilización.

Existe un repositorio central llamado Ansible Galaxy, donde los desarrolladores pueden compartir estos roles. Este repositorio está completamente integrado con GitHub, lo que nos permite ver el código de otros roles, usarlos o incluso realizar un fork si queremos modificarlos.

Los roles pueden tener soporte para más de una distribución a la vez, lo que nos permite ejecutarlos con independencia del sistema operativo.

Pero para conseguir esto, los desarrolladores emplean mucho tiempo probándolos contra infraestructura fresca en cada iteración.

Por ejemplo, para probar un rol que instala y configura un base de datos en cualquier distribución de Linux, tendremos que lanzar una máquina virtual (VM) para cada distribución que queramos probar. Cada vez que queramos probar de nuevo debemos crear una nueva VM limpia. Podemos usar snapshots pero aún así el proceso es lento y tedioso.

Ansible y Docker

Una buena idea sería usar Docker. En lugar de lanzar nuevas VMs para cada prueba de nuestro rol, crearemos un nuevo contenedor. Este contenedor puede estar basado en la imagen de cada distribución Linux que deseemos soportar. Esto es mucho más sencillo pero aún necesitamos ejecutar muchos comandos para cada test.

¿Por qué no automatizamos el proceso en nuestro Pipeline de Integración Continua? Eso es justamente lo que vamos a explicar aquí.

Integración Continua, Ansible y Travis CI

En Travis CI es extremadamente sencillo crear un pipeline donde podemos ejecutar a la vez varios trabajos en paralelo para un único build.

Esta característica es muy útil para modelar un pipeline donde probemos roles Ansible sobre múltiples plataformas a la vez. Además, al estar completamente integrado en GitHub, es muy conveniente para el testing de roles compartidos en Galaxy.

He preparado un rol sencillo que instala Nginx en muchas distribuciones Linux. El rol es lo de menos, nos centraremos en la definición del pipeline de Travis. Puedes encontrar todo aquí: https://github.com/drhelius/travis-ansible-demo

Ya que vamos a ejecutar Docker en nuestro job, seguiremos la documentación y configuraremos el script .travis.yml de la siguiente manera:

---
sudo: required

services:  
  - docker

Para que Travis ejecute jobs en paralelo crearemos una matriz de configuración con variables de entorno para cada distribución Linux, así:

env:  
  - distribution: centos
    version: 7
  - distribution: fedora
    version: 26
  - distribution: fedora
    version: 25
  - distribution: fedora
    version: 24
  - distribution: ubuntu
    version: xenial
  - distribution: ubuntu
    version: trusty
  - distribution: debian
    version: stretch
  - distribution: debian
    version: jessie

El build se dividirá en 8 jobs que se ejecutarán a la vez, cada uno de ellos con una pareja de variables como las que hemos definido en el paso anterior.

Para tener un control más fino de la imagen Docker que usaremos para cada distribución, extenderemos nuestras propias imágenes de las imágenes oficiales.

De esta manera podremos ejecutar Ansible, systemd o cualquier otra dependencia necesaria para probar nuestro rol.

Puedes encontrar estas imágenes extendidas para Ubuntu, Debian, Fedora, etc., en la carpeta travis del repositorio: https://github.com/drhelius/travis-ansible-demo/tree/master/travis

De vuelta a nuestro fichero .travis.yml, lo primero será traernos la imagen base y construir nuestra propia versión:

before_install:  
  - 'sudo docker pull ${distribution}:${version}'
  - 'sudo docker build --no-cache --rm --file=travis/Dockerfile.${distribution}-${version} --tag=${distribution}-${version}:ansible travis'

Donde ${distribution} y ${version} son las variables definidas en la matriz de configuración.

Después, ya podemos crear el contenedor. Este contenedor se quedará en ejecución en segundo plano. Guardaremos el ID del contenedor en la variable ${container_id} para poder referenciarlo después:

script:  
  - container_id=$(mktemp)
  - 'sudo docker run --detach --privileged -v /sys/fs/cgroup:/sys/fs/cgroup:ro --volume="${PWD}":/etc/ansible/roles/nginx_role:ro ${distribution}-${version}:ansible > "${container_id}"'

En este punto, nuestro contenedor estará en ejecución dentro de nuestro build y justo ahora ejecutaremos Ansible sobre él. Para ello he creado un playbook test.yml tan sólo para ejecutar el rol:

Como parte de las pruebas empezaremos comprobando la sintaxis del rol. Podríamos haber usado un linter también:

  - 'sudo docker exec "$(cat ${container_id})" env ANSIBLE_FORCE_COLOR=1 ansible-playbook -v /etc/ansible/roles/nginx_role/travis/test.yml --syntax-check'

Continuaremos ejecutando el rol en sí:

  - 'sudo docker exec "$(cat ${container_id})" env ANSIBLE_FORCE_COLOR=1 ansible-playbook -v /etc/ansible/roles/nginx_role/travis/test.yml'

Es conveniente ejecutar el rol dos veces, de esta manera comprobaremos si el rol es idempotente, algo bastante importante en Ansible:

  - >
    sudo docker exec "$(cat ${container_id})" env ANSIBLE_FORCE_COLOR=1 ansible-playbook -v /etc/ansible/roles/nginx_role/travis/test.yml
    | grep -q 'changed=0.*failed=0'
    && (echo 'Idempotence test: pass' && exit 0)
    || (echo 'Idempotence test: fail' && exit 1)

Una vez terminado podemos parar el contenedor y finalizar el build. En este punto todo debería haber acabado bien.

  - 'sudo docker rm -f "$(cat ${container_id})"'

Una vez hecho esto, cada vez que realicemos un push, o creemos un nuevo pull request, este proceso se desencadenará automáticamente probando nuestro rol en las 8 distribuciones que hemos definido, en tan sólo unos minutos.

Puedes ver el fichero .travis.yml final aquí.

Y los resultados del proceso de build aquí: https://travis-ci.org/drhelius/travis-ansible-demo

Este post fue publicado originalmente en inglés en el blog oficial de Travis: Continuous Testing of Ansible Roles with Docker and Travis CI

Si te ha gustado, ¡síguenos en Twitter para estar al día de nuevos posts!