Spring Boot 应用程序可以通过 Dockerfiles 进行容器化,或者通过使用 Cloud Native Buildpacks 创建优化的 Docker 兼容容器映像,您可以在任何地方运行。

1. 高效的容器映像

将 Spring Boot uber jar 打包为 Docker 映像是很容易的。然而,直接复制并在 Docker 映像中运行 uber jar 会有各种缺点。在不解压的情况下运行 uber jar 时总会有一定的开销,在容器化环境中这可能会很明显。另一个问题是将应用程序的代码和所有依赖项放在 Docker 映像中的一个层中是不够优化的。由于您可能更频繁地重新编译代码而不是升级 Spring Boot 版本,通常最好将它们分开一些。如果您将 jar 文件放在应用程序类之前的层中,Docker 通常只需要更改底层并可以从其缓存中获取其他内容。

1.1. 分层 Docker 映像

为了更轻松地创建优化的 Docker 映像,Spring Boot 支持向 jar 文件添加一个层索引文件。它提供了一个层列表以及应该包含在其中的 jar 部分。索引中的层列表根据应该将层添加到 Docker/OCI 映像中的顺序进行排序。Out-of-the-box,支持以下层:

  • dependencies(用于常规发布的依赖项)

  • spring-boot-loader(用于 org/springframework/boot/loader 下的所有内容)

  • snapshot-dependencies(用于快照依赖项)

  • application(用于应用程序类和资源)

以下是一个 layers.idx 文件的示例:

- "dependencies":
  - BOOT-INF/lib/library1.jar
  - BOOT-INF/lib/library2.jar
- "spring-boot-loader":
  - org/springframework/boot/loader/launch/JarLauncher.class
  - ... <other classes>
- "snapshot-dependencies":
  - BOOT-INF/lib/library3-SNAPSHOT.jar
- "application":
  - META-INF/MANIFEST.MF
  - BOOT-INF/classes/a/b/C.class

这种分层设计是根据应用程序构建之间更改的可能性来分隔代码的。库代码在构建之间更改的可能性较小,因此将其放在自己的层中以允许工具重用缓存中的层。应用程序代码在构建之间更有可能更改,因此将其隔离在单独的层中。

Spring Boot 还支持通过 layers.idx 对 war 文件进行分层。

对于 Maven,请参阅关于向存档添加层索引的更多详细信息的打包分层 jar 或 war 部分。对于 Gradle,请参阅 Gradle 插件文档的打包分层 jar 或 war 部分

2. Dockerfiles

虽然可以通过 Dockerfile 中的几行将 Spring Boot uber jar 转换为 docker 映像,但我们将使用 分层功能 来创建优化的 docker 映像。当您创建包含层索引文件的 jar 时,spring-boot-jarmode-layertools jar 将作为依赖项添加到您的 jar 中。有了这个 jar 在类路径上,您可以以特殊模式启动应用程序,该模式允许引导代码运行与您的应用程序完全不同的内容,例如,提取层。

无法将 layertools 模式与包含启动脚本的完全可执行的 Spring Boot 存档一起使用。在构建旨在与 layertools 一起使用的 jar 文件时,请禁用启动脚本配置。

以下是如何使用 layertools jar 模式启动您的 jar:

$ java -Djarmode=layertools -jar my-app.jar

这将提供以下输出:

Usage:
  java -Djarmode=layertools -jar my-app.jar

Available commands:
  list     List layers from the jar that can be extracted
  extract  Extracts layers from the jar for image creation
  help     Help about any command

extract 命令可用于轻松将应用程序拆分为要添加到 Dockerfile 的层。以下是使用 jarmode 的 Dockerfile 示例。

FROM eclipse-temurin:17-jre as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM eclipse-temurin:17-jre
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.launch.JarLauncher"]

假设上述 Dockerfile 在当前目录中,您可以使用 docker build . 构建您的 docker 映像,或者在以下示例中指定路径到您的应用程序 jar:

$ docker build --build-arg JAR_FILE=path/to/myapp.jar .

这是一个多阶段的 Dockerfile。构建器阶段提取后续需要的目录。每个 COPY 命令与 jarmode 提取的层相关联。

当然,可以编写不使用 jarmode 的 Dockerfile。您可以使用一些组合的 unzipmv 将内容移动到正确的层,但 jarmode 简化了这一过程。

3. 云原生 Buildpacks

Dockerfiles 只是构建 docker 映像的一种方式。另一种构建 docker 映像的方式是直接使用 buildpacks 从您的 Maven 或 Gradle 插件中,使用 buildpacks。如果您曾经使用过 Cloud Foundry 或 Heroku 等应用程序平台,那么您可能已经使用过 buildpack。Buildpacks 是平台的一部分,它将您的应用程序转换为平台实际可以运行的内容。例如,Cloud Foundry 的 Java buildpack 将注意到您正在推送一个 .jar 文件,并自动添加相关的 JRE。

使用云原生 Buildpacks,您可以创建 Docker 兼容的映像,可以在任何地方运行。Spring Boot 直接为 Maven 和 Gradle 提供了 buildpack 支持。这意味着您只需键入一个命令,就可以快速将一个合理的映像放入本地运行的 Docker 守护程序中。

请查看有关如何在 MavenGradle 中使用 buildpacks 的各自插件文档。

Paketo Spring Boot buildpack 支持 layers.idx 文件,因此应用于它的任何自定义将反映在 buildpack 创建的映像中。
为了实现可重现的构建和容器映像缓存,Buildpacks 可以操作应用程序资源元数据(例如文件的“最后修改”信息)。您应确保您的应用程序在运行时不依赖于该元数据。Spring Boot 在提供静态资源时可以使用该信息,但可以通过 spring.web.resources.cache.use-last-modified 禁用。

4. 接下来阅读什么

一旦您学会了如何构建高效的容器映像,您可以阅读有关将应用程序部署到云平台(如 Kubernetes)的内容。