Comment dockerize projet maven? et combien de façons de l'accomplir?

87

Je suis nouveau sur Docker et je ne sais pas comment exécuter un projet java avec maven même si j'ai lu de nombreux documents et essayé de nombreuses méthodes.

  1. Dois-je construire l'image en utilisant Dockerfile?
  2. A quoi ressemblent les commandes quand il s'agit d'exécuter le projet maven dans l'hôte avec Dockerfile?
Yashon Lin
la source

Réponses:

122

Exemple de travail.

Ce n'est pas un tutoriel de démarrage à ressort. C'est la réponse mise à jour à une question sur la façon d'exécuter une build Maven dans un conteneur Docker.

Question postée il y a 4 ans.

1. Générez une application

Utilisez l'initialiseur de printemps pour générer une application de démonstration

https://start.spring.io/

entrez la description de l'image ici

Extraire l'archive zip localement

2. Créez un Dockerfile

#
# Build stage
#
FROM maven:3.6.0-jdk-11-slim AS build
COPY src /home/app/src
COPY pom.xml /home/app
RUN mvn -f /home/app/pom.xml clean package

#
# Package stage
#
FROM openjdk:11-jre-slim
COPY --from=build /home/app/target/demo-0.0.1-SNAPSHOT.jar /usr/local/lib/demo.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/local/lib/demo.jar"]

Remarque

  • Cet exemple utilise une génération en plusieurs étapes . La première étape est utilisée pour construire le code. La deuxième étape ne contient que le jar construit et un JRE pour l'exécuter (notez comment le jar est copié entre les étapes).

3. Construisez l'image

docker build -t demo .

4. Exécutez l'image

$ docker run --rm -it demo:latest

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.3.RELEASE)

2019-02-22 17:18:57.835  INFO 1 --- [           main] com.example.demo.DemoApplication         : Starting DemoApplication v0.0.1-SNAPSHOT on f4e67677c9a9 with PID 1 (/usr/local/bin/demo.jar started by root in /)
2019-02-22 17:18:57.837  INFO 1 --- [           main] com.example.demo.DemoApplication         : No active profile set, falling back to default profiles: default
2019-02-22 17:18:58.294  INFO 1 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 0.711 seconds (JVM running for 1.035)

Divers

Lisez la documentation du concentrateur Docker sur la façon dont la version Maven peut être optimisée pour utiliser un référentiel local pour mettre en cache les fichiers JAR.

Mise à jour (2019-02-07)

Cette question a maintenant 4 ans et pendant ce temps, il est juste de dire que la création d'applications à l'aide de Docker a subi des changements importants.

Option 1: construction en plusieurs étapes

Ce nouveau style vous permet de créer des images plus légères qui n'encapsulent pas vos outils de construction et votre code source.

L'exemple ici utilise à nouveau l' image de base officielle de maven pour exécuter la première étape de la construction en utilisant une version souhaitée de Maven. La deuxième partie du fichier définit la façon dont le pot construit est assemblé dans l'image de sortie finale.

FROM maven:3.5-jdk-8 AS build  
COPY src /usr/src/app/src  
COPY pom.xml /usr/src/app  
RUN mvn -f /usr/src/app/pom.xml clean package

FROM gcr.io/distroless/java  
COPY --from=build /usr/src/app/target/helloworld-1.0.0-SNAPSHOT.jar /usr/app/helloworld-1.0.0-SNAPSHOT.jar  
EXPOSE 8080  
ENTRYPOINT ["java","-jar","/usr/app/helloworld-1.0.0-SNAPSHOT.jar"]  

Remarque:

  • J'utilise l' image de base sans distraction de Google , qui s'efforce de fournir juste assez de temps d'exécution pour une application java.

Option 2: flèche

Je n'ai pas utilisé cette approche mais semble digne d'être enquêtée car elle vous permet de créer des images sans avoir à créer des choses désagréables comme Dockerfiles :-)

