# 容器图像

Spring 引导应用程序可以是容器化的[使用 DockerFiles](#container-images.dockerfiles)，也可以是容器化的[使用本地云构建包创建可在任何地方运行的优化的 Docker 兼容容器映像](#container-images.buildpacks)。

## 1. 高效的容器图像


将 Spring 引导 fat jar 打包为 Docker 映像是很容易的。然而，复制和运行 Docker 映像中的 fat jar 有各种缺点。在不拆包的情况下运行 A fat jar 时，总会有一定的开销，在集装箱化的环境中，这一点可能是显而易见的。另一个问题是，将应用程序的代码及其所有依赖项放在 Docker 映像中的一个层中是次优的。由于你重新编译代码的次数可能比升级所使用的启动版本更多，所以更好的做法通常是多分离一些代码。如果将 jar 个文件放在应用程序类之前的层中，Docker 通常只需要更改最底层的文件，就可以从其缓存中获取其他文件。

### 1.1.打开 fat jar

如果你是从一个容器运行你的应用程序，那么你可以使用一个可执行文件 jar，但通常也有一个优势，那就是将其分解并以不同的方式运行它。某些 PaaS 实现还可能选择在运行归档文件之前对其进行解包。例如，Cloud Foundry 就是这样运作的。运行未打包归档文件的一种方法是启动适当的启动器，如下所示：

```
$ jar -xf myapp.jar
$ java org.springframework.boot.loader.JarLauncher
```

这实际上在启动时（取决于 jar 的大小）比在未爆炸的归档文件中运行稍微快一些。在运行时，你不应该期望有任何差异。

打开 jar 文件后，还可以通过使用“自然”主方法（而不是`JarLauncher`）运行应用程序来增加启动时间。例如：

```
$ jar -xf myapp.jar
$ java -cp BOOT-INF/classes:BOOT-INF/lib/* com.example.MyApplication
```

|   |在应用程序的主方法上使用`JarLauncher`具有可预测的 Classpath 顺序的附加好处。<br/> jar 包含一个`classpath.idx`文件，该文件由`JarLauncher`在构造 Classpath 时使用。|
|---|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

### 1.2.分层 Docker 图像

为了使创建优化的 Docker 映像更容易， Spring 启动支持向 jar 添加层索引文件。它提供了层的列表和 jar 中应该包含在其中的部分。索引中的层列表是根据将层添加到 Docker/OCI 图像中的顺序进行排序的。在开箱即用的情况下，支持以下层：

* `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/JarLauncher.class
  - org/springframework/boot/loader/jar/JarEntry.class
- "snapshot-dependencies":
  - BOOT-INF/lib/library3-SNAPSHOT.jar
- "application":
  - META-INF/MANIFEST.MF
  - BOOT-INF/classes/a/b/C.class
```

这种分层设计是为了根据在应用程序构建之间更改代码的可能性来分离代码。库代码不太可能在构建之间发生变化，因此将其放置在自己的层中，以允许工具重新使用缓存中的层。应用程序代码更有可能在不同的构建之间进行更改，因此它被隔离在一个单独的层中。

Spring 在`layers.idx`的帮助下，启动还支持对 WAR 文件进行分层。

对于 Maven，请参阅[packaging layered jar or war section](https://docs.spring.io/spring-boot/docs/2.6.4/maven-plugin/reference/htmlsingle/#repackage-layers)以获取有关向归档文件添加图层索引的更多详细信息。有关 Gradle，请参见 Gradle 插件文档的[packaging layered jar or war section](https://docs.spring.io/spring-boot/docs/2.6.4/gradle-plugin/reference/htmlsingle/#packaging-layered-archives)。

## 2. DockerFiles

虽然只需在 Dockerfile 中的几行就可以将 Spring 启动 fat jar 转换为 Docker 映像，但我们将使用[分层特征](#container-images.efficient-images.layering)来创建优化的 Docker 映像。当你创建包含层索引文件的 jar 时，`spring-boot-jarmode-layertools` jar 将作为依赖项添加到 jar 中。有了这个 jar 在 Classpath 上，你可以以一种特殊的模式启动你的应用程序，该模式允许引导代码运行与你的应用程序完全不同的东西，例如，提取层的东西。

|   |`layertools`模式不能与包含启动脚本的[fully executable Spring Boot archive](deployment.html#deployment.installing)一起使用。<br/>在构建打算与`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 adoptopenjdk:11-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract

FROM adoptopenjdk:11-jre-hotspot
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.JarLauncher"]
```

假设上面的`Dockerfile`在当前目录中，你的 Docker 映像可以使用`docker build .`构建，或者可选地指定应用程序的路径 jar，如以下示例所示：

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

这是一个多阶段的 DockerFile。构建器阶段提取以后需要的目录。每个`COPY`命令都与 JARMODE 提取的层有关。

当然，可以在不使用 JARMODE 的情况下编写 DockerFile。你可以使用`unzip`和`mv`的一些组合来将事情移动到正确的层，但是 Jarmode 简化了这一点。

## 3. 云原生构建包

DockerFiles 只是构建 Docker 映像的一种方式。另一种构建 Docker 映像的方法是直接从 Maven 或 Gradle 插件中使用 buildpacks。如果你曾经使用过 Cloud Foundry 或 Heroku 之类的应用程序平台，那么你很可能使用过 BuildPack。构建包是平台的一部分，它接收应用程序并将其转换为平台可以实际运行的内容。例如，Cloud Foundry 的 Java BuildPack 会注意到，你正在推送一个`.jar`文件，并自动添加一个相关的 JRE。

使用 Cloud Native 构建包，你可以创建可以在任何地方运行的与 Docker 兼容的映像。 Spring 引导包括直接针对 Maven 和 Gradle 的 BuildPack 支持。这意味着你只需键入一个命令，就可以快速地在本地运行的 Docker 守护进程中获得一个合理的映像。

请参阅有关如何使用[Maven](https://docs.spring.io/spring-boot/docs/2.6.4/maven-plugin/reference/htmlsingle/#build-image)和[Gradle](https://docs.spring.io/spring-boot/docs/2.6.4/gradle-plugin/reference/htmlsingle/#build-image)构建包的单个插件文档。

|   |[Paketo Spring Boot buildpack](https://github.com/paketo-buildpacks/spring-boot)也已更新，以支持`layers.idx`文件，因此应用于该文件的任何定制都将反映在 BuildPack 创建的图像中。|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

|   |为了实现可重复的构建和容器映像缓存，构建包可以操作应用程序资源元数据（例如文件“上次修改”信息）。<br/>你应该确保你的应用程序在运行时不依赖于该元数据。<br/> Spring 引导在服务静态资源时可以使用该信息，但这可以用`spring.web.resources.cache.use-last-modified`禁用|
|---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|

## 4. 接下来要读什么？

一旦你了解了如何构建高效的容器映像，你就可以了解[将应用程序部署到云平台](deployment.html#deployment.cloud.kubernetes)，比如 Kubernetes。

