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

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

文章目录

  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。


PS:已经在生产实践中验证,解决在生产环境下,网速带宽小,每次推拉镜像影响线上服务问题,按本文方式构建镜像,除了第一次拉取、推送、构建镜像慢,第二、三...次都是几百K大小传输,速度非常快,构建、打包、推送几秒内完成。

前言:

以前的 SpringCloud 微服务时代以 “Jar包" 为服务的基础,每个服务都打成 Jar 供服务间相互关联与调用。而 现在随着 Kubernetes 流行,已经变迁到一个镜像一个服务,依靠 Kubernetes 对镜像的统一编排进行对服务进行统一管理。在对 Kubernetes 微服务实践过程中,接触最多的肯定莫过于 Docker 镜像。由于本人使用的编程语言是 Java,所以对 Java SpringBoot 项目接触比较多,所以比较关心如何更好的通过 Dockerfile 编译 Docker 的镜像。

Kubernetes 微服务简单说就是一群镜像间的排列组合与相互间调的关系,故而如何编译镜像会使服务性能更优,使镜像构建、推送、拉取速度更快,使其占用网络资源更少这里优化,更易使用成为了一个重中之重的事情,也是一个非常值得琢磨的问题。这里我将对 SpringBoot 项目打包 Docker 镜像如何写 Dockerfile 的探究进行简单叙述。

系统环境:


一、探究常规 Springboot 如何编译 Docker 镜像

这里将用常规 SpringBoot 编译 Docker 镜像的 Dockerfile 写法,感受下这种方式编译的镜像用起来如何。

1、准备编译镜像的 SpringBoot 项目

这里准备一个经过 Maven 编译后的普通的 springboot 项目来进行 Docker 镜像构建,项目内容如下图所示,可以看到要用到的就是里面的应用程序的 Jar 文件,将其存入镜像内完成镜像构建任务。

  • jar 文件大小:70.86mb

2、准备 Dockerfile 文件

构建 Docker 镜像需要提前准备 Dockerfile 文件,这个 Dockerfile 文件中的内容为构建 Docker 镜像执行的指令。下面是一个常用的 SpringBoot 构建 Docker 镜像的 Dockerfile,将它放入 Java 源码目录(target 的上级目录),确保下面设置的 Dockerfile 脚本中设置的路径和 target 路径对应。

