Integración Contínua

En los tiempos de los diagramas de Gantt, el diseño en cascada y la ingeniería del software tradicional, existía un periodo llamado fase de integración. En esta fase, las diferentes partes escritas por diferentes desarrolladores se colocaban juntas. Se producían problemas muy difíciles de prever, que a veces incluían la reescritura de una parte o de todo el programa. Esto producía retrasos, costes y clientes insatisfechos. La integracíón continua trata de solucionar estos problemas.

La integración continua, en su forma más simple, implica el uso de una o varias herramientas que monitorizan cambios en nuestro sistema de control de versiones. Si se detecta un cambio, la herramienta lleva a cabo las acciones para obtener una copia funcional del software (compila en caso de ser necesario) y realiza las pruebas necesarias. Si algo no va como debe, la herramienta genera informes automáticamente de forma que los desarrolladores puedan resolver el problema inmediatamente.

Además de esto, la C.I. (Continuous Integration) puede ayudar a controlar el nivel de calidad del software mediante herramientas de métricas y a automatizar el proceso de despliegue, permitiendo un proceso de desplegado automático o bien como un proceso de un click.

CI implica por tanto obtener un feedback más rápido, que permita solucionar problemas antes, en un proceso más suave para desarrolladores y para clientes.

Introducción de CI en una organización

La introducción de CI en una organización puede ser gradual, cubriendo solo algunas de sus aspectos e incrementando su grado de implementación progresivamente. Una secuenciación de la implantación de CI podría componerse de las siguientes fases:

  1. Sin servidor de construcción: el software es escrito y compilado por el programador en su ordenador, y no se actualiza en el repositorio siguiendo una base regular. Las pruebas las hace cada programador de manera independiente a los demás. Antes de cada liberación de nueva versión se realiza manualmente el proceso de integración, haciendo pruebas de este tipo. Esto puede ser bastante desagradable.
  2. Con servidor de construcción: el equipo cuenta con un servidor de construcción que realiza tareas de manera programada. En este proceso no necesariamente entra la ejecución de tests. La diferencia con la fase anterior radica en el envío de confirmaciones de código al repositirio regularmente (cada día por ejemplo). El servidor genera informes de conflicto, y el equipo podría utilizar el servidor unicamente con propósitos informativos exclusivamente.
  3. Con servidor de construcción y automatización de tests: además de compilar el software, se automatiza la ejecución de tests y publicación de resultados.
  4. Introducción de métricas: Además de los aspectos anteriores se utilizan herramientas para evaluar automáticamente la calidad del software y de las pruebas.
  5. Introducción de los tests en el proceso de desarrollo: aplicación de técnicas como TDD para aumentar la confianza en el software.
  6. Tests de aceptación y despliegue continuo: en esta fase se dedican esfuerzos a proporcionar información de manera continua sobre el estado del proyecto y que sea fácilmente interpretable por personas sin conocimientos técnicos. La aplicación es desplegada automáticamente en un entorno de pruebas, y desplegada en un entorno en producción cuando se considera preparada. También se puede hacer un rollback a una versión anterior en caso de que algo vaya mal.
  7. Despliegue continuo: cuando el nivel de confianza en el proceso de automatización es suficientemente alto, se pueden aplicar técnicas de automatización para el despliegue, para éste ocurra automáticamente.

Instalación

Para la instalación de Jenkins vamos a utilizar inicialmente un servidor Ubuntu (no tiene por qué ser de esta distribución) en una máquina virtual local.

Más adelante podríamos utilizar una VPS para instalar Jenkins y usarlo como servidor de integración, de despliegue y de producción (aunque lo más recomendable sería que el servidor de CI no fuese el mismo que el de producción).

Para la instalación de Jenkins tenemos los siguientes prerequisitos:

  • Java JDK
  • Git
  • Una cuenta de GitHub
  • PHP
  • Un servidor web
# add-apt-repository ppa:openjdk-r/ppa # apt-get update # apt-get install openjdk-8-jdk # apt-get install apache2 // Para instalar una versión de PHP adecuada para PHPUnit # sudo apt-get install software-properties-common # sudo add-apt-repository ppa:ondrej/php5-5.6 # sudo apt-get update # sudo apt-get upgrade # sudo apt-get install php5 # service apache2 restart # wget -q -O - https://jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add - # sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list' # apt-get update # apt-get install jenkins # /etc/init.d/jenkins restart

