Skip to main content

Usa JLink para crear imágenes de Docker más pequeñas para tu aplicación Java con Spring Boot

Escrito por
feature kubernetes polp

24 de agosto de 2023

0 minutos de lectura

Los contenedores aportan flexibilidad y agilidad al desarrollo y la implementación de software. Sin embargo, también introducen una nueva superficie de ataque que los actores maliciosos pueden aprovechar. Si un contenedor se ve comprometido, un atacante puede acceder a otros contenedores e incluso al sistema host. Las imágenes más pequeñas, que contienen menos artefactos, son de gran ayuda para reducir la superficie de ataque.

En esta publicación del blog, exploraremos en profundidad cómo usar JLink para optimizar el tamaño de las imágenes de Docker y mejorar la seguridad y el rendimiento de las aplicaciones. Te mostraremos cómo usar JLink e integrarlo con Docker para implementar de forma eficiente tus aplicaciones Spring Boot o Java en general.

Java es uno de los lenguajes de programación más utilizados en todo el mundo para desarrollar aplicaciones empresariales. Sin embargo, los desarrolladores suelen tener dificultades con el tamaño de las imágenes de Docker al implementar aplicaciones Java en contenedores de Docker. Una forma de resolver este problema es usar JLink, una herramienta incorporada en JDK 9.

JLink (Java Linker) es una herramienta de línea de comandos que ensambla y optimiza un conjunto de módulos y sus dependencias en una imagen de runtime personalizada. En esencia, crea un entorno de ejecución de Java mínimo que contiene solo los módulos que necesita tu aplicación.

$ jlink --module-path $JAVA_HOME/jmods:mlib --add-modules my.module --output myRunTime

En el comando anterior, my.module es tu módulo y myRuntime es la imagen de runtime personalizada que creará JLink.

Al crear imágenes de Docker para aplicaciones Java, el tamaño de la imagen suele ser un motivo de preocupación, sobre todo en el caso de las aplicaciones Spring Boot, que incluyen muchas dependencias. Una imagen de Docker grande puede generar tiempos de inicio más prolongados, mayores costos de almacenamiento y procesos de implementación más lentos.

En las versiones anteriores de Java, el kit de desarrollo de Java (JDK) incluía un entorno de ejecución de Java (JRE). Para ejecutar el artefacto Java creado, solo se necesitaba el JRE. Por eso, antes era común usar el JRE en la imagen de Docker o elegir una imagen base con JRE para los contenedores. Las versiones más recientes de Java no siempre incluyen un JRE, aunque algunos proveedores aún podrían crear uno y ofrecer las imágenes base correspondientes. Puedes usar esas imágenes o un runtime de Java más específico, adaptado a tu aplicación.

JLink te permite crear un entorno de ejecución de Java mínimo que incluye solo los módulos necesarios. Así, reduce considerablemente el tamaño de la imagen de Docker. Por ejemplo, un entorno de ejecución estándar de Java puede superar los 200 MB, pero con JLink puedes reducirlo a menos de 50 MB.

Spring Boot crea un JAR con todas las dependencias de tus aplicaciones. Además, muchas aplicaciones Spring Boot no tienen una declaración de módulos. Esto no tiene por qué ser un problema, pero debemos determinar qué módulos necesita la aplicación y cuáles son todas sus dependencias.

Cómo usar Jdeps para encontrar los módulos


Jdeps es una herramienta de Java que muestra las dependencias a nivel de paquete o de clase. Esta herramienta, incorporada en Java 8, permite conocer las dependencias de una aplicación, que luego se pueden usar para crear una imagen de runtime personalizada con JLink.

Si nos aseguramos de que todas las dependencias estén en un mismo directorio, podemos usar jdeps para generar un resumen de ellas.

jdeps -cp 'mydeps/lib/*' -recursive --multi-release 17 -s target/MyJar.jar

Del mismo modo, podemos usar jdeps para mostrar de forma recursiva todas las dependencias de módulos de la aplicación Spring Boot y sus dependencias.

jdeps --ignore-missing-deps -q  --recursive  --multi-release 17  --print-module-deps  --class-path 'mydeps/lib/*'  target/MyJar.jar

El resultado que genera jdeps permite que JLink cree un entorno de ejecución de Java que contenga únicamente los módulos necesarios para esta aplicación Spring Boot.

Exportar las dependencias de Spring Boot a una carpeta

Como mencionamos, Spring Boot crea un JAR que incluye todas las dependencias. Sin embargo, las dependencias están empaquetadas de una forma particular dentro del JAR y, por lo tanto, jdeps no puede acceder a ellas fácilmente. Hay dos soluciones sencillas para obtener las dependencias con jdeps.

  • Desempaqueta el archivo fat JAR creado por Spring Boot.

    • Esta opción funciona muy bien si ya tienes el artefacto de compilación y no quieres o no puedes volver a compilar la aplicación. Las dependencias se desempaquetarán en /BOOT/libs/.

  • Usar un complemento en la herramienta de compilación que copie las dependencias a una carpeta específica.

    • En Maven, por ejemplo, esto se puede lograr con maven-dependency-plugin. En el siguiente ejemplo, las dependencias se copian a la carpeta /target/dependency cuando Maven termina la fase de empaquetado.

<project>
    <!-- ... other configurations ... -->

    <build>
        <plugins>
            <!-- Add the maven-dependency-plugin -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.1.2</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <!-- Configure the output directory for the dependencies -->
                            <outputDirectory>${project.build.directory}/dependency</outputDirectory>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

    <!-- ... other configurations ... -->
