yeiei.net

Integración Continua, QA, Docker,...

Como construir un entorno de Integración Continua con Jenkins y Docker

En esta entrada te explicaré cómo aprovechar la tecnología Docker para construir un entorno de Integración Continua que monitorice tu repositorio de código fuente, construya tu producto, pase los tests, audite el código de forma automática con SonarQube y deje los binarios listos para descargar.

En un escenario clásico tendríamos unas cuantas máquinas virtuales encargadas de hacer todas esas tareas, pero con Docker podemos ir a un escenario donde repartir las tareas en diferentes contenedores en lugar de máquinas virtuales, resultando un entorno mucho más ligero, más fácil de escalar y más sencillo de portar.

Antes de continuar y para que quede claro, en esta entrada NO explicaré cómo “dockerizar” aplicaciones, esto lo dejaremos para otro día 🙂

Nuestro entorno de Integración Continua estará compuesto por:

  • Un contenedor “master” de Jenkins, que orquestará las tareas
  • Uno o varios contenedores “agentes” (también conocidos como “esclavos”), que ejecutarán las tareas
  • Un analizador automático de código en un contenedor SonarQube

Para este tutorial utilizaré Docker Toolbox para Windows con la consola Docker QuickStart Terminal, aunque los comandos son los mismos si usas Docker para otras plataformas.

Un master para dominarlos a todos

Si ya has utilizado alguna vez Jenkins, sabrás que normalmente hay un nodo máster encargado de guardar la configuración y lanzar las tareas o “jobs“, y varios nodos “agentes” (también conocidos como “esclavos“) que son los que ejecutan las tareas.

Pues vamos a ello…. en la consola donde tengas Docker instalado  ejecuta:

docker pull jenkinsci/jenkins:lts

Esto descargará la imagen con la última versión de Jenkins LTS (Long Time Support)

Arrancamos el contenedor:

docker run --name jenkins_master -p 8080:8080 -p 50000:50000 -v jenkins_home:/var/jenkins_home jenkinsci/jenkins:lts

Un poco de explicación de este comando:

  • Ponle un nombre al contenedor: jenkins_master 
  • Publica los puertos 8080 y 50000 para que sean accesibles
  • Monta un volumen en la carpeta var/jenkins_home del HOST (externa al contenedor), donde se guardarán la configuración y los resultados de las tareas. De esta forma si el contenedor se destruye no se perderá la información.

Si ahora accedes con un navegador a http://IP_DEL_HOST:8080, te saldrá este bonito recibimiento:

La contraseña inicial será visible en la consola o en el archivo  /var/jenkins_home/secrets/initialAdminPassword

Introduce la clave y continúa con el proceso de configuración:

Por ahora deja la opción por defecto de “Install suggested plugins“.

Ok, ya casi estás, sólo falta crear un usuario administrador:

Ahora deberías ver la pantalla de bienvenida:

Ahh, el viejo “Hello World”

Para hacer una prueba rápida del funcionamiento de Jenkins, ve a “New item” para crear un nuevo job:

En la sección “Build“, haz click en “Add build step” y selecciona “Execute Shell

Escribe:

echo Hello World

Y ahora “Save” y “Build Now“. Tras unos segundos aparecerá el número de build:

Haz click en el número y después en “Console output

¡Enhorabuena! Ya has ejecutado “algo” con Jenkins. Más adelante ejecutaremos “algo útil” con un ejemplo del mundo real. Pero antes…

Agentes, esclavos y nodos

En el apartado anterior arrancaste un master de Jenkins que, además, es capaz de ejecutar tareas. Aunque esto está bien, no es un escenario real. En los sistemas de Integración Continua lo habitual es tener un master (o varios en sistemas muy grandes) que coordina tareas y varios agentes que se encargan de ejecutarlas.

Es habitual encontrar la palabra esclavos (slaves) o nodos (nodes) para referirse a los agentes. En Jenkins estos conceptos son sinónimos, yo me referiré a ellos como agentes, puedes consultar las últimas versiones de la documentación para más información.

