Finalizamos la serie de artículos de Kotlin y SpringBoot con el presente artículo con título «Kotlin, SpringBoot, Docker y DockerCompose III: docker y docker-compose». El objetivo de la entrada es dockerizar la aplicación para hacer funcionar la aplicación desarrollada en un contenedor Docker. Además, definiremos la aplicación para que funcione en una composición de contenedores con un contenedor con MySQL.
La serie está compuesta de tres entradas ordenadas de forma cronológica:
- «Kotlin, SpringBoot, Docker y DockerCompose I».- descripción del funcionamiento de la aplicación y los elementos básicos de funcionamiento.
- «Kotlin, SpringBoot, Docker y DockerCompose II: test unitarios».- descripción de cómo se realizan test unitarios en SpringBoot con Kotlin.
- «Kotlin, SpringBoot, Docker y DockerCompose III: docker y docker-compose».-descripción de la dockerización de la aplicación con Docker u DockerCompse.
La estructura del artículo está compuesto por los siguientes puntos:
- Docker
- Propuesta 1
- Propuesta 2
- Propuesta 3
- DockerCompose
1.- Docker
La aplicación la podemos dockerizar de tres formas posibles. Las diferencia reside en el tamaño de la imagen resultante.
Propuesta 1
La estrategia consiste en copiar el contenido de la aplicación en el interior de la imagen y posteriormente realizar la creación del artefacto. El snippet con el contenido del Dockerfile es el siguiente:
FROM openjdk:8-jdk-alpine VOLUME /tmp RUN mkdir /app COPY . /app WORKDIR /app RUN /app/gradlew build RUN mv /app/build/libs/*.jar /app/app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app/app.jar"]
Para crear la imagen ejecutamos el siguiente comando docker:
docker image build -t alvaroms/ejem1kotlindocker:v1 .
Para visualizar las imágenes creadas ejecutamos el siguiente comando:
docker image list
El resultado de la ejecución del comando muestra una imagen con un tamaño aproximado a 686MB.
Propuesta 2
La segunda estrategía consigue reducir el tamaño de la imagen. Para ello, se realiza la creación del artefacto fuera de la imagen y se copia al interior de la imagen. Es decir, respecto a la solución anterior, se elimina el paso de construcción del artefacto. Para realizar la creación del artefacto, es necesario definir una tarea de creación del fichero jar mediante una tarea en Gradle. La tarea Gradle es la siguiente:
tasks.register("buildDocker"){ dependsOn("build") doLast{ fileList("./build/libs").forEach { file -> file.copyTo(File("./ejem1kotlindocker.jar")) } } }
Como se observa en el snippet anterior, la tarea «buildDocker» tiene una dependencia con la tarea build; una vez ejecutado build, su resultado, generado en la carpeta «./build/libs» del proyecto, se copia a la carpeta raíz del proyecto. Esta tarea debe de ser ejecutada previamente a la construcción de la imagen.
La definición de la imagen en el Dockerfile es la siguiente:
FROM openjdk:8-jdk-alpine VOLUME /tmp RUN mkdir /app WORKDIR /app EXPOSE 8080 ADD ./ejem1kotlindocker.jar /app/ejem1kotlindocker.jar ENTRYPOINT ["java", "-jar", "/app/ejem1kotlindocker.jar"]
Para crear la imagen ejecutamos el siguiente comando docker:
docker image build -t alvaroms/ejem1kotlindocker:v2 .
Para visualizar las imagenes creadas ejecutamos el siguiente comando:
docker image list
El resultado de esta forma implica la creación de una imagen con un tamaño aproximado a 149MB. En relación a la primera forma, la reducción del tamaño es notable.
Propuesta 3
La tercera estrategia consiste en fusionar los objetivos de las anteriores. Para ello, primeramente, realizamos la creación del artefacto en una primera capa; posteriormente,
con el artefacto creado, construimos la imagen. Todo ello en el paso de construcción de la imagen y en elmomento de crear la imagen Docker. El snippet con el contenido del Dockerfile es el siguiente:
FROM openjdk AS build VOLUME /tmp RUN mkdir /app COPY . /app WORKDIR /app RUN /app/gradlew build FROM openjdk:8-jdk-alpine ARG APP_VERSION=1.0.0-SNAPSHOT ENV database__client=mysql ENV database__connection__host=mysql ENV database__connection__user=root ENV database__connection__password=password ENV database__connection__database=prueba ENV SPRING_PROFILES_ACTIVE=dev LABEL org.label-schema.version=$APP_VERSION COPY --from=build /app/build/libs/*.jar /app/app.jar ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-jar", "/app/app.jar" ]
Con esta opción, definimos las variables de entorno con la configuración por defecto a la base de datos cuyos valores pueden ser pasados por parámetro en la creación de la imagen. De la misma manera que los argumentos necesarios.
Para crear la imagen ejecutamos el siguiente comando docker:
docker image build -t alvaroms/ejem1kotlindocker:v3 --build-arg APP_VERSION=1.0.3-SNAPSHOT .
Para visualizar las imagenes creadas ejecutamos el siguiente comando:
docker image list
El resultado de esta forma implica la creación de una imagen con un tamaño aproximado a 149MB. En relación a la primera forma, la reducción del tamaño es notable;y, respecto a la segunda, no es necesario realizar ninguna operación intermedia. Así, de las tres definiciones de Dockerfile, esta tercera, es la solución más eficiente.
2.- DockerCompose
DockerCompose permite realizar la composición de contenedores interconectados. La definición de la composición se realiza en el fichero docker-compose.yml, ubicado en la raíz del proyecto. La composición se define en base a dos servicios: el primero, mysql, corresponde con un contenedor de una imagen de MySQL; el segundo, kotlindocker, corresponde con la aplicación desarrollada.
Cada contenedor está definida en dos redes: kotlinnetwork, para el contenedor de la aplicación; y, mysqlnetwork, para el contenedor de la base de datos MySQL. La conexión la realiza el servicio de kotlin al tener conexión a las dos redes. Para los volúmenes, se definen un volumen para cada servicio.
Otro detalle importante, reside en qué imagen de la aplicación seleccionamos, o bien, creamos la imagen a partir del código existente: opción build, creamos la imagen a partir del código; e, image, creamos el contenedor a partir de una imagen definida.
El contenido del fichero docker-compose es el siguiente:
version: '3' services: mysql: container_name: mysql image: mysql:5.7 environment: - MYSQL_DATABASE=prueba - MYSQL_ROOT_PASSWORD=password ports: - 3306:3306 volumes: - mysqlvolume:/var/lib/mysql networks: - mysqlnetwork kotlindocker: depends_on: - mysql container_name: kotlindocker # image: alvaroms/ejem1kotlindocker:v4 build: ./ environment: - SPRING_PROFILES_ACTIVE=dev ports: - 8087:8080 volumes: - kotlinvolume:/var/lib/kotlin networks: - kotlinnetwork - mysqlnetwork volumes: kotlinvolume: mysqlvolume: networks: kotlinnetwork: driver_opts: com.docker.network.bridge.name: kotlinNetwork mysqlnetwork: driver_opts: com.docker.network.bridge.name: MySQLNetwork
Para el lector interesado en el código del proyecto puede acceder en el siguiente enlace
Finalizamos con una reflexión sobre la serie sobre kotlin, Spring Boot y Docker: un punto a tener en cuenta es que el código Kotlin presentado es muy mejorable al no haber aplicado patrones funcionales como los que se pueden presentar con la librería Arrow; con ella, la mejora del código es notable, por ejemplo, utilizando contenedores binarios.