Una vez instalado Jenkins los logs serán escritos en /var/long/jenkins/jenkins.log

Acceso a Jenkins

Una vez que hemos instalado Jenkins, ya podemos acceder al mismo, atacando al puerto 8080 del servidor. Podremos ver lo siguiente:

Jenkins recién instalado.

Proteger Jenkins con usuario

El modelo de seguridad más sencillo que utiliza Jenkins incluye permitir a los usuarios logueados hacer cualquier tarea. Los usuarios no autenticados tendrán solamente permisos de solo lectura. Este nivel de seguridad es adecuado para pequeños equipos, donde los desarrolladores pueden trabajar en sus propias tareas, mientras que el resto de usuarios (testers, clientes, directores de proyecto, etc) pueden ver el estado del proyecto.

Para llevar esta tarea a cabo, debemos seguir los siguientes pasos:

  1. Administrar Jenkins \ Configuración global de la seguridad
  2. Accediendo a la configuración global de Jenkins
  3. Activar la casilla "Activar seguridad". Después marcar las opciones siguientes:
    • Usar base de datos de Jenkins
    • Permitir que los usuarios se registren
    • Usuarios autenticados tienen privilegios para todo
    Opciones marcadas para permitir seguridad básica en Jenkins
  4. Aceptar las opciones y registrarse como usuario desde la página inicial. Esto nos permitirá entrar con todos los privilegios, ya que marcamos la opción "Usuarios autenticados tiene privilegios para todo". Ahora debemos desactivar esta opción para que solo los usuarios debidos tengan privilegios. Para ello, volvemos a Administrar Jenkins \ Configuración global de la seguridad y cambiamos la opción Usuarios autenticados tienen privilegios para todo por la opción Configuración de seguridad
  5. Restringiendo el acceso a sólo algunos usuarios.
  6. Antes de nada, debemos añadir el nuevo usuario a la lista de usuarios controlados en la configuración de seguridad. Para ello, escribimos el nombre del usuario en la entrada de texto "Usuario/Grupo para añadir".
  7. Añadiendo el nuevo usuario
  8. Seleccionamos los privilegios que queramos garantizar al usuario, y hacemos clice en "Guardar".

Tareas en Jenkins

Las tareas en Jenkins permiten realizar diferentes acciones en el orden deseado y atendiendo a las condiciones deseadas. Para empezar con esta parte, vamos a instalar algunos prerequisitos:

  • Instalar git
  • Instalar el plugin de Git para Jenkins
  • Instalar el plugin de GitHub para Jenkins
  • Instalar el plugin de JUnit para Jenkins

Ahora, ya podemos crear una nueva tarea. En este caso, lo que vamos a hacer es lo siguiente:

  1. Descargar desde GitHub la rama master de un proyecto.
  2. Pasarle los tests.
  3. En caso de que estos sean positivos, desplegaremos el proyecto en el servidor web de la misma máquina.

Crear una nueva tarea

En primer lugar, hacemos clic sobre la opción del menú Nueva Tarea. Lo primero es configurar la tarea, asignándole un nombre y eliguiendo la forma en que especificaremos las tareas. Nosotros elegiremos Crear un proyecto de estilo libre

Creando una nueva tarea

Configurar la nueva tarea

En primer lugar, podemos añadir una descripción

Debemos indicar el repositorio donde hemos alojado nuestro proyecto.

Repositorio Git donde alojamos el proyecto

Después debemos programar la tarea. Hay diferentes alternativas (como por ejemplo cuando se hace un push sobre GitHub). En nuestro caso vamos a programar tareas al estilo cron, indicando que se ejecute cada hora.

Programar el disparador de la tarea

Después debemos indicar las acciones que se llevarán a cabo. En nuestro caso, estas tareas vendrán descritas como scripts de bash. El primero de ellos es el siguiente:

#!/bin/bash git pull origin master
Con esta tarea descargamos localmente el proyecto

Entre nuestras pruebas hay pruebas de Selenium, que requieren que el proyecto sea accesible desde el Servidor web. Existen diferentes alternativas. Lo bueno de Jenkins es que nos permite hacer cualquier cosa que podamos hacer con comandos. La alternativa que aplico yo en este caso es bien sencilla: crear una copia temporal del proyecto a una carpeta de Apache que coincida con la indicada en las pruebas de Selenium.

Estoy utilizando el proyecto de ejemplo https://github.com/mmatpein/calculadoraTDD. Si observamos en el archivo system-tests/indexTest.php la variable $url contiene la url http://localhost/asignaturas/ed/calculadoraTDD4/. Es decir, para que Selenium encuentre la url, el proyecto debería estar en /var/www/html/asignaturas/ed/calculadoraTDD4/ (si no queremos liarnos con virtualhosts de Apache).

La alternativa a esto, sería crear un archivo de virtual host en la configuración de Apache, redireccionando mediante un alias la ruta http://localhost/asignaturas/ed/calculadoraTDD4/ a la ruta donde Jenkins ubica dicho proyecto, en mi caso /var/lib/jenkins/jobs/CalculadoraTDD/workspace

Por ello, para empezar, creo manualmente en /var/www/html/ una carpeta llamada asignaturas y le doy permisos al usuario jenkins para que pueda copiar temporalmente el proyecto a dicha ubicación.

# cd /var/www/html # mkdir asignaturas # chown jenkins.jenkins asignaturas

Una vez hecho esto, voy a crear una nueva tarea en Jenkins, para que copie temporalmente el proyecto en la ruta citada:

Copiamos temporalmente el proyecto a una carpeta accesible desde apache, para que Selenium pueda ejecutar los tests.

Antes de ejecutar las pruebas, debemos arrancar el servidor Selenium. Esto lo podemos hacer convirtiendo a Selenium en un servicio que arranque durante el inicio. Para ello, podemos seguir las instrucciones indicadas en https://ivanshn.wordpress.com/2013/02/02/running-selenium-server-on-ubuntu-10-04/

Lo siguiente que haremos será ejecutar nuestras pruebas y crear un archivo con el resultado de las pruebas.

Pasamos las pruebas y volcamos el resultado sobre un archivo xml estilo JUnit, para poder visualizarlo gráficamente.

Después, una vez que las pruebas han terminado, podemos limpiar la carpeta temporal con el proyecto que Jenkins copió a /var/www/html/asignaturas/

Borrado de la copia temporal del proyecto para Selenium.

Selenium requiere un navegador para realizar las pruebas, pero si no contamos con un entorno gráfico, entonces necesitaremos utilizar un servidor X11 como Xvfb para que la carga del navegador se produzca en memoria y las pruebas se puedan realizar. En https://www.leaseweb.com/labs/2013/09/testing-your-project-with-phpunit-and-selenium/ podemos comprobar de qué manera podemos hacerlo.

¡OJO!

Es importante que la url indicada en las pruebas de Selenium coincidan con el sitio donde el proyecto está ubicado en el servidor web.

Acciones a llevar a cabo después

Hay ciertas acciones que llevaremos a cabo cuando las pruebas se hayan llevado a cabo. Por ejemplo, podemos publicar el resultado de las pruebas.

Este tipo de acciones las concentramos en la sección Acciones para ejecutar después

Publicamos los resultados generados por PHPUnit.

Incluir métricas

Existen diferentes artefactos para calcular métricas útiles durante la integración contínua. Por ejemplo, las métricas obtenidas a partir de PHPLOC, incluyen la complejidad ciclomática, que puede ser de interés.

Lo primero que debemos tener instalado es el artefacto correspondiente, es decir, phploc. La instalación puede ser global, o bien puede ser local, dentro del mismo proyecto, con composer.

Dependiendo del artefacto que vayamos a usar, necesitaremos uno u otro plugin para para poder trabajar con el resultado generado por la herramienta. Por ejemplo, imaginemos que queremos incluir la complejidad ciclomática con información útil sobre el proyecto. Entonces debemos instalar el plugin correspondiente, en este caso Plot

Una vez que hemos instalado el plugin, ahora debemos configurar lo siguiente:

  • Una tarea que ejecute el artefacto y genere los datos de interés
  • Una acción que muestre los resultados en la sección Acciones para ejecutar después. Aquí es donde haremos uso del plugin instalado (en nuestro caso, Plot).

La tarea a ejecutar para generar la información de interés sobre el proyecto, podría ser algo como esto:

Ejecución phploc y exportación de los datos a formato .csv.

El documento .csv generado anteriormente, sería algo como esto: phploc.csv

Dentro de este archivo hay mucha información que no es del todo de nuestro interés. En concreto, si solo queremos la información sobre complejidad ciclomática y longitud media de métodos, entonces debemos las siguientes columnas:

Directories,Files,Lines of Code (LOC),Comment Lines of Code (CLOC),Non-Comment Lines of Code (NCLOC),Logical Lines of Code (LLOC),LLOC outside functions or classes,Namespaces,Interfaces,Traits,Classes,Abstract Classes,Concrete Classes,Classes Length (LLOC),Methods,Non-Static Methods,Static Methods,Public Methods,Non-Public Methods,Functions,Named Functions,Anonymous Functions,Functions Length (LLOC),Constants,Global Constants,Class Constants,Attribute Accesses,Non-Static Attribute Accesses,Static Attribute Accesses,Method Calls,Non-Static Method Calls,Static Method Calls,Global Accesses,Global Variable Accesses,Super-Global Variable Accesses,Global Constant Accesses,Test Classes,Test Methods

La gráfica donde veremos la información de nuestro interés, viene dada por la siguiente configuración:

En la columna CSV Exlusion values se indican todos los campos que no se desean mostrar. Dicha lista es la citada en la aclaración anterior.

Una vez que nuestro proyecto de Jenkins se ejecute varias veces, la gráfica sobre la complejidad ciclomática se mostrará en la sección Plots, mostrando algo como lo siguiente:

La gráfica sólamente muestra los campos no exluidos del archivo CSV.

Webhooks

En el ejemplo anterior, el proceso de integración se realiza periódicamente, en un esquema dictado por cron. Otra alternativa es realizar la integración únicamente cuando se realice una confirmación a GitHub. A esto se le llama webhook. El proceso implica configuraciones en GitHub y en Jenkins.

Instalar el plugin de GitHub en Jenkins

Si aún no ha sido instalado el plugin de GitHub en Jenkins, debería ser instalado.

El plugin de GitHub está instalado en Jenkins

Mediante este plugin, Jenkins escuchará las llamadas que se produzcan desde GitHub cada vez que se produzca un push

Configurar la tarea para atender a los push en GitHub

Otra cuestión que debemos hacer en Jenkins, es configurar la tarea para que se dispare cuando se produzca un push en el repositorio de GitHub.

La opción Build when a change is pushed to GitHub está activada.

De momento hemos terminado en Jenkins. Pero ahora debemos decir a GitHub que envíe una notificación a Jenkins cada vez que se produzca una confirmación (push) en el repositorio.

Crear un webhook en GitHub

Lo primero es ir hasta GitHub. Dentro del repositorio de interés, nos vamos a Settings -> Webhooks & services. En el menú desplegable Add service buscamos "Jenkins (GitHub plugin)", y lo elegimos. Entraremos entonces en la sección "Services / Manage Jenkins (GitHub plugin). En esta sección se ofrecen instrucciones sobre cómo configurar el webhook para Jenkins.

En la sección Jenkins hook url se especifica la URL donde el plugin de Jenkins para GitHub escucha las notificaciones de actualización en el repositorio.

En la parte superior de la sección "Services / Manage Jenkins (GitHub plugin)" se puede ver un botón "Test service". Este botón permite comprobar si Jenkins el plugin de GitHub está escuchando en la URL especificada. Si hacemos clic sobre él, aparecerá un tic en verde indicando que todo está bien.

En la parte inferior de la imagen puede verse que las palabras "Jenkins (GitHub plugin)" están acompañadas por un tic de verificación de color verde. Este tic se activa tras el test.

Comprobar que las notificaciones están llegando

Una vez que hemos configurado todo lo necesario, podemos comprobar que están llegando las notificaciones en el archivo /var/log/jenkins/jenkins.log.

