Good day.
Recently, I faced the task of launching spring boot 2 applications in a kubernetes cluster using a docker image. This problem is not new, I quickly found examples in Google and packed my application. I was very surprised not to find an alpine image for jdk11 and hoped that slim would be quite small, but the moment of sending the image to the docker registry, I noticed that its size was almost 422 megabytes. Under the cut, a description of how I reduced the docker image with my spring boot and java 11 to 144 megabytes.
As I mentioned earlier, my application was built using spring boot 2 and is a REST API wrapper over a relational database (using @RepositoryRestResource). My dependencies include:
org.springframework.boot:spring-boot-starter-data-rest org.springframework.boot:spring-boot-starter-data-jpa org.flywaydb:flyway-core org.postgresql:postgresql
The jar file compiled has a size of 37.6 megabytes.
Dockerfile:
FROM openjdk:11-jdk-slim WORKDIR /home/demo ARG REVISION COPY target/spring-boot-app-${REVISION}.jar app.jar ENTRYPOINT ["java","-jar","app.jar"]
As a result of the build, I get an image size of 422 mb according to the output of the docker images command. It is interesting that when using the outdated 8-jdk-slim image, the size is reduced to 306 mb.
The first logical step was an attempt to find a more lightweight image, preferably on the basis of alpine. I scanned to the most popular repositories with Java:
(11 as the current LTS release and 8 as there are still a sufficient number of applications that could not migrate to more modern versions)
A table with images and tags (~ 2700), their size at the time of this writing is available here.
Here are some of them:
openjdk 8 488MB openjdk 8-slim 269MB openjdk 8-alpine 105MB openjdk 8-jdk-slim 269MB openjdk 8-jdk-alpine 105MB openjdk 8-jre 246MB openjdk 8-jre-slim 168MB openjdk 8-jre-alpine 84.9MB openjdk 11 604MB openjdk 11-slim 384MB openjdk 11-jdk 604MB openjdk 11-jdk-slim 384MB openjdk 11-jre 479MB openjdk 11-jre-slim 273MB adoptopenjdk/openjdk8 alpine 221MB adoptopenjdk/openjdk8 alpine-slim 89.7MB adoptopenjdk/openjdk8 jre 200MB adoptopenjdk/openjdk8 alpine-jre 121MB adoptopenjdk/openjdk11 alpine 337MB adoptopenjdk/openjdk11 alpine-slim 246MB adoptopenjdk/openjdk11 jre 218MB adoptopenjdk/openjdk11 alpine-jre 140MB
Thus, if you change the base image on adoptopenjdk / openjdk11: alpine-jre, you can reduce the image with the application to 177 mb.
Since the release of jdk9 and modularization, it has become possible to build your own runtime that contains only those modules that your application needs. More details about this functionality can be found here .
Let's try to identify the necessary modules for the test spring boot application:
~/app ᐅ jdeps -s target/app-1.0.0.jar app-1.0.0.jar -> java.base app-1.0.0.jar -> java.logging app-1.0.0.jar -> not found
Okay, it seems that jdeps can't handle the fat jar created using spring boot, but we can unpack the archive and set the classpath:
~/app ᐅ jdeps -s -cp target/app-1.0.0/BOOT-INF/lib/*.jar target/app-1.0.0.jar.original Error: byte-buddy-1.9.12.jar is a multi-release jar file but --multi-release option is not set ~/app ᐅ jdeps -s --multi-release 11 -cp target/app-1.0.0/BOOT-INF/lib/*.jar target/app-1.0.0.jar.original Error: aspectjweaver-1.9.2.jar is not a multi-release jar file but --multi-release option is set
On this occasion, the bug is currently open: https://bugs.openjdk.java.net/browse/JDK-8207162
I tried to download jdk12 to get this information, but I encountered the following error:
Exception in thread "main" com.sun.tools.classfile.Dependencies$ClassFileError ... Caused by: com.sun.tools.classfile.ConstantPool$InvalidEntry: unexpected tag at #1: 53
By trial, error, and module search by ClassNotFoundException, I determined that my application needed the following modules:
Rantym for them can be collected using:
jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.desktop,java.security.jgss --output /usr/lib/jvm/spring-boot-runtime
Let's try to build a basic docker image using this module:
FROM openjdk:11-jdk-slim RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,java.logging,java.sql,java.naming,java.management,java.instrument,java.desktop,java.security.jgss --output /usr/lib/jvm/spring-boot-runtime FROM debian:stretch-slim COPY --from=0 /usr/lib/jvm/spring-boot-runtime /usr/lib/jvm/spring-boot-runtime RUN ln -s /usr/lib/jvm/spring-boot-runtime/bin/java /usr/bin/java
and collect it:
docker build . -t spring-boot-runtime:openjdk-11-slim
As a result, the size was 106 megabytes, which is significantly less than the majority of found base images with openjdk. If you use it for my application, then the resulting size will be 144 megabytes.
Next, we can use spring-boot-runtime:openjdk-11-slim
as the base image for all spring boot applications if they have similar dependencies. In the case of various dependencies, it is possible to use the multistage image assembly for each of the applications where the java runtime will be assembled at the first stage, and the archive with the application is added at the second stage.
FROM openjdk:11-jdk-slim RUN jlink --no-header-files --no-man-pages --compress=2 --strip-debug --add-modules java.base,YOUR_MODULES --output /usr/lib/jvm/spring-boot-runtime FROM debian:stretch-slim COPY --from=0 /usr/lib/jvm/spring-boot-runtime /usr/lib/jvm/spring-boot-runtime WORKDIR /home/demo ARG REVISION COPY target/app-${REVISION}.jar app.jar ENTRYPOINT ["/usr/lib/jvm/spring-boot-runtime/bin/java","-jar","app.jar"]
Currently, most docker images for java are quite large, which can negatively affect the start time of the application, especially if the required layers are not already on the server. Using tags with jre or using the modularization of java, you can build your own runtime, which will significantly reduce the size of the application image.
Source: https://habr.com/ru/post/457476/
All Articles