Como encaixar o projeto maven? e quantas maneiras de fazer isso?

87

Sou novo no Docker e não sei como executar um projeto java com o maven, embora tenha lido muitos documentos e tentado vários métodos.

  1. Devo construir a imagem usando Dockerfile?
  2. Como são os comandos para executar o projeto maven no host Dockerfile?
Yashon Lin
fonte

Respostas:

122

Exemplo de trabalho.

Este não é um tutorial de boot de primavera. É a resposta atualizada a uma pergunta sobre como executar uma compilação Maven em um contêiner do Docker.

Pergunta postada originalmente há 4 anos.

1. Gere um aplicativo

Use o inicializador de primavera para gerar um aplicativo de demonstração

https://start.spring.io/

insira a descrição da imagem aqui

Extraia o arquivo zip localmente

2. Crie um 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

  • Este exemplo usa uma construção de vários estágios . O primeiro estágio é usado para construir o código. O segundo estágio contém apenas o jar construído e um JRE para executá-lo (observe como o jar é copiado entre os estágios).

3. Construa a imagem

docker build -t demo .

4. Execute a imagem

$ 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

Leia a documentação do hub Docker sobre como a construção do Maven pode ser otimizada para usar um repositório local para armazenar jars.

Atualização (07/02/2019)

Esta questão já tem 4 anos e, nesse tempo, é justo dizer que a construção de aplicativos usando o Docker passou por mudanças significativas.

Opção 1: compilação em vários estágios

Este novo estilo permite que você crie imagens mais leves que não encapsulam suas ferramentas de construção e código-fonte.

O exemplo aqui novamente usa a imagem base oficial do maven para executar o primeiro estágio da construção usando uma versão desejada do Maven. A segunda parte do arquivo define como o jar construído é montado na imagem de saída final.

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:

  • Estou usando a imagem de base sem distração do Google , que se esforça para fornecer tempo de execução suficiente para um aplicativo java.

Opção 2: Jib

Não usei essa abordagem, mas parece digna de investigação, pois permite que você crie imagens sem ter que criar coisas desagradáveis ​​como Dockerfiles :-)

https://github.com/GoogleContainerTools/jib

O projeto possui um plugin Maven que integra o pacote de seu código diretamente em seu fluxo de trabalho Maven.


Resposta original (incluída para ser completa, mas escrita há muito tempo)

Tente usar as novas imagens oficiais, há uma para Maven

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

A imagem pode ser usada para executar o Maven em tempo de construção para criar um aplicativo compilado ou, como nos exemplos a seguir, para executar uma construção Maven dentro de um contêiner.

Exemplo 1 - Maven em execução em um contêiner

O comando a seguir executa sua versão Maven dentro de um contêiner:

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

Notas:

  • O interessante dessa abordagem é que todo o software é instalado e executado dentro do contêiner. Só precisa do docker na máquina host.
  • Veja Dockerfile para esta versão

Exemplo 2 - Use Nexus para armazenar arquivos em cache

Execute o contêiner Nexus

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

Crie um arquivo "settings.xml":

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

Agora execute o Maven vinculando ao contêiner nexus, para que as dependências sejam armazenadas em 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

Notas:

  • Uma vantagem de executar o Nexus em segundo plano é que outros repositórios de terceiros podem ser gerenciados por meio da URL do administrador de forma transparente para as compilações do Maven em execução em contêineres locais.
Mark O'Connor
fonte
isso pode ser usado para substituir a central maven para uma construção gradle? conforme declarado em support.sonatype.com/entries/… Substituí mavenCentral()minhas dependências do Gradle por maven {url "http://nexus:8081..."e agora estou apenas tendo problemas de resolução.
mohamnag
@mohamnag Correto, o arquivo de "configurações" do Maven acima faz exatamente isso, redirecionando todas as solicitações do Maven Central para o repositório nexus local. Você precisa descrever que tipo de problemas de resolução está tendo. Pode ser qualquer coisa ... Por exemplo, você configurou o link do Docker para que o host "nexus" seja resolvido corretamente?
Mark O'Connor
Descobri que tudo estava bem, mas o Nexus precisava de tempo para construir o índice ou alguma outra coisa causava o problema. Também tentei acionar uma atualização de índice, portanto, não tenho certeza de qual caso corrigiu o problema. Obrigado, entretanto.
mohamnag
O Nexus 3 agora também está disponível como um contêiner docker. Use "sonatype / nexus3"
Thorbjørn Ravn Andersen
1
@avandeursen Você está correto ao dizer que o parâmetro --link está obsoleto (resposta acima de 3 anos). No entanto, a solução mais correta (na minha opinião) é criar uma rede docker e executar os dois contêineres nela. Dessa forma, você pode aproveitar o recurso DNS nativo no Docker e continuar a se referir ao contêiner nexus pelo nome. Atualizará o exemplo mais tarde
Mark O'Connor
47

Pode haver muitas maneiras .. Mas eu implementei seguindo duas maneiras

O exemplo dado é de projeto maven.

1. Usando Dockerfile no projeto maven

Use a seguinte estrutura de arquivo:

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

E atualize o Dockerfile como:

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

Navegue até a pasta do projeto e digite o seguinte comando que você poderá criar e executar a imagem:

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

Obtenha um vídeo no Spring Boot com Docker

2. Usando plug-ins Maven

Adicionar determinado plugin maven em 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>

Navegue até a pasta do projeto e digite o seguinte comando, você será capaz de criar e executar a imagem:

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

No primeiro exemplo, estamos criando Dockerfile e fornecendo imagem de base e adicionando jar e, depois de fazer isso, vamos executar o comando docker para construir uma imagem com nome específico e, em seguida, executar essa imagem.

Enquanto no segundo exemplo estamos usando o plugin maven no qual fornecemos baseImagee, imageNameportanto, não precisamos criar Dockerfile aqui .. após empacotar o projeto maven, obteremos a imagem do docker e só precisamos executar essa imagem.

Riddhi Gohil
fonte
Em vez de modificar o ponto de entrada para especificar o nome do artefato, você pode usar uma abordagem como aqui: alooma.com/blog/building-dockers - utilize o plugin maven-dependency para usar um nome comum. Não há necessidade de colocar um jar com versão no contêiner do docker, pois o próprio contêiner tem versão.
kboom
14

Como regra geral, você deve construir um JAR gordo usando Maven (um JAR que contém seu código e todas as dependências).

Em seguida, você pode escrever um Dockerfile que corresponda aos seus requisitos (se você pode construir um JAR grande, você só precisa de um sistema operacional de base, como o CentOS e o JVM).

Isso é o que eu uso para um aplicativo Scala (que é baseado em 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

Isso cria uma imagem baseada em CentOS com Java7. Quando iniciado, ele executará seu jar de aplicativo.

A melhor maneira de implantá-lo é por meio do Docker Registry, é como um Github para imagens Docker.

Você pode construir uma imagem como esta:

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

Você pode enviar uma imagem desta forma:

docker push username/projectname # this pushes all tags

Depois que a imagem estiver no Docker Registry, você pode extraí-la de qualquer lugar do mundo e executá-la.

Consulte o Guia do usuário do Docker para obter mais informações.

Algo para ter em mente :

Você também pode colocar seu repositório dentro de uma imagem e construir o jar como parte da execução do contêiner, mas não é uma boa abordagem, já que o código pode mudar e você pode acabar usando uma versão diferente do aplicativo sem aviso prévio.

Construir um frasco de gordura remove esse problema.

Matteo Pacini
fonte
HI, eu usei seu exemplo em meu arquivo docker para copiar o jar de gordura na imagem, mas a compilação falha por não ser capaz de encontrar o jar de gordura dado o caminho local. É algo como target / app.jar?
user_mda
Oi, há uma maneira de baixar o artefato do Nexus em tempo de execução? Especificar o artefato a ser baixado usando propriedades e não um link real para o próprio jar? No dockerfile: RUN wget -O {project.build.finalname}.jar Mas eu quero baixar o jar acima da Nexus.
Pramod Setlur
1
Incluir um FAT JAR para codificar o repo é lento para mim. Existe alguma maneira de usar o maven para construir a imagem?
WoLfPwNeR
1
Os frascos de gordura também apresentam alguns problemas, especialmente com os frascos assinados.
Thorbjørn Ravn Andersen
1
Como regra geral, nunca use um jar gordo, os pacotes que dependem do manifesto ou de outros arquivos de dados dentro de seus jar podem falhar, pois não há uma maneira segura de mesclar esses dados. Arquivos de manifesto com caminhos correspondentes dentro de um conflito de jar e o primeiro ou o último vencem (não consigo lembrar qual) A única vez em que você deve considerar o uso de um jar de gordura é se tiver um conjunto muito limitado de dependências e souber muito bem que seus jars não possuem dados de manifesto conflitantes. Caso contrário, mantenha os frascos de uso seguro, pois foram projetados para serem usados ​​(ou seja, separadamente).
PiersyP
3

Aqui está minha contribuição.
Não tentarei listar todas as ferramentas / bibliotecas / plug-ins que existem para aproveitar as vantagens do Docker com Maven. Algumas respostas já o fizeram.
em vez de, vou me concentrar na tipologia de aplicativos e no modo Dockerfile.
Dockerfileé realmente um conceito simples e importante do Docker (todas as imagens conhecidas / públicas dependem disso) e acho que tentar evitar o entendimento e o uso de Dockerfiles não é necessariamente a melhor maneira de entrar no mundo do Docker.

Dockerizing um aplicativo depende do próprio aplicativo e do objetivo

1) Para aplicativos que desejamos executar para executá-los no servidor Java instalado / autônomo (Tomcat, JBoss, etc ...)

O caminho é mais difícil e esse não é o alvo ideal porque adiciona complexidade (temos que gerenciar / manter o servidor) e é menos escalonável e menos rápido do que os servidores incorporados em termos de construção / implantação / remoção.
Mas para aplicativos legados, isso pode ser considerado uma primeira etapa.
Geralmente, a ideia aqui é definir uma imagem Docker para o servidor e definir uma imagem por aplicativo a ser implantado.
As imagens docker para os aplicativos produzem o WAR / EAR esperado, mas não são executadas como contêiner e a imagem para o aplicativo do servidor implementa os componentes produzidos por essas imagens como aplicativos implementados.
Para aplicativos enormes (milhões de linhas de códigos) com muitos materiais legados, e tão difíceis de migrar para uma solução embarcada de inicialização completa, essa é realmente uma boa melhoria.
Não vou detalhar mais essa abordagem, já que é para casos de uso menores do Docker, mas eu queria expor a ideia geral dessa abordagem porque acho que, para os desenvolvedores que enfrentam esses casos complexos, é ótimo saber que algumas portas estão abertas para integrar Docker.

2) Para aplicativos que incorporam / inicializam o próprio servidor (Spring Boot com servidor incorporado: Tomcat, Netty, Jetty ...)

