Primeros ejemplos

Creación del primer proyecto freestyle

Creamos el primer proyecto de Jenkins. Comprueba que Jenkins puede llamar a Docker. Para ello crea un nuevo proyecto tipo freestyle.

jenkins new hello docker
Fig. 1. Nuevo proyecto, freestyle

En la sección Build Steps, añade un nuevo paso, Ejecutar linea de comandos (shell) . Pega estos comandos:

whoami
git --version
java -version
docker -v

Guarda los cambios. Haz clic sobre Build now. Haz clic sobre la bolita verde para ver el la salida por consola.

jenkins new hello docker console output 2024
Fig. 2. Build now. Resultado del build
jenkins new hello docker console success 2024
Fig. 3. Salida por consola

Por consola se visualiza el resultado de ejecutar los comandos dentro del contenedor. Como puedes ver, git y java están instalados, venían ya en la imagen jenkins:lts de la que hemos partido en la definición del Dockerfile. Además, docker también está disponible, se ha instalado correctamente mediante la definición incluida en el Dockerfile. La ejecución finaliza correctamente, con el mensaje Finished: SUCCESS, por ello la bolita verde.

Creación del primer pipeline

Nodos de ejecución

De forma predeterminada, Jenkins ejecuta los proyectos sobre el nodo principal (main), que es el nodo (contenedor o máquina virtual) donde se ejecuta el propio Jenkins.

Puedes consultar los nodos disponibles en Jenkins en la sección Administrar Jenkins > Nodos.

jenkins admin nodos 2024
Fig. 4. Nodos disponibles en Jenkins

Hasta ahora no hemos configurado otros nodos ni clouds donde ejecutar los proyectos, así que por defecto se ejecutan en el principal.

Pipeline sobre el nodo principal

Creamos el primer proyecto de Jenkins tipo pipeline. Vamos a darle el nombre hello-maven-pipeline-on-master-node. Este pipeline se ejecutará sobre el nodo (agent) master, es decir, sobre el mismo contenedor que está ejecutando Jenkins.

Copia la siguiente definición de pipeline en el bloque Pipeline, Pipeline script.

pipeline {
    agent any
    tools {
        maven 'Default Maven' (1)
    }

    stages {
        stage('Build') {
            steps {
                sh '''
                    java -version
                    mvn -v
                  ''' (2)
            }
        }
    }
}
1 Utiliza la instalación de Maven que hemos hecho en la sección anterior, y le dimos el nombre Default Maven.
2 En la shell ejecutará los comandos para mostrar las versiones de Java y Maven.

Haz clic sobre Build now, y visualiza la consola. Como parte de la salida, se debe visualizar algo así (puede que los números de versiones varíen):

[Pipeline] sh
+ java -version
openjdk version "17.0.10" 2024-01-16
OpenJDK Runtime Environment Temurin-17.0.10+7 (build 17.0.10+7)
OpenJDK 64-Bit Server VM Temurin-17.0.10+7 (build 17.0.10+7, mixed mode)
+ mvn -v
Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)
Maven home: /var/jenkins_home/tools/hudson.tasks.Maven_MavenInstallation/Default_Maven
Java version: 17.0.10, vendor: Eclipse Adoptium, runtime: /opt/java/openjdk
Default locale: en, platform encoding: UTF-8
OS name: "linux", version: "6.5.0-1014-gcp", arch: "amd64", family: "unix"

Lanzar las construcciones de proyectos en el nodo principal puede acarrear problemas de seguridad. De hecho, Jenkins (dependiendo de la versión) avisa de que esta configuración es inadecuada, y recomienda configurar agentes independientes donde lanzar las ejecuciones de los proyectos.

issue build on master
Fig. 5. Aviso de evitar ejecuciones en el nodo máster de Jenkins

Pipeline con un contenedor como agente

La alternativa es que el pipeline se ejecute, en lugar de en el nodo principal, en un nodo tipo "agente" que se creará ex profeso a partir de un contenedor Docker disponible de DockerHub.