https://github.com/GoogleContainerTools/jib

Le projet dispose d'un plugin Maven qui intègre l'empaquetage de votre code directement dans votre workflow Maven.


Réponse originale (incluse pour être complète, mais écrite il y a longtemps)

Essayez d'utiliser les nouvelles images officielles, il y en a une pour Maven

https://registry.hub.docker.com/_/maven/

L'image peut être utilisée pour exécuter Maven au moment de la génération pour créer une application compilée ou, comme dans les exemples suivants, pour exécuter une build Maven dans un conteneur.

Exemple 1 - Maven s'exécutant dans un conteneur

La commande suivante exécute votre build Maven dans un conteneur:

docker run -it --rm \
       -v "$(pwd)":/opt/maven \
       -w /opt/maven \
       maven:3.2-jdk-7 \
       mvn clean install

Remarques:

  • L'avantage de cette approche est que tous les logiciels sont installés et exécutés dans le conteneur. Seulement besoin de docker sur la machine hôte.
  • Voir Dockerfile pour cette version

Exemple 2 - Utiliser Nexus pour mettre en cache des fichiers

Exécutez le conteneur Nexus

docker run -d -p 8081:8081 --name nexus sonatype/nexus

Créez un fichier "settings.xml":

<settings>
  <mirrors>
    <mirror>
      <id>nexus</id>
      <mirrorOf>*</mirrorOf>
      <url>http://nexus:8081/content/groups/public/</url>
    </mirror>
  </mirrors>
</settings>

Maintenant, exécutez Maven liant au conteneur nexus, de sorte que les dépendances soient mises en cache

docker run -it --rm \
       -v "$(pwd)":/opt/maven \
       -w /opt/maven \
       --link nexus:nexus \
       maven:3.2-jdk-7 \
       mvn -s settings.xml clean install

Remarques:

  • Un avantage de l'exécution de Nexus en arrière-plan est que d'autres référentiels tiers peuvent être gérés via l'URL d'administration de manière transparente pour les builds Maven exécutés dans des conteneurs locaux.
Mark O'Connor
la source
cela peut-il être utilisé pour remplacer le maven central pour une construction gradle? comme indiqué dans support.sonatype.com/entries/ ... J'ai remplacé mavenCentral()dans mes dépendances gradle par maven {url "http://nexus:8081..."et je reçois maintenant juste des problèmes de résolution.
mohamnag
@mohamnag Correct, le fichier "paramètres" Maven ci-dessus fait exactement cela, en redirigeant toutes les requêtes Maven Central vers le référentiel nexus local. Vous devez décrire le type de problèmes de résolution que vous rencontrez. Pourrait être quelque chose ... Par exemple, avez-vous configuré le lien Docker, de sorte que l'hôte "nexus" soit correctement résolu?
Mark O'Connor
Je me suis avéré que tout allait bien, mais nexus avait besoin de temps pour construire l'index ou quelque chose d'autre a causé le problème. J'ai également essayé de déclencher une mise à jour d'index, je ne suis donc pas sûr de savoir quel cas a résolu le problème. Merci cependant.
mohamnag
Le Nexus 3 est désormais également disponible en tant que conteneur Docker. Utiliser "sonatype / nexus3"
Thorbjørn Ravn Andersen
1
@avandeursen Vous avez raison de dire que le paramètre --link est obsolète (réponse datant de plus de 3 ans). Cependant, la solution la plus correcte (à mon avis) est de créer un réseau docker et d'exécuter les deux conteneurs dessus. De cette façon, vous pouvez tirer parti de la fonctionnalité DNS native de Docker et continuer à faire référence au conteneur nexus par son nom.
Mark O'Connor
47

Il peut y avoir de nombreuses façons.

L'exemple donné est celui du projet maven.

1. Utilisation de Dockerfile dans le projet maven

Utilisez la structure de fichiers suivante:

Demo
└── src
|    ├── main
|    │   ├── java
|    │       └── org
|    │           └── demo
|    │               └── Application.java
||    └── test
|
├──── Dockerfile
├──── pom.xml

Et mettez à jour le Dockerfile comme:

FROM java:8
EXPOSE 8080
ADD /target/demo.jar demo.jar
ENTRYPOINT ["java","-jar","demo.jar"]

Accédez au dossier du projet et tapez la commande suivante, vous serez capable de créer une image et d'exécuter cette image:

$ mvn clean
$ mvn install
$ docker build -f Dockerfile -t springdemo .
$ docker run -p 8080:8080 -t springdemo

Obtenir la vidéo sur Spring Boot avec Docker

2. Utilisation des plugins Maven

Ajouter le plugin maven donné dans pom.xml

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.4.5</version>
        <configuration>
            <imageName>springdocker</imageName>
            <baseImage>java</baseImage>
            <entryPoint>["java", "-jar", "/${project.build.finalName}.jar"]</entryPoint>
            <resources>
                <resource>
                    <targetPath>/</targetPath>
                    <directory>${project.build.directory}</directory>
                    <include>${project.build.finalName}.jar</include>
                </resource>
            </resources>
        </configuration>
    </plugin>

Accédez au dossier du projet et tapez la commande suivante, vous pourrez créer une image et exécuter cette image:

$ mvn clean package docker:build
$ docker images
$ docker run -p 8080:8080 -t <image name>

Dans le premier exemple, nous créons Dockerfile et fournissons une image de base et ajoutons un jar, après cela, nous exécuterons la commande docker pour créer une image avec un nom spécifique, puis exécuterons cette image.

Alors que dans le deuxième exemple, nous utilisons le plugin maven dans lequel nous fournissons baseImageet imageNameque nous n'avons donc pas besoin de créer Dockerfile ici .. après l'empaquetage du projet maven, nous obtiendrons l'image docker et nous avons juste besoin d'exécuter cette image.

Riddhi Gohil
la source
Plutôt que de modifier le point d'entrée pour spécifier le nom de l'artefact, vous pouvez utiliser une approche comme ici: alooma.com/blog/building-dockers - utilisez maven -dependency-plugin pour utiliser un nom commun. Pas besoin de mettre un fichier jar versionné dans le conteneur docker car le conteneur lui-même est versionné.
kboom
14

En règle générale, vous devez créer un JAR volumineux à l' aide de Maven (un JAR qui contient à la fois votre code et toutes les dépendances).

