如何将maven项目docker化?有多少种方法可以实现?

How to dockerize maven project? and how many ways to accomplish it?

我是Docker新手,虽然看了很多文档,试过很多方法,但是不知道如何用maven运行一个java项目。

  1. 我应该使用 Dockerfile 构建图像吗?
  2. Dockerfile宿主机运行maven项目的命令是怎样的?

根据经验,您应该使用 Maven(一个包含您的代码和所有依赖项的 JAR)构建一个 fat JAR

然后你可以编写一个符合你要求的Docker文件(如果你可以构建一个胖JAR,你只需要一个基础os,像 CentOS 和 JVM)。

这是我用于 Scala 应用程序(基于 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

这将使用 Java7 创建一个基于 CentOS 的映像。 启动时,它将执行您的应用程序 jar。

部署它的最佳方式是通过 Docker 注册表,它就像 Docker 图片的 Github。

您可以像这样构建图像:

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

然后可以这样推送图片:

docker push username/projectname # this pushes all tags

一旦图像在 Docker 注册表中,您就可以从世界任何地方提取它并 运行 它。

有关详细信息,请参阅 Docker User Guide

注意事项:

您也可以将您的 repository 拉入图像并构建 jar 作为容器执行的一部分,但这不是一个好方法,因为代码可能会更改并且您最终可能会使用不同版本的应用程序,恕不另行通知。

构建一个 fat jar 可以解决这个问题。

工作示例。

这不是 spring 引导教程。这是关于如何 运行 在 Docker 容器中构建 Maven 的问题的更新答案。

问题最初发布于 4 年前。

1。生成应用程序

使用 spring 初始化程序生成演示应用程序

https://start.spring.io/

在本地解压 zip 存档

2。创建一个Docker文件

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

备注

  • 这个例子使用了multi-stage build。第一阶段用于构建代码。第二阶段只包含构建的 jar 和一个 JRE 到 运行 它(注意 jar 是如何在阶段之间复制的)。

3。构建图像

docker build -t demo .

4。 运行 图片

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

其他

阅读 Docker 中心文档,了解如何优化 Maven 构建以使用本地存储库来缓存 jar。

更新 (2019-02-07)

这个问题已有 4 年历史,可以说使用 Docker 构建应用程序已经发生了重大变化。

选项 1:Multi-stage 构建

这种新样式使您能够创建更多 light-weight 图像,这些图像不会封装您的构建工具和源代码。

此处的示例再次使用 official maven 基础映像 运行 使用所需版本的 Maven 进行构建的第一阶段。该文件的第二部分定义了如何将构建的 jar 组装到最终输出图像中。

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

注:

  • 我正在使用 Google 的 distroless 基本图像,它努力为 java 应用程序提供足够的 run-time。

选项 2:吊臂

我没有使用过这种方法,但似乎值得研究,因为它使您能够构建图像而不必创建像 Docker 文件这样令人讨厌的东西:-)

https://github.com/GoogleContainerTools/jib

该项目有一个 Maven plugin 将您的代码打包直接集成到您的 Maven 工作流中。


原始答案(包括在内是为了完整性,但很久以前写的)

尝试使用新的官方镜像,Maven 有一个

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

图像可用于 运行 Maven 在构建时创建编译的应用程序,或者,如以下示例所示,运行 在容器中构建 Maven。

示例 1 - Maven 运行在容器中运行

以下命令运行在容器中构建您的 Maven:

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

备注:

  • 这种方法的巧妙之处在于,所有软件都已安装并运行安装在容器中。主机上只需要docker。
  • Dockerfile for this version

示例 2 - 使用 Nexus 缓存文件

运行 Nexus 容器

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

创建一个 "settings.xml" 文件:

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

现在运行 Maven 链接到nexus 容器,以便缓存依赖项

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

备注:

  • 在后台 运行ning Nexus 的一个优点是其他第 3 方存储库可以通过管理员 URL 透明地管理到本地容器中的 Maven 构建 运行ning。

可能有很多方法..但我是通过以下两种方式实现的

给出的示例是maven项目。

1.在maven项目中使用Dockerfile

使用以下文件结构:

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

并将 Dockerfile 更新为:

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

导航到项目文件夹并键入以下命令,您将获得 le 创建图像和 运行 该图像:

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

Spring Boot with Docker

获取视频

2。使用 Maven 插件

pom.xml

中添加给定的maven插件
<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>

导航到项目文件夹并键入以下命令,您将能够创建图像和 运行 该图像:

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

在第一个示例中,我们正在创建 Dockerfile 并提供基础映像并添加 jar 和 so,之后我们将 运行 docker 命令构建具有特定名称的映像,然后 运行 那张图片..

而在第二个示例中,我们使用的是提供 baseImageimageName 的 maven 插件,因此我们不需要在此处创建 Dockerfile。打包 maven 项目后,我们将获得 docker 图片,我们只需要 运行 那张图片..

这是我的贡献。
我不会尝试列出所有存在的 tools/libraries/plugins 以利用 Maven 的 Docker。有的回答已经做到了
相反,我将专注于应用程序类型学和 Docker 文件方式。
Dockerfile 确实是 Docker 的一个简单而重要的概念(所有 known/public 图像都依赖于它),我认为试图避免理解和使用 Dockerfile 不一定是进入 Docker 世界的更好方式。

Docker应用程序的启动取决于应用程序本身和要达到的目标

1) 对于我们想要在 installed/standalone Java 服务器上继续 运行 的应用程序 (Tomcat, JBoss, 等等...)

道路更难,这不是理想的目标,因为这增加了复杂性(我们必须 manage/maintain 服务器)并且可扩展性较差且速度较慢比嵌入式服务器 build/deploy/undeploy.
但对于遗留应用程序,这可能被视为第一步。
一般来说,这里的想法是定义一个包含应用程序服务器的 Docker 图像。您可以拥有自己的 Tomcat、JBoss 或 Weblogic 基础 Docker 文件来设置和配置服务器。
关于在服务器上部署的应用程序,并没有真正的标准方法。
一种可能的方法是按应用程序或一组应用程序定义一个 Docker 文件以一起部署。 Docker 文件将具有先前创建的应用程序服务器 Docker 文件作为基础映像。
该应用程序 Docker 文件将具有 objective 从 Git 或从 Maven 存储库管理器检索组件 (JARs/WARs/EARs) 并到 deploy/install it/them 在应用程序服务器上正确。
对于包含大量遗留内容且难以迁移到完整 spring 启动嵌入式解决方案的大型应用程序(数百万行代码),这确实是一个不错的改进。
我不会详细介绍该方法,因为它适用于 Docker 的次要用例,但我想公开该方法的总体思路,因为我认为对于面临这些复杂情况的开发人员来说,很高兴知道一些敞开大门整合 Docker.

2) 对于 embed/bootstrap 服务器本身的应用程序(Spring 使用嵌入式服务器启动:Tomcat、Netty、Jetty...)

这是Docker的理想目标。 我指定 Spring Boot 是因为这是一个非常好的框架来做到这一点并且它也具有非常高的可维护性,但理论上我们可以使用任何其他 Java 方式来实现它。
一般来说,这里的想法是为每个要部署的应用程序定义一个 Docker 图像。
应用程序的 docker 图像生成一个 JAR 或一组 JAR/classes/configuration 文件,当我们从这些图像创建和启动容器时,这些文件会使用应用程序(java 命令)启动 JVM。
对于新的应用程序或不太复杂而无法迁移的应用程序,这种方式必须优于独立服务器,因为这是使用容器的标准方式和最有效的方式。
我将详细说明该方法。

Docker正在创建 Maven 应用程序

1) 不带Spring开机