A continuación creamos el segundo proyecto de Jenkins tipo pipeline. Vamos a darle el nombre hello-maven-pipeline-on-container-node

pipeline {
    agent {
        docker {
            image 'maven:3.9-eclipse-temurin-21-jammy' (1)
            args '-v $HOME/.m2:/root/.m2' (2)
        }
    }
    stages {
        stage('Build') {
            steps {
                sh '''
                    java -version
                    mvn -v
                  '''
            }
        }
    }
}
1 Elige entre las imágenes de Maven disponibles. En este caso, se ha elegido la versión 3.9 con Java 21, que es distinta a la que hemos instalado en el nodo principal.
2 Mapea el volumen del host $HOME/.m2 donde se almacenan las dependencias de Maven, para que estén disponibles en el contenedor, eliminando así la necesidad de volver a descargarlas en sucesivas ejecuciones.

Te dará error al construir el proyecto.

[Pipeline] sh
+ docker inspect -f . maven:3.9-eclipse-temurin-21-jammy

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/containers/maven:3.9-eclipse-temurin-21-jammy/json": dial unix /var/run/docker.sock: connect: permission denied

El motivo es que en la máquina Jenkins, sobre el S.O. host, hay que abrir permisos en el socket de Docker para que desde dentro del contenedor Jenkins permita crear otros contenedores hermanos. Para ello, mediante el terminal ssh modifica los permisos así:

sudo chmod 666 /var/run/docker.sock

Tras ello deben construirse correctamente. La nueva salida será algo así:

[Pipeline] sh
+ java -version
openjdk version "21.0.2" 2024-01-16 LTS
OpenJDK Runtime Environment Temurin-21.0.2+13 (build 21.0.2+13-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.2+13 (build 21.0.2+13-LTS, mixed mode, sharing)
+ mvn -v
Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae)
Maven home: /usr/share/maven
Java version: 21.0.2, vendor: Eclipse Adoptium, runtime: /opt/java/openjdk
Default locale: en_US, platform encoding: UTF-8
OS name: "linux", version: "6.5.0-1014-gcp", arch: "amd64", family: "unix"

Para que tras reiniciar la máquina se mantengan los permisos del socket de Docker:

Crea el archivo /etc/rc.local, y añade el siguiente contenido:

#!/bin/sh -e
chmod 666 /var/run/docker.sock

Por último, dale los permisos adecuados al archivo /etc/rc.local:

sudo chmod 755 /etc/rc.local

Tras ello reinicia la máquina.

sudo reboot -h now

Tras ello, comprueba que el socket de Docker tiene los permisos adecuados:

$ ls -la /var/run/docker.sock
srw-rw-rw- 1 root docker 0 Mar  2 19:24 /var/run/docker.sock

No olvides que abrir permisos aL archivo /var/run/docker.sock supone ciertos problemas de seguridad: Avoid workarounds like this which could be a big potential security threat. The result of your chmod practically gives all local users read and write permissions to the docker-socket which allows anyone to interfere with your docker images. (fuente).

Otros ejemplos similares con contenedores NodeJS están disponibles en la documentación de Jenkins

Usando varios contenedores como agente

Es habitual tener varias tecnologías en un mismo proyecto. Por ejemplo, un repositorio puede tener tanto un back-end basado en Java como un front-end basado en JavaScript. Combinar Docker y Pipeline permite usar diferentes agentes en diferentes fases (stages) del pipeline. Crea un nuevo pipeline `hello-pipeline-multiple-containers`con el siguiente contenido:

pipeline {
    agent none
    stages {
        stage('Back-end') {
            agent {
                docker {
                    image 'maven:3.9-eclipse-temurin-21-jammy'
                    args '-v $HOME/.m2:/root/.m2'
                }
            }
            steps {
                sh 'mvn --version'
            }
        }
        stage('Front-end') {
            agent {
                docker { image 'node:20.11.1-alpine3.19' }
            }
            steps {
                sh 'node --version'
            }
        }
    }
}
jenkins pipeline multiple containers 2024
Fig. 6. Pipeline con varios contenedores como agentes

