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

layers.idx

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/"

classpath.idx

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 参数。

Footnotes

  1. Dockerfiles

  2. Efficient Container Images

相关推荐
HelloZheQ30 分钟前
Go:简洁高效,构建现代应用的利器
开发语言·后端·golang
caihuayuan51 小时前
[数据库之十四] 数据库索引之位图索引
java·大数据·spring boot·后端·课程设计
风象南2 小时前
Redis中6种缓存更新策略
redis·后端
码码哈哈0.02 小时前
2025最新:3分钟使用Docker快速部署Redis集群
redis·docker·容器
天上掉下来个程小白2 小时前
缓存菜品-04.功能测试
java·spring boot·缓存·微信小程序·需求分析·苍穹外卖
程序员Bears2 小时前
Django进阶:用户认证、REST API与Celery异步任务全解析
后端·python·django
非晓为骁2 小时前
【Go】优化文件下载处理:从多级复制到零拷贝流式处理
开发语言·后端·性能优化·golang·零拷贝
北极象2 小时前
Golang中集合相关的库
开发语言·后端·golang
Q_Q19632884753 小时前
python小说网站管理系统-小说阅读系统
开发语言·spring boot·python·django·flask·node.js·php
喵手3 小时前
Spring Boot 中的事务管理是如何工作的?
数据库·spring boot·后端