想法是使用 Maven(maven 程序集插件和 maven shade 插件帮助)创建一个 fat jar,其中包含已编译的 类 应用程序和所需的 maven 依赖项。
那么我们可以确定两种情况:

  • 如果应用程序是桌面或自主应用程序(不需要部署在服务器上):我们可以在 Dockerfile 中指定为 CMD/ENTRYPOINT java 应用程序的执行:java -cp .:/fooPath/* -jar myJar

  • 如果应用程序是服务器应用程序,例如 Tomcat,想法是相同的:获取应用程序的 fat jar 和 运行 中的 JVM CMD/ENTRYPOINT。但这里有一个重要的区别:我们需要包含一些逻辑和特定的库(org.apache.tomcat.embed 库和其他一些库),以便在主应用程序启动时启动嵌入式服务器。
    我们有一份综合指南 on the heroku website
    对于第一种情况(自主应用程序),这是使用 Docker.
    的一种直接且有效的方法 对于第二种情况(服务器应用程序),它可行但不直接,可能容易出错并且不是一个非常可扩展的模型,因为您没有将您的应用程序放在 Spring 等成熟框架的框架中引导可以为您完成许多这些事情,还提供高水平的扩展。
    但这有一个优点:你有很高的自由度,因为你直接使用嵌入的 Tomcat API.

2) 带Spring引导

终于,我们开始了。
这既简单、高效又有据可查。
在 Docker.
上制作 Maven/Spring 启动应用程序到 运行 确实有几种方法 将它们全部公开会很长,而且可能很无聊。
最佳选择取决于您的要求。
但无论如何,docker层的构建策略看起来都是一样的。
我们想使用多阶段构建:一个依赖 Maven 进行依赖解析和构建,另一个依赖 JDK 或 JRE 来启动应用程序。

构建阶段(Maven 镜像):

  • pom复制到图片
  • 依赖项插件下载。
    关于这一点,链接到 mvn dependency:resolvemvn dependency:resolve-plugins 可能会完成这项工作,但并非总是如此。
    为什么 ?因为这些插件和打包 fat jar 的 package 执行可能依赖于不同的 artifacts/plugins,甚至对于相同的 artifact/plugin,它们可能仍然会拉取不同的版本。 因此,一种更安全但可能更慢的方法是通过完全执行用于打包应用程序的 mvn 命令(这将完全提取您需要的依赖项)来解决依赖项,但跳过源代码编译并删除目标文件夹以进行处理速度更快,并防止对该步骤进行任何不希望的层更改检测。
  • 源代码复制到图片
  • 打包应用程序

运行 阶段(JDK 或 JRE 图像):

  • 复制上一阶段的jar
  • entrypoint/cmd : 运行 应用程序

这里举两个例子。

a) 一种简单的无需缓存下载maven依赖的方法

Docker文件:

########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

该解决方案的缺点? pom.xml 中的任何更改都意味着重新创建下载和存储 Maven 依赖项的整个层。 对于具有许多依赖项的应用程序(并且 Spring Boot 拉取许多依赖项),这通常是不可接受的,总的来说,如果您在映像构建期间不使用 maven 存储库管理器。

b) 一种更有效的缓存方式,用于下载 maven 依赖项

这里的方法是相同的,但是 Maven 依赖项下载缓存在 docker 构建器缓存中。
缓存操作依赖buildkit(实验api of docker)。
要启用 buildkit,必须设置 env 变量 DOCKER_BUILDKIT=1(您可以在需要的地方进行设置:.bashrc、命令行、docker 守护进程 json 文件...)。

Docker文件:

# 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 and download dependencies only when these are new (thanks to the cache)
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

                                                                                                                                                                        
                                                                       
                                                                       
Create a Dockerfile
#
# Build stage
#

FROM maven:3.6.3-jdk-11-slim AS build

WORKDIR usr/src/app

COPY . ./

RUN mvn clean package

#
# Package stage
#

FROM openjdk:11-jre-slim

ARG JAR_NAME="project-name"

WORKDIR /usr/src/app

EXPOSE ${HTTP_PORT}

COPY --from=build /usr/src/app/target/${JAR_NAME}.jar ./app.jar

CMD ["java","-jar", "./app.jar"]

Jib 作为 Maven 插件,使得使用 Docker 守护进程或忽略它变得足够简单和灵活。在命令行或 pom.xml.

更多信息可以参考build-a-docker-image-using-maven

这是一个简单的pom.xml:

...
<properties>
    <java.version>11</java.version>
    <docker.name>amirkeshavarz/hellomavendocker</docker.name>
    <docker.REGISTRY_USERNAME>your-dockerhub-username</docker.REGISTRY_USERNAME>
    <docker.REGISTRY_PASSWORD>your-dockerhub-password</docker.REGISTRY_PASSWORD>
</properties>
...
<build>
    <plugins>
        ...
        <plugin>
            <groupId>com.google.cloud.tools</groupId>
            <artifactId>jib-maven-plugin</artifactId>
            <version>2.8.0</version>
            <configuration>
                <from>
                    <image>openjdk:17-jdk-alpine</image>
                    <auth>
                        <username>${docker.REGISTRY_USERNAME}</username>
                        <password>${docker.REGISTRY_PASSWORD}</password>
                    </auth>
                </from>
                <to>
                    <image>${docker.name}</image>
                    <auth>
                        <username>${docker.REGISTRY_USERNAME}</username>
                        <password>${docker.REGISTRY_PASSWORD}</password>
                    </auth>
                </to>
                <container>
                    <environment></environment>
                    <ports>
                        <port>8080</port>
                    </ports>
                    <creationTime>USE_CURRENT_TIMESTAMP</creationTime>
                </container>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>build</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
...