Cómo ejecutar contenedores de Docker con Maven exec-maven-plugin

0
3337
Maven Docker Exec

Cuando estamos usando Maven y queremos levantar contenedores de Docker para, por ejemplo, ejecutar los tests de integración, es bastante típico usar alguno de los plugins específicos que existen. Donde a día de hoy posiblemente el más habitual es docker-maven-plugin de Fabric8.

Estos plugins suelen ser un recubrimiento sobre un cliente de Docker que al final se conecta con el Docker daemon para realizar las acciones necesarias. Si bien para casos sencillos son muy convenientes, y de hecho seguramente deberían ser la opción por defecto, el problema que tienen este tipo de plugins es que están más pensados para crear imágenes en vez de ejecutarlas, y sobre todo si queremos levantar distintos contenedores con dependencias entre ellos… la cosa se complica 😅.

En este tutorial vamos a ver cómo podemos usar el simple plugin de Maven exec-maven-plugin para quitarnos estos intermediarios y hacer lo que queramos directamente con el comando docker.

De hecho en este tutorial utilizaremos el comando docker compose que nos permitirá más flexibilidad a la hora de levantar distintos contenedores de forma simultánea.

Podéis encontrar todo el código de este tutorial en: https://github.com/alejandropg/tutorial-maven-docker-exec. Incluido un ejemplo de cómo ejecutarlo integrado con los GitHub Actions.

Índice

  1. Entorno
  2. Maven pom.xml
  3. El script para preparar el arranque de los tests de integración: docker-compose-it-up.sh
  4. El script para limpiar el entorno después de los tests de integración: docker-compose-it-down.sh
  5. La configuración de docker compose
  6. Conclusiones
  7. Sobre el autor


1. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 16″ (Apple M1 Pro, 32GB LPDDR5, 1TB SSD).
  • Sistema Operativo: macOS Monterey 12.0.1.
  • Java version: 11.0.13, vendor: Azul Systems, Inc., arch: “aarch64”, family: “mac”.
  • Apache Maven 3.8.3.
  • Docker Desktop 4.1.1 (69879) y también probado en 4.2.0 (70708).


2. Maven pom.xml

Para usar el plugin exec-maven-plugin simplemente haremos la siguiente configuración en nuestro Maven pom.xml.

...
<build>
    <plugins>
        <plugin>
            <groupId>org.codehaus.mojo</groupId>
            <artifactId>exec-maven-plugin</artifactId>
            <version>${exec-maven-plugin.version}</version>
            <executions>
                <execution>
                    <id>start</id>
                    <phase>pre-integration-test</phase>
                    <goals>
                        <goal>exec</goal>
                    </goals>
                    <configuration>
                        <workingDirectory>${maven.multiModuleProjectDirectory}</workingDirectory>
                        <executable>docker-compose-it-up.sh</executable>
                    </configuration>
                </execution>
                <execution>
                    <id>stop</id>
                    <phase>post-integration-test</phase>
                    <goals>
                        <goal>exec</goal>
                    </goals>
                    <configuration>
                        <workingDirectory>${maven.multiModuleProjectDirectory}</workingDirectory>
                        <executable>docker-compose-it-down.sh</executable>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
...

Aquí se ve como simplemente nos enganchamos a la fase de pre-integration-test y post-integration-test para ejecutar un script para arrancar “up” y parar “down” el Docker compose.

He elegido usar un script y no hacerlo directamente aquí porque eso nos puede dar mucha flexibilidad a la hora de ejecutar cualquier otro tipo de configuración de arranque que necesitemos para nuestros tests de integración. Lo veremos más adelante con un ejemplo.


3. El script para preparar el arranque de los tests de integración: docker-compose-it-up.sh

docker-compose-it-up.sh es un script sh normal y corriente, por lo que tendremos a nuestro alcance toda la potencia de la shell.

#!/usr/bin/env sh

DOCKER_COMPOSE_FILE=docker-compose-it.yml