Conectar un agente se hace en dos partes: se da de alta en el sistema y después se conecta mediante algún protocolo admitido como SSH o JNLP u otros.

Basta de charla y al lío. Comenzamos, haz click en “Manage Jenkins” y “New Node” :

Ponle un nombre, por ejemplo “agent1” y OK

En el siguiente paso asegúrate que:

  • Remote root directory: /home/jenkins
  • Launch method: Launch agent via Java Web Start. Hay otros métodos para conectarse como SSH, pero para este ejemplo utilizaremos JNLP

Y “Save”

Con estos pasos le estás diciendo a Jenkins que vas a conectar un nuevo agente. Necesitas un dato importante, un token secreto para conectarte que está indicado después del parámetro -secret:

Muy bien. Seguimos, el repositorio DockerHub de la gente de Jenkins, nos ofrece una imagen ya configurada para hacer de agente y que ya lleva OpenJDK instalado para poder comenzar a trabajar con proyectos Java.

Este comando descargará la imagen:

docker pull jenkinsci/jnlp-slave

Arranca el contenedor:

docker run --name jenkins_agent1 jenkinsci/jnlp-slave -url http://HOST_IP:8080 TUSECRETOabcdef0123456789 agent1
  • Nombra al contenedor como: jenkins_agent1
  • En HOST_IP escribe la dirección de tu servidor Jenkins, igual que la escribes en el navegador
  • El siguiente parámetro es el token secreto que te proporcionó Jenkins en el paso anterior
  • El último parámetro es el nombre del agente con el que lo diste de alta. En tu caso agent1

Si todo va bien estarás viendo algo así:

En el panel de control de Jenkins verás como el agente se ha conectado y el número de “ejecutores” (executors) que tiene. Por ahora estará “Idle”, es decir sin ejecutar nada.

La build se pone seria

Hasta ahora no hemos construido nada útil, pero ya tenemos un master y un agente listo para empezar a trabajar, así que vamos a construir un proyecto real. Para este tutorial he elegido el proyecto FitNesse, que es  una herramienta de colaboración tipo wiki y un framework de test de aceptación open source escrito en Java y se construye con gradle.

El código fuente original de está en https://github.com/unclebob/fitnesse. Para hacer este ejemplo te recomiendo que hagas un fork de https://github.com/jaruzafa/fitnesse_CI_DEMO,  que es un clon del original (con algunos cambios que veremos más adelante) y que mantengo congelado para este tutorial, así si el proyecto original cambia sustancialmente en el futuro, los pasos de este tutorial te seguirán funcionando.

Ok, vamos a darle caña.

En el panel de Jenkins ve a “New Job” y elige “Freestyle Project“, ponle un nombre original como “FitNesse”. Te llevará a la pantalla de configuración.

En el apartado “Source Code Management” le indicas el repositorio donde tienes el código fuente de tu proyecto.

  • Repositories: En mi caso le he puesto https://github.com/jaruzafa/fitnesse_CI_DEMO pero si haces un clon, pon la dirección de tu repositorio
  • Branches to build: */demo
  • Aunque no es estrictamente necesario para este ejemplo , en mis proyectos suelo añadirle un “Additional behaviour” -> “Clean before checkout”. De esta forma me aseguro de que no quedan “restos” de compilaciones o tests previos y siempre construyo el producto tal y como viene de su repositorio, aunque tarde un poco más.

Psss: Si eres veterano en Jenkins te preguntarás por qué no utilizo directamente el plugin de GitHub para este proyecto. En este ejemplo quiero ilustrar cómo conectar con cualquier repositorio Git ya sea GitHub u otro.

Vamos al apartado “Build Triggers“. Aquí se determina qué evento “dispara” la build.

  • Selecciona “Poll SCM” y escribe H/5 * * * *
    • Esto le indica a Jenkins que “pregunte”  tu repositorio cada 5 minutos si hay cambios. Si los hay, dispara el job.

En el apartado “Build” es donde todo ocurre.

  • Haz click en “Add build step” y selecciona Invoke Gradle Script
  • Selecciona “Use Gradle Wrapper” para este proyecto.
  • En el apartado “Tasks” escribe test y standaloneJar. Esto ejecutará los tests unitarios y generará los jar del proyecto.

