1、概览 {#1概览}
本文将带你了解如何利用 Docker 的多阶段构建功能高效地为多模块 Maven 项目构建 Docker 镜像,,以充分利用 Docker 的缓存机制。
然后,还会介绍 Google Jib Maven 插件,用于在没有 Dockerfile 或 Docker 的情况下构建 Docker 镜像。
2、多模块 Maven 项目 {#2多模块-maven-项目}
多模块 Maven 应用由不同功能的独立模块组成。Maven 通过管理依赖关系来构建应用,并将这些模块组装成一个可部署的单元。
在本文的代码示例中,我们将使用一个包含两个 Maven 模块的基本 Spring Boot 项目,这两个模块分别代表应用程序的 Domain 和 API。
Maven 项目的结构如下:
+-- parent
+-- api
| `-- src
| `-- pom.xml
+-- domain
| `-- src
| `-- pom.xml
`-- pom.xml
查看父模块的 pom.xml
文件,就会发现它继承了 spring-boot-starter-parent,并包含了 domain
和 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
模块依赖于 module
模块,而不是相反。
3、多阶段 Docker 构建 {#3多阶段-docker-构建}
Docker 中的多阶段构建允许我们在单个 Docker 文件中使用多个 FROM
指令来创建更小、更高效的镜像。每个阶段都可用于不同的目的,如编译代码或打包应用,只有最后阶段才会包含在最终镜像中。
例如,我们的示例可以使用三个阶段:提取依赖、构建应用程序和准备运行时环境。
创建包含这三个不同部分的 Dockerfile:
# 预取依赖
FROM maven:3.8.5-openjdk-17 AS DEPENDENCIES
# 构建 jar
FROM maven:3.8.5-openjdk-17 AS BUILDER
# 准备运行时环境
FROM openjdk:17-slim
3.1、预取依赖项 {#31预取依赖项}
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
goal 来解析和下载 pom.xml
文件中指定的所有依赖。此外,还通过指定 "-B" 选项以非交互模式运行命令,并通过 "-e" 提示所有错误:
RUN mvn -B -e org.apache.maven.plugins:maven-dependency-plugin:3.1.2:go-offline -DexcludeArtifactIds=domain
最后还添加了 excludeArtifactIds
属性,以防止 Maven 下载特定的组件。在本例中,它排除了 domain
组件。因此,domain JAR 将在本地构建,而不是从 Repository 中获取。
这个命令确保在下一个阶段运行构建过程时,所有依赖项都将在本地可用,无需再次下载。
3.2、构建镜像 {#32构建镜像}
要构建镜像,首先需要确保所有必要的依赖都已预先获取,并且源代码可用。在 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 来编译代码并构建 domain
和 api
JAR 文件。由于测试可能已经在之前运行过了,我们可以使用 -DskipTests
跳过测试来加快编译过程:
RUN mvn -B -e clean install -DskipTests
3.3、准备运行环境 {#33准备运行环境}
在 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"]
3.4、运行应用 {#34运行应用}
最后,我们就可以构建并运行镜像了。还可以添加 from-dockerfile 标签来区分该镜像:
docker build -t baeldung-demo:from-dockerfile .
docker run -p 8080:8080 baeldung-demo:from-dockerfile
此时,请求 localhost:8080/api/countries
,就可以获取到响应。
如你所见,多阶段 Dockerfile 通过将构建依赖与最终运行环境隔离开来,简化了依赖管理。此外,它还能帮助我们减少最终镜像的大小,因为它只复制了构建阶段的必要工件,从而提高了效率和安全性。
4、使用 Jib 构建项目 {#4使用-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
5、总结 {#5总结}
本文介绍了如何使用 Docker 的多阶段构建功能为多模块的 Maven 项目构建 Docker 镜像,以及如何使用 Jib Maven 插件来构建 Docker 镜像,而无需 Dockerfile。
Ref:https://www.baeldung.com/docker-maven-build-multi-module-projects