Зависимости кеша Maven докеров

Я пытаюсь использовать докер для автоматизации сборки maven. Проект, который я хочу построить, занимает около 20 минут, чтобы загрузить все зависимости, поэтому я попытался создать образ докера, который будет кэшировать эти зависимости, но он, похоже, не сохраняет его. Мой файл докеров

FROM maven:alpine
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
ADD pom.xml /usr/src/app
RUN mvn dependency:go-offline

Изображение строит, и он загружает все. Однако результирующее изображение имеет тот же размер, что и базовый образ maven:alpine, поэтому он, похоже, не кэшировал зависимости в изображении. Когда я пытаюсь использовать изображение в mvn compile, он проходит через полные 20 минут перезагрузки всего.

Можно ли создать изображение maven, которое кэширует мои зависимости, чтобы они не загружались каждый раз, когда я использую изображение для выполнения сборки?

Я запускаю следующие команды:

docker build -t my-maven .

docker run -it --rm --name my-maven-project -v "$PWD":/usr/src/mymaven -w /usr/src/mymaven my-maven mvn compile

Я понимаю, что все, что RUN делает во время процесса сборки докеров, становится частью результирующего изображения.

Ответ 1

Обычно нет никаких изменений в файле pom.xml, но только некоторые другие изменения исходного кода, когда вы пытаетесь запустить сборку образа Docker. При таких обстоятельствах вы можете сделать это:

FYI:

FROM maven:3-jdk-8

ENV HOME=/home/usr/app

RUN mkdir -p $HOME

WORKDIR $HOME

# 1. add pom.xml only here

ADD pom.xml $HOME

# 2. start downloading dependencies

RUN ["/usr/local/bin/mvn-entrypoint.sh", "mvn", "verify", "clean", "--fail-never"]

# 3. add all source code and start compiling

ADD . $HOME

RUN ["mvn", "package"]

EXPOSE 8005

CMD ["java", "-jar", "./target/dist.jar"]

Итак, ключ:

  1. добавить файл pom.xml.

  2. затем mvn verify --fail-never загрузит maven-зависимости.

  3. Затем добавьте все свои исходные файлы и начните компиляцию (mvn package).

Если в файле pom.xml есть изменения или вы запускаете этот скрипт в первый раз, docker выполнит 1 → 2 → 3. Если в файле pom.xml нет изменений, Docker пропустит шаг 1、2 и сделать 3 напрямую.

Этот простой трюк может быть использован во многих других ситуациях управления пакетами (gradle 、 yarn pm npm pip).

Изменить:

Вам также следует рассмотреть возможность использования mvn dependency:resolve или mvn dependency:go-offline соответственно в качестве других комментариев & ответы предполагают.

Ответ 2

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

VOLUME "$USER_HOME_DIR/.m2"

см.: https://github.com/carlossg/docker-maven/blob/322d0dff5d0531ccaf47bf49338cb3e294fd66c8/jdk-8/Dockerfile

В результате во время сборки все файлы записываются в $USER_HOME_DIR/.m2, но, поскольку ожидается, что это будет том, ни один из этих файлов не будет сохранен с образом контейнера.

В настоящее время в Docker нет способа отменить регистрацию этого определения тома, поэтому необходимо создать отдельный образ maven, а не использовать официальный образ maven.

Ответ 3

@Kim ближе всего, но пока не совсем. Я не думаю, что добавление --fail-never является правильным, даже через это сделать работу.

Команда verify вызывает много плагинов для выполнения, что является проблемой (для меня) - я не думаю, что они должны выполняться, когда все, что я хочу, это установить зависимости! У меня также есть многомодульная сборка и сборка javascript, так что это еще больше усложняет настройку.

Но запуска только verify недостаточно, потому что если вы запустите install в следующих командах, будет использоваться больше плагинов - что означает больше зависимостей для загрузки - maven отказывается загружать их в противном случае. Соответствующее чтение: Maven: введение в жизненный цикл сборки

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

WORKDIR /srv

