Creación de entornos de integración con Ansible y Vagrant

En este tutorial veremos como crear un entorno de integración utilizando las herramientas Ansible para automatizar tareas y Vagrant como proveedor de máquinas virtuales.

0. Índice de contenidos

1. Introducción

La mayoría de proyectos necesitan de un entorno de integración continua en el cual podemos ver y detectar los fallos de forma temprana, sin llegar al entorno de producción. Este tutorial pretende crear una plantilla de entorno de integración de modo que puedas desplegar de forma sencilla un entorno de integración continua gracias a la gran combinación de vagrant y ansible.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro Retina (2.93 Ghz Intel Core 2 Duo, 8GB DDR3).
  • Sistema Operativo: Mac OS Yosemite 10.10
  • Virtual Box
  • Vagrant 1.7.2
  • Ansible 1.9.2

3. Instalación de requisitos

Para preparar el entorno de integración continua, crearemos la máquina virtual con Vagrant, un generador de máquinas virtuales a partir de imágenes previamente construidas (boxes). La plataforma en la que estará nuestra máquina virtual será Virtual Box y para configurar la máquina e instalar el software necesario utilizaremos ansible, que automatiza las tareas y despliega de forma sencilla, de modo que pueda replicarse en cualquier máquina remota.

3.1. Instalación de Virtual Box

Nos descargamos Virtual Box desde aquí, seleccionamos el paquete que necesitemos dependiendo de nuestro sistema operativo, y seguimos las instrucciones del instalador.

3.2. Instalación de Vagrant

Obtenemos Vagrant desde su página de descarga donde seguimos igualmente el proceso de descarga que requiera nuestro sistema operativo. Para comprobar que lo tenemos correctamente instalado, ejecutamos por línea de comandos:

3.3. Instalación de Ansible

Las instrucciones de descarga de ansible pueden ser encontradas aquí.

La instalación recomendada es vía pip, el gestor de paquetes de Python. Si no lo tenemos instalado debemos instalar pip primero y luego instalar Ansible.

4. Creación y configuración de la máquina virtual

Una vez instalados Vagrant y Ansible, estamos preparados para crear la máquina virtual de pruebas de nuestro entorno de integración, y configurarla para que nuestras aplicaciones funcionen detrás de un apache que hace de proxy interno.

4.1. Elección del box de Vagrant

Para crear la configuración de la máquina virtual creamos el directorio en el cual queramos tener la configuración de la máquina virtual, y después le decimos a vagrant que se descarge la imagen (box) con la que queremos que esté contruida nuestra máquina virtual con el siguiente comando:

Vagrant ofrece una gran cantidad de boxes, así como la posibilidad de crear tus propias boxes personalizadas. Puedes encontrar boxes en el siguiente enlace.

4.2. Creación Vagrantfile

A continuación, para generar el fichero de configuración base de la máquina virtual usamos el comando:

Este comando crea el fichero de configuración que será leído cuando arranquemos o levantemos la máquina, y es donde especificaremos la box que va a utilizar vagrant para crear la máquina virtual, si disponemos de algún ‘provider’ para que instale software por defecto, así como configuración entre la máquina real y la máquina virtual. Tras ejecutar este comando, en la carpeta elegida para almacenar la configuración deberían tener la siguiente estructura:

img1

Comprobamos que ha aparecido el fichero Vagrantfile, donde estableceremos la configuración de que imagen va a utilizar la máquina virtual, así como quien se va a encargar de instalar sofware en la misma. El fichero debería quedar de la siguiente manera:

Vagrantfile

A continuación analizamos el significado del siguiente fichero:

  • El “2” de la primera línea representa la versión del objeto de configuración config que será usado para la configuración del bloque.
  • Con la siguiente línea le indicamos a vagrant que box utilizará para la creación de la máquina virtual.
  • La siguiente línea nos permite acceder a un puerto de nuestra máquina real (host) y que todos los datos sean redirigidos a la máquina virtual (guest) a través de un determinado puerto. La propiedad auto_correct:true indica que si hay un conficto debido a que ese puerto está actualmente en funcionamiento, vagrant se encargará de solucionarlo de forma automática cuando esté levantando la máquina virtual. Esto quiere decir que para poder acceder al apache de nuestra máquina virtual por un navegador, usaremos el puerto 8080 en nuestra máquina real (quitar esta opción si vamos a desplegar la aplicación en una máquina remota)
  • El uso de la propiedad define sirve para especificar un entorno dentro de la máquina de configuración (ya que vagrant permite la configuración de multiples máquinas virtuales en el mismo Vagrantfile). Nosotros definimos un entorno en el cual todas las propiedades que estén dentro del ámbito de este entorno, solo afectarán a esa única máquina sobreescribiendo las propiedades por defecto en caso de conflicto.
  • Nota: Dentro del ambito de define, “continuous_integration” es sinónimo de “config”.

  • Dentro del entorno de integración indicamos a la configuración de vagrant que trandrá un encargado de instalar el software a la hora de crear la máquina. En este caso, será ansible.
  • Para que ansible se ejecute necesita dos ficheros clave: Uno es el inventory, donde se indicarán los grupos que tiene ansible, así como las ip que están dentro de cada uno de ellos. El otro fichero necesario es el playbook, que contiene cuales son las tareas que ansible va a realizar, y en que grupos de host las va a realizar (previamente definidos en el inventory).
  • Como estamos realizando pruebas en una máquina virtual, le damos más memoria para que no haya problemas a la hora de levantar las aplicaciones.

4.3. Creación del inventory

Cuando usamos ansible, este necesita saber en qué máquinas debería correr un determinado playbook. La forma de indicarle estás máquinas es mediante el inventory. Vagrant tiene dos formas de crear este fichero, una de forma automática y otra de forma manual. Nosotros usaremos la forma manual, para lo cual rellenaremos el fichero con el siguiente contenido.

ansible/environments/continuous_integration/inventory
  • En la primera línea especificamos el host de la máquina creada, y el puerto que habíamos establecido anteriormente que redireccionaba al puerto 22 de la máquina que va a ser creada. Esta información debe ser especificada tanto en el Vagrantfile como en el Inventory.
  • A continuación entre paréntesis tenemos el grupo de máquinas en el cual se ejecutará un determinado playbook. Esto quiere decir que si queremos que otra máquina ejecute estas tareas de ansible, a la hora de ejecutar el comando, solo tenemos que añadir aquí la ip de esa máquina y también ejecutará el mismo playbook.
  • Por último, indicamos las máquinas que estan dentro del grupo de máquinas. El nombre de las máquinas en el Vagrantfile y en el inventory deben ser iguales por defecto, de ahí que llamemos continuous_integration a nuestra máquina.

4.4. Uso de variables en las tareas de ansible

Durante la creación de los playbook usaremos un único lugar para establecer variables específicas, como el nombre a cambiar de un directorio creado con ansible, o una url de descarga, de forma que no tengamos que buscar dentro de los playbook, y evitar posibles errores humanos.

Para minimizar este riesgo usaremos variables que estarán localizadas en un directorio group_vars creado a la altura del inventoryque tendrá dentro un fichero con el nombre del grupo de hosts que estemos ejecutando (en nuestro caso se llamará ‘ci’) en el que guardaremos todas estas variables.

Las variables que utilizaremos en las tareas de los roles son:

environments/continuous_integration/group_vars/ci

Podemos agrupar las variables en grupos más generales.

Las variables tienen el formato nombre_variable: valor (El espacio es importante!!!) de modo que si quisieramos la variable version del grupo de variables nexus la llamaríamos de la siguiente forma en el playbook:

4.5. Creación del playbook

Ahora que ya tenemos definida las máquinas en las que se ejecutarán nuestras tareas de ansible, nos falta definir esas tareas. Esto se hace en el playbook, un documento YAML que define una serie de pasos que deben ser ejecutado en una o más máquinas.

Como de costumbre, escribiremos la estructura de nuestro playbook y luego explicaremos los datos más relevantes:

continuous_integration.yml

Un playbook se estructura en “plays”. Estos son pequeñas divisiones que se van ejecutando en orden descendente.

  • La propiedad hosts indica cual es el grupo de máquinas en las cuales se ejecutaran las tareas de ansible, que coincide con el definido en nuestro inventory.
  • La propiedad sudo indica si se necesitan permisos de root para realizar algunas de las tareas.
  • gather_facts es información derivada de comunicarnos con nuestros sistemas remotos, como información sobre la dirección ip del host remoto, o que sistema operativo usa. En nuestro caso no nos comunicamos con ningún sistema remoto, por lo que esta opción está desabilitada.
  • La propiedad roles irá ejecutando las tareas de cada rol en orden descendente cuando se cargue el playbook. Las tareas podrían ir directamente en el playbook, pero de esta manera, es mucho más sencillo organizarlas por lo que hacen, así como tener separadas las responsabilidades por si en el futuro no necesitas realizar todas las tareas, sino reutilizar algunas antiguas y crear unas nuevas 😉

