Открыть список
Как стать автором
Обновить

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

JavaKubernetes
Tutorial

Добрый день.


Недавно передо мной встала задача запуска 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 можно собрать собственный рантайм что позволит значительно сократить размер образа приложения.

Теги:javadockerspring
Хабы: Java Kubernetes
Всего голосов 11: ↑11 и ↓0 +11
Просмотры10.6K

Комментарии 13

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Похожие публикации

Java/Kotlin разработчик
от 120 000 ₽СофтТелематикаМосква
Java разработчик
от 150 000 до 230 000 ₽KOTELOVСанкт-Петербург
Team Lead (Java)
от 280 000 ₽ITFB GroupМосква
Java разработчик
от 120 000 до 180 000 ₽Hunt4YouМожно удаленно
Java developer
от 150 000 до 300 000 ₽EmphasoftСанкт-Петербург

Лучшие публикации за сутки