# cache Maven dependencies
ADD cli/pom.xml /srv/cli/
ADD core/pom.xml /srv/core/
ADD parent/pom.xml /srv/parent/
ADD rest-api/pom.xml /srv/rest-api/
ADD web-admin/pom.xml /srv/web-admin/
ADD pom.xml /srv/
RUN mvn -B clean install -DskipTests -Dcheckstyle.skip -Dasciidoctor.skip -Djacoco.skip -Dmaven.gitcommitid.skip -Dspring-boot.repackage.skip -Dmaven.exec.skip=true -Dmaven.install.skip -Dmaven.resources.skip

# cache YARN dependencies
ADD ./web-admin/package.json ./web-admin/yarn.lock /srv/web-admin/
RUN yarn --non-interactive --frozen-lockfile --no-progress --cwd /srv/web-admin install

# build the project
ADD . /srv
RUN mvn -B clean install

но некоторые плагины не так легко пропустить - я не эксперт по maven (поэтому я не знаю, почему он игнорирует опцию cli - это может быть ошибка), но следующее работает как и ожидалось для org.codehaus.mojo:exec-maven-plugin

<project>
    <properties>
        <maven.exec.skip>false</maven.exec.skip>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.3.2</version>
                <executions>
                    <execution>
                        <id>yarn install</id>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <phase>initialize</phase>
                        <configuration>
                            <executable>yarn</executable>
                            <arguments>
                                <argument>install</argument>
                            </arguments>
                            <skip>${maven.exec.skip}</skip>
                        </configuration>
                    </execution>
                    <execution>
                        <id>yarn run build</id>
                        <goals>
                            <goal>exec</goal>
                        </goals>
                        <phase>compile</phase>
                        <configuration>
                            <executable>yarn</executable>
                            <arguments>
                                <argument>run</argument>
                                <argument>build</argument>
                            </arguments>
                            <skip>${maven.exec.skip}</skip>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

обратите внимание на явный <skip>${maven.exec.skip}</skip> - другие плагины выбирают это из параметров cli, но не этот (ни -Dmaven.exec.skip=true ни -Dexec.skip=true работа сама по себе)

Надеюсь это поможет

Ответ 4

Аналогично с ответом @Kim, но я использую dependency:resolve решите команду mvn. Итак, вот мой полный Dockerfile:

FROM maven:3.5.0-jdk-8-alpine

WORKDIR /usr/src/app

# First copy only the pom file. This is the file with less change
COPY ./pom.xml .

# Download the package and make it cached in docker image
RUN mvn -B -f ./pom.xml -s /usr/share/maven/ref/settings-docker.xml dependency:resolve

# Copy the actual code
COPY ./ .

# Then build the code
RUN mvn -B -f ./pom.xml -s /usr/share/maven/ref/settings-docker.xml package

# The rest is same as usual
EXPOSE 8888

CMD ["java", "-jar", "./target/YOUR-APP.jar"]

Ответ 5

Я не думаю, что другие ответы здесь являются оптимальными. Например, ответ mvn verify выполняет следующие этапы и делает гораздо больше, чем просто разрешает зависимости:

validate - подтвердить правильность проекта и получить всю необходимую информацию

compile - скомпилировать исходный код проекта

test - протестируйте скомпилированный исходный код, используя подходящую среду модульного тестирования. Эти тесты не должны требовать, чтобы код был упакован или развернут

package - взять скомпилированный код и упаковать его в распространяемый формат, такой как JAR.

проверить - выполнить любые проверки результатов интеграционных тестов, чтобы убедиться в соответствии критериям качества

Все эти этапы и связанные с ними цели не нужно запускать, если вы хотите разрешить зависимости.

Если вы хотите разрешить только зависимости, вы можете использовать dependency:go-offline цель:

FROM maven:3-jdk-12
WORKDIR /tmp/example/

COPY pom.xml .
RUN mvn dependency:go-offline

COPY src/ src/
RUN mvn package

Ответ 6

