Implementación de una Aplicación SpringBoot Utilizando Docker y Jenkins (Docker-outside-of-Docker - DooD)
Tags: docker, jenkins, tutorial
Published: 2024-06-11 05:52:47

Inicialmente tenemos que instalar docker en nuestra maquina host, en este caso estoy en windows, [Docker Desktop](https://docs.docker.com/desktop/install/windows-install/).

> Importante entrar a docker desktop y habilitar en settings

![epose daemon](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r27hmlqjfqgdgcon4suh.png)



Para fines prácticos se ha realizado pasos que se podrían mejorar usando nodos en Jenkins.

Usaremos el concepto de construcción de contenedores a partir de __DockerFiles__ y usando __Docker compose__. 

Lo que haremos es un flujo para que cuando realicemos un push a nuestro repositorio remoto Jenkins pueda iniciar un pipeline que permita la creación de un contenedor de una aplicación. 

## Creación de contenedor Jenkins y Base de datos
### DockerFile.jenkinslocal
Este primer _DockerFile_ es para el contenedor Jenkins, llamado _DockerFile.jenkinslocal_.

```DockerFile
# Utiliza la imagen oficial de Jenkins
FROM jenkins/jenkins:lts

# Cambia al usuario root para instalar paquetes
USER root

# Instala wget, dpkg y dependencias necesarias
RUN apt-get update && apt-get install -y wget dpkg apt-transport-https ca-certificates curl gnupg2 software-properties-common

# Instala Java
RUN apt-get install -y java-common

  
# Descarga e instala Amazon Corretto 17
ADD https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jdk.deb /tmp/

RUN dpkg -i /tmp/amazon-corretto-17-x64-linux-jdk.deb

# Limpia el cache de apt y elimina el paquete de instalación de Corretto
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/amazon-corretto-17-x64-linux-jdk.deb

# Descarga e instala Maven
ADD https://dlcdn.apache.org/maven/maven-3/3.9.6/binaries/apache-maven-3.9.6-bin.tar.gz /opt/

RUN tar -xzf /opt/apache-maven-3.9.6-bin.tar.gz -C /opt/

ENV PATH="/opt/apache-maven-3.9.6/bin:${PATH}"

# Instala Docker CLI
RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add -

RUN add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"

RUN apt-get update && apt-get install -y docker-ce-cli


# Agrega el usuario de Jenkins al grupo docker
RUN groupadd docker

RUN usermod -aG docker jenkins

# Cambia al usuario de Jenkins
USER jenkins
```

Se necesita tener instalado en el contenedor de jenkins:
- Dependencias necesarias 
- Java, en este caso usando Amazon Coretto 17
- Maven
- El daemon de docker

### Creación del docker-compose.yml

Este docker file sera usado dentro del docker-compose.yml y dentro también crearemos el contenedor de la base de datos con parámetros establecidos para crear una bd inicial llamada crud.

```yml
version: '3.8'

services:
  jenkinslocal:
    container_name: jenkinslocal
    build:
      context: .
      dockerfile: Dockerfile.jenkinslocal
    ports:
      - "8080:8080"
      - "500:5000"
    environment:
      JAVA_OPTS: "-Djenkins.install.runSetupWizard=false"
      DOCKER_HOST: "tcp://host.docker.internal:2375"
    depends_on:
      - postgres

    volumes:
      - jenkins_home:/var/jenkins_home
      - /var/run/docker.sock:/var/run/docker.sock

  postgres:
    container_name: db-crud
    image: postgres
    restart: always
    ports:
      - "5432:5432"
    environment:
      POSTGRES_DB: crud
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres

volumes:
  jenkins_home:
```

El primer servicio es para el contenedor de jenkins:
- Se crea a partir del DockerFile.jenkinslocal
- se habilitan los puertos
- Se agrega el host del docker
- Y como volumen se incluye la ruta del socket de docker

El segundo servicio es para el contenedor de docker es para la base de datos, en postgres.
- Se crea a partir de la imagen de postgres
- Se habilitan los puertos
- Se crean variables de entorno para configuraciones iniciales de la base de datos

### Levantar Contenedores
Para poder levantar el docker compose se usa:
- `docker compose up -d`

Si hacemos cambios en el internos en los servicios tenemos que hacer:
- docker compose down
- docker compose up -d (--detach/segundo plano)

![Compose up](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fmqb45vjpn1retqfz06f.png)



## Configurar Aplicación
La aplicación, en este caso una api usa el puerto 8081, adicional tendro del pom.xml se define el nombre del artefacto que se crea, llamado `mi-app.jar` .

> Es importante saber que jenkins dentro de un contenedor necesita la __ip__ del host local

Para SpringBoot el cambio de puerto se puede hacer en el archivo _application.properties_ :

```properties
#Ejemplo
spring.application.name=crud
spring.datasource.url=jdbc:postgresql://ip_local:5432/crud
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.jpa.hibernate.ddl-auto=update
server.port=8081
```


Para el pom en este caso se uso el finalName:
```xml
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                    <finalName>mi-app</finalName>
                </configuration>
            </plugin>
        </plugins>
    </build>
```


## Creación del DockerFile para la aplicación
Requisitos para la app:
- Instalar y configurar Java
- Jar que se genera en el pipeline

```DockerFile
# Dockerfile para el contenedor app-crud
FROM debian:bullseye-slim

# Instala wget y dpkg
RUN apt-get update && apt-get install -y wget && apt-get install -y dpkg
RUN apt-get update && apt-get install -y java-common


# Descarga e instala Amazon Corretto 17
ADD https://corretto.aws/downloads/latest/amazon-corretto-17-x64-linux-jdk.deb /tmp/

RUN dpkg -i /tmp/amazon-corretto-17-x64-linux-jdk.deb

# Limpia el cache de apt y elimina el paquete de instalación de Corretto
RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/amazon-corretto-17-x64-linux-jdk.deb

# Configura las variables de entorno JAVA_HOME y PATH
ENV JAVA_HOME /usr/lib/jvm/java-17-amazon-corretto

ENV PATH $JAVA_HOME/bin:$PATH

# Define el directorio de trabajo
WORKDIR /app

# # Copia el JAR generado a la imagen
COPY target/mi-app.jar /app/mi-app.jar

# # Comando para ejecutar la aplicación
CMD ["java", "-jar", "/app/mi-app.jar"]
```


## Crear el JenkinsFile
Se necesita el flujo para poder hacer el despliegue.
```Jenkinsfile
pipeline {
    agent any
    environment {
        DOCKER_HOST = 'tcp://host.docker.internal:2375'
        CONTAINER_NAME = 'mi-app-container'
        IMAGE_NAME = 'mi-app:latest'
    }

    stages {
        stage('Build') {
            steps {
                sh 'mvn clean package'
            }

        }

        stage('Build Docker Image') {
            steps {
                script {
                    sh 'docker build -t ${IMAGE_NAME} -f Dockerfile.app-crud .'
                }
            }
        }

        stage('Deploy Docker Container') {
            steps {
                script {
                    // Stop and remove the old container if it exists
                    sh '''
                    if [ "$(docker ps -aq -f name=${CONTAINER_NAME})" ]; then
                        if [ "$(docker ps -aq -f status=running -f name=${CONTAINER_NAME})" ]; then
                            docker stop ${CONTAINER_NAME}
                        fi
                        docker rm ${CONTAINER_NAME}
                    fi
                    '''
                    
                    // Run the new container
                    sh '''
                    docker run -d --name ${CONTAINER_NAME} -p 8081:8081 ${IMAGE_NAME}
                    '''
                }

            }

        }

    }

}
```

Este pipeline:

1. Compila la aplicación y crea el JAR (`mi-app.jar`).
2. Construye una imagen Docker.
3. Despliega el contenedor, deteniendo y eliminando el existente si es necesario.

## Configuración de Jenkins
>Cuando se crea un contenedor jenkins hay casos que te piden una clave, esta sale en el log de ejecución del contenedor. Y luego sigues los pasos hasta ver la pagina principal


![Jenkins Config](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4ek5ext8obvkro7xnpu.png)



### Plugins
Instala los siguientes plugins en Jenkins:
- [SSH Credentials Plugin](https://plugins.jenkins.io/ssh-credentials)
- [Pipeline: Job](https://plugins.jenkins.io/workflow-job)
- [Git](https://plugins.jenkins.io/git/)
- [GitHub plugin](https://plugins.jenkins.io/github)
- [Docker plugin](https://plugins.jenkins.io/docker-plugin)

### Configuración del Job
Crea un nuevo Job en Jenkins con estas configuraciones:
- **Trigger:** GitHub hook trigger for GITScm polling
- **Pipeline:** Pipeline script from SCM
- **SCM:** Git con URL SSH
- En Repository URL le colocamos la URL pero con el protocolo SSH
- En credential lo dejamos vacío por ahora.


![Pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gegfs2csztddxz552foh.png)


Abriremos una terminal del contenedor de Jenkins para crear una llave ssh:
- `docker exec -it -u jenkins id_contenedor bash`

Luego dentro de la terminal
```bash
# Comandos de ejemplo
cd ~
ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/var/jenkins_home/.ssh/id_rsa):
# Output...

eval $(ssh-agent -s)
ssh-add .ssh/id_rsa
# Reconoceremos a github para la conexion por ssh
ssh -T git@github.com

# Mostramos la llave privada
cat .ssh/id_rsa
# Mostramos la llave publica
cat .ssh/id_rsa.pub

```

Para agregar la llave privada a las credenciales de Jenkins hacemos los iguiente:
- Abrimos la interfaz de jenkins y vamos a la configuración del job creado y agregamos una

![Add credential](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/agc7znj7wxhbg4wxxhzc.png)


Les saldrá una ventana asi

![Credential](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k34jw2pzkmklqeo62x04.png)



- En Kind le colocamos SSH Username with private key
- El ID debe ser único a la otras credenciales que ya tienes
- En Private Key le das click en Enter directly para agregar la llave privada directamente y le damos en Add.
- Luego si es que la llave tiene clave la agregamos en el campo Passphrase
- Al final de dan en agregar para guardar la credencial.

La llave publica la agregaremos al repositorio remoto en Settings>Deploys Keys>Add deploy key.

![Add deploy key](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/io6b7jeh64et7nxitd1x.png)

Vamos al Job creado y la credencial ya podríamos escogerla sin inconvenientes.

Cuando se ejecute el pipeline se creara un contenedor y ya estaria el deploy

![Contenedor pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/16xj9dk8ay4q2zrqgaa1.png)