</project>

Crear una imagen de Docker con un entorno de ejecución de Java personalizado

Ahora combinemos jdeps y JLink para crear un entorno de ejecución de Java personalizado. Con este runtime, podemos crear una imagen de Docker mínima y perfecta, diseñada específicamente para una aplicación Spring Boot.

FROM maven:3-eclipse-temurin-17 as build
RUN mkdir /usr/src/project
COPY . /usr/src/project
WORKDIR /usr/src/project
RUN mvn package -DskipTests
RUN jar xf target/JavaCoffeeShop.jar
RUN jdeps --ignore-missing-deps -q  \
    --recursive  \
    --multi-release 17  \
    --print-module-deps  \
    --class-path 'BOOT-INF/lib/*'  \
    target/JavaCoffeeShop.jar > deps.info
RUN jlink \
    --add-modules $(cat deps.info) \
    --strip-debug \
    --compress 2 \
    --no-header-files \
    --no-man-pages \
    --output /myjre
FROM debian:bookworm-slim
ENV JAVA_HOME /user/java/jdk17
ENV PATH $JAVA_HOME/bin:$PATH
COPY --from=build /myjre $JAVA_HOME
RUN mkdir /project
COPY --from=build /usr/src/project/target/JavaCoffeeShop.jar /project/
WORKDIR /project
ENTRYPOINT java -jar JavaCoffeeShop.jar

En el ejemplo anterior, usé una compilación de Docker en varias etapas. La etapa inicial de compilación se basa en una imagen eclipse-temurin JDK 17 que incluye Maven. Esta etapa se usa para:

  • Crear el artefacto Java. Con Maven, creo el archivo JAR ejecutable que contiene la aplicación completa.

  • Desempaquetar el archivo JAR para obtener todas las dependencias. Esto solo es necesario si no usas maven-dependency-plugin, como se describió antes. Si lo incluiste, puedes omitir este paso.

  • Usar jdeps para obtener los módulos necesarios. Indica el archivo que contiene todos los archivos JAR de las dependencias y el artefacto final, y guarda la lista en deps.info.

  • Ejecutar JLink para crear un entorno de ejecución de Java personalizado. Usa deps.info como entrada y guarda el resultado en /myjre. Solo agregamos a JLink los módulos necesarios y eliminamos la información de depuración, las páginas del manual y los archivos de encabezado.

La segunda y última etapa crea la imagen de producción basada en una imagen debian:stable-slim.

  • Configurar las variables de entorno. Asigna a JAVA_HOME la ruta donde copiaré myjre y agrega JAVA_HOME a PATH.

  • Copiar el entorno de ejecución de Java creado por JLink. Haz referencia a la primera etapa y copia el entorno de ejecución de Java personalizado en la ubicación definida como JAVA_HOME.

  • Copiar el artefacto Java creado. El JAR ejecutable de Spring Boot creado se copia en el directorio dedicado del proyecto.

  • Configurar el punto de entrada

JLink ofrece varias ventajas para crear imágenes de Docker para aplicaciones Java con Spring Boot:

1. Menor tamaño de imagen: Como mencionamos antes, JLink puede reducir el tamaño de la imagen de Docker, lo que agiliza la implementación y disminuye los costos de almacenamiento.

2. Menores tiempos de inicio: Una imagen de Docker más pequeña permite que la aplicación se inicie más rápido, algo fundamental para las aplicaciones que necesitan escalar rápidamente.

3. Seguridad: Al incluir solo los módulos necesarios, reduces la superficie de ataque de tu aplicación. Menos módulos significan menos posibles vulnerabilidades de seguridad.

En cuanto a la seguridad, es esencial mencionar el papel de Snyk para proteger tus aplicaciones. Snyk es una herramienta de seguridad para desarrolladores que puede analizar tu código fuente, paquetes de código abierto, imágenes de contenedores y configuraciones de la nube para detectar vulnerabilidades. Con Snyk Container y Snyk Open Source, puedes detectar y corregir problemas de seguridad en tu aplicación y sus dependencias, incluidos los que se encuentran en tus imágenes de Docker.

$ snyk container test your-repo/your-image:tag

En el comando anterior, your-repo/your-image:tag es tu imagen de Docker. Snyk la analizará e informará las vulnerabilidades detectadas, junto con sugerencias para corregirlas.

Crea imágenes de Docker más pequeñas y seguras para aplicaciones Java

Ten en cuenta que los ejemplos de esta publicación del blog muestran cómo usar JLink para crear una imagen de Docker más compacta para tus proyectos Java. No cumplen con todas las prácticas recomendadas para crear imágenes de Docker seguras. Si quieres obtener más información, consulta nuestro artículo «10 prácticas recomendadas para crear un contenedor Java con Docker».

En conclusión, JLink es una herramienta potente que puede ayudarte a crear imágenes de Docker más pequeñas y seguras para tus aplicaciones Java con Spring Boot. Si la combinas con herramientas de seguridad como Snyk, puedes asegurarte de que tus aplicaciones tengan un buen rendimiento y sean seguras. ¿Qué esperas? Regístrate en Snyk hoy mismo y empieza a proteger tus aplicaciones.

Seguridad de contenedores centrada en los desarrolladores

Snyk encuentra y corrige automáticamente vulnerabilidades en imágenes de contenedores y cargas de trabajo de Kubernetes.