El propósito del artículo es proponer una solución sencilla para ejecutar en Jenkins pipelines de construcción sobre contenedores instanciados al vuelo mediante Docker.
Se utiliza Docker y los plugins de Jenkins Build Flow, Swarm, NodeLabel y Publish Over SSH.
Imagen Docker
Configuramos una toolchain que nos permita construir en el esclavo. Montaremos cliente Git, Java, Groovy y Maven, y un cliente Swarm.
FROM debian:jessie
RUN apt-get update
RUN apt-get -y install wget
RUN apt-get -y install openjdk-7-jdk
RUN apt-get -y install groovy2
RUN apt-get -y install git
RUN apt-get -y install maven2
RUN mkdir /jenkins
RUN mkdir -p /opt/sw/swarm
RUN cd /opt/sw/swarm
RUN wget -O /opt/sw/swarm/swarm-client-2.0-jar-with-dependencies.jar http://repo.jenkins-ci.org/releases/org/jenkins-ci/plugins/swarm-client/2.0/swarm-client-2.0-jar-with-dependencies.jar
ENV JAVA_HOME /usr/lib/jvm/java-7-openjdk-amd64
ENV GROOVY_HOME /usr/share/groovy2
ENV GIT_PATH /usr/bin/git
CMD java -jar /opt/sw/swarm/swarm-client-2.0-jar-with-dependencies.jar -name ${NODE_NAME} -master http://master_jenkins:9080 -labels esclavo_swarm -t Groovy-1.8.7=$GROOVY_HOME -t default-git=$GIT_PATH
El esclavo se conecta a Jenkins como NODE_NAME mediante Swarm.
Configuración de Jenkins
En nuestro ejemplo, Jenkins se comunica por SSH con la máquina que usaremos para levantar contenedores Docker:
Pasos de construcción
Se debe admitir un parámetro 'node':
Gestión de contenedores desde Jenkins
El Build Flow utiliza dos jobs que arrancan o detienen un contenedor, identificado por un alias handle. Entre ambas llamadas, invoca los pasos sobre el nodo recién levantado (también identificado por handle).
runDockerContainer -> docker run -e NODE_NAME='${handle}' --name=${handle} swarm-slave &
stopDockerContainer -> docker stop ${handle} && docker rm -v=true ${handle}
Build Flow
// Nodo levantado en el contenedor Docker
def getNode(String handle) {
// Detección de timeout de esclavo: se puede sacar a parámetros globales
// en la configuración de Jenkins
long msecWait = 500;
int triesLeft = 10;
def lookForNode = { String pattern ->
def ret = null;
for (slave in hudson.model.Hudson.instance.slaves) {
if (slave.getNodeName().startsWith(pattern)){
ret = slave;
}
}
return ret;
}
def ret = null;
// lo intenta $triesLeft veces, separadas cada una por periodos de
// de $msecWait milisegundos. Pasado este tiempo, asume que el
// esclavo swarm no ha conseguido levantarse.
while (ret == null && triesLeft > 0) {
ret = lookForNode(handle);
if (ret == null) {
triesLeft--;
Thread.sleep(msecWait);
}
}
return ret;
}
long timestamp = new Date().getTime();
String handle = "swarm_slave_$timestamp";
String parentWorkspace = "/jenkins/BuildWorkflow$timestamp";
ignore(FAILURE) {
build("runDockerContainer", handle: "$handle" )
def swarmNode = getNode(handle);
if (swarmNode == null) throw new Exception("El esclavo no ha arrancado");
println "Nodo levantado en contenedor Docker: $swarmNode";
// Lanzar los jobs en la lista contra el nodo indicado
def jobs = params["jobsList"].split("\n");
jobs.each { String job ->
println "********** EJECUTANDO $job";
// Este ejemplo no gestiona errores ni parámetros
Map jobParams = [:]
jobParams.put("parentWorkspace", parentWorkspace);
jobParams.put("node", swarmNode.getNodeName());
build(jobParams, job);
}
}
build("stopDockerContainer", handle: "$handle" )
Para un workflow que baja un proyecto de Git y construye, la ejecución queda así:
(Se marca en azul aquellos jobs ejecutados sobre un esclavo)
Mejoras posibles...
Se puede reducir la imagen Docker siguiendo consejos como el de Henn Idan en su blog, teniendo en cuenta las advertencias que hace al respecto.
Se podría crear un pool de máquinas, esclavas de Jenkins, sobre las que lanzar contenedores, cambiando el lanzamiento de forma que mantenga ocupado el ejecutor. Así, con el plugin Least Load, el propio Jenkins nos ayudaría a escalar en horizontal.
El script groovy del Build Flow admite varias mejoras: parametrización, lanzamientos en paralelo, control de errores, etc.
Si te ha gustado y quieres estar al día de nuevos posts ¡Sigue a “En mi local funciona” en Twitter!