作者:超级小豆丁

http://www.mydlq.club/article/98/

目录

  • 什么是镜像分层

  • SpringBoot 2.3.x 新增对分层的支持

  • 创建测试的 SpringBoot 应用

    • Maven 中引入相关依赖和插件

    • 创建测试的 Controller 类

    • 创建 SpringBoot 启动类

  • 创建两种构建镜像的 Dockerfile 脚本

    • 普通镜像构建脚本文件 dockerfile-number

    • 分层镜像构建脚本文件 dockerfile-layer

  • 使用两种 Dockerfile 构建项目镜像

    • 在服务器一构建普通 Docker 镜像

    • 在服务器二构建分层 Docker 镜像

  • 镜像推送到镜像仓库测试

    • 推送镜像到镜像仓库测试

    • 镜像仓库拉取镜像测试

  • 镜像构建、推送、拉取时间汇总

    • 不使用分层构建镜像

    • 使用分层构建镜像

    • 总结


系统环境:

  • Docker 版本:19.03.13

  • Open JDK 基础镜像版本:openjdk:8u275

  • 私有的 Harbor 镜像仓库:自建 Harbor 私库

  • 项目 Github:

https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-layer

参考地址:

https://docs.docker.com/storage/storagedriver/

一、什么是镜像分层

镜像的构成

现在一谈起镜像大部分都是指 Docker 引擎构建的镜像,一般 Docker 镜像是由很多层组成,底层是操作系统,然后在其之上是基础镜像或者用户自定义 Dockerfile 脚本中定义的中间层。

其中镜像在构建完成后,用户只能对镜像进行读操作,而不能进行写操作,只有镜像启动后变为容器,才能进行读写操作。镜像整体结构,可以观看下图:

该图中展示了镜像的基本组成,但是图中这一个个中间层是什么呢?要想了解这些层具体是什么,那得知道如何构建 Docker 镜像了。平时我们构建 Docker 镜像时候,都是编写 Dockerfile 脚本,然后使用 Docker 镜像构建命令,按照脚本一行行执行构建。

如下就是一个 Dockerfile 脚本,脚本内容就构建 Java 项目镜像常用的 Dockerfile 命令:

