QA + DEVOPS - Dockerizando un pipeline de Jenkins

QA + DEVOPS - Dockerizando un pipeline de Jenkins

14 de mayo de 2021

¿Qué vamos a ver?

En este artículo se tratará cómo enfocar la calidad del desarrollo software hacia la parte DevOps. Se mostrará un modelo de integración contínua en el que un cambio sobre el código esté disponible en entornos finales sin errores, con el mínimo impacto en las infraestructuras disponibles y tardando el menor tiempo posible.

En este caso, veremos cómo conseguirlo mediante la combinación de los pipelines de Jenkins y Docker.

proyecto jenkins - qa devops -00

¿Por qué es interesante?

El objetivo será tener un modelo para la construcción de un entorno de Integración Continua con Jenkins y Docker en el que se implementarán estrategias de testing, automatización de pruebas y análisis de calidad de código de las aplicaciones implicadas.

Se trata de poner en práctica una estrategia de calidad que englobe todo el ciclo de vida de software en el pipeline de Jenkins y teniendo como objetivo principal, el mantener el entorno de la máquina limpio y lo más estable posible. Es decir, se tratará de evitar albergar en ella todas las instalaciones y/o versiones del software específico de cada proyecto que se administre.

Para conseguir este objetivo, se utilizarán entornos de ejecución “estancos” mediante la tecnología Docker. Con ella se podrán repartir las distintas tareas en diferentes contenedores en lugar de máquinas físicas y/o virtuales consiguiendo así entornos más ligeros, fáciles de escalar y portables. Este último punto resulta especialmente interesante, puesto que aseguraremos en todo momento que los entornos de ejecución de la aplicación son idénticos en todo el ciclo de vida de la aplicación. (entorno local, integración, producción,etc.)

Modelo tradicional

Habitualmente, para ejecutar los procesos de validación y testing de las aplicaciones, ya sea de manera manual o automática, es preciso que los sistemas estén desplegados en los entornos correspondientes, los cuales, además, suelen estar compartidos por varios miembros o equipos.

Esta manera de proceder tiene varios inconvenientes. Por un lado, suele ser necesaria la participación del equipo de “Operaciones” de la empresa para que los despliegues se realicen de manera adecuada. Aquí, es posible que se esté creando un cuello de botella de manera innecesaria puesto que el despliegue quedará a expensas de la disponibilidad del personal de operaciones y retrasará la fase de pruebas.

Por otro lado, tener entornos de validación compartidos por varias personas puede generar problemas de pruebas cruzadas y disponibilidad. Estos casos también son causas de ineficiencias en el proceso de pruebas y pueden generar resultados confusos en las pruebas en el caso de que se produzcan solapamiento de ejecuciones.

El modelo habitual para las funciones de calidad que se desarrolla tradicionalmente en los ciclos de vida del software se basan en la incorporación por parte del equipo de operaciones de la infraestructura necesaria para cada proyecto. Ésto tiene como desencadenante una gran cantidad de equipamiento, tanto físico como virtual para que se cumplan los requisitos del sistema a construir.

Jenkins instancia única vs Jenkins dockerizado

proyecto jenkins - qa devops

¿Dónde lo puedes aplicar?

Casos de uso habituales

Este modelo será aplicable en todos los casos en los que se use un servidor de aplicaciones para automatizar tareas de tipo construcción, pruebas y despliegue de aplicaciones.
Este modelo es aconsejable cuando trabajemos con proyectos de varias tecnologías diferentes, no dispongamos de entornos de pruebas reservados y/o el equipo de operaciones tenga una disponibilidad limitada.

Dónde no tiene sentido aplicarlo

Nos parece que esta aproximación tiene muchas ventajas, aún así, cuando nos encontremos con empresas donde se utilice un stack tecnológico uniforme y los entornos de trabajo estén claramente definidos y no se produzcan colisiones en pruebas, podría no aportar tantas ventajas como en otro tipo de contextos.

¿Cómo funciona?

Prerrequisitos

  • Windows 10 PRO
  • Java 8+
  • Docker y docker-compose
  • Git

Repositorio

Todo el código necesario para seguir y ejecutar este ejemplo está disponible en este repositorio.

Componentes

  • Se necesita una aplicación sobre la que se ejecutará el ciclo de integración continúa.
  • Disponer de una máquina con jenkins y docker instalados (en este caso, nuestro laptop).
  • Un servidor SonarQube ( en este ejemplo también está dockerizado).

A continuación se define cada uno de ellos:

Aplicación

Se trata de una aplicación construida con Spring Boot Rest API que usa Spring Data MongoDB y Maven para gestionar la construcción. A modo de ejemplo, implementa las operaciones CRUD de un sistema de gestión de estudiantes.

Contiene los test unitarios así como dos formas de ejecución de pruebas funcionales o de integración e2e: Una de ellas cucumber + Rest-Assured y otra mediante Postman.

