如何在 Docker 中构建多模块 Maven 项目

在本教程中,我们将学习如何高效地为多模块 Maven 项目构建 Docker 镜像。我们将首先探索多阶段 Docker 构建,以充分利用 Docker 的缓存机制。

然后,我们将研究使用 Google 的Jib Maven 插件的替代方法。此工具允许我们创建优化的 Docker 映像,而无需Dockerfile或 Docker 守护程序。

在本文中,我们学习如何为具有多个模块的 Maven 项目构建 Docker 镜像。首先,我们手动创建一个Dockerfile ,在其中复制所有pom.xml文件,解析所有依赖项并构建了镜像。此外,我们使用 Docker 的多阶段功能来充分利用其缓存机制。

之后,我们探索 Jib Maven 插件,并使用它来构建 Docker 镜像,而无需 Dockerfile 。 Jib插件有效地管理依赖项并构建镜像,而无需手动定义每个构建步骤的开销。

多模块 Maven 项目
多模块 Maven 应用程序由用于不同功能的独立模块组成。Maven通过管理依赖项并将这些模块组装成单个可部署单元来构建应用程序。

对于本文中的代码示例,我们将使用一个包含两个 Maven 模块的基本 Spring Boot 项目 - 代表我们应用程序的域和API。让我们直观地了解一下这个 Maven 项目的结构:

+-- parent
   +-- api
   |   -- src
   |  
-- pom.xml
   +-- domain
   |   -- src
   |  
-- pom.xml
    `-- pom.xml

如果我们看一下父模块的pom.xml文件,我们可以预期它扩展spring-boot-starter-parent并包含域和api模块:

<project>
    <groupId>com.baeldung.docker-multi-module-maven</groupId>
    <artifactId>parent</artifactId>
    <packaging>pom</packaging>
    <version>0.0.1-SNAPSHOT</version>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.3.2</version>
        <relativePath />
    </parent>
    <modules>
        <module>api</module>
        <module>domain</module>
    </modules>
    <!--  other configuration  -->
</project>

此外,我们将遵循干净架构原则,确保所有源代码依赖关系都指向正确的方向。简而言之,我们将确保api模块依赖于域模块,而不是相反。

多阶段 Docker 构建
Docker 中的多阶段构建功能允许我们在单个Dockerfile中使用多个FROM指令来创建更小、更高效的镜像。每个阶段可用于不同的目的,例如编译代码或打包应用程序,并且只有最后阶段才会包含在最终镜像中。

例如,我们的示例可以使用三个阶段:提取依赖项、构建应用程序和准备运行时环境。让我们使用以下三个不同的部分创建Dockerfile  :

pre-fetch dependencies
FROM maven:3.8.5-openjdk-17 AS DEPENDENCIES
build the jar
FROM maven:3.8.5-openjdk-17 AS BUILDER
prepeare runtime env
FROM openjdk:17-slim

预取依赖项
DEPENDENCIES阶段将为我们的应用程序预取和缓存 Maven 依赖项。让我们首先选择我们喜欢的maven 镜像,然后复制三个pom.xml文件:

FROM maven:3.8.5-openjdk-17 AS DEPENDENCIES
WORKDIR /opt/app
COPY api/pom.xml api/pom.xml
COPY domain/pom.xml domain/pom.xml
COPY pom.xml .

之后,我们需要使用maven-dependency-plugin及其go-offline目标来解析和下载pom.xml文件中指定的所有依赖项。此外,我们将通过指定“-B”选项以非交互模式运行该命令,并通过“-e”提示所有错误:

RUN mvn -B -e org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline -DexcludeArtifactIds=domain

最后,我们添加了excludeArtifactIds属性,以防止 Maven 下载特定工件。在本例中,它排除了域工件。因此,域JAR 将在本地构建,而不是从存储库中获取。

此命令确保当我们在下一阶段运行构建过程时,所有依赖项都将在本地可用,无需再次下载。

构建镜像
要构建镜像,我们首先需要确保所有必需的依赖项都已预取并且源代码可用。在BUILDER阶段,我们首先从DEPENDENCIES阶段复制必要的资源:

FROM maven:3.8.5-openjdk-17 AS BUILDER
WORKDIR /opt/app
COPY --from=DEPENDENCIES /root/.m2 /root/.m2
COPY --from=DEPENDENCIES /opt/app/ /opt/app
COPY api/src /opt/app/api/src
COPY domain/src /opt/app/domain/src

接下来,让我们运行mvn clean install来编译代码并构建域和api JAR 文件。由于测试可能已在之前运行,因此我们可以使用-DskipTests来加快构建过程:

RUN mvn -B -e clean install -DskipTests


准备运行环境
在Dockerfile的最后阶段,我们将为应用程序设置最小的运行时环境。我们将选择应用程序将在其上运行的基础映像,从上一阶段复制 JAR 文件,并定义启动它的入口点:

FROM openjdk:17-slim
WORKDIR /opt/app
COPY --from=BUILDER /opt/app/api/target/*.jar /app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]

运行应用程序
最后,我们可以构建并运行镜像。我们还将添加from-dockerfile标签来区分此镜像:

docker build -t baeldung-demo:from-dockerfile .
docker run -p 8080:8080 baeldung-demo:from-dockerfile

不用说,如果我们向localhost:8080/api/countries发送GET请求,我们会注意到我们的应用程序已启动并正在运行。

可以看出,多阶段Dockerfile 通过将构建依赖项与最终运行时环境隔离,简化了依赖项管理。此外,它还通过仅从构建阶段复制必要的工件来帮助我们减小最终镜像大小,从而提高效率和安全性。

使用 Jib 构建项目
我们还可以使用专用工具(例如 Jib)构建 Docker 镜像。Jib Maven 插件是一种工具,它可以直接从我们的 Maven 构建中为 Java 应用程序构建优化的 Docker 镜像,而无需 Dockerfile或Docker 守护程序。

Jib 要求我们配置一些关键属性:

  • Java 基础镜像
  • 生成的 Docker 镜像的名称
  • 应用程序的入口点
  • 暴露的端口

让我们将maven-jib-plugin添加到我们的 API 模块的pom.xml中:

<plugin>
    <groupId>com.google.cloud.tools</groupId>
    <artifactId>jib-maven-plugin</artifactId>
    <version>3.4.0</version>
    <configuration>
        <from>
            <image>openjdk:17-slim</image>
        </from>
        <to>
            <image>baeldung-demo:from-jib</image>
        </to>
        <container>
            <mainClass>com.baeldung.api.Application</mainClass>
            <ports>
                <port>8080</port>
            </ports>
        </container>
    </configuration>
</plugin>

之后我们就可以使用Maven来构建镜像了:

mvn compile jib:dockerBuild

结果,Jib 构建了 Docker 镜像,我们现在可以使用docker run命令运行该应用程序:

docker run -p 8080:8080 baeldung-demo:from-jib