Skip to content
欢迎扫码关注公众号

Spring Boot 优化容器镜像

Spring Boot 应用使用 spring-boot-maven-plugin 打包的是 uber jar(也就是常说的 fat jar)。

开发时大部分情况下只有应用程序对应的 jar 包有修改,但使用 uber jar 时对于 Docker 来说,复制 jar 的这一层是有变动的,就需要对整个一层做推送或拉取。

如果能将应用所在的 jar 包单独放在一层,不就可以减少镜像变动的大小了吗。

下面的 Dockerfile 提供了一个通用的示例 [1]

dockerfile
# Perform the extraction in a separate builder container
FROM bellsoft/liberica-openjre-debian:17-cds AS builder
WORKDIR /builder
# This points to the built jar file in the target folder
# Adjust this to 'build/libs/*.jar' if you're using Gradle
ARG JAR_FILE=target/*.jar
# Copy the jar file to the working directory and rename it to application.jar
COPY ${JAR_FILE} application.jar
# Extract the jar file using an efficient layout
RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted

# This is the runtime container
FROM bellsoft/liberica-openjre-debian:17-cds
WORKDIR /application
# Copy the extracted jar contents from the builder container into the working directory in the runtime container
# Every copy step creates a new docker layer
# This allows docker to only pull the changes it really needs
COPY --from=builder /builder/extracted/dependencies/ ./
COPY --from=builder /builder/extracted/spring-boot-loader/ ./
COPY --from=builder /builder/extracted/snapshot-dependencies/ ./
COPY --from=builder /builder/extracted/application/ ./
# Execute the CDS training run
RUN java -XX:ArchiveClassesAtExit=application.jsa -Dspring.context.exit=onRefresh -jar application.jar
# Start the application jar with CDS enabled - this is not the uber jar used by the builder
# This jar only contains application code and references to the extracted jar files
# This layout is efficient to start up and CDS friendly
ENTRYPOINT ["java", "-XX:SharedArchiveFile=application.jsa", "-jar", "application.jar"]

这个 Dockerfile 主要分为两部分:

  • 上面的部分是构建的 uber jar 按照分层分别提取到对应的目录;
  • 下面的部分是将提取的目录复制到镜像。
    • 由于是分别复制的,对应的层如果没有变化,Docker 就不会重新拉取。
    • 这部分还预生成了 CDS 缓存文件,用于加快应用启动的速度。

开发环境:

  • Spring Boot 3.4.3
  • JDK 17

下面以之前的一个 WebSocket 示例项目为基础,在本地执行一下提取的命令看一下效果:

bash
java -Djarmode=tools -jar application.jar extract --layers --destination extracted

提取的文件

提取的目录:[2]

  • dependencies (for regular released dependencies)

  • spring-boot-loader (for everything under org/springframework/boot/loader)

  • snapshot-dependencies (for snapshot dependencies)

  • application (for application classes and resources)

每个目录对应一个 Docker 层。

其实也可以解压缩 jar 文件,然后将文件复制到对应的目录。

解压缩后可以在 BOOT-INF 目录下看到两个 .idx 后缀的文件。这两个就是用来配置分层的。如果需要修改分层的配置,可以参考官方文档:Custom Layers Configuration

md
- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"
md
- "BOOT-INF/lib/spring-boot-3.4.3.jar"
- "BOOT-INF/lib/spring-boot-autoconfigure-3.4.3.jar"
- "BOOT-INF/lib/logback-classic-1.5.16.jar"
- "BOOT-INF/lib/logback-core-1.5.16.jar"
- "BOOT-INF/lib/log4j-to-slf4j-2.24.3.jar"
- "BOOT-INF/lib/log4j-api-2.24.3.jar"
- "BOOT-INF/lib/jul-to-slf4j-2.0.16.jar"
- "BOOT-INF/lib/jakarta.annotation-api-2.1.1.jar"
- "BOOT-INF/lib/snakeyaml-2.3.jar"
- "BOOT-INF/lib/jackson-databind-2.18.2.jar"
- "BOOT-INF/lib/jackson-annotations-2.18.2.jar"
- "BOOT-INF/lib/jackson-core-2.18.2.jar"
- "BOOT-INF/lib/jackson-datatype-jdk8-2.18.2.jar"
- "BOOT-INF/lib/jackson-datatype-jsr310-2.18.2.jar"
- "BOOT-INF/lib/jackson-module-parameter-names-2.18.2.jar"
- "BOOT-INF/lib/tomcat-embed-core-10.1.36.jar"
- "BOOT-INF/lib/tomcat-embed-el-10.1.36.jar"
- "BOOT-INF/lib/tomcat-embed-websocket-10.1.36.jar"
- "BOOT-INF/lib/spring-web-6.2.3.jar"
- "BOOT-INF/lib/spring-beans-6.2.3.jar"
- "BOOT-INF/lib/micrometer-observation-1.14.4.jar"
- "BOOT-INF/lib/micrometer-commons-1.14.4.jar"
- "BOOT-INF/lib/spring-webmvc-6.2.3.jar"
- "BOOT-INF/lib/spring-aop-6.2.3.jar"
- "BOOT-INF/lib/spring-context-6.2.3.jar"
- "BOOT-INF/lib/spring-expression-6.2.3.jar"
- "BOOT-INF/lib/spring-messaging-6.2.3.jar"
- "BOOT-INF/lib/spring-websocket-6.2.3.jar"
- "BOOT-INF/lib/slf4j-api-2.0.16.jar"
- "BOOT-INF/lib/spring-core-6.2.3.jar"
- "BOOT-INF/lib/spring-jcl-6.2.3.jar"
- "BOOT-INF/lib/spring-boot-jarmode-tools-3.4.3.jar"

拉取基础镜像(可选):

如果拉取 bellsoft/liberica-openjre-debian:17-cds 基础镜像比较困难,可以使用这个阿里云的镜像地址,之后再修改下镜像 tag 即可。

bash
docker pull registry.cn-hangzhou.aliyuncs.com/pusher/liberica-openjre-debian:17-cds
docker tag registry.cn-hangzhou.aliyuncs.com/pusher/liberica-openjre-debian:17-cds bellsoft/liberica-openjre-debian:17-cds

项目打包:

bash
mvn clean package -DskipTests

构建镜像:

bash
docker build -t liujiajia/websocket:1.0 .

运行镜像:

bash
docker run -p 8080:8080 liujiajia/websocket:1.0

查看容器:

bash
docker ps

进入容器:

bash
docker exec -it {container-id} bash

查看 application 目录下的文件:

bash
root@c9d989a4a0e3:/application# ls -l
total 32304
-rw-r--r-- 1 root root     9552 Mar 25 05:46 application.jar
-r--r--r-- 1 root root 33062912 Mar 25 05:46 application.jsa
drwxr-xr-x 2 root root     4096 Mar 25 05:46 lib

application.jsa 就是预生成的 CDS 缓存文件。

这个文件比较大,会导致镜像的总大小变大,但是可以带来应用启动速度的提升。以本地开发的 WebSocket 示例项目为例,正常启动大约 3.7s,而使用 CDS 缓存文件启动大约 2.4s。

如果不需要的话,可以从 Dockerfile 中移除生成这个文件的步骤以及最后的 -XX:SharedArchiveFile=application.jsa 参数。


  1. Dockerfiles ↩︎

  2. Efficient Container Images ↩︎

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.