Conexión con la máquina de despliegue

Para automatizar el despliegue sobre la instancia que tenemos creada para ello, deberás permitir que Jenkins ejecute comandos sobre la máquina de despliegue a través de SSH. Para ello, la instancia Jenkins debe poder conectarse a la instancia de despliegue mediante una conexión SSH basada en autenticación por pareja de claves pública/privada, que ha demostrado ser más seguro sobre la autenticación estándar de nombre de usuario/contraseña.

deploy schema full
Fig. 7. Esquema de despliegue con Jenkins

Para ello, los pasos que se detallan a continuación permiten:

  • generar una nueva pareja de claves que usaremos para el despliegue,

  • copiar la clave pública generada en la instancia de despliegue,

  • y por último probar que la conexión se realiza correctamente.

Ejecuta los siguientes pasos:

Generar la nueva pareja de claves de despliegue

  1. Conecta por SSH a la máquina Jenkins: ssh ubuntu@instancia-jenkins

ssh from developer to jenkins
Fig. 8. Conexión SSH a la instancia Jenkins
  1. Crea la carpeta donde se va a guardar la nueva pareja de claves: mkdir /home/ubuntu/jenkins_home/.ssh

  2. Crea una pareja de claves ssh de despliegue: ssh-keygen -t rsa -b 4096

  3. Cuando pida el nombre, escribe el nuevo nombre id_rsa_deploy junto con la ubicación donde Jenkins va a buscar las claves de forma predeterminada, que es: /home/ubuntu/jenkins_home/.ssh/id_rsa_deploy

  4. Por último, deja la contraseña en blanco (pulsa ENTER): Enter passphrase (empty for no passphrase):

Esto crea la clave privada en /home/ubuntu/jenkins_home/.ssh/id_dsa_deploy y una clave pública asociada en /home/ubuntu/jenkins_home/.ssh/id_dsa_deploy.pub. Esta nueva pareja de claves la usaremos exclusivamente para el despliegue de nuestros proyectos.

Al haberla guardado en la carpeta /home/ubuntu/jenkins_home/ los archivos están accesibles dentro del contenedor de Jenkins, porque como recordarás, al lanzar el contenedor Jenkins esa carpeta del host la habíamos mapeado con la carpeta /var/jenkins_home del contenedor.

jenkins ls deploy keys
Fig. 9. Pareja de claves id_rsa_deploy

Copiar la clave pública a la instancia de despliegue

  1. Muestra el contenido de la clave pública:

cat /home/ubuntu/jenkins_home/.ssh/id_rsa_deploy.pub
  1. Copia el contenido: con el ratón, selecciona el contenido de la clave, desde “ssh-rsa” hasta el final, y pulsa ENTER (o CTRC+C)

jenkins cat public key
Fig. 10. Copia el contenido de id_rsa_deploy.pub

Debido a que algunos terminales añaden saltos delinea al copiar texto desde el terminal, como ocurre con cloud shell de GCP, es recomendable copiar el contenido de la clave pública en cualquier editor de texto "plano" (Notepad++, Sublime, VS Code, etc) y eliminar los saltos de línea, si los hubiera.

  1. Ahora pégalo en tu PC, lo necesitaremos más adelante.

  2. Desconecta de la máquina Jenkins: exit

  3. Conecta por ssh a la instancia de despliegue

ssh from developer to deploy
Fig. 11. Conexión SSH a la instancia Jenkins
  1. Edita el archivo authorized_keys:

nano /home/ubuntu/.ssh/authorized_keys
  1. Ese archivo ya tenía una clave pública, la correspondiente a tu pareja de claves personal que inyectamos en la creación de la instancia con Terraform (por eso has podido conectar por ssh a esa máquina). Pega el contenido de la clave pública de despliegue. Ahora debe tener 2 claves públicas.

  2. Ya puedes desconectar de la instancia de despliegue.

Prueba de la conexión desde jenkins a despliegue

