SpringBoot + Docker分层打包
背景
SpringBoot默认使用org.springframework.boot:spring-boot-maven-plugin
Maven插件把项目编译成jar包。默认编译的jar包是一个整体,通过java -jar
命令可直接启动。结合docker后,我们可以通过DockerFile
或者Docker Compose
方式打包成Docker镜像。但每次Maven会将SpringBoot项目文件编译出一个全量jar包在target文件夹下,其jar包内包含我们自己写的代码和依赖的第三方jar包,常常一个jar包在100M上下,这导致在结合docker打包的情况下,每次docker push
都会上传全量的jar包。最近SpringBoot2.3.0发布,更新包含了支持分层打包,下面我们看看SpringBoot结合Docker如何实现分层打包。
demo项目仓库:https://gitee.com/wxdfun/springboot-layers.git
分析
SpringBoot项目如何编译成jar包以及Docker使用方式就不介绍了,可自行百度,直接步入今天的正题。文章中会用到一款docker镜像分析工具dive
,安装和使用可以参考文档https://github.com/wagoodman/dive
先来看看原来的SpringBoot打包和DockerFile编译镜像。我们新建一个简单的demo项目来做下测试。
pom配置SpringBoot打包插件
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
根目录的DockerFile文件
FROM openjdk:8-jre
MAINTAINER ttzommed@foxmail.com
WORKDIR application
EXPOSE 36665
ADD ./target/*.jar ./app.jar
CMD java -jar app.jar
然后我们执行相关命令打包
mvn clean package
docker build . -t hc.registry.docker.com:5000/springboot-layers:1.0.0
先来看看这个docker镜像的分层情况,分别用docker inspect
和dive
来看看。
docker inspect hc.registry.docker.com:5000/springboot-layers:1.0.0
dive hc.registry.docker.com:5000/springboot-layers:1.0.0
接下来我们更改一点点java代码,重新进行上面步骤看看问题。
mvn clean package
docker build . -t hc.registry.docker.com:5000/springboot-layers:1.0.1
docker inspect hc.registry.docker.com:5000/springboot-layers:1.0.1
docker镜像分层文件中只有最后一处与上次编译不一样。
dive hc.registry.docker.com:5000/springboot-layers:1.0.1
可以看出我们只改动了几个字符,docker打包的时候依然给我们重新上传了springboot的全量jar包。
改造
最近springboot2.3.0发布,支持springboot分层打包,我们看看新特性怎么结合docker使用。升级springboot依赖到2.3.0.RELEASE。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.0.RELEASE</version>
<relativePath/>
</parent>
在SpringBoot-maven插件中加入配置支持分层打包
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<layers>
<enabled>true</enabled>
</layers>
</configuration>
</plugin>
</plugins>
</build>
再次进行打包分析操作mvn clean package
,现在我们可以用下面命令来看我们springboot2.3.0分层打包编译的jar包结构
java -Djarmode=layertools -jar target/springboot-layers-1.0.0.jar list
可以看到layertools识别出jar包内将依赖打包到不同文件夹中,接下来我们改造下原有的dockerfile
FROM openjdk:8-jre as builder
WORKDIR application
ADD ./target/*.jar ./app.jar
RUN java -Djarmode=layertools -jar app.jar extract
FROM openjdk:8-jre
MAINTAINER ttzommed@foxmail.com
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/ ./
EXPOSE 36665
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
这个dockerfile表示先进行一次临时镜像构建标记为builder,并加载一次全量jar包,然后执行java -Djarmode=layertools -jar app.jar extract
命令将jar包分解为分层打包目录,再次构建一个新镜像,按照list的目录顺序分批将分层目录加载到docker镜像中。
再次构建docker镜像docker build . -t hc.registry.docker.com:5000/springboot-layers:2.0.0
docker inspect hc.registry.docker.com:5000/springboot-layers:2.0.0
可以看到在docker镜像的文件分层中,层级变多了。我们改动一点java代码再次构建看看。
mvn clean package
docker build . -t hc.registry.docker.com:5000/springboot-layers:2.0.1
再次分析docker镜像层级
docker inspect hc.registry.docker.com:5000/springboot-layers:2.0.1
仍然只有最后一个文件层有变动,用dive来分析看看
dive hc.registry.docker.com:5000/springboot-layers:2.0.1
神奇的事情发生了,这次docker中的改动只有4.6k的文件变动。单独push 2.0.0和2.0.1 docker镜像验证下是否只重新上传这两处改动分层文件
可以发现跟我们想的一样,2.0.1 docker镜像push的改动文件正是上面标注的仅有4.6k的文件,而上传的分层文件也大大缩小,最后启动我们的镜像即可验证成功了。
docker run -p 36665:36665 hc.registry.docker.com:5000/springboot-layers:2.0.1
结果
在配置了springboot2.3.0版本支持分层打包,并且dockerfile文件也改成相应的格式后,确实能达到我们预期的效果。我们每次重新上传的只是我们自己写的代码,第三方依赖、SpringBoot内部配置、快照依赖 ,这些SpringBoot都为我们打包到不同的文件夹下,再依靠docker的分层特征,分次加入文件即可达到分层打包的效果。有想一探究竟的朋友可以执行java -Djarmode=layertools -jar target/springboot-layers-2.0.1.jar extract
,然后看看下图的里面的文件内容就真相大白了。
SpringBoot内部配置、快照依赖 ,这些SpringBoot都为我们打包到不同的文件夹下,再依靠docker的分层特征,分次加入文件即可达到分层打包的效果。有想一探究竟的朋友可以执行java -Djarmode=layertools -jar target/springboot-layers-2.0.1.jar extract
,然后看看下图的里面的文件内容就真相大白了。