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

相关推荐
在努力的韩小豪34 分钟前
【微服务架构】本地负载均衡的实现(基于随机算法)
后端·spring cloud·微服务·架构·负载均衡
声声codeGrandMaster4 小时前
Django项目入门
后端·mysql·django
千里码aicood4 小时前
【2025】基于springboot+vue的医院在线问诊系统设计与实现(源码、万字文档、图文修改、调试答疑)
vue.js·spring boot·后端
云上艺旅5 小时前
K8S学习之基础四十七:k8s中部署fluentd
学习·云原生·容器·kubernetes
yang_love10115 小时前
Spring Boot 中的 @ConditionalOnBean 注解详解
java·spring boot·后端
Pandaconda6 小时前
【后端开发面试题】每日 3 题(二十)
开发语言·分布式·后端·面试·消息队列·熔断·服务限流
鱼樱前端6 小时前
mysql事务、行锁、jdbc事务、数据库连接池
java·后端
Adellle7 小时前
MySQL
数据库·后端·mysql
帽儿山的枪手7 小时前
程序员必掌握docker六种网络模式
网络协议·docker·容器
夏夏不吃糖7 小时前
基于Spring Boot + Vue的银行管理系统设计与实现
java·vue.js·spring boot·maven