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 ↩

相关推荐
王中阳Go7 小时前
两个入口答案不一致先别调模型:6 行入口口径对账表(附脚本)
人工智能·后端·python·大语言模型·1024程序员节
码事漫谈8 小时前
消除AI幻觉,普通人能做的五件事
后端
paopaokaka_luck10 小时前
宠物门店经营管理系统(销售与库存联动、进货自动记账、 退换与退款关联、员工个人业绩、ECharts 经营分析、滞销识别与客户分层、宠物档案年龄计算)
前端·javascript·spring boot·mysql·echarts·mybatis·1024程序员节
金銀銅鐵10 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(下)
java·后端·ai编程
Mk5987812 小时前
给 Agent 装上对话框 —— AgentScope-Java 接入 Spring Boot 与 SSE 流式实战
后端
付威202312 小时前
从 Loop 到 Runtime:rpi Agent Loop 与 Cordis 的边界、取舍与组合
后端
IT_陈寒12 小时前
Redis的LRU淘汰策略坑了我一整天
前端·人工智能·后端
paopaokaka_luck12 小时前
心理咨询管理系统(WebSocket实时沟通、基于用户的协同过滤、 ECharts多维分析、 预约业务流程、预约校验与定时处理)
java·vue.js·spring boot·mysql·spring·echarts·1024程序员节
2301_322414280412 小时前
springboot农产品交易平台的设计与实现08055-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·spring boot·redis·mysql·tomcat·maven·mybatis