Y por último, para poder ver  los resultados de los tests y que Jenkins guarde los jar que se han generado en la build, añade dos “Post-build actions“:

  • Archive the artifacts
    • Files to archivebuild/libs/*.jar
    • Esto guardará los jar en Jenkins. En sistemas más grandes los binarios se suelen almacenar en repositorios dedicados para ello (como Nexus, Artifactory, etc.), pero para este ejemplo está bien.
  • Publish JUnit test result report
    • Test report XMLs**/test-results/**/*.xml
    • Aquí le estás diciendo a Jenkins dónde encontrar los resultados de los tests unitarios

Y sólo queda… lanzar el job!

Puedes lanzar el job de dos formas:

  • Manual: Haz click en el botón “Build Now
  • Automática: Cómo recordarás, le hemos dicho a Jenkins que mire cada 5 minutos en el repositorio Git por si hay cambios. Haz un commit en el repositorio y espera a que se dispare solo. Recuerda que el commit deberás hacerlo en la misma rama que indicaste en el apartado “Source Code Configuration” (en mi caso es la rama “demo”). Este es una forma común de arrancar un job en un entorno de Integración Continua, pero hay otras.

En cuanto arranque el job verás esto:

 

Espera unos minutos y en cuanto acabe tendrás algo así:

En la columna de la izquierda tendrás el histórico de builds. En el centro enlaces a los jar generados (¡que puedes descargar!) y a los resultados de los tests unitarios. Cuando ejecutes varias veces el job, a la derecha te saldrá una gráfica con la tendencia de los resultados de los tests. Está fuera del alcance de este post explicar cada una de las opciones, así que te animo a que explores y le eches un vistazo a la documentación de Jenkins.

Inspeccionando el código

En sistemas de Integración Continua reales, además de compilar y ejecutar tests, es habitual realizar auditorias automáticas de código, así como mediciones de cobertura de código. En este ejemplo te mostraré como conectar SonarQube (una herramienta open source muy popular) al sistema de Integración Continua. Por supuesto seguiremos utilizando Docker 🙂

La gente de SonarQube amablemente ha preparado una serie de imágenes listas para usar, así que vamos a ello. Descarga la imagen con:

docker pull sonarqube:lts

Y arranca con:

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

Un poquito de explicación de este último comando:

  • Nombra el contenedor como sonarqube
  • -d: Ejecuta el contenedor en modo “daemon”
  • -p 9000:9000 -p 9092:9092: Publica los puertos 9000 y 9002
  • -v sonarqube_home:/opt/sonarqube/data: Monta un volumen donde guardará los datos fuera del contenedor. Esto es necesario ya que si el contenedor se destruyera perderías toda la información

Vamos a asegurarnos de que SonarQube está levantado y le vas a instalar el plugin de Java. Con tu navegador ve a http://HOST_IP:9000 (ojo al puerto!) ,  deberías ver:

En el enlace de arriba a la derecha haz click en “Log In“, el usuario y contraseña por defecto son admin / admin.  Ahora ve a “Administration” -> “System” -> “Update Center“-> “Available” y busca “Java” . Instalas el plugin y se reiniciará el servicio,

 

Bien. Ya tenemos el contenedor con SonarQube levantado y configurado con el plugin de Java. Ahora hay que decirle a Jenkins dónde está. Por defecto Jenkins no viene con el plugin de SonarQube instalado, así que vamos a instalarlo.

Ve al panel de control de Jenkins en http://HOST_IP:8080, pincha “Manage Jenkins” y haz click en “Manage Plugins

Ahora ve a la pestaña de “Available” y busca “SonarQube Scanner for Jenkins” y “Install

Con el plugin de SonarQube ya instalado, toca decirle donde está tu servidor Sonar y que Scanner vas a usar. Así que ve a “Manage Jenkins” y “Configure System“. Busca el apartado SonarQube Servers y haz click en “Add SonarQube“. Basta con rellenar los campos Name (dale un nombre descriptivo) y Server URL que será http://HOST_IP:9000. Te adjunto una captura de mi configuración:

 

