SpringBoot 分层构建Docker镜像

SpringBoot 分层构建Docker镜像

Scroll Down

越来越多的项目容器化,Docker已经成为软件开发中的重要工具。通常我们可以通过如下的DockerfileSpring Boot应用的fat jar打包成docker镜像:

FROM adoptopenjdk:8-jre-hotspot
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

看起来不错,但是你会发现如果我们修改了业务代码,镜像都会重新构建,哪怕你仅仅修改了一个字符串。如果你使用了CI/CD去构建部署Docker镜像,你会发现有时候CI管道中构建的时间会很长,甚至卡住不动,特别在镜像比较多的时候这种感觉非常明显。所以我们项目要优化这个地方。

Docker的分层机制

要优化就要了解Docker镜像的构建分层机制。Docker镜像由很多层组成,每个层代表Dockerfile中的一条指令。每一层都是基础层上变化的增量,而且自下而上的进行增量构建

Docker镜像层

Spring Boot 镜像的优化

Spring Bootfat jar 如果能拆分成一层一层构建,把重复的层从主机缓存中复用起来就可以大大提高效率。所以按照变动的频率 Spring Boot应用可以划分如下的层级:

  • dependencies (依赖项一般变化不大)
  • spring-boot-loader(spring boot 加载器变化也不大)
  • snapshot-dependencies (快照依赖,为快照版本的依赖,更新迭代的会快一些)
  • application (业务层,也就是我们最频繁变动的)

Spring Boot 2.3起,Spring Boot提供了spring-boot-jarmode-layertools的jar包,该jar将作为依赖项添加到应用的jar中。通过layertoolsjar模式启动jar:

java -Djarmode=layertools -jar my-app.jar

就会生成上述四种层级的索引文件 layers.idx

微信图片_20210402140406.jpg

上面就是该模式下构建的 Spring Boot 应用jar的信息,我们可以看到这两个东西。

这个功能依赖spring-boot-maven-plugin插件。

我们只需要将Dockerfile修改为:

# 第一阶段使用 layertools 的 extract 命令将应用程序拆分为多个层  本次构建标记为builder
FROM adoptopenjdk:8-jre-hotspot as builder
WORKDIR application
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
RUN java -Djarmode=layertools -jar app.jar extract

#  第二阶段从分层中复制并构建镜像
FROM adoptopenjdk:8-jre-hotspot
WORKDIR application
# 从上面构建的builder 中复制层  注意保证层的顺序
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"]

就可以复用主机中的Docker缓存层来加速构建效率。构建命令为:

docker build --build-arg JAR_FILE=path/to/myapp.jar . -tag demo

然后会输出:

Sending build context to Docker daemon  41.87MB
Step 1/12 : adoptopenjdk:8-jre-hotspot as builder
 – -> 973c18dbf567
Step 2/12 : WORKDIR application
 – -> Using cache
 – -> b6b89995bd66
Step 3/12 : ARG JAR_FILE=target/*.jar
 – -> Using cache
 – -> 2065a4ad00d4
Step 4/12 : COPY ${JAR_FILE} app.jar
 – -> c107bce376f9
Step 5/12 : RUN java -Djarmode=layertools -jar app.jar extract
 – -> Running in 7a6dfd889b0e
Removing intermediate container 7a6dfd889b0e
 – -> edb00225ad75
Step 6/12 : FROM  adoptopenjdk:8-jre-hotspot
 – -> 973c18dbf567
Step 7/12 : WORKDIR application
 – -> Using cache
 – -> b6b89995bd66
Step 8/12 : COPY – from=builder application/dependencies/ ./
 – -> Using cache
 – -> c9a01ed348a9
Step 9/12 : COPY – from=builder application/spring-boot-loader/ ./
 – -> Using cache
 – -> e3861c690a96
Step 10/12 : COPY – from=builder application/snapshot-dependencies/ ./
 – -> Using cache
 – -> f928837acc47
Step 11/12 : COPY – from=builder application/application/ ./
 – -> 3a5f60a9b204
Step 12/12 : ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
 – -> Running in f1eb4befc4e0
Removing intermediate container f1eb4befc4e0
 – -> 8575cc3ac2e3
Successfully built 8575cc3ac2e3
Successfully tagged demo:latest

java -Djarmode=layertools -jar 不建议在启动脚本中使用,仅推荐在构建中使用 。

补充

关于插件目前有版本上的差异,在Spring Boot 2.3中需要:

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <configuration>
          <layers>
            <enabled>true</enabled>
          </layers>
      </configuration>
    </plugin>
    ...
  </plugins>
  ...
</build>

springboot 2.4.x以上版本默认:

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <!--当然你可以修改layers.enabled为false以关闭此功能。-->
        </plugin>
    </plugins>
</build>

通过启用分层构建Spring Boot应用镜像,我明显感觉到推送镜像的速度快了不少(当然这是依赖没有改动的情况下);另外从远程拉取镜像时只需要拉取变化的层,速度也明显加快了;对构建其实上是构建了两次,虽然配合缓存,效率其实变化不大。也就是说在镜像的网络传输上分层构建有明显的优势,值得一试

作者:码农小胖哥
链接:https://mp.weixin.qq.com/s/JMcBd6OULcVd9aRKyEGc0w

参考