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