# springboot-layers **Repository Path**: wxdfun/springboot-layers ## Basic Information - **Project Name**: springboot-layers - **Description**: springboot docker分层打包 - **Primary Language**: Java - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 6 - **Forks**: 1 - **Created**: 2020-06-15 - **Last Updated**: 2024-04-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 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打包插件 ```xml org.springframework.boot spring-boot-maven-plugin ``` 根目录的DockerFile文件 ```dockerfile FROM openjdk:8-jre MAINTAINER ttzommed@foxmail.com WORKDIR application EXPOSE 36665 ADD ./target/*.jar ./app.jar CMD java -jar app.jar ``` 然后我们执行相关命令打包 ```shell mvn clean package docker build . -t hc.registry.docker.com:5000/springboot-layers:1.0.0 ``` ![docker编译镜像](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615104125.jpg) 先来看看这个docker镜像的分层情况,分别用`docker inspect`和`dive`来看看。 `docker inspect hc.registry.docker.com:5000/springboot-layers:1.0.0` ![inspect_1.0.0](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615104414.jpg) `dive hc.registry.docker.com:5000/springboot-layers:1.0.0` ![dive_1.0.0](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615104923.jpg) 接下来我们更改一点点java代码,重新进行上面步骤看看问题。 ![改动_1.0.1](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615105447.jpg) ```shell 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` ![inspect_1.0.1](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615105734.jpg) docker镜像分层文件中只有最后一处与上次编译不一样。 `dive hc.registry.docker.com:5000/springboot-layers:1.0.1` ![dive_1.0.1](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615105947.jpg) 可以看出我们只改动了几个字符,docker打包的时候依然给我们重新上传了springboot的全量jar包。 ## 改造 最近springboot2.3.0发布,支持springboot分层打包,我们看看新特性怎么结合docker使用。升级springboot依赖到2.3.0.RELEASE。 ```xml org.springframework.boot spring-boot-starter-parent 2.3.0.RELEASE ``` 在SpringBoot-maven插件中加入配置支持分层打包 ```xml org.springframework.boot spring-boot-maven-plugin true ``` 再次进行打包分析操作`mvn clean package`,现在我们可以用下面命令来看我们springboot2.3.0分层打包编译的jar包结构 `java -Djarmode=layertools -jar target/springboot-layers-1.0.0.jar list` ![layertools_list](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615112426.jpg) 可以看到layertools识别出jar包内将依赖打包到不同文件夹中,接下来我们改造下原有的dockerfile ```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` ![inspect_2.0.0](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615113218.jpg) 可以看到在docker镜像的文件分层中,层级变多了。我们改动一点java代码再次构建看看。 ![改动2.0.1](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615113403.jpg) ```shell 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` ![inspect_2.0.1](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615113631.jpg) 仍然只有最后一个文件层有变动,用dive来分析看看 `dive hc.registry.docker.com:5000/springboot-layers:2.0.1` ![dive_2.0.1](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615113813.jpg) 神奇的事情发生了,这次docker中的改动只有4.6k的文件变动。单独push 2.0.0和2.0.1 docker镜像验证下是否只重新上传这两处改动分层文件 ![push_2.0.0](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615114334.jpg) ![push_2.0.1](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615114347.jpg) 可以发现跟我们想的一样,2.0.1 docker镜像push的改动文件正是上面标注的仅有4.6k的文件,而上传的分层文件也大大缩小,最后启动我们的镜像即可验证成功了。 `docker run -p 36665:36665 hc.registry.docker.com:5000/springboot-layers:2.0.1` ![结果](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615114753.jpg) ## 结果 在配置了springboot2.3.0版本支持分层打包,并且dockerfile文件也改成相应的格式后,确实能达到我们预期的效果。我们每次重新上传的只是我们自己写的代码,第三方依赖、SpringBoot内部配置、快照依赖 ,这些SpringBoot都为我们打包到不同的文件夹下,再依靠docker的分层特征,分次加入文件即可达到分层打包的效果。有想一探究竟的朋友可以执行`java -Djarmode=layertools -jar target/springboot-layers-2.0.1.jar extract`,然后看看下图的里面的文件内容就真相大白了。 ![image-20200615115315550](https://imgb.hnhcyy.com/ctr_cloud/doc/md/20200615115315.png)