_docker_compose_get_image_name() {
    image_name=$(fgrep --no-filename --max-count=1 image "${DOCKER_COMPOSE_FILE}" | cut -d':' -f2-)
    # remove leading whitespace characters
    image_name="${image_name#"${image_name%%[![:space:]]*}"}"
    # remove trailing whitespace characters
    image_name="${image_name%"${image_name##*[![:space:]]}"}"  
    echo "${image_name}"
}

_docker_compose_pull_image() {    
    docker image inspect "${1}" 2>&1 1>/dev/null
    image_exist=$?
    if [ $image_exist != 0 ]; then
        echo '[INFO] Pull IT Docker images...'
        docker compose -f "${DOCKER_COMPOSE_FILE}" pull
    fi
}

# We don't want that the images download affect to the timeout, 
# so pull images before up if they are not in the local image repository
image_name=$(_docker_compose_get_image_name)
_docker_compose_pull_image "${image_name}"

echo '[INFO] Up IT Docker containers...'
expect <<EOD
set timeout 5
spawn docker compose -f "${DOCKER_COMPOSE_FILE}" up
expect "database system is ready to accept connections"
EOD

Este script seguramente es lo más complicado de todo el tutorial. Al principio encontramos un par de funciones de ayuda donde el propósito de estas es una pequeña optimización para no hacer el docker compose pull constantemente y ahorrar unos preciosos segundos cuando lo estamos ejecutando en local. Es decir, si analizamos en detalle la función _docker_compose_pull_image, podemos ver que lo que hace es comprobar si la imagen ya está en local o no. De no encontrarla intentará hacer un pull de todo el Docker compose.

Luego la otra parte con “truco” es el uso del expectLa intención del expect es no terminar la ejecución del script hasta que se encuentre en la salida la línea especificada. De esta forma garantizamos que los tests de integración no empezarán a ejecutarse hasta que esté debidamente levantado el contenedor.

expect "database system is ready to accept connections"

Esto sería el equivalente a, por ejemplo, cuando usando el docker-maven-plugin de Fabric8 y hacemos:

<wait>
    <log>database system is ready to accept connections</log>
</wait>

También se define un timeout de 5 segundos, de forma que si en este tiempo no se encuentra la línea especificada el script terminará abruptamente. Precisamente por este timeout es por lo que separamos el pull de las imágenes del up del Docker compose, ya que si no lo separamos será muy fácil que salte el timeout la primera vez que lo ejecutemos, cuando intente descargar las imágenes.


4. El script para limpiar el entorno después de los tests de integración: docker-compose-it-down.sh

De nuevo docker-compose-it-down.sh es un script sh normal donde podremos ejecutar todo lo que necesitemos para limpiar el entorno tras ejecutar los tests de integración.

#!/usr/bin/env sh

echo '[INFO] Down IT Docker containers...'
docker compose --file docker-compose-it.yml down

Se ve como en nuestro caso simplemente paramos el Docker compose. Además aquí son comandos normalitos y no necesitamos usar ningún “truco” como vimos con el expect en el punto anterior.


5. La configuración de docker compose

Para configurar el Docker compose usamos un fichero de configuración normal y corriente: docker-compose-it.yml

services:
  it-ddbb:
    image: postgres:13.3-alpine
    ports:
      - 5432:5432
    volumes:
      - it-ddbb:/var/lib/postgresql/data:delegated
    environment:
      POSTGRES_PASSWORD: password

volumes:
  it-ddbb:

En el ejemplo solo levantamos un servicio, pero como es un fichero normal de configuración de Docker compose podríamos hacer cualquier cosa que este permita, como levantar n servicios, con dependencias entre ellos, montar volúmenes…


6. Conclusiones

Con este ejemplo estamos yendo un poco a “bajo nivel”, pero si os fijáis tampoco tanto, puesto que hay pocas lineas que no tuviéramos que poner de todas formas: nombre de la imagen, nombre de los volúmenes, en que fase del ciclo de Maven queremos engancharlo…

Y sin embargo conseguimos un aumento de flexibilidad enorme, ya que ponemos a nuestro alcance toda la potencia de la shell, de Docker y de Docker compose.

Así que desde luego esta puede ser una buena opción a tener en cuenta si vemos que se nos complica la cosa a la hora de levantar contenedores dentro del ciclo de vida de Maven.


7. Sobre el autor

Alejandro Pérez García (@alejandropgarci).

Ingeniero en Informática (especialidad de Ingeniería del Software) y Certified ScrumMaster.

Socio fundador de Autentia Real Business Solutions S.L. – “Soporte a Desarrollo”.

Alejandro es socio fundador de Autentia y nuestro experto en Java EE, Linux y optimización de aplicaciones empresariales. Ingeniero en Informática y Certified ScrumMaster. Seguir @alejandropgarci Si te gusta lo que ves, puedes contratarle para darte ayuda con soporte experto, impartir cursos presenciales en tu empresa o para que realicemos tus proyectos como factoría (Madrid). Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad