Come build un contenitore docker per un’app java

Quello che voglio fare è build un’immagine docker per la mia applicazione Java, ma le seguenti considerazioni dovrebbero essere vere per la maggior parte delle lingue compilate.

problema

Sul mio server di build voglio produrre un’immagine docker per la mia applicazione come deliverable. Per questo devo compilare l’applicazione usando alcuni strumenti di compilazione (tipicamente Gradle, Maven o Ant) e quindi aggiungere il file JAR creato all’immagine della finestra mobile. Come voglio che l’immagine della finestra mobile esegua semplicemente il file JAR, ovviamente partirò da un’immagine di base con Java già installato.

Ci sono tre modi per farlo:

lasciare che lo strumento di compilazione controlli il processo

In questo caso il mio strumento di compilazione controlla l’intero processo. Quindi prepara il file JAR e dopo la creazione del JAR chiama Docker per creare l’immagine. Funziona come il JAR è stato creato in anticipo e Docker può essere ignaro del processo di compilazione necessario per creare il JAR.

Ma il mio Dockerfile non è più autonomo. Dipende dai passaggi da eseguire al di fuori di Docker perché funzioni. Nel mio Dockerfile avrò una dichiarazione COPY o ADD che dovrebbe copiare il file JAR nell’immagine. Questa affermazione fallirà quando il jar non viene creato in precedenza. Quindi solo l’esecuzione del Dockerfile potrebbe non funzionare. Questo diventa un problema se vuoi integrarti con servizi che si limitano a build usando il Dockerfile attuale come la funzione di auto-build su DockerHub.

lasciare che Docker controlli la costruzione

In questo caso tutti i passaggi necessari per creare l’immagine vengono aggiunti al Dockerfile in modo che l’immagine possa essere creata semplicemente eseguendo la build Docker.

Il problema principale di questo approccio è che non è ansible aggiungere a un comando Dockerfile che deve essere eseguito al di fuori dell’immagine della finestra mobile creata. Ciò significa che devo aggiungere il mio codice sorgente e i miei strumenti di costruzione all’immagine della finestra mobile e creare il mio file JAR all’interno dell’immagine. Ciò comporterà la mia immagine è più grande di quanto deve essere a causa di tutti i file aggiunti che non saranno necessari in fase di esecuzione. Questo aggiungerà anche ulteriori strati alla mia immagine.

Modificare:

Come @ adrian-mouat ha indicato se aggiungerei i sorgenti, compilare l’applicazione ed eliminare i sorgenti in un’unica istruzione RUN, evitando di aggiungere file e livelli non necessari all’immagine Docker. Ciò significherebbe creare un comando folle e incatenato.

due build separati

In questo caso dividiamo la nostra build in due: per prima cosa creiamo il file JAR usando il nostro strumento di compilazione e caricalo su un repository (repository Maven o Ivy). Quindi attiviamo una build Docker separata che aggiunge solo il file JAR dal repository.

conclusione

A mio parere, il modo migliore sarebbe lasciare che lo strumento di compilazione controlli il processo . Ciò si tradurrà in un’immagine docker pulita e poiché l’immagine è ciò che vogliamo consegnare è importante. Per evitare di avere un Dockerfile potenzialmente non funzionante che giace intorno a questo dovrebbe essere creato come parte della build. Quindi nessuno lo userebbe accidentalmente per iniziare una build rotta.

Ma questo non mi permetterà di integrarmi con DockerHub.

domanda

C’è un altro modo che mi manca?

L’hub del registro docker ha un’immagine Maven che può essere utilizzata per creare contenitori java.

Utilizzando questo approccio la macchina di compilazione non ha bisogno di avere preinstallato Java o Maven, Docker controlla l’intero processo di compilazione.

Esempio

 ├── Dockerfile ├── pom.xml └── src ├── main │  ├── java │  │  └── org │  │  └── demo │  │  └── App.java │  └── resources │  └── log4j.properties └── test └── java └── org └── demo └── AppTest.java 

Il contenitore è costruito come segue:

 docker build -t my-maven . 

E esegui come segue:

 $ docker run -it --rm my-maven 0 [main] INFO org.demo.App - hello world 

Dockerfile

 FROM maven:3.3-jdk-8-onbuild CMD ["java","-jar","/usr/src/app/target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"] 

Aggiornare

Se si desidera ottimizzare il contenitore per escludere l’origine, è ansible creare un file Docker che includa solo il jar creato:

 FROM java:8 ADD target/demo-1.0-SNAPSHOT-jar-with-dependencies.jar /opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar CMD ["java","-jar","/opt/demo/demo-1.0-SNAPSHOT-jar-with-dependencies.jar"] 

E crea il contenitore in due passaggi:

 docker run -it --rm -w /opt/maven \ -v $PWD:/opt/maven \ -v $HOME/.m2:/root/.m2 \ maven:3.3-jdk-8 \ mvn clean install docker build -t my-app . 

__

Aggiornamento (27-07-2017)

Docker ora ha una capacità di creazione multistadio . Ciò consente a Docker di creare un contenitore con un’immagine contenente gli strumenti di compilazione ma di generare un’immagine con solo le dipendenze di runtime.

Nell’esempio seguente viene illustrato questo concetto, si noti come il jar viene copiato dalla directory di destinazione della prima fase di creazione

 FROM maven:3.3-jdk-8-onbuild FROM java:8 COPY --from=0 /usr/src/app/target/demo-1.0-SNAPSHOT.jar /opt/demo.jar CMD ["java","-jar","/opt/demo.jar"] 

