Come ancorare il progetto Maven? e quanti modi per realizzarlo?


90

Sono nuovo in Docker e non so come eseguire un progetto java con Maven anche se ho letto molti documenti e provato molti metodi.

  1. Devo costruire l'immagine usando Dockerfile?
  2. Come sono i comandi quando si esegue il progetto Maven nell'host con Dockerfile?

Risposte:


125

Esempio di lavoro.

Questo non è un tutorial per l'avvio primaverile. È la risposta aggiornata a una domanda su come eseguire una build Maven all'interno di un container Docker.

Domanda originariamente pubblicata 4 anni fa.

1. Genera un'applicazione

Usa l'inizializzatore di primavera per generare un'app demo

https://start.spring.io/

inserisci qui la descrizione dell'immagine

Estrai l'archivio zip localmente

2. Creare 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"]

Nota

  • Questo esempio utilizza una build in più fasi . La prima fase viene utilizzata per creare il codice. Il secondo stadio contiene solo il jar costruito e un JRE per eseguirlo (nota come il jar viene copiato tra gli stadi).

3. Costruisci l'immagine

docker build -t demo .

4. Eseguire l'immagine

$ 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)

Misc

Leggi la documentazione dell'hub Docker su come la build Maven può essere ottimizzata per utilizzare un repository locale per memorizzare nella cache i jar.

Aggiornamento (2019-02-07)

Questa domanda risale a 4 anni fa e in questo lasso di tempo è giusto dire che la creazione di applicazioni utilizzando Docker ha subito cambiamenti significativi.

Opzione 1: build in più fasi

Questo nuovo stile ti consente di creare immagini più leggere che non incapsulano gli strumenti di compilazione e il codice sorgente.

L'esempio qui utilizza di nuovo l' immagine di base ufficiale di Maven per eseguire la prima fase della build utilizzando una versione desiderata di Maven. La seconda parte del file definisce come il jar costruito viene assemblato nell'immagine di output 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"]  

Nota:

  • Sto usando l' immagine di base senza difficoltà di Google , che si sforza di fornire un tempo di esecuzione sufficiente per un'app java.

Opzione 2: Jib

Non ho usato questo approccio ma sembra degno di indagine in quanto ti consente di creare immagini senza dover creare cose brutte come Dockerfiles :-)

https://github.com/GoogleContainerTools/jib

Il progetto ha un plug-in Maven che integra la confezione del codice direttamente nel flusso di lavoro Maven.


Risposta originale (inclusa per completezza, ma scritta secoli fa)

Prova a usare le nuove immagini ufficiali, ce n'è una per Maven

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

L'immagine può essere utilizzata per eseguire Maven in fase di compilazione per creare un'applicazione compilata o, come negli esempi seguenti, per eseguire una build Maven all'interno di un contenitore.

Esempio 1 - Maven in esecuzione all'interno di un container

Il seguente comando esegue la build di Maven all'interno di un contenitore:

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

Appunti:

  • La cosa bella di questo approccio è che tutto il software è installato e in esecuzione all'interno del contenitore. Richiede solo docker sulla macchina host.
  • Vedi Dockerfile per questa versione

Esempio 2: utilizza Nexus per memorizzare i file nella cache

Esegui il contenitore Nexus

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

Crea un file "settings.xml":

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

Ora esegui Maven che collega al contenitore nexus, in modo che le dipendenze vengano memorizzate nella 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

Appunti:

  • Un vantaggio dell'esecuzione di Nexus in background è che altri repository di terze parti possono essere gestiti tramite l'URL di amministrazione in modo trasparente per le build Maven in esecuzione in contenitori locali.