Ensuite, vous pouvez écrire un Dockerfile qui correspond à vos besoins (si vous pouvez créer un gros JAR, vous n'aurez besoin que d' un système d' exploitation de base, comme CentOS, et de la JVM).

C'est ce que j'utilise pour une application Scala (qui est basée sur Java).

FROM centos:centos7

# Prerequisites.

RUN yum -y update
RUN yum -y install wget tar

# Oracle Java 7

WORKDIR /opt

RUN wget --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/7u71-b14/server-jre-7u71-linux-x64.tar.gz
RUN tar xzf server-jre-7u71-linux-x64.tar.gz
RUN rm -rf server-jre-7u71-linux-x64.tar.gz
RUN alternatives --install /usr/bin/java java /opt/jdk1.7.0_71/bin/java 1

# App

USER daemon

# This copies to local fat jar inside the image
ADD /local/path/to/packaged/app/appname.jar /app/appname.jar

# What to run when the container starts
ENTRYPOINT [ "java", "-jar", "/app/appname.jar" ]

# Ports used by the app
EXPOSE 5000

Cela crée une image basée sur CentOS avec Java7. Une fois démarré, il exécutera votre fichier jar d'application.

La meilleure façon de le déployer est via le registre Docker, c'est comme un Github pour les images Docker.

Vous pouvez créer une image comme celle-ci:

# current dir must contain the Dockerfile
docker build -t username/projectname:tagname .

Vous pouvez ensuite pousser une image de cette manière:

docker push username/projectname # this pushes all tags

Une fois l'image sur le registre Docker, vous pouvez la récupérer de n'importe où dans le monde et l'exécuter.

Consultez le Guide de l'utilisateur de Docker pour plus d'informations.

Quelque chose à garder à l'esprit :

Vous pouvez également extraire votre référentiel dans une image et créer le fichier jar dans le cadre de l'exécution du conteneur, mais ce n'est pas une bonne approche, car le code pourrait changer et vous pourriez finir par utiliser une version différente de l'application sans préavis.

Construire un gros pot élimine ce problème.

Matteo Pacini
la source
Salut, j'ai utilisé votre exemple dans mon fichier docker pour copier le fat jar dans l'image mais la construction échoue car il n'est pas possible de trouver le fat jar étant donné le chemin local. Est-ce quelque chose comme target / app.jar?
user_mda
Salut, existe-t-il un moyen de télécharger l'artefact à partir de nexus au moment de l'exécution? Pour spécifier l'artefact à télécharger à l'aide des propriétés et non d'un lien réel vers le fichier jar lui-même? Dans le dockerfile: RUN wget -O {project.build.finalname}.jar Mais je veux télécharger le fichier ci-dessus à partir de nexus.
Pramod Setlur
1
Inclure un FAT JAR pour coder le repo est lent pour moi. Existe-t-il un moyen d'utiliser maven pour créer l'image?
WoLfPwNeR
1
Les gros pots ont aussi quelques problèmes, en particulier avec les pots signés.
Thorbjørn Ravn Andersen
1
En règle générale, n'utilisez jamais de fat jar, les packages qui reposent sur le manifeste ou d'autres fichiers de données dans leur jar sont susceptibles d'échouer car il n'y a pas de moyen sûr de fusionner ces données. Fichiers manifestes avec des chemins correspondants à l'intérieur d'un conflit de jar et le premier ou le dernier gagne (je ne me souviens plus lequel). Le seul moment où vous devriez envisager d'utiliser un fat jar est si vous avez un ensemble très limité de dépendances et que vous savez très bien que leurs fichiers jars n'ont pas de données de manifeste conflictuelles. Sinon, utilisez les bocaux en toute sécurité car ils ont été conçus pour être utilisés (c'est-à-dire séparément).
PiersyP
3

Voici ma contribution.
Je n'essaierai pas de lister tous les outils / bibliothèques / plugins qui existent pour tirer parti de Docker avec Maven. Certaines réponses l'ont déjà fait.
au lieu de, je me concentrerai sur la typologie des applications et la manière Dockerfile.
Dockerfileest vraiment un concept simple et important de Docker (toutes les images connues / publiques en dépendent) et je pense qu'essayer d'éviter de comprendre et d'utiliser Dockerfiles n'est pas nécessairement la meilleure façon d'entrer dans le monde de Docker.

L'ancrage d'une application dépend de l'application elle-même et de l'objectif à atteindre

1) Pour les applications que nous voulons continuer à exécuter sur un serveur Java installé / autonome (Tomcat, JBoss, etc ...)

La route est plus difficile et ce n'est pas la cible idéale car cela ajoute de la complexité (il faut gérer / maintenir le serveur) et c'est moins évolutif et moins rapide que les serveurs embarqués en termes de build / déploiement / annulation de déploiement.
Mais pour les applications héritées, cela peut être considéré comme une première étape.
Généralement, l'idée ici est de définir une image Docker pour le serveur et de définir une image par application à déployer.
Les images docker pour les applications produisent le WAR / EAR attendu, mais celles-ci ne sont pas exécutées en tant que conteneur et l'image de l'application serveur déploie les composants produits par ces images en tant qu'applications déployées.
Pour les applications volumineuses (des millions de lignes de codes) avec beaucoup de trucs hérités, et si difficiles à migrer vers une solution intégrée à démarrage à ressort complet, c'est vraiment une belle amélioration.
Je ne détaillerai pas plus cette approche car il s'agit de cas d'utilisation mineurs de Docker mais je voulais exposer l'idée globale de cette approche car je pense que pour les développeurs confrontés à ces cas complexes, il est bon de savoir que certaines portes sont ouvertes à intégrer Docker.

2) Pour les applications qui embarquent / bootstrap le serveur elles-mêmes (Spring Boot avec serveur intégré: Tomcat, Netty, Jetty ...)