4.6. Creación de Roles

Como ya hemos explicado anteriormente, los roles nos permiten organizar nuestras tareas. Crearemos el directorio roles en la cual estarán localizados todos los roles justo debajo de la carpeta ansible.

Dentro de la carpeta roles la estructura es la siguiente:

  • roles/[nombre_rol]/tasks: Aquí dentro irá un fichero main.yml que contendrá las tareas de este rol.
  • roles/[nombre_rol]/handlers: Tendrá un fichero main.yml que contiene los handlers, que son respuestas a eventos que pueden cambiar el estado de las tareas. Los veremos más adelante.
  • roles/[nombre_rol]/templates: Dentro van las plantillas de los ficheros que se puede rellenar utilizando las variables declaradas anteriormente.
  • roles/[nombre_rol]/files: A direfencia de las plantillas, aquí pasamos el fichero tal cual, sin modificaciones de variables. Cuando necesitamos un fichero del host real, el mrol viene a buscarlo aquí por defecto.

Si no va a utilizar handlers, templates, o files no es necesario crearlas. Task suele ser la carpeta que está creada debido a que tiene las tareas a ejecutar por ansible.

Vamos a crear los roles definidos en el playbook comentando las partes más relevantes de cada uno.

4.6.1. common_os_setup

Este rol está encargado de realizar las tareas generales, como la descarga de paquetes de idiomas y asegurarse de que el sistema está actualizado.

roles/common_os_setup/tasks/main.yml

Cada tarea diferente comienza con un guión (-) y luego tiene una serie de propiedades. La propiedad name indica de forma intuitiva que hace cada tarea. En nuestro caso, tenemos 4 tareas, en las que hay que destacar:

  • En la primera tarea usamos el módulo apt, que maneja los paquetes apt siendo esta linea equivalente al comando apt-get update.
  • En la segunda tarea, usamos el mismo módulo, pero con la propiedad name. Esta propiedad indica el nombre del paquete a instalar, pero también permite el uso de variables. Ansible posee internamente la variable item (las variables van rodeadas de dobles corchetes {{variable}}) que indica que esa operación debe realizarse tantas veces como items haya dentro de la propiedad with_items. En este caso, equivale a los comandos:
  • Nota: En la documentación de ansible posees una lista de todos los módulos, así como las propiedades de cada uno.

  • Ansible también permite el uso de comandos de forma esplícita, como es el caso de la tercera tarea, donde se le dice que ejecute el comando descrito, encargado de actualizar los idiomas.

4.6.2. dependencies

Este rol se encarga de incluir dependencias que podrías necesitar en un entorno de integración continua.

roles/dependencies/tasks/main.yml

La propiedad: tag es muy útil durante el testing de tareas. Al especificar que que quieres correr un determinado playbook con una serie de roles, pero solo quieres probar lo último que has añadido, o un grupo determinado de tareas específicas, cuando corra la ejecución de ansible, puedes especificarle que solo ejecute las tareas con un tag determinado.

4.6.3. java8

Este rol se encarga de instalar java en la máquina remota.

roles/java8/tasks/main.yml

Los dos módulos a destacar en este rol son:

  • apt-repository: que sirve para añadir o eliminar repositorios apt en ubuntu y debian.
  • debconf se encarga de configurar un paquete, en este caso para autoaceptar la licencia de java.

4.6.4. sonar

Este rol es el encargado de instalar y configurar sonar, que nos permite controlar y supervisar la calidad del código.

roles/sonar/tasks/main.yml

Este rol tiene chichilla 😀 vamos a analizarlo detenidamente:

  • En la primera tarea, vemos como nos descargamos sonar, pero en este caso desde una url con el módulo get_url. Este módulo tiene dos parametros obligatorios: url, que indica el lugar desde donde descargaremos el archivo y dest que es el lugar donde lo almacenaremos. La segunda cosa a destacar es que estamos usando variables para decirle cual es la url de descarga, así como el nombre del archivo descargado, localizadas en el fichero ci descrito al final del punto anterior.
  • Con los módulos group y user, somos capaces de crear grupos y usuarios respectivamente.
  • El módulo unarchive permite desempaquetar un archivo , y la opción copy=no indica que este archivo ya se encuentra en la máquina remota, y que no necesita ser copiado desde el host.
  • El módulo shell es igual que el módulo visto previamente para realizar comandos.
  • El módulo file nos permite crear directorios, así como estableces sus grupos, usuarios o permisos.
  • La propiedad template en la tarea con el nombre copy sonar properties indica que el archivo mencionado va a ser recogido desde la carpeta templates localizada justo dentro del rol. En esta carpeta podremos introducir nuestras plantillas que pueden utilizar las variables de ansible, y serán rellenadas en el momento en el que las llames. El contenido de la plantilla es:
  • roles/sonar/templates/sonar.properties