De cara a poder ejecutar este proyecto en entornos locales y de integración contínua, existen las siguientes piezas de docker.

  • Para la construcción de la imagen se utiliza el archivo Dockerfile que contiene los comandos necesarios para ello.
  • Para levantar y detener esta aplicación, así como las demás que serán necesarias para su funcionamiento (BBDD) y definir la red común en la que alojaremos todos los contenedores, se crea un archivo docker-compose.
  • También se definirá otro archivo docker-compose con la definición de las pruebas de integración en la que se utilizará un contenedor con newman en la red común con la que de forma totalmente aislada ejecute los test definidos con Postman.
    • Esto está hecho sólo como propósito de demo. Para mostrar el concepto de que es posible dockerizar cualquier tipo de prueba, independientemente de la tecnología o herramienta que se utilice.

Para todas las fases en las que sea necesario utilizar Maven, se ejecutarán también en contenedores de docker, de manera que no sea necesario tener instalada una JDK o versión específica de Maven en el entorno de ejecución de Jenkins.

La aplicación ejemplo se encuentra en la carpeta: demo-project.

Jenkins

La instalación se hará sobre una máquina local, en este caso será Windows 10. Tendrá que tener instalado: la jdk, git, docker y docker-compose como hemos mencionado anteriormente en los prerrequisitos.
Para instalarlo hay que descargar el .zip con la versión deseada desde aquí, descomprimir y ejecutar el archivo y seguir los pasos manteniendo los valores de instalación por defecto.

Para comprobar su correcta instalación, se abre un navegador y se introduce “http://localhost:8080”. Tras esto aparecerá una pantalla para introducir la clave de administrador.

post jenkins 2

Se instalarán los plugins por defecto y posteriormente el Cucumber reports(https://plugins.jenkins.io/cucumber-reports/).

post jenkins 1

Se configurará como variable global la herramienta Git.

post jenkins 3

Se creará una nueva tarea de tipo pipeline en la que se configurará la definición de “Pipeline script” que está contenida en el repo de la aplicación en el archivo Jenkinsfile o se podrá también configurar como un “Pipeline script from SCM” que ejecutará directamente en archivo Jenkinsfile en el repo de la aplicación.

La primera opción se usa durante la configuración por ser más rápida la realización de pruebas. En cuanto esa configuración sea definitiva se recomienda usar la segunda.

SonarQube

Para verificar la calidad interna de la aplicación se realizará un análisis de código con esta herramienta. Para ello se ejecutará en un contenedor docker con las siguientes instrucciones:

docker pull sonarqube:lts
docker run --name sonarqube -d -p 9000:9000 -p 9092:9092 -v sonarqube_home:/opt/sonarqube/data sonarqube:lts
docker start sonarqube


Se puede acceder entrando en “http://localhost:9000” con el login: admin/admin.

Ciclo de CI - Pipeline

El pipeline que se describe a continuación está realizado a modo de ejemplo, es por ello que puede no resultar óptimo para proyectos reales.

El objetivo principal es mostrar conceptos que cubran posibles escenarios de integración de dockers con los pipelines de jenkins y cómo esto beneficia la velocidad de los despliegues y la ejecución de pruebas de manera automática.

Todos los pasos que a continuación se detallan, a excepción de la copia en local del proyecto en la que se utiliza Git, se ejecutarán en entornos aislados. Es decir, cada ciclo o “stage” se ejecutará en contenedores independientes utilizando Docker.

  1. Checkout del proyecto.
  2. Compilación y archivado.
  3. Ejecución Tests unitarios.
  4. Análisis estático de código contra Sonarqube.
  5. Levantar aplicación y bases de datos necesarias.
  6. Ejecución de Tests Cucumber + REST-Assured.
  7. Ejecución de Tests de integración con Postman.
  8. Detener la aplicación y contenedores.

Visualmente, podría resumirse de la siguiente manera, donde puede apreciarse que cada fase del pipeline se ejecutar en un contenedor docker independiente.

proyecto jenkins - qa devops -05

Para poder dockerizar todos estos pasos se crea una composición de contenedores, que dependen unos de otros y están vinculados entre sí en una red privada virtual.

Los pasos antes mencionados se traducen en el siguiente archivo pipeline de Jenkins:

node('master'){
    # Especificar la ruta adecuada en cada caso
    def mavenFolfer = "C:/Users/{{user}}/.m2";
    stage('checkout') {
        # Es necesario configurar la credenciales previamente en el jenkins
        git branch: 'master', credentialsId: 'GitLab', url: 'https://gitlab.sngular.com/sngulartech/devops_jenkins_and_docker'
    }
    stage('compile and save files'){
        dir("./demo-project") {
            bat 'docker run -i --rm --name maven-image -v /var/run/docker.sock:/var/run/docker.sock -v "'+mavenFolfer+'":/root/.m2 -v "%cd%":/usr/src/app  -w /usr/src/app maven:3.5.2-jdk-8-alpine mvn clean package -DskipTests'       
        }
    }
    stage('unit tests'){
        dir("./demo-project") {
            bat 'docker run -i --rm --name my-maven-test -v "%cd%":/usr/src/app -v "'+mavenFolfer+'":/root/.m2 -w /usr/src/app maven:3.5.2-jdk-8-alpine mvn test -Pcucumber'           
        }
    }
    stage('Result'){
       dir("./demo-project") {
        junit '**/target/surefire-reports/TEST-*.xml'
     archiveArtifacts artifacts: 'demo-project/target*//*.jar', followSymlinks: false, onlyIfSuccessful: true
       }
    }
    stage('sonar'){
        dir("./demo-project") {
            def localUrl = InetAddress.localHost.hostAddress
            URLSONAR = "http://"+localUrl+":9000"
            bat 'docker run -i --rm --name my-maven-project -v "%cd%":/usr/src/app  -v "'+mavenFolfer+'":/root/.m2 -e SONAR_HOST_URL='+URLSONAR+' -w /usr/src/app maven:3.5.2-jdk-8-alpine mvn sonar:sonar'    
        }
        
    }
    stage('docker up app'){
        dir("./demo-project") {
            bat 'docker-compose -f docker-compose.yml up -d'       
        }
    }
    stage('cucumber'){
        dir("./demo-project") {
            bat 'docker run -i --rm --name my-maven-test -v "%cd%":/usr/src/app -v "'+mavenFolfer+'":/root/.m2 -w /usr/src/app maven:3.5.2-jdk-8-alpine mvn test'           
        }
       cucumber 'demo-project/target/surefire-reports/cucumber.json'
    }
    stage('integration tests with postman'){
        dir("./demo-project") {
            bat 'docker-compose -f docker-compose-integration-tests.yml up'
        }
   }
   stage("docker down"){
       dir("./demo-project") {
            bat 'docker-compose down --remove-orphans'
       }
   }
}

Para ejecutar este pipeline previamente hay que realizar varios pasos:

  • Definir la variable de localización de la carpeta .m2 de Maven sustituyendo {{user}} por la variable correcta.
  • Crear las credenciales de acceso a GitLab y poner el identificador ‘GitLab’.

La primera vez que ejecutamos este pipeline obtenemos el siguiente error:

Scripts not permitted to use staticMethod java.net.InetAddress getLocalHost. Administrators can decide whether to approve or reject this signature.

Se debe a la inserción del script “InetAddress.localHost.hostAddress” para la obtención de la ip de la máquina la cual tiene que ser conocida para lanzar el sonar. Para evitar este error habría que añadirlo a la whitelist como script permitido en Jenkins. Bastaría con pulsar el enlace sugerido que nos muestra el log de la consola o acceder a “jenkins > Manage jenkins > In-process Script Approval” y añadirlo de forma manual. Éste paso requerirá permisos de administrador.

Como resultado al construir la tarea se obtiene un log con el resultado de cada etapa, el .jar con la aplicación compilada y los informes, tanto de los tests unitarios como el de los Cucumber:

proyecto jenkins - qa devops -06

proyecto jenkins - qa devops -07

Por otro lado, al concluir la ejecución podremos ver los resultado del análisis estático de código accediendo a la URL del SonarQube.

proyecto jenkins - qa devops -08

Conclusiones

Ventajas

La principal ventaja que esto proporciona es que minimiza la cantidad y complejidad de la infraestructura necesaria para realizar el ciclo de integración. Por otro lado, hace que los administradores de ésta puedan escalar las aplicaciones sin necesidad de conocer internamente su funcionamiento.

Otra motivación detrás del uso de docker para la realización de compilaciones, pruebas, etc. es la de crear la misma experiencia de implementación que tendríamos en el caso de levantar los servicios y el resto de imágenes de forma física dentro de la plataforma.

El uso de docker, en particular docker-compose, también proporciona sobre un entorno de integración continua la posibilidad de agrupar la configuración en un solo archivo como código. Además de que dispone de muchas imágenes con distintas configuraciones listas para usar que ahorran bastante tiempo.

Inconvenientes

En situaciones donde sobre un mismos jenkins se esté ejecutando un número elevado de builds simultáneamente, podremos alcanzar el punto de saturación de la máquina si no está correctamente dimensionada. Hay que tener en cuenta, que la ejecución de contenedores docker conlleva un consumo de recursos.

Para casos de este estilo, sería conveniente optar modelos de nodos. Es decir, un nodo jenkins principal que derive la ejecución de los pasos de pipelines a nodos a asociados (hijos). De esta manera, se conseguirá repartir la carga de trabajo y no saturar ninguna de las máquinas intervinientes en el proceso.

Qué nos ha parecido

Como se ha podido comprobar es muy sencillo configurar para levantar y probar una aplicación usando este modelo. Basta con tener Jenkins y crear un Jenkinsfile usando Docker dentro de este proceso consiguiendo hacer todo de forma automatizada.