Pull to refresh

Уменьшение размера docker образа с spring boot приложением

Reading time 4 min
Views 17K

Добрый день.


Недавно передо мной встала задача запуска spring boot 2 приложения в kubernetes кластере используя docker образ. Эта проблема не является новой, достаточно быстро я нашел примеры в гугле и запаковал свое приложение. Я был очень удивлен не найдя alpine образ для jdk11 и надеялся что slim будет достаточно небольшим, но момент отправки образа на docker registry я обратил внимание что его размер составлял почти 422 мегабайт. Под катом описание того как я уменьшил docker образ с моим spring boot и java 11 до 144 мегабайт.



Приложение


Как я уже упомянул ранее, мое приложение построено используя spring boot 2 и представляет из себя REST API обертку над реляционной базой данных (используя @RepositoryRestResource). Мои зависимости включают:


org.springframework.boot:spring-boot-starter-data-rest
org.springframework.boot:spring-boot-starter-data-jpa
org.flywaydb:flyway-core
org.postgresql:postgresql

Собранный jar файл имеет размер: 37,6 мегабайт.


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"]

В результате сборки я получаю образ размером: 422 мб согласно выводу команды docker images. Интересно что при использовании устаревшего образа 8-jdk-slim, размер уменьшается до 306 мб.


Попытка 1: другой базовый образ


Первым логичным шагом была попытка найти более легковесный образ, желательно на основе alpine. Я просканировал на наиболее популярные репозитории с джавой:



(11 как текущий LTS релиз и 8 так как все еще есть достаточное количество приложений которые не смогли мигрировать на более современные версии)


Таблица с образами и тегами (~2700), их размерами на момент написания статьи доступна тут


Вот некоторые из них:


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

Таким образом, если поменять базовый образ на adoptopenjdk/openjdk11:alpine-jre то можно уменьшить образ с приложением до 177 мб.


Попытка 2: custom runtime


С момента выпуска jdk9 и модуляризации появилась возможность собрать собственный рантайм который содержит только те модули что необходимы вашему приложению. Детальнее об этой функциональности можно прочитать тут.


Попробуем определить необходимые модули для тестового spring boot приложения:


~/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

Окей, похоже что jdeps не может справиться с fat-jar созданным при помощи spring boot, но мы можем распаковать архив и прописать 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

По этому поводу на текущий момент открыт баг: https://bugs.openjdk.java.net/browse/JDK-8207162


Я попробовал скачать jdk12 чтобы получить эту информацию, но столкнулся со следующей ошибкой:


Exception in thread "main" com.sun.tools.classfile.Dependencies$ClassFileError
...
Caused by: com.sun.tools.classfile.ConstantPool$InvalidEntry: unexpected tag at #1: 53

Методом проб, ошибок и поиска модулей по ClassNotFoundException я определил что моему приложению необходимы следующие модули:


  • java.base
  • java.logging
  • java.sql
  • java.naming
  • java.management
  • java.instrument
  • java.desktop
  • java.security.jgss

Рантайм для них можно собрать используя:


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

Попробуем построит базовый docker образ используя эту модули:


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

и соберем его:


docker build . -t spring-boot-runtime:openjdk-11-slim

В результате размер составил 106 мегабайт, что значительно меньше большинства найденных базовых образов с openjdk. Если использовать его для моего приложения, то результирующий размер получится 144 мегабайт.


Далее мы можем использовать spring-boot-runtime:openjdk-11-slim как базовый образ для всех spring boot приложений если они имеют схожие зависимости. В случае различных зависимостей, возможно использовать multistage сборку образа для каждого из приложений где на первом этапе будет собираться java runtime, а на втором добавляться архив с приложением.


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"]

Вывод


На текущий момент большинство docker образов для java имеют достаточно большой объем, что может негативно сказаться на времени старта приложения, особенно в случае если необходимых слоев еще нет на сервере. Используя теги с jre либо воспользовавшись модуляризацией java можно собрать собственный рантайм что позволит значительно сократить размер образа приложения.

Tags:
Hubs:
+11
Comments 13
Comments Comments 13

Articles