После нескольких дней борьбы мне удалось сделать это кеширование позже, используя промежуточный контрайнер, и я хотел бы обобщить мои выводы здесь, так как эта тема настолько полезна и часто показывается на главной странице поиска Google:

  1. Ответ Kim работает только при определенном условии: pom.xml не может быть изменен, плюс Maven регулярно обновляет ежедневно по умолчанию
  2. Зависимость mvn: go-offline -B --fail-никогда не имеет подобного недостатка, поэтому, если вам нужно получить свежий код из репо, высока вероятность того, что Maven будет запускать полную проверку каждый раз
  3. Монтирование тома также не работает, потому что нам нужно разрешить зависимости во время сборки образа
  4. Наконец, у меня есть совместимое работоспособное решение (может не работать с другими):
    • Сначала создайте изображение, чтобы разрешить все зависимости (не промежуточное изображение)
    • Создайте еще один Dockerfile с промежуточным изображением, с примерами dockerfile:
#docker build -t dependencies .
From ubuntu
COPY pom.xml pom.xml
RUN mvn dependency:go-offline -B --fail-never
From dependencies as intermediate

From tomcat
RUN git pull repo.git (whatsoever)
RUN mvn package

Идея состоит в том, чтобы сохранить все зависимости в другом изображении, которое Maven может использовать немедленно

Могут быть и другие сценарии, с которыми я еще не сталкивался, но это решение немного облегчает мне загрузку 3ГБ мусора каждый раз, когда я не могу представить, почему Java стала таким жирным китом в современном скудном мире

Ответ 7

У меня была эта проблема только litle. Множество решений в Интернете, но тот, который работал у меня, просто монтирует том для каталога maven modules:

mkdir /opt/myvolumes/m2

затем в файле Docker:

...
VOLUME /opt/myvolumes/m2:/root/.m2
...

Есть лучшие решения, но не так просто.

Это сообщение в блоге добавляет лишнюю милю, помогая вам кэшировать все:

https://keyholesoftware.com/2015/01/05/caching-for-maven-docker-builds/

Ответ 8

Существует два способа кэширования зависимостей maven:

  1. Выполните "mvn verify" как часть выполнения контейнера, НЕ создавайте и убедитесь, что вы монтируете .m2 из тома.

    Это эффективно, но плохо работает с облачной сборкой и несколькими ведомыми ведомыми

  2. Используйте "контейнер кэша зависимостей" и периодически обновляйте его. Вот как это делается:

    а. Создайте Dockerfile, который копирует pom, и создайте автономные зависимости:

    FROM maven:3.5.3-jdk-8-alpine
    WORKDIR /build
    COPY pom.xml .
    RUN mvn dependency:go-offline
    

    б. Создавайте его периодически (например, по ночам) как "Deps: latest"

    с. Создайте еще один Dockerfile для фактического построения системы в соответствии с фиксацией (желательно с использованием многоэтапного) - и убедитесь, что это FROM Deps.

Используя эту систему, вы будете иметь быстрые, восстанавливаемые сборки с достаточно хорошим кешем.

Ответ 9

Использование BuildKit

Начиная с Docker v18.03, вы можете использовать BuildKit вместо томов, которые были упомянуты в других ответах. Это позволяет монтировать кэши, которые могут сохраняться между сборками, и вы можете избежать загрузки содержимого соответствующего .m2/repository каждый раз.

Предполагая, что Dockerfile находится в корне вашего проекта:

# syntax = docker/dockerfile:1.0-experimental

FROM maven:3.6.0-jdk-11-slim AS build
COPY . /home/build
RUN mkdir /home/.m2
WORKDIR /home/.m2
USER root
RUN --mount=type=cache,target=/root/.m2 mvn -f /home/build/pom.xml clean compile

target=/root/.m2 монтирует кеш в указанное место в Dockerfile с изображением maven image docs.

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

DOCKER_BUILDKIT=1 docker build --rm --no-cache  .   

Более подробную информацию о BuildKit можно найти здесь.

Ответ 10

Если зависимости загружаются после того, как контейнер уже вставлен, вам необходимо зафиксировать изменения в этом контейнере и создать новое изображение с загруженными артефактами.