FROM openjdk:8u275
VOLUME /tmp
ADD target/*.jar app.jar
ENV TZ="Asia/Shanghai"
ENV JAVA_OPTS=""
ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar"]

有了 Dockerfile 脚本,我们需要执行 Docker 的构建镜像命令对执行 Dockerfile 脚本构建镜像,其中构建镜像的过程如下:

## 构建镜像的命令
$ docker build -t java-test:latest . ## 命令执行的过程
Step 1/7 : FROM openjdk:8u275---> 82f24ce79de6
Step 2/7 : VOLUME /tmp---> Running in a6361fdfc193
Removing intermediate container a6361fdfc193---> a43948bf1b98
Step 3/7 : ADD target/*.jar app.jar---> 18f4bc60818f
Step 4/7 : ENV TZ="Asia/Shanghai"---> Running in cc738aa5865b
Removing intermediate container cc738aa5865b---> 538adb85609e
Step 5/7 : ENV JAVA_OPTS=""---> Running in f8b635d32b2b
Removing intermediate container f8b635d32b2b---> 34e7a8cd7b6e
Step 6/7 : ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"---> Running in 9331cb6e443e
Removing intermediate container 9331cb6e443e---> 232b9c6c1d29
Step 7/7 : ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]---> Running in c3a24fba3a10
Removing intermediate container c3a24fba3a10---> a41974d5f0e3

可以看到总共存在 7 个构建步骤,每步都与 Dockerfile 里面一行指令对应。样子和下图相似:

如果这时候,我们改变原来 Dockerfile 内容,创建一个新的镜像,其 Dockerfile 如下:

FROM openjdk:8u275
VOLUME /tmp
ADD target/*.jar app.jar
ENV TZ="Asia/Macao"                  #与原来 Dockerfile 不同
ENV JVM_OPTS="-Xmx512m -Xss256k"     #与原来 Dockerfile 不同
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]

执行 Docker 命令构建镜像:

$ docker build -t java-test2:latest .Step 1/6 : FROM openjdk:8u275---> 82f24ce79de6
Step 2/6 : VOLUME /tmp---> Using cache---> a43948bf1b98
Step 3/6 : ADD target/*.jar app.jar---> Using cache---> 18f4bc60818f
Step 4/6 : ENV TZ="Asia/Macao"---> Running in fd98b90a5485
Removing intermediate container fd98b90a5485---> afab3fcdab07
Step 5/6 : ENV JVM_OPTS="-Xmx512m -Xss256k"---> Running in 19a99576fba9
Removing intermediate container 19a99576fba9---> 4eeab7d7c720
Step 6/6 : ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS -jar /app.jar" ]---> Running in 2dba72e1eef4
Removing intermediate container 2dba72e1eef4---> 7c706ecf7698

可以观察到执行过程中,从一开始执行的构建步骤中显示,并没有生成新的中间层镜像,而是直接使用了已经存在的缓存镜像。直至 4⁄6 这部中,由于新的 Dockerfile 与原来 Dockerfile 发生变动,所以这部中间层镜像直接是新创建的,并没有使用缓存中间层镜像。

然后往下观察,发现之后的全部构建都是新创建的中间层镜像,即是脚本最后的一行和原来相同,也没有使用缓存中间层镜像。

上面现象说明,Docker 镜像在构建过程中按照 Dockerfile 自上往下的执行顺序中,如果从最上层开始,其脚本内容和已有的缓存中间层镜像内容一致,就会引入缓存中的中间层镜像(并不是直接复制缓存镜像,而是引入镜像文件地址,多个镜像共享这些中间层镜像)。

但是,如果执行过程中中间任意一行镜像构建的内容发生变化,那么当前行和之后的全部行在执行时就不会使用缓存中的中间层镜像,而是全部创建新的镜像。

这就是 Docker 镜像中缓存中间层镜像的复用,学会使用缓存构建镜像将大大减少存储空间的占用以及镜像的构建的构建速度,镜像的缓存不仅仅体现在镜像的构建上,在执行”镜像推送”、”镜像拉取”操作时都能观察到其的好处。

  • 镜像缓在镜像推送的体现: 如镜像推送时候,也是将镜像整体构成的中间层镜像并行推送到镜像仓库,如果镜像仓库中已经存某个中间层镜像,那么推送过程就不会再次将该层镜像推送到镜像仓库,而是将仓库中并不存在中间层镜像推送到其中。

  • 镜像缓存在镜像拉取的体现: 在拉取镜像时候,如果本地某个大镜像的中间层镜像的组成中,已经包含新拉取镜像的中间层部分镜像,那么将直接复用本地已经镜像的中间层镜像,不必再将其进行拉取,而本地不存在的中间层镜像将会被继续拉取。

说了这么多,相信大家已经对镜像缓存的使用有了初步了解,那么再谈及为什么需要镜像分层就很好解释,其原因就是 Docker 想提高资源的复用率,将一个大镜像拆分成很多层小镜像组成,以达到镜像中间层的复用的目的。

二、SpringBoot 2.3.x 新增对分层的支持

SpringBoot 2.3.x 以后支持分层打包应用,需要 Pom.xml 中引入 SpringBoot 2.3.x 后的父依赖和使用 SpringBoot 打包插件 spring-boot-maven-plugin,并且开启 layers 功能,然后执行 Maven 编译源码构建 Jar 包,使用该 Jar 包就可以构建基于分层模式的 Docker 镜像:

项目 pom.xml 中引入 SpringBoot 2.3.x 依赖:

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version><relativePath/>
</parent>

项目 pom.xml 中引入 spring-boot-maven-plugin 打包插件,并且开启分层功能:

<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><!--开启分层编译支持--><layers><enabled>true</enabled></layers></configuration></plugin></plugins>
</build>

执行 Maven 命令,构建分层的 JAR 包,命令和平时的 Maven 构建命令相同:

$ mvn install

观察 Jar 结构,可以看到里面多了 classpath.idx 与 layers.idx 两个文件:

  • classpath.idx: 文件列出了依赖的 jar 包列表,到时候会按照这个顺序载入。

  • layers.idx: 文件清单,记录了所有要被复制到 Dokcer 镜像中的文件信息。

根据官方介绍,在构建 Docker 镜像前需要从 Jar 中提起出对应的分层文件到 Jar 外面,可用使用下面命令列出可以从分层 Jar 中提取出的文件夹信息:

$ java -Djarmode=layertools -jar target/springboot-layer-0.0.1.jar list

可用该看到以下输出,下面的内容就是接下来使用分层构建后,生成的 Jar 提取出对应资源后的结构:

dependencies
spring-boot-loader
snapshot-dependencies
application

上面即是使用分层工具提取 Jar 的内容后生成的文件夹,其中各个文件夹作用是:

  • dependencies: 存储项目正常依赖 Jar 的文件夹。

  • snapshot-dependencies: 存储项目快照依赖 Jar 的文件夹。

  • resources: 用于存储静态资源的文件夹。

  • application: 用于存储应用程序类相关文件的文件夹。

三、创建测试的 SpringBoot 应用

创建测试的 SpringBoot 项目,并且在 pom.xml 中开启镜像分层。

1、Maven 中引入相关依赖和插件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.3.6.RELEASE</version></parent><artifactId>springboot-dockerfile-layer</artifactId><packaging>jar</packaging><name>springboot-dockerfile-layer</name><description>springboot build layer example</description><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><configuration><layers><enabled>true</enabled></layers></configuration></plugin></plugins></build></project>

2、创建测试的 Controller 类

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;@RestController
public class TestController {@GetMapping("/hello")public String hello() {return "hello world!";}}

3、创建 SpringBoot 启动类

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}}

四、创建两种构建镜像的 Dockerfile 脚本

为了方便体现出 SpringBoot 2.3.x 支持的分层构建 Dockerfile 的优点,这里在 Java 源码文件夹下,创建普通与分层两种构建镜像的 Dockerfile 脚本,后续会使用这两种脚本构建 Docker 镜像进行构建速度、推送速度、拉取速度的对比。

1、普通镜像构建脚本文件 dockerfile-number

FROM openjdk:8u275
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV TZ="Asia/Shanghai"
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"
ENV JAVA_OPTS=""
ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]

说明:

  • TZ: 时区设置,而 Asia/Shanghai 表示使用中国上海时区。

  • JVM_OPTS: 指定 JVM 启动时候的参数,-XX:MaxRAMPercentage 参数和 -Xmx 类似,都是限制堆内存大小,只不过 -Xmx 需要手动指定限制大小,而 -XX:MaxRAMPercentage 则是根据虚拟机可用内存百分比限制。

  • JAVA_OPTS: 在镜像启动时指定的自定义 Java 参数,例如 -Dspring.application.name=xxx。

2、分层镜像构建脚本文件 dockerfile-layer

FROM openjdk:8u275 as builder
WORKDIR application
COPY target/*.jar application.jar
RUN java -Djarmode=layertools -jar application.jar extractFROM openjdk:8u275
WORKDIR application
COPY --from=builder application/dependencies/ ./
COPY --from=builder application/snapshot-dependencies/ ./
COPY --from=builder application/spring-boot-loader/ ./
COPY --from=builder application/application/ ./
ENV TZ="Asia/Shanghai"
ENV JVM_OPTS="-XX:MaxRAMPercentage=80.0"
ENV JAVA_OPTS=""
ENTRYPOINT ["sh","-c","java $JVM_OPTS $JAVA_OPTS org.springframework.boot.loader.JarLauncher"]

说明:

  • TZ: 时区设置,而 Asia/Shanghai 表示使用中国上海时区。

  • -Djarmode=layertools: 指定构建 Jar 的模式。

  • extract: 从 Jar 包中提取构建镜像所需的内容。

  • -from=builder 多级镜像构建中,从上一级镜像复制文件到当前镜像中。

五、使用两种 Dockerfile 构建项目镜像

1、在服务器一构建普通 Docker 镜像

(1)、第一次构建

## 执行 Maven 命令,将源代码构建 Jar 包
$ mvn clean install## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.1 .

Docker 镜像构建总共花费 24.98s 时间。

(2)、第二次构建(修改依赖 pom.xml 文件)

## 修改 pom.xml 里面的依赖,随意添加一个新的依赖包,然后再次将源代码构建 Jar 包
$ mvn clean install## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.2 .

Docker 镜像构建总共花费 1.27s 时间。

(3)、第三次构建(修改代码内容)

## 修改源代码任意内容后,然后再次将源代码构建 Jar 包
$ mvn clean install## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-normal:0.0.3 .

Docker 镜像构建总共花费 1.32s 时间。

2、在服务器二构建分层 Docker 镜像

(1)、第一次构建

## 执行 Maven 命令,将源代码构建 Jar 包
$ mvn clean install## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.1 .

Docker 镜像构建总共花费 26.12s 时间。

(2)、第二次构建(修改依赖 pom.xml 文件)

## 修改 pom.xml 里面的依赖,随意添加一个新的依赖包,然后再次将源代码构建 Jar 包
$ mvn clean install## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.2 .

Docker 镜像构建总共花费 3.44s 时间。

(3)、第三次构建(修改代码内容)

## 修改源代码任意内容后,然后再次将源代码构建 Jar 包
$ mvn clean install## 构建 SpringBoot 应用的 Docker 镜像
$ time docker build -t hub.mydlq.club/library/springboot-layer:0.0.3 .

Docker 镜像构建总共花费 2.82s 时间。

六、镜像推送到镜像仓库测试

1、推送镜像到镜像仓库测试

服务器一推送普通镜像到镜像仓库1:

## 第一次推送镜像
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.1real    0m35.215s## 第二次推送镜像
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.2real    0m14.051s## 第三次推送镜像
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.3real    0m14.183s

服务器二推送分层镜像到镜像仓库2:

## 第一次推送镜像
$ time docker push hub.mydlq.club/library/springboot-layer:0.0.1real    0m34.121s## 第二次推送镜像
$ time docker push hub.mydlq.club/library/springboot-layer:0.0.2real    0m13.605s## 第三次推送镜像
$ time docker push hub.mydlq.club/library/springboot-layer:0.0.3real    0m4.805s

2、镜像仓库拉取镜像测试

服务器一推送从镜像仓库1拉取镜像:

## 清理全部镜像
$ docker rm --force $(docker images -qa)## 拉取镜像 springboot-normal:0.0.1
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.1real    0m35.395s## 拉取镜像 springboot-normal:0.0.2
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.2real    0m6.501s## 拉取镜像 springboot-normal:0.0.3
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.3real    0m6.993s

服务器二推送从镜像仓库2拉取镜像:

## 清理全部镜像
$ docker rm --force $(docker images -qa)## 拉取镜像 springboot-layer:0.0.1
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.1real    0m30.615s## 拉取镜像 springboot-layer:0.0.2
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.2real    0m4.811s## 拉取镜像 springboot-layer:0.0.3
$ time docker push hub.mydlq.club/library/springboot-normal:0.0.3real    0m1.293s

七、镜像构建、推送、拉取时间汇总

1、不使用分层构建镜像

如下图:

2、使用分层构建镜像

如下图:

3、总结

上面进行了使用 SpringBoot2.3.x 分层的方式构建镜像与普通的方式构建镜像,在镜像的构建、推送、拉取方面进行了执行速度对比,总结出如下结论:

  • 镜像构建: 在构建上,使用分层 Jar 构建镜像可能比普通方式构建镜像更繁琐,所以也更耗时,故而在构建上分层 Jar 构建镜像没有太多优势。

  • 镜像推送: 在推送上,如果每次构建镜像都只是修改构建镜像项目的源码,使用分层 Jar 构建镜像,可以大大加快镜像推送速度。如果是修改构建镜像项目中的依赖包,则和普通构建一样速度很慢。

  • 镜像拉取: 拉取和推送类似,如果只修改构建镜像项目的源码,只会拉取源码相关的中间层镜像,该层非常小(一般几百KB),拉取速度自然非常快。而对构建镜像项目的依赖包进行变动(增加依赖、删除依赖、修改依赖版本等),则会和普通方式构建镜像一样,拉取速度很慢,这是因为依赖包层是中间层镜像最大的一层(一般在10MB~200MB之间),如果该层发生变动则整个层会进行重新拉取,这样速度自然会很慢。

END

推荐好文

强大,10k+点赞的 SpringBoot 后台管理系统竟然出了详细教程!分享一套基于SpringBoot和Vue的企业级中后台开源项目,代码很规范!
能挣钱的,开源 SpringBoot 商城系统,功能超全,超漂亮!

【超全教程】SpringBoot 2.3.x 分层构建 Docker 镜像实践相关推荐

  1. SpringBoot 2.3.x 分层构建 Docker 镜像实践

    目录[-] . 一.什么是镜像分层 . 二.SpringBoot 2.3.x 新增对分层的支持 . 三.创建测试的 SpringBoot 应用 . 1.Maven 中引入相关依赖和插件 . 2.创建测 ...

  2. oppoJava面试!springboot分层构建Docker镜像实践

    前言 你的努力,终将成就无可替代的自己 本科毕业后就一直从事Java开发的工作,和多数人一样,最开始从事crud的工作,看着自己的同学一步一步往上走,自己还是在原地踏步,说实话这不是自己想要的状态. ...

  3. Spring Boot 2.3.x 分层构建 Docker 镜像实战

    目录 一.什么是镜像分层 二.SpringBoot 2.3.x 新增对分层的支持 三.创建测试的 SpringBoot 应用 1.Maven 中引入相关依赖和插件 2.创建测试的 Controller ...

  4. Spring Boot 分层构建 Docker 镜像实战

    目录 一.什么是镜像分层 二.SpringBoot 2.3.x 新增对分层的支持 三.创建测试的 SpringBoot 应用 1.Maven 中引入相关依赖和插件 2.创建测试的 Controller ...

  5. cad计算机中怎么用除号,CAD中特殊符号如何输入?超全教程,一看就懂!

    原标题:CAD中特殊符号如何输入?超全教程,一看就懂! CAD制图不同行业有不同的设计符号,但是你知道哪些特殊的符号要怎么输入呢?一旦入门CAD设计,你必须要熟悉了解这些符号输入操作. 本文会和大家分 ...

  6. 超全总结 | 阿里电商故障治理和故障演练实践

    超全总结 | 阿里电商故障治理和故障演练实践 本次分享包括两个部分:第一部分会从分布式系统经典依赖故障出发,剖析故障成因,介绍治理方案和技术演进:第二部分从宏观视角探讨构建一套"防故障&qu ...

  7. SpringBoot 项目构建 Docker 镜像调优实践

    点击上方"Java知音",选择"置顶公众号" 技术文章第一时间送达! 作者:超级小豆丁 http://www.mydlq.club/article/16/ PS ...

  8. jar构建docker镜像_dockerfile构建docker镜像详细说明,主要是springboot的jar包构建镜像样例...

    dockerfile构建docker镜像详细说明,主要是springboot的jar包构建镜像样例 1.镜像构建命令:docker build 图解 启动命令:(注意最后面有一个点,不要忘记) doc ...

  9. docker 运行jar exit_使用Dockerfile为SpringBoot应用构建Docker镜像

    上次写过一篇使用Maven插件构建Docker镜像 ,讲述了通过docker-maven-plugin来构建docker镜像的方式,此种方式需要依赖自建的Registry镜像仓库.本文将讲述另一种方式 ...

最新文章

  1. sqlserver 批量插入数据(此方式同样适用mysql)
  2. bcb 接收webservice tbytedynarray_文章推荐:基于分集接收技术的可见光接收机前端电路...
  3. MySQL返回多行错误怎么处理_结果包含多个行错误mysql
  4. POJ2296二分2sat
  5. studio项目上传svn服务器,SVN在AndroidStudio中的使用(一),SVN安装配置和项目检出更新...
  6. 机器学习理论《统计学习方法》学习笔记:第十章 隐马尔可夫模型(HMM)
  7. 表的插入、更新、删除、合并操作_10_仅复制表结构
  8. python基本输入输出系统_Python的输入输出
  9. iOS开发之如何将字典转为模型
  10. 对std::listT的封装
  11. (stl排序+检索)大理石在哪
  12. 实战案例丨ModelArts在数据标注、数据过滤上的应用技巧:自动分组
  13. C#面向对象基础(四) 静态成员与实例成员
  14. 去掉超链接或图片热点链接虚线框
  15. 电脑qq浏览器怎么滚动截长图_电脑怎么快速截图?
  16. (操作系统)系统调用讨论
  17. WPS for linux 安装及windows字体添加
  18. linux所有目录和子目录和文件777,关于linux:Chmod 777到一个文件夹和所有内容
  19. 美还是丑?这有一个CNN开发的颜值评分器 | 实战
  20. 计算机学期总结与学业计划,计算机教学工作总结以及来年计划范文

热门文章

  1. 因“突发肾结石” 孙宇晨宣布取消与巴菲特的午餐会面
  2. 华为Mate 20 X 5G版打通5G电话:音质饱满画面清晰
  3. Linux的shell编程(二)
  4. python更新_MacOS升级python版本
  5. 我的docker随笔30:C++程序的自动化构建
  6. linux时区的几个代码片段
  7. GCC编译警告选项的学习
  8. Oracle中的数据库对象
  9. 【java】httpclient 链接偶尔会 Read timed out
  10. 【算法】弗洛伊德算法 最短路径算法