4.6.5. ansible

Este rol se encarga de instalar ansible, exactamente de la misma manera en la que antes lo instalamos en nuestra máquina. La razón por la que necesitamos ansible en remoto es para automatizar tareas como los despliegues de la aplicación

roles/ansible/tasks/main.yml

4.6.6. jenkins

Este rol se encarga de instalar jenkins y sus plugins

roles/jenkins/tasks/main.yml

Lo más destacable es la propiedad notify en la última tarea. La acción restart jenkins es denominada por ansible como handlers, y son acciones que se escriben a parte porque pueden ser reutilizadas por varias tareas del mismo rol.

Por lo tanto para que nuestro rol funcione necesitamos el handler del rol jenkins:

roles/jenkins/handlers/main.yml

Como se puede observar, el nombre del handler debe ser igual a la propiedad -name de ansible.

En la tarea:

En la propiedad src no estamos indicandole la ruta. cuando esto sucede, ansible busca por defecto en el directorio files dentro de la carpeta de su rol. Este fichero sobreescribe las propiedades por defecto de jenkins para cambiar el context path de / a /jenkins:

roles/jenkins/files/jenkins

4.6.7. maven

Este rol se encarga de instalar maven.

roles/maven/tasks/main.yml

El único módulo a destacar es alternatives, usado cuando hay multiples programas instalados que proveen funcionalidad similar.

4.6.8. nexus

Este rol se encarga de instalar nexus, nuestro repositorio de artefactos binario.

roles/nexus/tasks/main.yml

Nexus también posee un handler que restaura el servicio, por lo que lo creamos:

roles/nexus/handlers/main.yml

4.6.9 apache

Este rol se encarga de la instalación y la confi

roles/apache/tasks/main.yml

En la tarea copy ssl certificate actualmente se enviarían los dos archivos necesarios para que apache funcionara con ssl, de modo que introduciendo en la carpeta files/ tus certificados ansible se encargaría de realizar el resto de la configuración.

Este rol también necesita de un handler para reiniciar el servicio de apache:

roles/apache/handlers/main.yml

Por último, le pasamos la configuración del virtual host de forma que apache actúe como un reverse proxy a la hora de acceder a los servicios de sonar, jenkins y nexus.

NOTA: la configuración para que este acceso a servicios sea via https está DESHABILITADA. Se recomienda crear par de claves para la autenticación ssl. Después solo es necesario descomentar la parte comentada en el primer virtual host y eliminar las propiedades del proxy del mismo.

roles/apache/templates/virtualhost.conf

5. Arranque de la máquina virtual

Una vez creados los roles del playbook, simplemente levantamos la máquina virtual con el comando:

Esto levantará la máquina virtual y llamará a ansible para que instale y configure todos los elementos del entorno que hemos visto previamente en los roles. Hay que tener en cuenta que ansible lo único que hace es ejecutar todos los comando que haríamos nosotros de forma automática, así que la primera vez que ejecutemos el comando tardará sus 10-15 minutos 😀

6. Comprobación del funcionamiento

Para comprobar su funcionamiento con nuestro navegador vamos a http://localhost:8080/jenkins y comprobamos como nos hace una redirección al servicio de jenkins. El motivo de usar el puerto 8080 es que en el fichero de configuración de la máquina virtual creamos una redirección del puerto 8080 en el host al puerto 80 de la máquina virtual para poder comprobar nuestros cambios. En caso de que usemos ansible para configurar una máquina externa, no sería necesario definir esta propiedad, sino que nos refeririamos directamente al puerto 80 de esa máquina.

Si nos fijamos en la url escrita, se ve como ocultamos los puertos internos de la aplicación, usando solo el puerto 80 del apache, que está redirigido al puerto 8080 de la máquina real.

img2 img3 img4

Puedes descargar la configuración de vagrant y ansible de este tutorial desde aquí

7. Conclusiones

Gracias a la combinación de vagrant y ansible, somos capaces de crear un entornos con gran facilidad, de modo que no solo sirva para crear entornos de integración, si no también entornos para el equipo de desarrollo.

8. Referencias