Vamos a probar que funciona:

jenkins ssh to deploy
Fig. 12. Conexión SSH desde la instancia Jenkins a la de despliegue
  1. Conecta de nuevo a la instancia jenkins y prueba la conexión ssh a la instancia de despliegue. Recuerda que puesto que Jenkins se está ejecutando como un contenedor, debes probar la conexión ssh desde dentro del contenedor:

docker exec -it jenkins-docker ssh ubuntu@instancia_deploy -i /var/jenkins_home/.ssh/id_rsa_deploy

En el comando anterior:

  • docker exec -it indica ejecutar un comando desde dentro del contenedor

  • jenkins-docker es el nombre del contenedor

  • ssh ubuntu@instancia_deploy -i /var/jenkins_home/.ssh/id_rsa_deploy es el comando a ejecutar en el contenedor. En este caso, ssh con el parámetro -i …​ para indica la clave privada que debe usar para conectar.

Antes de ejecutar el comando, modifica instancia_deploy por el nombre DNS de tu instancia de despliegue.

  • Recuerda que /var/jenkins_home es la carpeta HOME del usuario jenkins dentro del contenedor, y jenkins es el usuario del contenedor que ejecuta Jenkins.

  1. La primera vez que realizas una conexión ssh desde un usuario en una máquina origen a una destino, te pregunta si deseas almacenar la clave de host de destino en la lista de hosts conocidos (known_hosts) de tu máquina origen. Contesta: yes

ssh host autentication
Fig. 13. Validar la clave del host: yes
  1. Si todo ha ido bien, la conexión se ha debido realizar. Puedes comprobarlo porque en el prompt te aparecerá que estás en la máquina de despliegue. Sal con exit. Ahora el prompt te muestra que estás en la máquina Jenkins.

ssh host connection deploy exit
Fig. 14. Conexión correcta

Si no ha correcto, verifica que la ruta al archivo de la clave privada es correcta, y que el nombre de la máquina de despliegue es correcto.

  1. Comprueba que la clave de host de la máquina de destino (despliegue) se ha guardado en la máquina origen (jenkins) en el archivo ~/.ssh/known_hosts del usuario que ha ejecutado el comando ssh, en nuestro caso, del usuario jenkins de contenedor:

docker exec -it jenkins-docker cat /var/jenkins_home/.ssh/known_hosts
ssh known hosts
Fig. 15. Contenido del archivo known_hosts en el contenedor
  1. Puedes comprobarlo también mostrando el contenido de known_hosts en el archivo /home/ubuntu/jenkins_home/.ssh/known_hosts. Ambos coinciden, recuerda que hay un volumen mapeado entre la carpeta local /home/ubuntu/jenkins_home y la carpeta del contenedor /var/jenkins_home.

ssh known hosts local
Fig. 16. Contenido del archivo known_hosts en la carpeta local
  1. Ahora que la conexión por SSH entre la máquina Jenkins y la máquina de despliegue es correcta, vamos a hacer que Jenkins automatice la ejecución de comandos sobre la máquina de despliegue: entra en Jenkins y añade el siguiente comando al proyecto hello_docker existente, sustituyendo MAQUINA_DEPLOY por el nombre DNS de la máquina de despliegue.

ssh -i ~/.ssh/id_rsa_deploy ubuntu@MAQUINA_DEPLOY "pwd && ls -la"

Como aclaración de este comando:

  • el parámetro -i indica la clave privada que queremos usar en la conexión ssh

  • "pwd && ls -la" son comandos básicos que ejecuta sobre la máquina remota. Hemos indicado estos comandos simplemente para probar que la conexión se realiza correctamente.

jenkins hello docker ssh to deploy
Fig. 17. Modificación del proyecto para que ejecute un comando sobre la instancia de despliegue

Tras ejecutar el proyecto en Jenkins, el resultado debe ser correcto.

jenkins hello docker ssh to deploy output 2024
Fig. 18. Salida por consola. El comando se ha ejecutado correctamente.