Struttura dell’aplicazione java

 Demo └── src | ├── main | │ ├── java | │ │ └── org | │ │ └── demo | │ │ └── App.java | │ └── resources | │ └── application.properties | └── test | └── java | └── org | └── demo | └── App.java ├──── Dockerfile ├──── pom.xml 

Contenuto di Dockerfile

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

Comandi per build ed eseguire immagini

  • Vai alla directory del progetto. Diciamo D: / Demo
 $ cd D/demo $ mvn clean install $ docker build demo . $ docker run -p 8080:8080 -t demo 

Verifica che il contenitore sia in esecuzione o meno

 $ docker ps 

L’output sarà

 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 55c11a464f5a demo1 "java -jar demo.jar" 21 seconds ago Up About a minute 0.0.0.0:8080->8080/tcp cranky_mayer 

Un paio di cose:

  • Se elimini i file nella stessa istruzione che li aggiungi, non consumano spazio nell’immagine. Se guardi alcuni dei Dockerfiles per le immagini ufficiali, vedrai che scaricano l’origine, la compilano e la eliminano tutti nello stesso passo (es. https://github.com/docker-library/python/blob/0fa3202789648132971160f686f5a37595108f44/3.5 / slim / Dockerfile ). Questo significa che devi fare qualche fastidiosa ginnastica, ma è perfettamente fattibile.

  • Non vedo il problema con due Dockerfile separati. La cosa bella di questo è che potresti usare il JRE piuttosto che il JDK per ospitare il tuo jar.

Il modo più semplice è lasciare che lo strumento di compilazione controlli il processo . Altrimenti, dovresti mantenere il file di build dello strumento di creazione (come pom.xml per Maven o build.gradle per Gradle) e un file Dockerfile .

Un modo semplice per build un contenitore Docker per la tua app Java è utilizzare Jib , disponibile come plugin Maven e Gradle .

Ad esempio, se si utilizza Maven e si desidera creare il contenitore sul daemon Docker in esecuzione, è sufficiente eseguire questo comando:

 mvn compile com.google.cloud.tools:jib-maven-plugin:0.9.2:dockerBuild 

È inoltre ansible creare direttamente un registro Docker con Jib senza dover installare la docker , eseguire un daemon Docker (che richiede i privilegi di root) o scrivere un Dockerfile . È anche più veloce e costruisce le immagini in modo riproducibile.

Scopri di più su Jib al suo repository Github: https://github.com/GoogleContainerTools/jib

ci sono usi alternativi per lanciare barattoli di vetro o di guerra

  • aggiungi un barattolo nell’immagine.
  • imposta heapsize per java
  • eseguire il comando jar tramite entrypoint

esempio di file docker

 FROM base ADD sample.jar renamed.jar ENV HEAP_SIZE 256m ENTRYPOINT exec java -Xms$HEAP_SIZE -Xmx$HEAP_SIZE -jar renamed.jar 

in aggiunta l’esempio di distribuzione del pacchetto su tomcat

 FROM tomcat7 ADD sample.war ${CATALINA_HOME}/webapps/ROOT.war CMD ${CATALINA_HOME}/bin/catalina.sh run 

Creare file docker come immagine

 cp tomcat.dockerfile /workingdir/Dockerfile docker build -t name /workingdir/Dockerfile . 

Elenca le immagini

 docker images 

Usa l’immagine per creare un contenitore

 docker run --name cont_name --extra-vars var1=val1 var2=val2 imagename 

Qui descrivo come lo faccio nel mio ambiente di sviluppo.

  • Costruisci la guerra / barattolo localmente con Maven
  • Copialo in una cartella Docker locale
  • Eseguire il plug-in Intellij Docker che crea un’immagine docker che contiene war / jar, eseguire il server delle applicazioni e distribuirlo sul server Docker remoto

Spero che sia d’aiuto.

Abbiamo usato il Plugin Maven di Spotify Docker per un po ‘. Il plugin ti consente di associare una finestra mobile a una fase del ciclo di vita di Maven.

Un esempio: esegui il Docker compilare dopo l’imballaggio (fase: pacchetto) l’applicazione configurando il plug-in per aggiungere l’applicazione creata come risorsa al contesto di build di Docker. Nella fase di distribuzione eseguire l’objective push Docker per spingere l’immagine Docker in un registro. Questo può essere eseguito accanto al normale plug-in di distribuzione, che pubblica l’artefatto in un repository come Nexus.

Successivamente, abbiamo diviso la build in due processi separati sul server CI. Dato che Docker è solo un modo per eseguire l’applicazione (a volte abbiamo bisogno dell’applicazione rilasciata su ambienti diversi non solo Docker), il build Maven non dovrebbe fare affidamento su Docker.

Quindi il primo lavoro rilascia l’applicazione in Nexus (tramite Maven deploy). Il secondo lavoro (che può essere una dipendenza downstream del primo lavoro) scarica l’ultimo artefatto di rilascio, esegue la build Docker e inserisce l’immagine nel registro. Per scaricare la versione più recente utilizziamo il plug-in Maven di Versions (versioni: use-latest-releases) e il plugin Maven Dependency (dipendenza: get e dependency: copy).

Il secondo lavoro può anche essere avviato per una versione specifica dell’applicazione per (ri) build l’immagine Docker per una versione precedente. Inoltre è ansible utilizzare una pipeline di build (su Jenkins), che esegue entrambi i lavori e passa la versione di rilascio o l’artefatto di rilascio alla build di Docker.