può essere usato per sostituire la centrale Maven per una build gradle? come indicato in support.sonatype.com/entries/… Ho sostituito mavenCentral()nelle mie dipendenze gradle con maven {url "http://nexus:8081..."e ora sto solo riscontrando problemi di risoluzione.
Mohamnag

@mohamnag Correct, il file "settings" di Maven sopra fa esattamente questo, reindirizzando tutte le richieste di Maven Central al repository nexus locale. È necessario delineare il tipo di problemi di risoluzione che si verificano. Potrebbe essere qualsiasi cosa ... Ad esempio, hai impostato il collegamento Docker, in modo che l'host "nexus" venga risolto correttamente?
Mark O'Connor

Ho scoperto che tutto andava bene, ma nexus aveva bisogno di tempo per costruire l'indice o qualcos'altro ha causato il problema. Ho anche provato ad attivare un aggiornamento dell'indice, quindi non sono sicuro di quale caso abbia risolto il problema. Grazie comunque.
Mohamnag

Nexus 3 è ora disponibile anche come contenitore docker. Usa "sonatype / nexus3"
Thorbjørn Ravn Andersen

1
@avandeursen Hai ragione sul fatto che il parametro --link è deprecato (risposta più vecchia di 3 anni). Tuttavia la soluzione più corretta (secondo me) è creare una rete docker ed eseguire entrambi i contenitori su di essa. In questo modo puoi sfruttare la funzionalità DNS nativa in Docker e continuare a fare riferimento al container nexus per nome. Aggiornerà l'esempio più tardi
Mark O'Connor

47

Ci possono essere molti modi .. Ma l'ho implementato seguendo due modi

L'esempio dato è del progetto Maven.

1. Utilizzo di Dockerfile nel progetto Maven

Utilizza la seguente struttura di file:

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

E aggiorna il Dockerfile come:

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

Passa alla cartella del progetto e digita il seguente comando sarai in grado di creare un'immagine ed eseguire quell'immagine:

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

Guarda il video su Spring Boot con Docker

2. Utilizzo dei plugin Maven

Aggiungi il plug-in Maven specificato in 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>

Passa alla cartella del progetto e digita il seguente comando per creare un'immagine ed eseguire quell'immagine:

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

Nel primo esempio stiamo creando Dockerfile e fornendo un'immagine di base e aggiungendo jar e quindi, dopo averlo fatto eseguiremo il comando docker per creare un'immagine con un nome specifico e quindi eseguiremo quell'immagine.

Considerando che nel secondo esempio stiamo usando il plugin Maven in cui forniamo baseImagee imageNamequindi non abbiamo bisogno di creare Dockerfile qui .. dopo aver impacchettato il progetto Maven avremo l'immagine Docker e dobbiamo solo eseguire quell'immagine ..


Piuttosto che modificare il punto di ingresso per specificare il nome del manufatto, potresti usare un approccio come qui: alooma.com/blog/building-dockers - utilizza il plugin maven -dependency per usare un nome comune. Non è necessario inserire un jar con versione nel contenitore docker poiché il contenitore stesso è dotato di versione.
kboom

14

Come regola generale, dovresti creare un JAR grasso usando Maven (un JAR che contiene sia il tuo codice che tutte le dipendenze).

Quindi puoi scrivere un Dockerfile che corrisponda alle tue esigenze (se puoi creare un JAR grasso, avrai solo bisogno di un sistema operativo di base, come CentOS e JVM).

Questo è ciò che uso per un'app Scala (che è basata su 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

Questo crea un'immagine basata su CentOS con Java7. Una volta avviato, eseguirà il jar dell'app.

Il modo migliore per distribuirlo è tramite il registro Docker, è come un Github per le immagini Docker.

Puoi costruire un'immagine come questa:

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

Puoi quindi spingere un'immagine in questo modo:

docker push username/projectname # this pushes all tags

Una volta che l'immagine si trova nel registro Docker, puoi estrarla da qualsiasi parte del mondo ed eseguirla.

Consulta la guida utente Docker per ulteriori informazioni.

Qualcosa da tenere a mente :

Puoi anche estrarre il tuo repository all'interno di un'immagine e costruire il jar come parte dell'esecuzione del contenitore, ma non è un buon approccio, poiché il codice potrebbe cambiare e potresti finire per utilizzare una versione diversa dell'app senza preavviso.

Costruire un barattolo grasso rimuove questo problema.


Ciao, ho usato il tuo esempio nel mio file docker per copiare il fat jar nell'immagine ma la compilazione fallisce perché non è stato possibile trovare il fat jar dato il percorso locale. È qualcosa come target / app.jar?
user_mda

Ciao, c'è un modo per scaricare l'artefatto da nexus in fase di esecuzione? Per specificare l'artefatto da scaricare utilizzando le proprietà e non un collegamento effettivo al jar stesso? Nel dockerfile: RUN wget -O {project.build.finalname}.jar ma voglio scaricare il jar di cui sopra da nexus.
Pramod Setlur

1
Includere un FAT JAR nel repository di codice è lento per me. C'è un modo per utilizzare Maven per costruire l'immagine?
WoLfPwNeR

1
Anche i barattoli di grasso hanno qualche problema, soprattutto con i barattoli firmati.
Thorbjørn Ravn Andersen

1
Come regola pratica, non usare mai un fat jar, i pacchetti che si basano sul manifest o altri file di dati all'interno dei loro jar rischiano di fallire poiché non esiste un modo sicuro per unire questi dati. File manifest con percorsi corrispondenti all'interno di un conflitto jar e il primo o l'ultimo vince (non ricordo quale). L'unico momento in cui dovresti prendere in considerazione l'utilizzo di un fat jar è se hai un insieme molto limitato di dipendenze e sai molto bene che i loro jar non hanno dati manifest in conflitto. In caso contrario, rimanere in condizioni di sicurezza utilizzare i barattoli poiché sono stati progettati per essere utilizzati (cioè separatamente).
PiersyP

3

Ecco il mio contributo.
Non cercherò di elencare tutti gli strumenti / librerie / plug-in esistenti per sfruttare Docker con Maven. Alcune risposte lo hanno già fatto.
invece di, mi concentrerò sulla tipologia delle applicazioni e il modo Dockerfile.
Dockerfileè davvero un concetto semplice e importante di Docker (tutte le immagini note / pubbliche si basano su questo) e penso che cercare di evitare di capire e usare Dockerfiles non sia necessariamente il modo migliore per entrare nel mondo di Docker.

La dockerizzazione di un'applicazione dipende dall'applicazione stessa e dall'obiettivo da raggiungere

1) Per le applicazioni che vogliamo continuare a eseguirle su server Java installati / standalone (Tomcat, JBoss, ecc ...)

