Spring Boot, Maven и Docker

Разработку и развёртывание современного серверного программного обеспечения невозможно представить себе без использования контейнеров, тем более, когда речь идёт о развёртывании в Kubernetes. В этой статье я продемонстрирую несколько вариантов того, как можно упаковать Maven-проект на основе Spring Boot в контейнер в формате OCI (Open Container Initiative), ну или попросту в Docker-контейнер.

Необходимые инструменты

Для выполнения сборки проекта и OCI-образов вам потребуется:

  • OpenJDK 17+ (протестировано на BellSoft Liberica OpenJDK 17.0.8.1)
  • Apache Maven 3.6.3+ (протестировано на Apache Maven 3.9.4)
  • Docker (протестировано на Docker 24.0.6)

Все примеры протестированы на Linux Mint 21.2 Victoria, но так же должны работать и на других операционных системах.

Подготовка проекта к упаковке в контейнер

Для упаковки приложения в контейнер потребуются все файлы, используемые приложением во время исполнения, и в случае с проектом на платформе Java в целом нам потребуются скомпилированные class-файлы проекта, различные ресурсы (конфигурационные yaml-файлы, html-страницы, изображения и т.д.), а так же jar-архивы с библиотеками.

При использовании Maven скомпилированные class-файлы и ресурсы мы можем взять из директории target/classes, в то время как jar-файлы библиотек лежат в локальном репозитории Maven (.m2/repository в домашней директории пользователя), и нам их нужно скопировать в одну директорию для упрощения работы с ними. Предлагаю рассмотреть два варианта копирования библиотек: при помощи Maven Dependency Plugin и при помощи Spring Boot Maven Plugin.

Копирование зависимостей при помощи Maven Dependency Plugin

Для использования Maven Dependency Plugin потребуется добавить в pom.xml соответствующий плагин:

Подробную документацию по этому плагину вы можете изучить на странице плагина.

Скопировать зависимости проекта можно при помощи команды:

Чтобы в директории с зависимостями оказались только зависимости, необходимые во время выполнения, я указал атрибут -DincludeScope=runtime, в противном случае были бы скопированы вообще все зависимости, включая тестовые.

Можно не добавлять плагин в проект, в этом случае вам придётся указывать полное имя плагина:

Результатом выполнения этой команды будет директория target/dependency со всеми зависимостями, требуемыми во время выполнения:

Если вы добавили плагин в файл pom.xml вашего проекта, то зависимости будут копироваться и при вызове команды mvn install.

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

Альтернативно вы можете скопировать вместо jar-архива вашего проекта директорию target/classes, тогда копирование и запуск приложения будут выглядеть так:

Примерно те же действия вам нужно будет выполнить при описании образа.

Сборка образа

Теперь можно перейти к написанию Dockerfile для образа нашего проекта:

Теперь мы можем собрать OCI-образ при помощи Docker:

При помощи аргумента -t задаётся тэг для собираемого образа, а точка в конце указывает на то, что в качестве сборочной директории нужно использовать текущую, в ней должен находиться Dockerfile.

Результат успешной сборки образа должен выглядеть примерно следующим образом:

Теперь мы можем создать и запустить контейнер из нашего образа:

Остановить и удалить контейнер мы можем при помощи docker stop и docker rm:

Так же мы можем передавать параметры для приложения через переменные окружения и аргументы:

В данном примере я задал HTTP-порт 8081 и активный профиль sandbox.

На данном этапе полная сборка проекта в образ состоит из двух команд:

Однако сборку образа мы можем сократить до одной команды docker build …​, переместив сборку maven-проекта тоже в Docker! Очевидная выгода от такого решения заключается в отсутствии необходимости в Maven и JDK на компьютере, достаточно будет только Docker. Для этого в самое начало нашего Dockerfile нужно дописать следующие строки:

После этого нужно внести изменения в копирование файлов собранного проекта:

Весь Dockerfile теперь выглядит следующим образом (комментарии опущены):

Теперь OCI-образ собирается одной командой:

Полученный образ не отличается скромными размерами, и в моём случае его размер получается почти 300Мб! Вы можете поэкспериментировать с используемыми JDK и JRE в основном образе, так замена bellsoft/liberica-openjdk-debian:17 на ubuntu/jre:17_edge уменьшает размер образа до 170Мб.

Spring Boot Maven Plugin и «толстый» JAR

Spring Boot Maven Plugin позволяет собирать «толстые» JAR-файлы, которые содержат не только классы и ресурсы вашего проекта, но и все зависимости, необходимые для его запуска. Получить «толстый» JAR при сборке проекта вы можете, добавив в сборку проекта Spring Boot Maven Plugin с целью repackage:

При использовании «толстого» JAR вам достаточно скопировать итоговый JAR-файл в образ и запустить его:

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

Распаковать этот JAR-архив можно при помощи команды:

В результате чего в директории target/lib будут находиться все файлы из архива, а зависимости — в директории target/lib/BOOT-INF/lib:

Теперь собрать OCI-образ, аналогичный тому, что я собирал без использования Spring Boot Maven Plugin, можно следующим образом:

Spring Boot: слои и JarLauncher

Ещё один альтернативный способ запуска приложения, доступный нам при использовании Spring Boot Maven Plugin — загрузка через JarLauncher. Кроме этого Spring Boot имеет встроенную поддержку слоёв, использование которых может сделать сборку Docker-образов более эффективной. Распаковать «толстый» JAR-архив с использованием слоёв можно при помощи команды:

В результате мы получим в директории target/extracted директории, соответствующие четырём слоям:

  • application — файлы нашего проекта
  • dependencies — runtime-зависимости проекта
  • snapshot-dependencies — SNAPSHOT-зависимости проекта
  • spring-boot-loader — файлы загрузчика Spring Boot

Для запуска приложения нужно будет скопировать все четыре директории и выполнить команду:

В Dockerfile это всё будет выглядеть следующим образом:

Spring Boot Maven Plugin и сборка образов

Spring Boot Maven Plugin также предоставляет возможность собрать OCI-образ при помощи Cloud Native Buildpacks, в этом случае вам даже не нужен Dockerfile, достаточно Spring Boot Maven Plugin в pom.xml вашего проекта. Сборка образа осуществляется при помощи команды:

Настроить параметры собираемого образа можно при помощи параметров Spring Boot Maven Plugin:

В указанном примере я указал своё название образа, хотя в данном случае это не обязательно, т.к. оно соответствует названию проекта. Так же я изменил список используемых Buildpacks, т.к. мне нужна была версия packetobuildpacks/bellsoft-liberica 10.4.0, а не 10.2.6, но несмотря на то, что изменения касаются только одного Buildpack, указывать нужно их все. Так же я при помощи <bindings> прокинул локальную директорию с самоподписанным CA-сертификатом в контейнер сборки, чтобы в дальнейшем приложение могло использовать защищённые соединения.

Полезные ссылки