Sólo falta decirle qué Scanner vas a usar. Esto se hace vía “Manage Jenkins” y “Global tool configuration“. busca el apartado “SonarQube Scanner” y haz click en “SonarQube Scanner installations” y “Add SonarQube Scanner” . Escribe un nombre descriptivo,  y para que Jenkins se encargue de instalar el software necesario marca “Install automatically” . Esta es una de las cosas que más me gustan de Jenkins, la posibilidad de dejar que se encargue de instalar las herramientas cuando las necesita, ya que descarga mucho trabajo del mantenimiento de los nodos agente.

Bien. Con esto Jenkins ya sabe donde tenemos SonarQube y que Scanner vamos a usar, pero nuestro job todavía no.

Vamos a continuar con el ejemplo del apartado anterior de  FitNesse. Para poder hacer el análisis del código y obtener la cobertura de los test unitarios tienes que hacer dos cosas: preparar el archivo project-sonar.properties y activar el plugin JaCoCo en build.gradle para tener la información de cobertura de código.

En el archivo build.gradle añade esta línea después del bloque plugins:

apply plugin: "jacoco"

Esto prepara el proyecto para guardar la información de cobertura al ejecutar los tests.

Además SonarQube necesita saber algunas cosas de tu proyecto. Crea un archivo llamado  project-sonar.properties con este contenido:

sonar.projectKey=my:Fitnesse
sonar.projectName=Fitnesse
sonar.projectVersion=1.0
sonar.sources=src
sonar.exclusions=**/*.min.js,src/**/bootstrap.js,src/**/codemirror.js,src/**/jquery.tagsinput.js
sonar.tests=test
sonar.java.source=1.7
sonar.java.binaries=build/classes
sonar.java.libraries=lib
sonar.jacoco.reportPath=build/jacoco/test.exec

El significado de cada campo lo puedes encontrar en la documentación de SonarQube, pero básicamente le estamos indicando donde encontrar los fuentes, algunas exclusiones, dónde se generan los binarios y dónde está la información de cobertura.

Sólo falta decirle al job que vamos a hacer un análisis con SonarQube. Ve al job que has creado en el apartado anterior y haz click en “Configure“. Ve a la sección “Build” y “Add Build Step” y añade “Execute SonarQube Scanner“.

 

Guarda la configuración y dale a “Build Now” para probar que todo está bien. En unos minutos deberías ver:

En los resultados del job de FitNesse verás un nuevo enlace a los resultados del análisis de código y cobertura de SonarQube. Siguiendo el enlace te llevará a la página con los resultados del proyecto:

¡Ya lo tienes! Acabas de construir un entorno de Integración Continua con un proyecto real. Si no conoces SonarQube te animo a que te leas la documentación y explores las posibilidades que te ofrece.

Apagando y encendiendo

Docker proporciona comandos para controlar el ciclo de vida de los contenedores. Si no has trabajado antes con Docker estos son los que necesitas para este tutorial:

Lista los contenedores que están actualmente en ejecución:

docker ps

Lista todos los contenedores, incluso los que están parados:

docker ps -a

Detiene el contenedor especificado. Puedes pasarle el id del contenedor o por nombre. Si le pasas el id, puedes indicarle sólo los 3 o 4 primeros carácteres en lugar de todo el “churro”.

docker stop identificador o nombre

Por ejemplo:

docker stop sonarqube

docker stop ab12

Arranca el contenedor especificado, anteriormente lo habrás detenido con un docker stop.

docker start  identificador o nombre

Todo esto también lo podrías hacer mediante Docker Compose pero eso lo dejaremos para otra ocasión.

¡Espero que hayas disfrutado esta guía! Me encantaría leer tu comentarios.

2 Comentarios

  1. Hola, una vez que creo el entorno de integracion y lo cierro, ¿Cómo hago para volverlo a iniciar? algo asi como un start

Deja un comentario

© 2017 yeiei.net

Tema por Anders NorenArriba ↑

A %d blogueros les gusta esto: