一、简介
在本教程中,我们将展示如何在Docker 中构建Maven 项目。首先,我们将从一个简单的单模块Java 项目开始,并展示如何利用Docker 中的多阶段构建来对构建过程进行docker 化。接下来,我们将展示如何使用Buildkit 来缓存多个构建之间的依赖关系。最后,我们将介绍如何在多模块应用程序中利用层缓存。
2.多阶段分层构建
在本文中,我们将使用Guava 作为依赖项创建一个简单的Java 应用程序。我们将使用maven-assembly 插件创建一个胖JAR。代码和Maven 配置将从本文中省略,因为它们不是主要主题。
多阶段构建是优化Docker 构建过程的好方法。它们使我们能够将整个过程保存在一个文件中,还可以帮助我们保持Docker 映像尽可能小。在第一阶段,我们将运行Maven 构建并创建我们的胖JAR,在第二阶段,我们将复制JAR 并定义一个入口点:
FROM maven:alpine as build ENV HOME=/usr/app RUN mkdir -p $HOME WORKDIR $HOME ADD . $HOME RUN mvn package FROM openjdk:8-jdk-alpine COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar ENTRYPOINT java -jar /app/runner.jar
这种方法可以让我们保持最终的Docker 镜像更小,因为它不包含Maven 可执行文件或我们的源代码。
让我们创建Docker 镜像:
docker build -t maven-caching .
接下来,让我们从镜像中启动一个容器:
docker run maven-caching
当我们更改代码中的某些内容并重新运行构建时,我们会注意到Mavenpackage
任务之前的所有命令都被缓存并立即执行。由于我们的代码更改比项目依赖项更频繁,我们可以使用Docker 层缓存将依赖项下载和代码编译分开来缩短构建时间:
FROM maven:alpine as build ENV HOME=/usr/app RUN mkdir -p $HOME WORKDIR $HOME ADD pom.xml $HOME RUN mvn verify --fail-never ADD . $HOME RUN mvn package FROM openjdk:8-jdk-alpine COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar ENTRYPOINT java -jar /app/runner.jar
当我们只更改代码时运行后续构建会快得多,因为Docker 将从缓存中获取层。
3. 使用BuildKit 进行缓存
Docker 版本18.09 引入了BuildKit 作为对现有构建系统的大修。大修背后的想法是提高性能、存储管理和安全性。我们可以利用BuildKit 来保持多个构建之间的状态。这样,Maven 不会每次都下载依赖项,因为我们有永久存储。要在我们的Docker 安装中启用BuildKit,我们需要编辑daemon.json
文件:
... { "features": { "buildkit": true }} ...
启用BuildKit 后,我们可以将Dockerfile 更改为:
FROM maven:alpine as build ENV HOME=/usr/app RUN mkdir -p $HOME WORKDIR $HOME ADD . $HOME RUN --mount=type=cache,target=/root/.m2 mvn -f $HOME/pom.xml clean package FROM openjdk:8-jdk-alpine COPY --from=build /usr/app/target/single-module-caching-1.0-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar ENTRYPOINT java -jar /app/runner.jar
当我们更改代码或pom.xml
文件时,Docker 将始终执行ADD 和RUN Maven 命令。首次运行时构建时间将是最长的,因为Maven 必须下载依赖项。随后的运行将使用本地依赖项并执行得更快。
这种方法需要维护Docker 卷作为依赖项的存储。有时,我们必须强制Maven 使用Dockerfile 中的-U
标志更新我们的依赖项。
4. 多模块Maven 项目的缓存
在前面的部分中,我们展示了如何利用不同的方法来加快单模块Maven 项目的Docker 映像的构建时间。对于更复杂的应用,这些方法不是最佳的。多模块Maven 项目通常有一个模块作为我们应用程序的入口点。一个或多个模块包含我们的逻辑并被列为依赖项。
由于子模块被列为依赖项,它们将阻止Docker 进行层缓存并触发Maven 再次下载所有依赖项。BuildKit 的这个解决方案在大多数情况下都很好,但正如我们所说,它可能需要不时强制更新以获取更新的子模块。为了避免这种情况,我们可以将项目分层并使用Maven 增量构建:
FROM maven:alpine as build ENV HOME=/usr/app RUN mkdir -p $HOME WORKDIR $HOME ADD pom.xml $HOME ADD core/pom.xml $HOME/core/pom.xml ADD runner/pom.xml $HOME/runner/pom.xml RUN mvn -pl core verify --fail-never ADD core $HOME/core RUN mvn -pl core install RUN mvn -pl runner verify --fail-never ADD runner $HOME/runner RUN mvn -pl core,runner package FROM openjdk:8-jdk-alpine COPY --from=build /usr/app/runner/target/runner-0.0.1-SNAPSHOT-jar-with-dependencies.jar /app/runner.jar ENTRYPOINT java -jar /app/runner.jar
在这个Dockerfile 中,我们复制所有pom.xml
文件并增量构建每个子模块,最后打包整个应用程序。经验法则是我们构建的子模块在链的后期更频繁地更改。
5. 结论
在本文中,我们介绍了如何使用Docker 构建Maven 项目。首先,我们介绍了如何利用分层来缓存不经常更改的部分。接下来,我们介绍了如何使用BuildKit 来保持构建之间的状态。最后,我们展示了如何使用增量构建构建多模块Maven 项目。
0 评论