En el archivo se muestra que se ha recibido una notificación desde el repositorio https://github.com/mmatpein/calculadoraTDD. También se indica que se ha lanzado la tarea número 4 del proyecto en Jenkins

De hecho, dicha construcción debe salir reflejada en el historial de tareas del proyecto.

En el historial de tareas, se puede ver la tarea lanzada en el mismo instante reflejado en el log de Jenkins, en la imagen anterior.

Encadenar acciones

En ocasiones puede ser interesante encadenar varias tareas, de forma que se si una tarea se completa correctamente se dispare otra tarea que haga ciertas cosas.

Las tareas se lanzan dependiendo del resultado de la tarea anterior.

La ejecución de proyectos encadenados se puede especificar en dos sitios diferentes:

  • En la sección Acciones para ejecutar después de la tarea que lanza a las demás. En dicha sección tenemos la acción Ejecutar otros proyectos.
  • En la sección Disparadores de ejecuciones de la tarea que es disparada.

podemos elegir la opción Build after other projects are built. Bajo esta casilla aparece el selector del proyecto a controlar. Hay tres posibilidades:

  • Trigger only if build is stable: Disparar la nueva tarea solamente si el resultado del proyecto anterior es positivo. Si todos los tests son positivos y no se produce ningún fallo durante la ejecución de la tarea, la tarea se disparará.
  • Trigger even if the build is unstable: Disparar la nueva tarea independientemente de si el resultado de la tarea ha sido exitoso o no.
  • Trigger even if the build fails: Disparar la nueva tarea aunque no se haya podido completar la actual.

Jenkins utiliza una terminología algo ambigua. En https://wiki.jenkins-ci.org/display/JENKINS/Terminology aparece esta terminología aclarada:

  • Construcción estable: el resultado publicado por los diferentes artefactos es exitoso. Por ejemplo, PHPUnit no reporta ningún fallo en los tests.
  • Construcción inestable: alguno de los resultados publicados ha fallado. Por ejemplo, PHPUnit reporta algún fallo.
  • Construcción rota/fallida: la tarea no ha podido ser completada.
Configuración del disparador de una tarea. La tarea se dispara cuando se completa la anterior con éxito.

Se pueden especificar diferentes proyectos (más de uno) si se separan por comas. Por ejemplo Tarea1, Tarea2, Tarea3.

Actividad 1. Crea en Jenkins una tarea para integrar de manera continua tu proyecto tu proyecto de Transformación de Unidades. Debes tener en cuenta los siguientes requisitos:

  • La tarea descarga desde GitHub el proyecto
  • Se ejecuta cada hora
  • Pasa los tests PHPUnit y muestra el resultado gráficamente
  • Calcula alguna métirica de interés y la muestra gráficamente

Guarda la página donde configuras la tarea, con el nombre Act1-ci.html.

Toma una captura donde se pueda ver el resultado de las pruebas y guárdala con el nombre Act1-pruebas-ci.png

Toma una captura donde se pueda ver la métrica obtenida, y guárdala con el nombre Act1-metricas-ci.png

Para una de las tareas ejecutadas, muestra la salida de consola, y guárdala con el nombre Act1-consola-ci.png

Actividad 2. Reconfigura la tarea para que se dispare cuando se realice una confirmación en la rama "master" del repositorio de GitHub.

Toma una captura de la configuración de la tarea en Jenkins y guárdala con el nombre Act2-jenkins-ci.png

Toma una captura de la configuración del webhook de GitHub para Jenkins, y guárdala con el nombre Act2-github-ci.png

Toma una captura llamada Act2-jenkins-log-ci.png donde se pueda ver un log de Jenkins, en el archivo /var/log/jenkins/jenkins.log que indique que se ha recibido una notificación desde GitHub

Toma una captura donde se pueda ver el instante en que se lanzó la tarea mediante el webhook, y guárdala con el nombre Act2-jenkins-webhook-ci.png

Actividad 3. Crea una tarea de despliegue cuyas acciones estén únicamente encaminadas a desplegar una versión estable en el servidor de producción. La tarea va a ser lanzada manualmente cuando se considere oportuno, por lo que no tendrá un disparador configurado.

Guarda la configuración de la tarea con el nombre Act3-ci.html