前言

通常SpringBoot会使用spring-boot-maven-plugin插件将项目和依赖一起打包到一个jar包中,并且添加程序入口描述,使得可以通过java -jar application.jar的形式直接运行。

在Dockerize容器化实施过程中,把一个臃肿的SpringBoot jar包打包成一个docker镜像是很简单的,但是由于代码和全部的依赖都在一层中,复制和运行这种镜像有非常多的缺点。一般情况下,重新编译代码的频率会比升级SpringBoot的版本以及依赖的频率要高,因此这种情况下,把相关的事物分离会比较好。将依赖和代码分置于docker镜像的不同层,这样通常只需要更改编译代码的那一层,其他的依赖部分可以从Docker的缓存中获取。

解决方案

解压Jar包

如果你要从一个容器中运行你的应用程序,可以使用一个可执行的jar包,但是将其分解并用一种不同的方式运行它通常也是一种优势。运行未打包的归档文件的一种方式是启动适当的启动程序,如下所示:

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

这样实际上在启动时比从未分解的归档文件运行要稍快(取决于jar的大小)。但在运行时,您不应当期待能发现什么不同。

一旦您有了解压过的jar包文件,你可以通过应用程序的“natural”主方法而不是JarLauncher来运行应用来进一步缩短启动时间。如下所示:

jar -xf myapp.jar
java -cp BOOT-INF/classes:BOOT-INF/lib/* com.example.MyApplication
在应用程序的主方法上使用 JarLauncher 有一个额外的好处:类路径的顺序是可预测的。jar 包包含一个 classpath.idx 文件,当 JarLauncher 构造类路径时会使用该文件。

镜像分层

为了使得创建优化过的 Docker 镜像更方便,Spring Boot 支持在 jar 包中增加一个分层索引文件。这个文件提供了一个分层列表以及各层应当包含的 jar 包的部分。该索引中的分层列表是根据各层添加至 Docker/OCI 镜像的顺序排列的。开箱即用,支持以下分层:

  • 依赖(定期发布的依赖项)
  • spring-boot-loader(在 org/springframework/boot/loader 目录下的所有文件)
  • 快照依赖(快照版本的依赖项)
  • 应用程序(应用程序的类文件和资源文件)

下面展示了一个 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

这种分层旨在根据代码在应用程序多次构建之间发生更改的可能性大小将其分离开。库代码不太可能在多次构建中发生变化,所以将其放置在自己的分层中以允许工具重用缓存中的分层。应用代码在多次构建中最可能发生改变,因此它被隔离在单独的层中。

虽然通过 Dockerfile 里的几行命令就可以将一个 Spirng Boot 臃肿 jar 包转换成 docker 镜像,但我们将使用分层特性创建一个优化过的 docker 镜像。当您创建一个包含分层索引文件的 jar 包时,spring-boot-jarmode-layertools 的 jar 包将被作为依赖项添加到您的 jar 包中。通过类路径上的这个 jar 包,您可以在特殊模式下启动您的程序:该模式允许引导代码运行一些与您的代码完全不同的东西,比如能够提取分层信息的程序。

layertools 模式不能与包含启动脚本的完全可执行 Spring Boot 归档文件一起使用。在要构建用于 layertools 的 jar 包文件时禁用启动脚本配置。

这里展示了你怎么通过 layertools 模式启动你的 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 build . 命令构建 docker 镜像,或者可以指定应用程序的 jar 包路径,如下例所示:

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

这是一个多阶段的 dockerfile 。builder 阶段提取下一个阶段所需要的目录。每一个 COPY 命令都与 jarmode 提取的分层相关。

最后修改:2023 年 11 月 22 日
如果觉得我的文章对你有用,请随意赞赏