web-dev-qa-db-fra.com

Dépendances du cache du docker Maven

J'essaie d'utiliser Docker pour automatiser les builds Maven. Le projet que je veux construire prend près de 20 minutes pour télécharger toutes les dépendances, j'ai donc essayé de construire une image docker qui mettrait en cache ces dépendances, mais il ne semble pas l'enregistrer. Mon Dockerfile est

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

L'image se construit et télécharge tout. Cependant, l'image résultante est de la même taille que la base maven:Alpine image, il ne semble donc pas avoir mis en cache les dépendances dans l'image. Lorsque j'essaie d'utiliser l'image pour mvn compile il passe par les 20 minutes complètes de tout retélécharger.

Est-il possible de créer une image maven qui met en cache mes dépendances afin qu'elles n'aient pas à télécharger à chaque fois que j'utilise l'image pour effectuer une génération?

J'exécute les commandes suivantes:

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

Ma compréhension est que tout ce que RUN fait pendant le processus de construction du docker devient une partie de l'image résultante.

27
Daniel Watrous

Généralement, il n'y a aucun changement dans pom.xml fichier mais juste quelques autres modifications du code source lorsque vous essayez de démarrer la construction de l'image Docker. Dans de telles circonstances, vous pouvez le faire:

Pour info:

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"]

La clé est donc:

  1. ajouter pom.xml fichier.

  2. puis mvn verify --fail-never it, il téléchargera les dépendances maven.

  3. ajoutez ensuite tout votre fichier source et lancez votre compilation (mvn package).

Lorsqu'il y a des changements dans votre pom.xml fichier ou si vous exécutez ce script pour la première fois, docker fera 1 -> 2 -> 3. Lorsqu'il n'y a aucun changement dans pom.xml fichier, docker sautera l'étape 1、2 et fera directement 3.

Cette astuce simple peut être utilisée dans de nombreuses autres circonstances de gestion de paquets (gradle 、 fil 、 npm 、 pip).

Modifier:

Vous devriez également envisager d'utiliser mvn dependency:resolve ou mvn dependency:go-offline en conséquence, comme le suggèrent d'autres commentaires et réponses.

25
Kim

Il s'avère que l'image que j'utilise comme base a une image parent qui définit

VOLUME "$USER_HOME_DIR/.m2"

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

Le résultat est que lors de la construction, tous les fichiers sont écrits dans $USER_HOME_DIR/.m2, mais comme il devrait s'agir d'un volume, aucun de ces fichiers n'est conservé avec l'image du conteneur.

Actuellement, dans Docker, il n'y a aucun moyen de désenregistrer cette définition de volume, il serait donc nécessaire de créer une image maven distincte, plutôt que d'utiliser l'image maven officielle.

7
Daniel Watrous

@Kim est le plus proche, mais il n'est pas encore tout à fait là. Je ne pense pas que l'ajout de --fail-never est correct, même si le travail est fait.

La commande verify provoque l'exécution de nombreux plugins, ce qui est un problème (pour moi) - je ne pense pas qu'ils devraient être exécutés quand tout ce que je veux c'est installer des dépendances! J'ai également une construction multi-modules et une sous-construction javascript, ce qui complique encore la configuration.

Mais exécuter uniquement verify ne suffit pas, car si vous exécutez install dans les commandes suivantes, il y aura plus de plugins utilisés - ce qui signifie plus de dépendances à télécharger - maven refuse de les télécharger autrement. Lecture pertinente: Maven: Introduction to the Build Lifecycle

Vous devez essentiellement trouver quelles propriétés désactivent chaque plugin et les ajouter un par un, afin qu'elles ne cassent pas votre build.

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