La strada è più difficile e questo non è l'obiettivo ideale perché ciò aggiunge complessità (dobbiamo gestire / mantenere il server) ed è meno scalabile e meno veloce dei server embedded in termini di build / deploy / undeploy.
Ma per le applicazioni legacy, questo può essere considerato come un primo passo.
In generale, l'idea qui è definire un'immagine Docker per il server e definire un'immagine per applicazione da distribuire.
Le immagini docker per le applicazioni producono il WAR / EAR previsto ma queste non vengono eseguite come container e l'immagine per l'applicazione server distribuisce i componenti prodotti da queste immagini come applicazioni distribuite.
Per applicazioni enormi (milioni di righe di codice) con molte cose legacy e così difficili da migrare a una soluzione integrata con avvio a molla completo, questo è davvero un bel miglioramento.
Non descriverò più dettagliatamente questo approccio poiché è per casi d'uso minori di Docker, ma volevo esporre l'idea generale di tale approccio perché penso che per gli sviluppatori che affrontano questi casi complessi, sia bello sapere che alcune porte sono aperte a integrare Docker.

2) Per le applicazioni che incorporano / eseguono il bootstrap del server stesso (Spring Boot con server incorporato: Tomcat, Netty, Jetty ...)

Questo è l'obiettivo ideale con Docker . Ho specificato Spring Boot perché è davvero un bel framework per farlo e che ha anche un livello molto alto di manutenibilità, ma in teoria potremmo usare qualsiasi altro modo Java per ottenerlo.
In generale, l'idea qui è di definire un'immagine Docker per applicazione da distribuire.
Le immagini docker per le applicazioni producono un JAR o un set di JAR / classes / file di configurazione e questi avviano una JVM con l'applicazione (comando java) quando creiamo e avviamo un contenitore da queste immagini.
Per nuove applicazioni o applicazioni non troppo complesse da migrare, in questo modo è necessario privilegiare i server standalone perché è il modo standard e il modo più efficiente di utilizzare i contenitori.
Descriverò in dettaglio tale approccio.

Dockerizzare un'applicazione esperta

1) Senza Spring Boot

L'idea è di creare un fat jar con Maven (il plug-in di assemblaggio di maven e l'aiuto del plug-in per l'ombra di maven) che contiene sia le classi compilate dell'applicazione che le dipendenze di maven necessarie.
Quindi possiamo identificare due casi:

  • se l'applicazione è un desktop o applicazione autonoma (che non ha bisogno di essere distribuito su un server): potremmo specificare come CMD/ENTRYPOINTnel Dockerfilel'esecuzione java dell'applicazione:java -cp .:/fooPath/* -jar myJar

  • se l'applicazione è un'applicazione server, ad esempio Tomcat, l'idea è la stessa: ottenere un fat jar dell'applicazione ed eseguire una JVM nel file CMD/ENTRYPOINT. Ma qui con una differenza importante: dobbiamo includere alcune librerie logiche e specifiche ( org.apache.tomcat.embedlibrerie e alcune altre) che avvia il server incorporato quando viene avviata l'applicazione principale.
    Abbiamo una guida completa, sul sito web di heroku .
    Per il primo caso (applicazione autonoma), questo è un modo semplice ed efficiente per utilizzare Docker.
    Per il secondo caso (applicazione server), che funziona ma non è semplice, può essere soggetto a errori e non è un modello molto estensibile perché non si colloca la propria applicazione nel frame di un framework maturo come Spring Boot che ne fa molti di queste cose per te e fornisce anche un alto livello di estensione.
    Ma questo ha un vantaggio: hai un alto livello di libertà perché usi direttamente l'API Tomcat incorporata.

2) Con Spring Boot

Finalmente eccoci qui.
Ciò è semplice, efficiente e molto ben documentato.
Esistono davvero diversi approcci per eseguire un'applicazione Maven / Spring Boot su Docker.
Esporre tutti loro sarebbe lungo e forse noioso.
La scelta migliore dipende dalle tue esigenze.
Ma qualunque sia il modo, la strategia di compilazione in termini di livelli docker sembra la stessa.
Vogliamo utilizzare una build in più fasi: una che si affida a Maven per la risoluzione delle dipendenze e per la compilazione e un'altra che si affida a JDK o JRE per avviare l'applicazione.

Fase di costruzione (immagine Maven):

  • copia pom all'immagine
  • download di dipendenze e plug-in.
    A proposito, mvn dependency:resolve-pluginsincatenato mvn dependency:resolvepuò fare il lavoro, ma non sempre.
    Perché ? Poiché questi plug-in e l' packageesecuzione per impacchettare il fat jar possono fare affidamento su artefatti / plug-in diversi e anche per uno stesso artefatto / plug-in, questi possono comunque estrarre una versione diversa. Quindi un approccio più sicuro, sebbene potenzialmente più lento, è risolvere le dipendenze eseguendo esattamente il mvncomando utilizzato per impacchettare l'applicazione (che estrarrà esattamente le dipendenze di cui hai bisogno) ma saltando la compilazione di origine ed eliminando la cartella di destinazione per rendere l'elaborazione più veloce e impedire qualsiasi rilevamento indesiderato del cambio di livello per quella fase.
  • copia del codice sorgente nell'immagine
  • impacchettare l'applicazione

Fase di esecuzione (immagine JDK o JRE):

  • copia il barattolo dalla fase precedente

Ecco due esempi.

a) Un modo semplice senza cache per le dipendenze Maven scaricate

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

Inconveniente di quella soluzione? Qualsiasi modifica nel pom.xml significa ricreare l'intero livello che scarica e memorizza le dipendenze di Maven. Ciò non è generalmente accettabile per applicazioni con molte dipendenze (e Spring Boot ne estrae molte), nel complesso se non si utilizza un gestore di repository esperto durante la creazione dell'immagine.

b) Un modo più efficiente con la cache per le dipendenze Maven scaricate

L'approccio qui è lo stesso, ma i download di dipendenze maven vengono memorizzati nella cache del Docker Builder.
L'operazione di cache si basa su buildkit (API sperimentale di docker).
Per abilitare buildkit, è necessario impostare la variabile env DOCKER_BUILDKIT = 1 (puoi farlo dove vuoi: .bashrc, riga di comando, file json daemon 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.