背景
Spring Boot 2.3(截至目前版本为 M2)为容器化部署提供了一个新特性 Layered Jar。一般来说 Spring Boot 程序都是以 fat jar 的方式构建的,文件大小动辄 50M、100M 这样子,对 docker image 其实很不友好。Docker image 本身是分层结构,如果某一层没有变化在 pull
时就不必上传,一旦有变化就要上传整层。一个程序中,程序自身代码、资源的变更频率要远大于依赖库的变更频率,大多数时候因为几行代码变化导致上传整个 jar 文件,无论是存储占用还是时间效率上都是很大的浪费,后者在国内网速下尤其让人揪心。
Layered Jar
新特性 layered jar 为不同变更频率内容分层提供了支持工具。本质上这个特性是 org.springframework.boot:spring-boot-maven-plugin
提供的一种新的 layout
,当使用新 layout
打包时,一个 spring-boot-layertools
jar 会打包到 fat jar 中,新特性是由这个 jar 提供的。
要使用这个新特性需要做的事情非常少,首先是在 pom 中增加 layout
配置,我猜未来这个选项会成为默认值,从而不需任何显式配置。
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layout>LAYERED_JAR</layout>
</configuration>
</plugin>
...
</plugins>
...
</build>
新增的部分就是这 3 行
<configuration>
<layout>LAYERED_JAR</layout>
</configuration>
增加配置之后 package
打包,然后可以执行以下命令验证配置正确与否
java -Djarmode=layertools -jar <target.jar>
一个示例输出:
Usage:
java -Djarmode=layertools -jar metis-server-0.1.0-SNAPSHOT.jar
Available commands:
list List layers from the jar that can be extracted
extract Extracts layers from the jar for image creation
help Help about any command
这是 maven 上所有需要做的事情,gradle 参照 maven 也可以类似处理。然后需要在 Dockerfile 中再做一点处理,这用到了 docker 的多阶段构建功能(17.05 增加的功能,几乎 3 年前,希望没人说这版本太新~)。一个示例 Dockerfile
FROM azul/zulu-openjdk:13 as builder #01
#02
WORKDIR application #03
COPY /maven/${project.build.finalName}.jar application.jar #04
RUN java -Djarmode=layertools -jar application.jar extract #05
#06
FROM azul/zulu-openjdk:13 #07
#08
WORKDIR /opt/bin/ #09
USER 1000:1000 #10
EXPOSE 8080 #11
#12
COPY --from=builder application/dependencies/ ./ #13
COPY --from=builder application/snapshot-dependencies/ ./ #14
COPY --from=builder application/resources/ ./ #15
COPY --from=builder application/application/ ./ #16
#17
ENV TZ=Asia/Shanghai #18
#19
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] #20
对上面的 Dockerfile
逐行解释一下:
#01
、#07
2 个FROM
保持统一为运行时使用的基础镜像即可;#04
处COPY
的源根据使用的 docker 插件不同会有所变化,本文使用的插件为io.fabric8:docker-maven-plugin:0.33.0
。目的文件保持application.jar
即可,它要跟#05
的 jar 文件名一致;#05
如果报错需要检查是否遗漏了参数,比如#20
需要--enable-preview
的话#05
也需要加--enable-preview
;#09
、#10
、#11
、#18
其实跟本文无关,但是算是 Spring Boot 容器化的良好实践,可以照抄,日后有机会专文再讲;#13
、#14
、#15
、#16
、#20
4 个COPY
1 个ENTRYPOINT
照抄即可,这是本文新特性的关键。
然后,用这个 Dockerfile
构建出来的镜像内容已经是拆解分成多层的镜像了,当变更源码、资源、快照版依赖、正式版依赖时会依次影响更多层镜像,从而实现每次构建上传仓库时存储和传输耗时最小化。
总结
无论从易用程度还是达到的效果上讲,layered jar 都非常值得使用。要使用该特性只需要开启 pom 中一个配置项并使用一个几乎不需要任何修改的 Dockerfile
模板,几乎零负担获得可观的收益。
最后把不带行号的 Dockerfile
再重复一次以方便复制
FROM azul/zulu-openjdk:13 as builder
WORKDIR application
COPY /maven/${project.build.finalName}.jar application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM azul/zulu-openjdk:13
WORKDIR /opt/bin/
USER 1000:1000
EXPOSE 8080
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/resources/ ./
COPY --from=builder application/application/ ./
ENV TZ=Asia/Shanghai
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
来源:oschina
链接:https://my.oschina.net/u/1762727/blog/3187451