前言
小伙伴们好呀,今天来和大家聊聊这个 Springboot 在为创建高效容器方面中做的一个改动 。
当然,写这篇文章也不是因为实际项目真的需要我去研究这东西,而是我在上篇文章
《为什么SpringBoot可以直接运行 jar 包?》 中留了坑🕳 ,还有错误得纠正🐖 (原文也稍微改了下,但是只能改20字🙃)
这里应该改为
用 jarmode 的 extract 参数会自动将 jar 包中的文件按 layers.idx 中的分层提取出来。
改进原因
There’s always a certain amount of overhead when running a fat jar without unpacking it, and in a containerized environment this can be noticeable.
The other issue is that putting your application’s code and all its dependencies in one layer in the Docker image is sub-optimal.
上面这两句摘自 Springboot2.4.13 官方文档,但是这个改动是 2.3 就有了的(看了下 2.3 的,也还是这几句话🐷)
https://docs.spring.io/spring-boot/docs/2.4.13/reference/html/spring-boot-features.html#boot-features-container-images
大意就是说,
- 在容器中,如果没有解压就直接运行一个 jar 包,它带来的开销是明显的
- 将 应用代码和包依赖都混在同一层,也是可以被优化的地方。
w(゚Д゚)w ,感觉说的很有道理 哈哈哈
默默三连认同
但是这里有个疑问,那之前 2.3 之前是怎么解决这个问题的呢?还是说现在才解决的呢?🤔
求证
怀着严谨求证的态度,我打开了 Springboot2.2.9 的版本 👇
https://docs.spring.io/spring-boot/docs/2.2.9.RELEASE/reference/htmlsingle/#containers-deployment
可以看到这里列举了不高效的写法,以及推荐写法
这里我将 Springboot2.2.9 版本中 创建高效容器 的 Dockerfile 抄下来
FROM openjdk:8-jdk-alpine AS builder
WORKDIR target/dependency
ARG APPJAR=target/*.jar
COPY ${APPJAR} app.jar
RUN jar -xf ./app.jar
FROM openjdk:8-jre-alpine
VOLUME /tmp
ARG DEPENDENCY=target/dependency
COPY --from=builder ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY --from=builder ${DEPENDENCY}/META-INF /app/META-INF
COPY --from=builder ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","com.example.MyApplication"]
发现在 Springboot2.2.9 ,是通过解压 jar 包,并 copy /BOOT-INF/classes , /BOOT-INF/lib 以及 /META-INF 这三个文件夹来创建高效容器的 🐖 。
再次回忆下这几个文件夹 👇
当然,根据我们上文的结论,可以知道这里拷贝这个 META-INF 文件夹也没啥意义。毕竟最后已经指定 Main Class 去运行了。
这里我也去掉并做了个简单验证,依然部署成功,并且可以正常访问(当然,官方那样做可能也有其他我没考虑到的因素,就不多赘述啦🐖)
验证
接着,我们来看看 Springboot2.4.13 版本 的 👇
https://docs.spring.io/spring-boot/docs/2.4.13/reference/html/spring-boot-features.html#boot-features-container-images
FROM adoptopenjdk:11-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} application.jar
RUN java -Djarmode=layertools -jar application.jar extract
FROM adoptopenjdk:11-jre-hotspot
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
这里就是我们上文中截取的代码了,当时也有强调说这个东西的作用 👇
所以再次回顾下这个官方的这段话,再次细品这段代码
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/application/ ./
可以发现,为了优化容器的创建,这里的 copy 顺序也是极为用心的。
先后顺序是
- 依赖包
- Springboot loader 源码
- 依赖包中的快照
- 应用代码,配置文件等
因为按照 docker 容器的创建原理,底层没改变的话,是可以直接使用缓存的。
这就意味着,我们创建容器时,很多都可以用到缓存,直到到达 应用代码和配置文件这一层 ,这就极大的提高了容器的创建速度。
细心的小伙伴可以从上面 “验证” 这张图中发现有 Using cache 的字眼😝
小结
那么,到了这里,你有没有感受到前面提到的改进原因呢?
- 在容器中,如果没有解压就直接运行一个 jar 包,它带来的开销是明显的
感受:Springboot 2.2.9 和 Springboot 2.4.13 都提供 高效的 dockerfile 方式- 将 应用代码和包依赖都混在同一层,也是可以被优化的地方。
感受:Springboot 2.2.9 和 Springboot 2.4.13 都有改动,但是 2.4 中分的更细,而且基本上,2.4 的 dockerfile 在任何地方都可以直接用,不用做过多修改,很方便。
所以结论就是在 2.2.9 中就有了优化了,但是 2.3 之后又弄了个 layers.idx 来进一步优化,规范这个分层,提供了一个更简便的方式!
所以最后就变得更好使了😂
下面进入项目的搭建教学~
这个小项目主要是用来获取各种 验证码 的(中英文动态都有),是好久以前发现的,而且这个小 demo,还有隐藏的 API 等着小伙伴去发现。😄
感兴趣的小伙伴可以简单了解下这个 captcha 的生成,还是很有意思的😝
IDEA 部署 docker 容器
# 修改Docker服务文件,需要先切换到root用户
vim /lib/systemd/system/docker.service
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375
# 然后重启 docker
IDEA 中建一个 docker
创建 Dockerfile 容器。
最后运行即可~
可以在这里看到容器运行的 log。
项目地址
最后
本分就分享到这里了。
- 了解了 Springboot 在创建高效容器上的细节—— layers.idx。通过 jarmode 的 extract 参数去提取分好的层,提供高效的同时还方便开发人员去使用。
- 同时了解到 IDEA 怎么部署项目到远程容器。
- 又解决一个技术问题 嘻嘻嘻😝