Esse é o alvo ideal com o Docker . Especifiquei Spring Boot porque essa é uma estrutura muito boa para fazer isso e também tem um alto nível de manutenção, mas, em teoria, poderíamos usar qualquer outra forma de Java para fazer isso.
Geralmente, a ideia aqui é definir uma imagem Docker por aplicativo a ser implantado.
As imagens docker para os aplicativos produzem um JAR ou um conjunto de arquivos JAR / classes / configuração e estes iniciam uma JVM com o aplicativo (comando java) quando criamos e iniciamos um contêiner a partir dessas imagens.
Para novos aplicativos ou aplicativos não muito complexos para migrar, essa forma deve ser preferida em relação aos servidores autônomos, porque essa é a forma padrão e a forma mais eficiente de usar contêineres.
Vou detalhar essa abordagem.

Dockerizing a maven application

1) Sem Spring Boot

A ideia é criar um jar gordo com o Maven (o plugin maven assembly e o plugin maven sombra ajudam para isso) que contém as classes compiladas do aplicativo e as dependências maven necessárias.
Então podemos identificar dois casos:

  • se o aplicativo é desktop ou autônomo (que não precisa ser implantado em um servidor): poderíamos especificar como CMD/ENTRYPOINTna Dockerfileexecução java do aplicativo:java -cp .:/fooPath/* -jar myJar

  • se o aplicativo for um aplicativo servidor, por exemplo Tomcat, a ideia é a mesma: obter um grande jar do aplicativo e executar um JVM no CMD/ENTRYPOINT. Mas aqui com uma diferença importante: precisamos incluir algumas lógicas e bibliotecas específicas ( org.apache.tomcat.embedbibliotecas e algumas outras) que iniciam o servidor embutido quando o aplicativo principal é iniciado.
    Temos um guia completo no site do heroku .
    Para o primeiro caso (aplicativo autônomo), essa é uma maneira direta e eficiente de usar o Docker.
    Para o segundo caso (aplicativo de servidor), isso funciona, mas não é direto, pode estar sujeito a erros e não é um modelo muito extensível porque você não coloca seu aplicativo no quadro de uma estrutura madura como Spring Boot que faz muitos dessas coisas para você e também fornece um alto nível de extensão.
    Mas isso tem uma vantagem: você tem um alto nível de liberdade porque usa diretamente a API Tomcat incorporada.

2) Com bota de mola

Por fim, aqui vamos nós.
Isso é simples, eficiente e muito bem documentado.
Na verdade, existem várias abordagens para fazer um aplicativo Maven / Spring Boot rodar no Docker.
Expor todos eles seria longo e talvez enfadonho.
A melhor escolha depende de sua necessidade.
Mas de qualquer maneira, a estratégia de construção em termos de camadas docker parece a mesma.
Queremos usar uma construção de vários estágios: uma contando com Maven para a resolução de dependência e para construção e outra contando com JDK ou JRE para iniciar o aplicativo.

Estágio de construção (imagem Maven):

  • pom cópia para a imagem
  • dependências e downloads de plug - ins.
    Sobre isso, mvn dependency:resolve-pluginsacorrentado mvn dependency:resolvepode fazer o trabalho, mas nem sempre.
    Por quê ? Como esses plug-ins e a packageexecução para empacotar o fat jar podem depender de diferentes artefatos / plug-ins e até mesmo para um mesmo artefato / plug-in, eles ainda podem obter uma versão diferente. Portanto, uma abordagem mais segura, embora potencialmente mais lenta, é resolver dependências executando exatamente o mvncomando usado para empacotar o aplicativo (que puxará exatamente as dependências de que você precisa), mas ignorando a compilação de origem e excluindo a pasta de destino para tornar o processamento mais rápido e evitar qualquer detecção de mudança de camada indesejável para essa etapa.
  • cópia do código fonte para a imagem
  • empacote o aplicativo

Etapa de execução (imagem JDK ou JRE):

  • copie a jarra do estágio anterior

Aqui estão dois exemplos.

a) Uma maneira simples sem cache para dependências do maven baixadas

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

Desvantagem dessa solução? Quaisquer alterações no pom.xml significam recriação de toda a camada que baixa e armazena as dependências do maven. Isso geralmente não é aceitável para aplicativos com muitas dependências (e Spring Boot puxa muitas dependências), em geral se você não usar um gerenciador de repositório maven durante a construção da imagem.

b) Uma maneira mais eficiente com o cache para dependências do maven baixadas

A abordagem aqui é a mesma, mas os downloads de dependências do maven são armazenados em cache no cache do docker builder.
A operação do cache depende do buildkit (API experimental do docker).
Para habilitar o buildkit, a variável env DOCKER_BUILDKIT = 1 deve ser definida (você pode fazer isso onde quiser: .bashrc, linha de comando, arquivo docker daemon json ...).

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
fonte