1. Введение
В этом руководстве мы покажем, как создавать проекты Maven в Docker. Во-первых, мы начнем с простого одномодульного Java-проекта и покажем, как докеризировать процесс сборки, используя многоэтапные сборки в Docker. Далее мы покажем, как использовать Buildkit для кэширования зависимостей между несколькими сборками. В конце мы расскажем, как использовать кеш слоев в многомодульных приложениях.
2. Многоступенчатая многоуровневая сборка
В этой статье мы создадим простое Java-приложение с Guava в качестве зависимости. Мы создадим толстый JAR с помощью плагина maven-assembly . Код и конфигурация Maven будут сокращены из этой статьи, так как они не являются основной темой.
Многоэтапные сборки — отличный способ оптимизировать процесс сборки Docker. Они позволяют нам хранить весь процесс в одном файле, а также помогают нам сделать образ Docker как можно меньше . На первом этапе мы запустим сборку Maven и создадим наш толстый JAR, а на втором этапе мы скопируем JAR и определим точку входа:
FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD . $HOME
RUN mvn package
FROM openjdk:8-jdk-alpine
COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar
Этот подход позволяет нам уменьшить окончательный образ Docker, поскольку он не будет содержать исполняемые файлы Maven или наш исходный код.
Создадим Docker-образ:
docker build -t maven-caching .
Далее запустим контейнер из образа:
docker run maven-caching
Когда мы что-то изменим в коде и повторно запустим сборку, мы заметим, что все команды перед задачей пакета
Maven кэшируются и выполняются немедленно . Поскольку наш код меняется чаще, чем зависимости проекта, мы можем разделить загрузку зависимостей и компиляцию кода, чтобы сократить время сборки с помощью кеша слоя Docker :
FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD pom.xml $HOME
RUN mvn verify --fail-never
ADD . $HOME
RUN mvn package
FROM openjdk:8-jdk-alpine
COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar
Запуск последующих сборок, когда мы изменим только наш код, будет намного быстрее, так как Docker будет извлекать слои из кеша.
3. Кэширование с помощью BuildKit
В версии Docker 18.09 BuildKit представляет собой капитальный ремонт существующей системы сборки . Идея капитального ремонта состоит в том, чтобы улучшить производительность, управление хранилищем и безопасность. Мы можем использовать BuildKit для сохранения состояния между несколькими сборками. Таким образом, Maven не будет загружать зависимости каждый раз, поскольку у нас есть постоянное хранилище. Чтобы включить BuildKit в нашей установке Docker, нам нужно отредактировать файл daemon.json
:
...
{
"features": {
"buildkit": true
}}
...
После включения BuildKit мы можем изменить наш Dockerfile на:
FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD . $HOME
RUN --mount=type=cache,target=/root/.m2 mvn -f $HOME/pom.xml clean package
FROM openjdk:8-jdk-alpine
COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar
Когда мы меняем код или файл pom.xml
, Docker всегда будет выполнять ADD и RUN команду Maven. Время сборки будет самым долгим при первом запуске, так как Maven придется загружать зависимости. Последующие запуски будут использовать локальные зависимости и выполняться намного быстрее.
Этот подход требует поддержки томов Docker в качестве хранилища зависимостей. Иногда нам приходится заставлять Maven обновлять наши зависимости с помощью флага -U
в Dockerfile.
4. Кэширование для многомодульных проектов Maven
В предыдущих разделах мы показали, как можно использовать различные методы для ускорения сборки образов Docker для одномодульного проекта Maven. Для более сложных приложений эти методы не оптимальны. Многомодульные проекты Maven обычно имеют один модуль, который является точкой входа нашего приложения. Один или несколько модулей содержат нашу логику и перечислены как зависимости.
Поскольку подмодули перечислены как зависимости, они не позволят Docker кэшировать слои и заставят Maven снова загрузить все зависимости. Это решение с BuildKit хорошо в большинстве случаев , но, как мы уже говорили, время от времени может потребоваться принудительное обновление для получения обновленных подмодулей. Чтобы избежать таких ситуаций, мы можем разделить наш проект на слои и использовать инкрементальные сборки Maven:
FROM maven:alpine as build
ENV HOME=/usr/app
RUN mkdir -p $HOME
WORKDIR $HOME
ADD pom.xml $HOME
ADD core/pom.xml $HOME/core/pom.xml
ADD runner/pom.xml $HOME/runner/pom.xml
RUN mvn -pl core verify --fail-never
ADD core $HOME/core
RUN mvn -pl core install
RUN mvn -pl runner verify --fail-never
ADD runner $HOME/runner
RUN mvn -pl core,runner package
FROM openjdk:8-jdk-alpine
COPY --from=build /usr/app/runner/target/runner-0.0.1-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar
ENTRYPOINT java -jar /app/runner.jar
В этом Dockerfile мы копируем все файлы pom.xml
и постепенно собираем каждый подмодуль, а затем в конце упаковываем все приложение. Эмпирическое правило заключается в том, что мы создаем подмодули, которые чаще изменяются позже в цепочке.
5. Вывод
В этой статье мы рассмотрели, как создавать проекты Maven с помощью Docker. Во-первых, мы рассмотрели, как использовать многоуровневость для кэширования частей, которые не изменяются часто. Далее мы рассмотрели, как использовать BuildKit для сохранения состояния между сборками. В конце мы показали, как создавать многомодульные проекты Maven с инкрементными сборками. Как всегда, полный код можно найти на GitHub .