Spring Boot 优化容器镜像
Spring Boot 优化容器镜像
Spring Boot 应用使用 spring-boot-maven-plugin 打包的是 uber jar(也就是常说的 fat jar)。
开发时大部分情况下只有应用程序对应的 jar 包有修改,但使用 uber jar 时对于 Docker 来说,复制 jar 的这一层是有变动的,就需要对整个一层做推送或拉取。
如果能将应用所在的 jar 包单独放在一层,不就可以减少镜像变动的大小了吗。
下面的 Dockerfile 提供了一个通用的示例 1:
# 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 示例项目为基础,在本地执行一下提取的命令看一下效果:
java -Djarmode=tools -jar application.jar extract --layers --destination extracted

提取的目录:2
dependencies(for regular released dependencies)
spring-boot-loader(for everything underorg/springframework/boot/loader)
snapshot-dependencies(for snapshot dependencies)
application(for application classes and resources)
每个目录对应一个 Docker 层。
其实也可以解压缩 jar 文件,然后将文件复制到对应的目录。
解压缩后可以在 BOOT-INF 目录下看到两个 .idx 后缀的文件。这两个就是用来配置分层的。如果需要修改分层的配置,可以参考官方文档:Custom Layers Configuration。
拉取基础镜像(可选):
如果拉取 bellsoft/liberica-openjre-debian:17-cds 基础镜像比较困难,可以使用这个阿里云的镜像地址,之后再修改下镜像 tag 即可。
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
项目打包:
mvn clean package -DskipTests
构建镜像:
docker build -t liujiajia/websocket:1.0 .
运行镜像:
docker run -p 8080:8080 liujiajia/websocket:1.0
查看容器:
docker ps
进入容器:
docker exec -it {container-id} bash
查看 application 目录下的文件:
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 参数。