文章

SpringBoot 构建高效的容器镜像

前言

使用普通的 Dockerfile 构建会有哪些缺点?通常情况下构建一个 Spring Boot 的 Docker 镜像,一般会写一个下面这样的Dockerfile:

FROM eclipse-temurin:21-jre
ARG JAR_FILE=build/libs/application.jar # 这里使用Gradle构建,若是 Maven 的话,使用target/application.jar
ADD ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

确实很简单,然而复制并运行docker镜像中的 fat jar有以下几个缺点:

  1. 解压 Jar 包运行需要额外的开销:在不解压的情况下运行fat jar,总会有一定的开销,而在容器化环境中,这种开销是很明显的。

  2. 变更程序代码后重新构建镜像效率低:Docker镜像是分层构建的,而上面例子将依赖和程序代码都放在一个层中。在实际场景下,修改程序代码的频率将大大高于依赖的变化,所以最好将它们分在不同的层,这样不变的层在docker中可以直接使用缓存。

SpringBoot Layered JAR

从 Spring Boot 2.3.0 开始,使用 Spring Boot Maven 或 Gradle 插件构建的 JAR 文件包含下图的分层信息。

SpringBoot Layered Jar

默认情况下,存在以下分层:

  • dependencies:适用于常规发布的依赖项。

  • spring-boot-loader:Spring Boot Jar 包加载类,在org/springframework/boot/loader 下的所有内容。

  • snapshot-dependencies:用于快照依赖项。

  • application:应用程序的类和资源。

在Spring Boot 2.3之后编译的jar包多了一个文件layers.idx,通过这个文件来提供层被添加的顺序。从 layer.idx 中可以看到默认的顺序是:dependencies, spring-boot-loader,snapshot-dependencies, application

在控制台运行解压命令查看:

java -Djarmode=layertools -jar application.jar extract

解压后的 Jar 包目录

推荐的 Dockerfile 文件

Dockerfile文件

假设上述 Dockerfile 在当前目录下,你的docker镜像可以用 docker build . 来构建,也可以选择指定 应用程序jar的路径,比如:你是使用 Maven 进行构建的。

$ docker build --build-arg JAR_FILE=target/application.jar .

这是一个多阶段的docker文件。 构建者阶段提取了以后需要的目录。 每个 COPY 命令都与jarmode提取的层有关。

构建镜像的命令展示如下:

docker 镜像构建过程

可以通过修改程序代码,重新构建来观察各层的缓存情况。

推荐阅读

Container Images :: Spring Boot

License:  CC BY 4.0