C'est la cible idéale avec Docker . J'ai spécifié Spring Boot parce que c'est un très bon framework pour le faire et qui a également un très haut niveau de maintenabilité, mais en théorie, nous pourrions utiliser n'importe quel autre moyen Java pour y parvenir.
Généralement, l'idée ici est de définir une image Docker par application à déployer.
Les images docker pour les applications produisent un JAR ou un ensemble de fichiers JAR / classes / configuration et ceux-ci démarrent une JVM avec l'application (commande java) lorsque nous créons et démarrons un conteneur à partir de ces images.
Pour les nouvelles applications ou applications pas trop complexes à migrer, cette méthode doit être privilégiée par rapport aux serveurs autonomes, car c'est la manière standard et la manière la plus efficace d'utiliser les conteneurs.
Je détaillerai cette approche.

Ancrer une application maven

1) sans botte à ressort

L'idée est de créer un fat jar avec Maven (le plugin d'assemblage maven et l'aide du plugin maven shadow pour cela) qui contient à la fois les classes compilées de l'application et les dépendances maven nécessaires.
Ensuite, nous pouvons identifier deux cas:

  • si l'application est une application de bureau ou autonome (qui n'a pas besoin d'être déployée sur un serveur): nous pourrions spécifier comme CMD/ENTRYPOINTdans l' Dockerfileexécution java de l'application:java -cp .:/fooPath/* -jar myJar

  • si l'application est une application serveur, par exemple Tomcat, l'idée est la même: obtenir un gros pot de l'application et exécuter une JVM dans le CMD/ENTRYPOINT. Mais ici avec une différence importante: nous devons inclure des bibliothèques logiques et spécifiques ( org.apache.tomcat.embedbibliothèques et quelques autres) qui démarrent le serveur intégré lorsque l'application principale est lancée.
    Nous avons un guide complet sur le site Web heroku .
    Pour le premier cas (application autonome), c'est un moyen simple et efficace d'utiliser Docker.
    Pour le deuxième cas (application serveur), cela fonctionne mais ce n'est pas simple, peut être sujet aux erreurs et n'est pas un modèle très extensible car vous ne placez pas votre application dans le cadre d'un framework mature tel que Spring Boot qui en fait beaucoup de ces choses pour vous et fournit également un haut niveau d'extension.
    Mais cela présente un avantage: vous disposez d'une grande liberté car vous utilisez directement l'API Tomcat intégrée.

2) avec Spring Boot

Enfin, nous y voilà.
C'est à la fois simple, efficace et très bien documenté.
Il existe en fait plusieurs approches pour créer une application Maven / Spring Boot à exécuter sur Docker.
Les exposer tous serait long et peut-être ennuyeux.
Le meilleur choix dépend de vos besoins.
Mais quelle que soit la manière, la stratégie de construction en termes de couches docker ressemble à la même chose.
Nous voulons utiliser une construction en plusieurs étapes: une s'appuyant sur Maven pour la résolution des dépendances et pour la construction et une autre s'appuyant sur JDK ou JRE pour démarrer l'application.