mais certains plugins ne sont pas si faciles à ignorer - je ne suis pas un expert en maven (donc je ne sais pas pourquoi il ignore l'option cli - ce pourrait être un bug), mais le suivant fonctionne comme prévu pour 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>

veuillez noter le <skip>${maven.exec.skip}</skip> - d'autres plugins récupèrent cela dans les paramètres cli mais pas celui-ci (ni -Dmaven.exec.skip=true ni -Dexec.skip=true travailler seul)

J'espère que cela t'aides

4
Filip Procházka

Similaire avec la réponse @Kim mais j'utilise dependency:resolve commande mvn. Voici donc mon Dockerfile complet:

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"]
2
ikandars

Après quelques jours de difficultés, j'ai réussi à faire cette mise en cache plus tard en utilisant un contrainer intermédiaire, et je voudrais résumer mes résultats ici car ce sujet est si utile et apparaît fréquemment dans la page d'accueil de la recherche Google:

  1. La réponse de Kim ne fonctionne qu'à une certaine condition: pom.xml ne peut pas être changé, et Maven fait une mise à jour régulière par défaut
  2. dépendance mvn: go-offline -B --fail-n'a jamais d'inconvénient similaire, donc si vous avez besoin d'extraire du code de repo, il y a de fortes chances que Maven déclenche une vérification complète à chaque fois
  3. Le volume de montage ne fonctionne pas aussi bien car nous devons résoudre les dépendances lors de la construction de l'image
  4. Enfin, j'ai une solution viable combinée (peut ne pas fonctionner pour les autres):
    • Construisez une image pour résoudre toutes les dépendances en premier (Image non intermédiaire)
    • Créez un autre Dockerfile avec une image intermédiaire, des exemples de Dockerfiles comme ceci:
#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

L'idée est de conserver toutes les dépendances dans une image différente que Maven peut utiliser immédiatement

Cela pourrait être d'autres scénarios que je n'ai pas encore rencontrés, mais cette solution me soulage un peu du téléchargement de 3 Go de déchets chaque fois que je ne peux pas imaginer pourquoi Java est devenu une grosse baleine dans le monde maigre d'aujourd'hui

1
Calvin Zhou

Je ne pense pas que les autres réponses ici soient optimales. Par exemple, le mvn verify answer exécute les phases suivantes et fait bien plus que simplement résoudre les dépendances:

valider - valider le projet est correct et toutes les informations nécessaires sont disponibles

compile - compile le code source du projet

test - testez le code source compilé en utilisant un cadre de test unitaire approprié. Ces tests ne devraient pas exiger que le code soit empaqueté ou déployé

package - prenez le code compilé et empaquetez-le dans son format distribuable, tel qu'un JAR.

vérifier - effectuer des vérifications sur les résultats des tests d'intégration pour s'assurer que les critères de qualité sont respectés

Toutes ces phases et leurs objectifs associés n'ont pas besoin d'être exécutés si vous souhaitez uniquement résoudre les dépendances.

Si vous souhaitez uniquement résoudre les dépendances, vous pouvez utiliser le dependency:go-offline objectif:

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

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

COPY src/ src/
RUN mvn package
1

Utilisation de BuildKit

De Docker v18.03 à partir de là, vous pouvez utiliser BuildKit au lieu des volumes mentionnés dans les autres réponses. Il permet de monter des caches qui peuvent persister entre les builds et vous pouvez éviter de télécharger le contenu du .m2/repository à chaque fois.

En supposant que le Dockerfile se trouve à la racine de votre projet:

# 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 monte le cache à l'endroit spécifié dans l'image maven Dockerfile docs .

Pour la construction, vous pouvez exécuter la commande suivante:

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

Plus d'informations sur BuildKit peuvent être trouvées ici .

0
Farzad Vertigo

Il existe deux façons de mettre en cache les dépendances maven:

  1. Exécutez "mvn verify" dans le cadre d'une exécution de conteneur, PAS de build, et assurez-vous de monter .m2 à partir d'un volume.

    C'est efficace, mais cela ne fonctionne pas bien avec la génération de cloud et plusieurs esclaves de construction

  2. Utilisez un "conteneur de cache de dépendances" et mettez-le à jour régulièrement. Voici comment:

    une. Créez un Dockerfile qui copie le pom et créez des dépendances hors ligne:

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

    b. Construisez-le périodiquement (par exemple tous les soirs) en tant que "Deps: latest"

    c. Créez un autre Dockerfile pour réellement construire le système par commit (utilisez de préférence plusieurs étapes) - et assurez-vous qu'il s'agit de FROM Deps.

En utilisant ce système, vous aurez des versions rapides et reconstructibles avec un cache généralement assez bon.

0
Omri Spector

J'ai eu ce problème il y a peu de temps. Il existe de nombreuses solutions sur le Web, mais celle qui a fonctionné pour moi consiste simplement à monter un volume pour le répertoire des modules maven:

mkdir /opt/myvolumes/m2

puis dans le Dockerfile:

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

Il existe de meilleures solutions, mais pas aussi simples.

Ce billet de blog fait un effort supplémentaire pour vous aider à tout mettre en cache:

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

0
Bruno9779