1FROM openjdk:8u212-b04-jre-slim
2VOLUME /tmp
3ADD target/*.jar app.jar
4RUN sh -c 'touch /app.jar'
5ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai"
6ENV APP_OPTS=""
7ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]

3、构建 Docker 镜像

通过 Docker build 命令构建 Docker 镜像,观察编译的时间。

由于后续需要将镜像推送到 Aliyun Docker 仓库,所以镜像前缀用了 Aliyun。

  • time:此参数会显示执行过程经过的时间
1$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1 .

构建过程

 1Sending build context to Docker daemon  148.7MB
 2Step 1/7 : FROM openjdk:8u212-b04-jre-slim
 38u212-b04-jre-slim: Pulling from library/openjdk
 4743f2d6c1f65: Already exists 
 5b83e581826a6: Pull complete 
 604305660f45e: Pull complete 
 7bbe7020b5561: Pull complete 
 8Digest: sha256:a5bcd678408a5fe94d13e486d500983ee6fa594940cbbe137670fbb90030456c
 9Status: Downloaded newer image for openjdk:8u212-b04-jre-slim
10 ---> 7c6b62cf60ee
11Step 2/7 : VOLUME /tmp
12 ---> Running in 13a67ab65d2b
13Removing intermediate container 13a67ab65d2b
14 ---> 52011f49ddef
15Step 3/7 : ADD target/*.jar app.jar
16 ---> 26aa41a404fd
17Step 4/7 : RUN sh -c 'touch /app.jar'
18 ---> Running in 722e7e44e04d
19Removing intermediate container 722e7e44e04d
20 ---> 7baedb10ec62
21Step 5/7 : ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai"
22 ---> Running in 2681d0c5edac
23Removing intermediate container 2681d0c5edac
24 ---> 5ef4a794b992
25Step 6/7 : ENV APP_OPTS=""
26 ---> Running in 5c8924a2a49d
27Removing intermediate container 5c8924a2a49d
28 ---> fba87c19053a
29Step 7/7 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
30 ---> Running in c4cf97009b3c
31Removing intermediate container c4cf97009b3c
32 ---> d5f30cdfeb81
33Successfully built d5f30cdfeb81
34Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
35
36real	0m13.778s
37user	0m0.078s
38sys	0m0.153s

看到这次编译在 14s 内完成。

4、将镜像推送到镜像仓库

将镜像推送到 Aliyun 仓库,然后查看并记录推送时间

1$ time docker push registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1

执行过程

 1The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
 2cc1a2376d7c0: Pushed 
 32b940d07e9e7: Pushed 
 49544e87fb8dc: Pushed 
 5feb5d0e1e192: Pushed 
 68fd22162ddab: Pushed 
 76270adb5794c: Pushed 
 80.0.1: digest: sha256:dc60d304383b1441941ca4e9abc08db775d7be57ccb7c534c929b34ff064a62f size: 1583
 9
10real	0m24.335s
11user	0m0.052s
12sys	0m0.059s

看到这次在 25s 内完成。

5、拉取镜像

这里切换到另一台服务器上进行镜像拉取操作,观察镜像拉取时间。

1$ time docker pull registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1

拉取过程

 10.0.1: Pulling from mydlq/springboot
 2743f2d6c1f65: Already exists 
 3b83e581826a6: Pull complete 
 404305660f45e: Pull complete 
 5bbe7020b5561: Pull complete 
 64847672cbfa5: Pull complete 
 7b60476972fc4: Pull complete 
 8Digest: sha256:dc60d304383b1441941ca4e9abc08db775d7be57ccb7c534c929b34ff064a62f
 9Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
10
11real	0m27.528s
12user	0m0.033s
13sys	0m0.192s

看到这次拉取总共用时 28s 内完成。

6、修改 Java 源码重新打包 Jar 后再次尝试

这里将源码的 JAVA 文件内容修改,然后重新打 Jar 包,这样再次尝试编译、推送、拉取过程,由于 Docker 在执行构建时会采用分层缓存,所以这是一个执行较快过程。

(1)、编译

 1$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2 .
 2
 3Sending build context to Docker daemon  148.7MB
 4Step 1/7 : FROM openjdk:8u212-b04-jre-slim
 5 ---> 7c6b62cf60ee
 6Step 2/7 : VOLUME /tmp
 7 ---> Using cache
 8 ---> 52011f49ddef
 9Step 3/7 : ADD target/*.jar app.jar
10 ---> c67160dd2a23
11Step 4/7 : RUN sh -c 'touch /app.jar'
12 ---> Running in 474900d843a2
13Removing intermediate container 474900d843a2
14 ---> 3ce9a8bb2600
15Step 5/7 : ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai"
16 ---> Running in f48620b1ad36
17Removing intermediate container f48620b1ad36
18 ---> 0478f8f14e5b
19Step 6/7 : ENV APP_OPTS=""
20 ---> Running in 98485fb15fc8
21Removing intermediate container 98485fb15fc8
22 ---> 0b567c848027
23Step 7/7 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
24 ---> Running in e32242fc6efe
25Removing intermediate container e32242fc6efe
26 ---> 7b223b23ebfd
27Successfully built 7b223b23ebfd
28Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
29
30real	0m3.190s
31user	0m0.039s
32sys	0m0.403s

可以看到在编译镜像过程中,前1、2层用的缓存,所以速度非常快。总编译过程耗时 4s 内完成。

(2)、推送

 1$ time docker push registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
 2
 3The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
 4d66a2fec30b5: Pushed 
 5f4da2c7581aa: Pushed 
 69544e87fb8dc: Layer already exists 
 7feb5d0e1e192: Layer already exists 
 88fd22162ddab: Layer already exists 
 96270adb5794c: Layer already exists 
10
11real	0m20.816s
12user	0m0.024s
13sys	0m0.081s

可以看到只推送了前两层,其它四次由于远程仓库未变化,所以没有推送。整个推送过程耗时 21s 内完成。

(3)、拉取

 1$ time docker pull registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
 2
 30.0.2: Pulling from mydlq/springboot
 4743f2d6c1f65: Already exists 
 5b83e581826a6: Already exists 
 604305660f45e: Already exists 
 7bbe7020b5561: Already exists 
 8d7e364f0d94a: Pull complete 
 98d688ada35b1: Pull complete 
10Digest: sha256:7c13c40fa92ec2fdc3a8dfdd3232be1be9c1a1a99bf123743ff2a43907ee03dc
11Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
12
13real	0m23.214s
14user	0m0.053s
15sys	0m0.097s

本地以及缓存前四层,只拉取有变化的后两层。这个过程耗时 24s 内完成。

7、使用镜像过程中的感受

通过这种方式对 SpringBoot 项目构建 Docker 镜像来使用,给我的感受就是只要源码中发生一点点变化,那么 SpringBoot 项目就需要将项目经过 Maven 编译后再经过 Docker 镜像构建,每次都会将一个 70M+ 的应用 Jar 文件存入 Docker 中,有时候明明就改了一个字母,可能又得把整个程序 Jar 重新存入 Docker 镜像中,然后在推送和拉取过程中,每次都得推一个大的镜像或者拉取一个大的镜像来进行传输,感觉非常不方便。

二、了解 Docker 分层及缓存机制

1、Docker 分层缓存简介

Docker 为了节约存储空间,所以采用了分层存储概念,不同镜像可以共享基础镜像的访问,关于镜像分层可以访问 Docker 官方文档的镜像分层这块知识,里面有对相关内的详细介绍,文档地址如下:

Docker 镜像缓存官方文档:https://docs.docker.com/storage/storagedriver/

在构建镜像的过程中 Docker 将按照 Dockerfile 中指定的顺序逐步执行 Dockerfile 中的指令。随着每条指令的检查,Docker 将在其缓存中查找可重用的现有镜像,而不是创建一个新的(重复)镜像。

Dockerfile 的每一行命令都创建新的一层,包含了这一行命令执行前后文件系统的变化。为了优化这个过程,Docker 使用了一种缓存机制:只要这一行命令不变,那么结果和上一次是一样的,直接使用上一次的结果即可。

为了充分利用层级缓存,我们必须要理解 Dockerfile 中的命令行是如何工作的,尤其是 RUN,ADD 和 COPY 这几个命令。

2、SpringBoot Docker 镜像的分层

SpringBoot 编译成镜像后,底层会是一个系统,如 Ubantu,上一层是依赖的 JDK 层,然后才是 SpringBoot 层。最下面两层是依赖的基础镜像,没有多少优化空间,所以我们优化镜像过程中考虑更多的是如何优化 SpringBoot 层,在此层进行琢磨。

三、是什么导致 Jar 包臃肿

从上面实验中了解到之所以每次编译、推送、拉取过程中较为缓慢,原因就是庞大的镜像文件。了解到 Docker 缓存概念后就就产生一种想法,如果不经常改变的文件缓存起来,将常改动的文件不进行缓存。由于 SpringBoot 项目是经常变换的,那么应该怎么利用缓存机制来实现呢?如果强行利用缓存那么每次打的镜像不都是缓存中的旧的程序内容吗。

所以就考虑一下应用 Jar 包里面都包含了什么文件, Java 的哪些文件是经常变动的,哪些不经常变动,对此,下面将针对 SpringBoot 打的应用 Jar 包进行分析。

1、解压 Jar 包查看内容

显示解压后的列表,查看各个文件夹大小

 1$ tree -L 3 --si --du
 2
 3.
 4├── [ 74M]  BOOT-INF 
 5│   ├── [2.1k]  classes
 6│   └── [ 74M]  lib
 7├── [ 649]  META-INF
 8│   ├── [ 552]  MANIFEST.MF
 9│   └── [  59]  maven
10└── [  67]  org
11    └── [  38]  springframework

可以看到最大的文件就是 lib 这个文件夹,打开这个文件夹,里面是一堆相关依赖 Jar,这其中一个 Jar 不大,但是一堆 Jar 组合起来就非常大了,一般 SpringBoot 的项目依赖 Jar 大小维持在 40MB ~ 160MB。

在看看 org 文件夹,里面代码加起来才几百 KB。故此 SpringBoot 程序 Jar 包就是这些 Classes 文件和依赖的 Jar 组成,这些依赖 Jar 总共 74 MB,几乎占了这个应用 Jar 包的全部大小。

2、解决臃肿的新思路

如果一个 Jar 包只包含 class 文件,那么这个 Jar 包的大小可能就几百 KB。现在要探究一下,如果将 lib 依赖的 Jar 和 class 分离,设置应用的 Jar 包只包含 class 文件,将 lib 文件夹下的 Jar 文件放在 SpringBoot Jar 的外面。

当我们写一个程序的时候,常常所依赖的 Jar 不会经常变动,变动多的是源代码程序,依赖的 Jar 包非常大而源代码非常小。仔细思考一下,如果在打包成 Docker 镜像的时候将应用依赖的 Jar 包单独设置一层缓存,而应用 Jar 包只包含 Class 文件,这样在 Docker 执行编译、推送、拉取过程中,除了第一次是全部都要执行外,再往后的执行编译、推送、拉取过程中,只会操作改动的那个只包含 Class 的 Jar 文件,就几百 KB,可以说是能够瞬间完成这个过程。所以思考一下,如何将 lib 文件夹下的依赖 Jar 包和应用 Jar 包分离开来。

3、如何解决 lib 和 class 文件分离

经过查找很多相关资料,发现 SpringBoot 的 Maven 插件在执行 Maven 编译打 Jar 包时候做了很多事情,如果改变某些插件的打包逻辑,就可以实现在构建应用 Jar 包时,将应用依赖 lib 文件夹下的所有 Jar 都拷贝到应用 Jar 外面,只留下编译好的字节码 class 文件。

经过网上多番查找,找到了一种实现办法,如下,将这几个 Maven 工具引入到项目 pom.xml 中

 1<build>
 2    <plugins>
 3        <!--设置应用 Main 参数启动依赖查找的地址指向外部 lib 文件夹-->
 4        <plugin>
 5            <groupId>org.apache.maven.plugins</groupId>
 6            <artifactId>maven-jar-plugin</artifactId>
 7            <configuration>
 8                <archive>
 9                    <manifest>
10                        <addClasspath>true</addClasspath>
11                        <classpathPrefix>lib/</classpathPrefix>
12                    </manifest>
13                </archive>
14            </configuration>
15        </plugin>
16        <!--设置 SpringBoot 打包插件不包含任何 Jar 依赖包-->
17        <plugin>
18            <groupId>org.springframework.boot</groupId>
19            <artifactId>spring-boot-maven-plugin</artifactId>
20            <configuration>
21                 <includes>
22                     <include>
23                         <groupId>nothing</groupId>
24                         <artifactId>nothing</artifactId>
25                     </include>
26                 </includes>
27            </configuration>
28        </plugin>
29        <!--设置将 lib 拷贝到应用 Jar 外面-->
30        <plugin>
31            <groupId>org.apache.maven.plugins</groupId>
32            <artifactId>maven-dependency-plugin</artifactId>
33            <executions>
34                <execution>
35                    <id>copy-dependencies</id>
36                    <phase>prepare-package</phase>
37                    <goals>
38                        <goal>copy-dependencies</goal>
39                    </goals>
40                    <configuration>
41                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
42                    </configuration>
43                </execution>
44            </executions>
45        </plugin>
46    </plugins>
47</build>

执行 Maven 命令打包 Jar

1$ mvn clean install

当 Maven 命令执行完成后,查看 target 目录如下图:

可以看到应用 jar 已经变的非常小,且 lib 文件夹已经被放置到 jar 外面,jar 包中不再集成,而是在应用 jar 运行时候指定 lib 地址,使其能够加载。不管怎么样,我们都需要测试下这个 Jar 文件是否能正常运行,只有正常执行才能说明此方案可行。

执行下面命令,测试 jar 是否能够正常启动:

1$ java -jar springboot-helloworld-0.0.1.jar

本人在运行过程中能够看到运行日志,且显示运行成功,表示这种方案是可行的。不过我们改造工作尚未结束,接下来继续进行 Dockerfile 改造工作。

四、聊聊如何改造 Springboot 编译 Docker 镜像

项目 Github 地址:https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-dockerfile

1、修改 Dockerfile 文件

这里修改上面的 Dockerfile 文件,需要新增下面一条指令:

1COPY target/lib/ ./lib/

该指令用于将 lib 目录里面的依赖 Jar 复制到镜像中,其它保持和上面 Dockerfile 一致。

1FROM openjdk:8u212-b04-jre-slim
2VOLUME /tmp
3COPY target/lib/ ./lib/
4ADD target/*.jar app.jar
5RUN sh -c 'touch /app.jar'
6ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai"
7ENV APP_OPTS=""
8ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]

这里新增了一层指令,作用为将 lib 文件夹复制到镜像之中,由于 Docker 缓存机制原因,这层一定要在复制应用 Jar 之前,这样改造后每次只要 lib 文件夹里面的依赖 Jar 不变,就不会新创建层,而是复用缓存。

2、改造 Docker 镜像后的首次编译、推送、拉取

这里我们开始真正尝试执行编译、推送、拉取来测试其速度如何,执行之前,我们先将服务器上次镜像相关的所有资源都清除掉,这样来减少对测试环境的影响。

(1)、编译速度测试及记录

 1$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1 .
 2
 3Sending build context to Docker daemon  223.2MB
 4Step 1/8 : FROM openjdk:8u212-b04-jre-slim
 58u212-b04-jre-slim: Pulling from library/openjdk
 6743f2d6c1f65: Already exists 
 7b83e581826a6: Pull complete 
 804305660f45e: Pull complete 
 9bbe7020b5561: Pull complete 
10Digest: sha256:a5bcd678408a5fe94d13e486d500983ee6fa594940cbbe137670fbb90030456c
11Status: Downloaded newer image for openjdk:8u212-b04-jre-slim
12 ---> 7c6b62cf60ee
13Step 2/8 : VOLUME /tmp
14 ---> Running in 529369acab24
15Removing intermediate container 529369acab24
16 ---> ad689d937118
17Step 3/8 : COPY target/lib/ ./lib/
18 ---> 029a64c15853
19Step 4/8 : ADD target/*.jar app.jar
20 ---> 6265a83a1b90
21Step 5/8 : RUN sh -c 'touch /app.jar'
22 ---> Running in 839032a58e6b
23Removing intermediate container 839032a58e6b
24 ---> 5d877dc35b2b
25Step 6/8 : ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai"
26 ---> Running in 4043994c5fed
27Removing intermediate container 4043994c5fed
28 ---> 7cf32beb571f
29Step 7/8 : ENV APP_OPTS=""
30 ---> Running in b7dcfa10458a
31Removing intermediate container b7dcfa10458a
32 ---> b6b332bcf0e6
33Step 8/8 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
34 ---> Running in 539093461b59
35Removing intermediate container 539093461b59
36 ---> d4c095c4ffec
37Successfully built d4c095c4ffec
38Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
39
40real	0m22.983s
41user	0m0.051s
42sys	0m0.540s

(2)、推送速度测试及记录

 1$ time docker push registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
 2
 3The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
 4c16749205e05: Pushed 
 57fef1a146748: Pushed 
 6a3bae74bbdf2: Pushed 
 79544e87fb8dc: Pushed
 8feb5d0e1e192: Pushed
 98fd22162ddab: Pushed
106270adb5794c: Pushed 
110.0.1: digest: sha256:e2f4db740880dbe5338b823112ba9467fedf8b27cd75572611d0d3837c80f157 size: 1789
12
13real	0m30.335s
14user	0m0.052s
15sys	0m0.059s

(3)、拉取速度测试及记录

 1$ time docker pull registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
 2
 30.0.1: Pulling from mydlq/springboot
 4743f2d6c1f65: Already exists 
 5b83e581826a6: Pull complete 
 604305660f45e: Pull complete 
 7bbe7020b5561: Pull complete 
 8de6c4f15d75b: Pull complete 
 97066947b7d89: Pull complete 
10e0742de67c75: Pull complete 
11Digest: sha256:e2f4db740880dbe5338b823112ba9467fedf8b27cd75572611d0d3837c80f157
12Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
13
14real	0m36.585s
15user	0m0.024s
16sys	0m0.092s

3、再次编译、推送、拉取

我们再次执行镜像构建,来测试在构建到 Step 3/8 : COPY target/lib/ ./lib/ 时,是否复用缓存中的镜像。

(1)、编译速度测试及记录

 1$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2 .
 2
 3Sending build context to Docker daemon  223.2MB
 4Step 1/8 : FROM openjdk:8u212-b04-jre-slim
 5 ---> 7c6b62cf60ee
 6Step 2/8 : VOLUME /tmp
 7 ---> Using cache
 8 ---> ad689d937118
 9Step 3/8 : COPY target/lib/ ./lib/
10 ---> Using cache
11 ---> 029a64c15853
12Step 4/8 : ADD target/*.jar app.jar
13 ---> 563773953844
14Step 5/8 : RUN sh -c 'touch /app.jar'
15 ---> Running in 3b9df57802bd
16Removing intermediate container 3b9df57802bd
17 ---> 706a0d47317f
18Step 6/8 : ENV JAVA_OPTS="-XX:MaxRAMPercentage=80.0 -Duser.timezone=Asia/Shanghai"
19 ---> Running in defda61452bf
20Removing intermediate container defda61452bf
21 ---> 742c7c926374
22Step 7/8 : ENV APP_OPTS=""
23 ---> Running in f09b81d054dd
24Removing intermediate container f09b81d054dd
25 ---> 929ed5f8b12a
26Step 8/8 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
27 ---> Running in 5dc66a8fc1e6
28Removing intermediate container 5dc66a8fc1e6
29 ---> c4942b10992c
30Successfully built c4942b10992c
31Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
32
33real	0m2.524s
34user	0m0.051s
35sys	0m0.493s

可以看到,这次在第 3 层直接用的缓存,整个编译过程才花了 2.5 秒时间

(2)、推送速度测试及记录

 1$ time docker push registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
 2
 3The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
 4d719b9540809: Pushed 
 5d45bf4c5fb92: Pushed 
 6a3bae74bbdf2: Layer already exists 
 79544e87fb8dc: Layer already exists 
 8feb5d0e1e192: Layer already exists 
 98fd22162ddab: Layer already exists 
106270adb5794c: Layer already exists 
110.0.2: digest: sha256:b46d81b153ec64321caaae7ab28da0e362ed7d720a7f0775ea8d1f7bef310d00 size: 1789
12
13real	0m0.168s
14user	0m0.016s
15sys	0m0.032s

可以看到在 0.2s 内就完成了镜像推送

(3)、拉取速度测试及记录

 1$ time docker pull registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
 2
 30.0.2: Pulling from mydlq/springboot
 4743f2d6c1f65: Already exists 
 5b83e581826a6: Already exists 
 604305660f45e: Already exists 
 7bbe7020b5561: Already exists 
 8de6c4f15d75b: Already exists 
 91c77cc70cc41: Pull complete 
10aa5b8cbca568: Pull complete 
11Digest: sha256:b46d81b153ec64321caaae7ab28da0e362ed7d720a7f0775ea8d1f7bef310d00
12Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
13
14real	0m1.947s
15user	0m0.017s
16sys	0m0.042s

可以看到在 2s 内就完成了镜像拉取

五、最后总结

由于网络波动和系统变化,所以时间只能当做参考,不过执行编译、推送、拉取过程的确快了不少,大部分用文件都进行了缓存,只有几百 KB 的流量交互自然速度比几十 MB 甚至几百 MB 速度要快很多。

最后说明一下,这种做法只是提供了一种参考,现在的微服务服务 Docker 镜像化以来,维护的是整个镜像而不是一个服务程序,所以关心的是 Docker 镜像能否能正常运行,怎么构建镜像会使构建的镜像更好用。

在生产环境下由于版本变化较慢,不会动不动就更新,所以在生产环境下暂时最好还是按部就班,应用原来 SpringBoot 镜像编译方式以确保安装(除非已大量实例验证该构建方法)。


  !版权声明:本博客内容均为原创,每篇博文作为知识积累,写博不易,转载请注明出处。