Étape de construction (image Maven):

  • copie pom à l'image
  • téléchargements de dépendances et de plugins.
    À propos de cela, mvn dependency:resolve-pluginsenchaîné à mvn dependency:resolvepeut faire le travail mais pas toujours.
    Pourquoi ? Parce que ces plugins et l' packageexécution pour empaqueter le fat jar peuvent dépendre de différents artefacts / plugins et même pour un même artefact / plugin, ceux-ci peuvent toujours extraire une version différente. Donc, une approche plus sûre, bien que potentiellement plus lente, consiste à résoudre les dépendances en exécutant exactement la mvncommande utilisée pour empaqueter l'application (qui extraira exactement les dépendances dont vous avez besoin) mais en ignorant la compilation source et en supprimant le dossier cible pour accélérer le traitement et empêcher toute détection de changement de couche indésirable pour cette étape.
  • copie du code source sur l'image
  • empaqueter l'application

Étape d'exécution (image JDK ou JRE):

  • copier le pot de l'étape précédente

Voici deux exemples.

a) Un moyen simple sans cache pour les dépendances maven téléchargées

Dockerfile:

########Maven build stage########
FROM maven:3.6-jdk-11 as maven_build
WORKDIR /app

#copy pom
COPY pom.xml .

#resolve maven dependencies
RUN mvn clean package -Dmaven.test.skip -Dmaven.main.skip -Dspring-boot.repackage.skip && rm -r target/

#copy source
COPY src ./src

# build the app (no dependency download here)
RUN mvn clean package  -Dmaven.test.skip

# split the built app into multiple layers to improve layer rebuild
RUN mkdir -p target/docker-packaging && cd target/docker-packaging && jar -xf ../my-app*.jar

########JRE run stage########
FROM openjdk:11.0-jre
WORKDIR /app

#copy built app layer by layer
ARG DOCKER_PACKAGING_DIR=/app/target/docker-packaging
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/lib /app/lib
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/classes /app/classes
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/META-INF /app/META-INF

#run the app
CMD java -cp .:classes:lib/* \
         -Djava.security.egd=file:/dev/./urandom \
         foo.bar.MySpringBootApplication

Inconvénient de cette solution? Toute modification dans le pom.xml signifie recréer la couche entière qui télécharge et stocke les dépendances maven. Cela n'est généralement pas acceptable pour les applications avec de nombreuses dépendances (et Spring Boot extrait de nombreuses dépendances), dans l'ensemble si vous n'utilisez pas de gestionnaire de référentiel maven pendant la construction de l'image.

b) Un moyen plus efficace avec le cache pour les dépendances maven téléchargées

L'approche est ici la même mais les téléchargements de dépendances maven sont mis en cache dans le cache du constructeur du docker.
L'opération de cache repose sur buildkit (api expérimentale de docker).
Pour activer buildkit, la variable env DOCKER_BUILDKIT = 1 doit être définie (vous pouvez le faire où vous le souhaitez: .bashrc, ligne de commande, fichier json du démon docker ...).

Dockerfile:

# syntax=docker/dockerfile:experimental

########Maven build stage########
FROM maven:3.6-jdk-11 as maven_build
WORKDIR /app

#copy pom
COPY pom.xml .

#copy source
COPY src ./src

# build the app (no dependency download here)
RUN --mount=type=cache,target=/root/.m2  mvn clean package -Dmaven.test.skip

# split the built app into multiple layers to improve layer rebuild
RUN mkdir -p target/docker-packaging && cd target/docker-packaging && jar -xf ../my-app*.jar

########JRE run stage########
FROM openjdk:11.0-jre
WORKDIR /app

#copy built app layer by layer
ARG DOCKER_PACKAGING_DIR=/app/target/docker-packaging
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/lib /app/lib
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/BOOT-INF/classes /app/classes
COPY --from=maven_build ${DOCKER_PACKAGING_DIR}/META-INF /app/META-INF

#run the app
CMD java -cp .:classes:lib/* \
         -Djava.security.egd=file:/dev/./urandom \
         foo.bar.MySpringBootApplication
davidxxx
la source