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

注意,SpringBoot 2.3.x 已经默认支持分层功能,请参考:java SpringBoot 2.3.x 分层构建 Docker 镜像

前言:

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

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

系统环境:

  1. Docker 版本: 18.09.3
  2. Open JDK 基础镜像版本:openjdk:8u212-b04-jre-slim
  3. 测试用的镜像仓库:阿里云 Docker Hub
  4. 项目 Github https://github.com/my-dlq/blog-example/tree/master/springboot/springboot-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 路径对应。

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

3、构建 Docker 镜像

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

  1. 由于后续需要将镜像推送到 Aliyun Docker 仓库,所以镜像前缀用了 Aliyun
  2. time:此参数会显示执行过程经过的时间
  1. $ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1 .

构建过程

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

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

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

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

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

执行过程

  1. The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
  2. cc1a2376d7c0: Pushed
  3. 2b940d07e9e7: Pushed
  4. 9544e87fb8dc: Pushed
  5. feb5d0e1e192: Pushed
  6. 8fd22162ddab: Pushed
  7. 6270adb5794c: Pushed
  8. 0.0.1: digest: sha256:dc60d304383b1441941ca4e9abc08db775d7be57ccb7c534c929b34ff064a62f size: 1583
  9. real 0m24.335s
  10. user 0m0.052s
  11. sys 0m0.059s

看到这次在 25s 内完成。

5、拉取镜像

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

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

拉取过程

  1. 0.0.1: Pulling from mydlq/springboot
  2. 743f2d6c1f65: Already exists
  3. b83e581826a6: Pull complete
  4. 04305660f45e: Pull complete
  5. bbe7020b5561: Pull complete
  6. 4847672cbfa5: Pull complete
  7. b60476972fc4: Pull complete
  8. Digest: sha256:dc60d304383b1441941ca4e9abc08db775d7be57ccb7c534c929b34ff064a62f
  9. Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
  10. real 0m27.528s
  11. user 0m0.033s
  12. sys 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. Sending build context to Docker daemon 148.7MB
  3. Step 1/7 : FROM openjdk:8u212-b04-jre-slim
  4. ---> 7c6b62cf60ee
  5. Step 2/7 : VOLUME /tmp
  6. ---> Using cache
  7. ---> 52011f49ddef
  8. Step 3/7 : ADD target/*.jar app.jar
  9. ---> c67160dd2a23
  10. Step 4/7 : RUN sh -c 'touch /app.jar'
  11. ---> Running in 474900d843a2
  12. Removing intermediate container 474900d843a2
  13. ---> 3ce9a8bb2600
  14. Step 5/7 : ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
  15. ---> Running in f48620b1ad36
  16. Removing intermediate container f48620b1ad36
  17. ---> 0478f8f14e5b
  18. Step 6/7 : ENV APP_OPTS=""
  19. ---> Running in 98485fb15fc8
  20. Removing intermediate container 98485fb15fc8
  21. ---> 0b567c848027
  22. Step 7/7 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
  23. ---> Running in e32242fc6efe
  24. Removing intermediate container e32242fc6efe
  25. ---> 7b223b23ebfd
  26. Successfully built 7b223b23ebfd
  27. Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2
  28. real 0m3.190s
  29. user 0m0.039s
  30. sys 0m0.403s

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

(2)、推送

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

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

(3)、拉取

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

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

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

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

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

1、Docker 分层缓存简介

Docker 为了节约存储空间,所以采用了分层存储概念。共享数据会对镜像和容器进行分层,不同镜像可以共享相同数据,并且在镜像上为容器分配一个 RW 层来加快容器的启动顺序。

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

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

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

  1. 参考 Docker 文档了解 Docker 镜像缓存:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

2、SpringBoot Docker 镜像的分层

SpringBoot 编译成镜像后,底层会是一个系统,如 Ubantu,上一层是依赖的 JDK 层,然后才是 SpringBoot 层,最下面两层我们无法操作,考虑优化只能是 SpringBoot 层琢磨。

三、是什么导致 Jar 包臃肿

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

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

1、解压 Jar 包查看内容

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

$ tree -L 3 --si --du

.

├── [ 74M] BOOT-INF

│ ├── [2.1k] classes

│ └── [ 74M] lib

├── [ 649] META-INF

│ ├── [ 552] MANIFEST.MF

│ └── [ 59] maven

└── [ 67] org

└── [ 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 外面,只留下编译好的字节码文件。

将这几个 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 文件是否能正常运行

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

然后看到运行日志,OK!下面将继续进行 Dockerfile 改造工作。

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

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

1、修改 Dockerfile 文件

这里修改上面的 Dockerfile 文件,需要新增一层指令用于将 lib 目录里面的依赖 Jar 复制到镜像中,其它保持和上面 Dockerfile 一致。

  1. FROM openjdk:8u212-b04-jre-slim
  2. VOLUME /tmp
  3. COPY target/lib/ ./lib/
  4. ADD target/*.jar app.jar
  5. RUN sh -c 'touch /app.jar'
  6. ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
  7. ENV APP_OPTS=""
  8. ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
  1. 这里新增了一层指令,作用为将 lib 文件夹复制到镜像之中,由于 Docker 缓存机制原因,这层一定要在复制应用 Jar 之前,这样改造后每次只要 lib/ 文件夹里面的依赖 Jar 不变,就不会新创建层,而是复用缓存。

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

在执行编译、推送、拉取之前,先将服务器上次镜像相关的所有资源都清除掉,然后再执行。

(1)、编译

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

(2)、推送

  1. $ time docker push registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1
  2. The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
  3. c16749205e05: Pushed
  4. 7fef1a146748: Pushed
  5. a3bae74bbdf2: Pushed
  6. 9544e87fb8dc: Pushed
  7. feb5d0e1e192: Pushed
  8. 8fd22162ddab: Pushed
  9. 6270adb5794c: Pushed
  10. 0.0.1: digest: sha256:e2f4db740880dbe5338b823112ba9467fedf8b27cd75572611d0d3837c80f157 size: 1789
  11. real 0m30.335s
  12. user 0m0.052s
  13. sys 0m0.059s

(3)、拉取

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

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

(1)、编译

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

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

(2)、推送

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

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

(3)、拉取

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

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

五、最后总结

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

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

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

Java SpringBoot 项目构建 Docker 镜像调优实践的更多相关文章

  1. 【Docker】Maven打包SpringBoot项目成Docker镜像并上传到Harbor仓库(Eclipse、STS、IDEA、Maven通用)

    写在前面 最近,在研究如何使用Maven将SpringBoot项目打包成Docker镜像并发布到Harbor仓库,网上翻阅了很多博客和资料,发现大部分都是在复制粘贴别人的东西,没有经过实践的检验,根本 ...

  2. Azure Devops实践(5)- 构建springboot项目打包docker镜像及容器化部署

    使用Azure Devops构建java springboot项目,创建镜像并容器化部署 1.创建一个springboot项目,我用现有的项目 目录结构如下,使用provider项目 在根目录下添加D ...

  3. SpringBoot项目优化和Jvm调优(转)

    原文:https://blog.csdn.net/wd2014610/article/details/82182617 项目调优作为一名工程师,项目调优这事,是必须得熟练掌握的事情. 在SpringB ...

  4. SpringBoot项目优化和Jvm调优(楼主亲测,真实有效)

    项目调优 作为一名工程师,项目调优这事,是必须得熟练掌握的事情. 在SpringBoot项目中,调优主要通过配置文件和配置JVM的参数的方式进行. 在这边有一篇比较好的文章,推荐给大家! Spring ...

  5. SpringBoot项目优化和Jvm调优

    https://www.cnblogs.com/jpfss/p/9753215.html 项目调优 作为一名工程师,项目调优这事,是必须得熟练掌握的事情. 在SpringBoot项目中,调优主要通过配 ...

  6. springboot项目打包docker镜像maven插件

    <!-- profile docker config --> <profiles> <profile> <id>docker</id> &l ...

  7. SpringBoot打包成Docker镜像

    1. 本文环境 Maven:3.6.3(Maven配置参考) SpringBoot version:2.3.4.RELEASE Docker version: 19.03.11(Docker搭建参考) ...

  8. SpringBoot 构建 Docker 镜像的 3 种方式

    本文将介绍3种技术,通过 Maven 把 SpringBoot 应用构建成 Docker 镜像. (1)使用 spring-boot-maven-plugin 内置的 build-image. (2) ...

  9. SpringBoot 构建 Docker 镜像的最佳 3 种方式

    本文将介绍3种技术,通过 Maven 把 SpringBoot 应用构建成 Docker 镜像. (1)使用 spring-boot-maven-plugin 内置的 build-image. (2) ...

随机推荐

  1. 可落地的DDD(7)-战术设计上的一些误区

    背景 几年前我总结过DDD战术设计的一些落地经验可落地的DDD(5)-战术设计,和一次关于聚合根的激烈讨论最近两年有些新的落地体验,回过头来发现,当初对这些概念的理解还是没有深入,这篇文章重新阐述下. ...

  2. 函数式接口的概念&函数式接口的定义和函数式接口的使用

    函数式接口概念 函数式接口在Java中是指:有且仅有一个抽象方法的接口. 函数式接口,即适用于函数式编程场景的接口.而Java中的函数式编程体现就是Lambda,所以函数式接口就是可以适用于Lambd ...

  3. Template -「高斯消元」

    #include <cstdio> #include <vector> #include <algorithm> using namespace std; doub ...

  4. efcore在Saas系统下多租户零脚本分表分库读写分离解决方案

    efcore在Saas系统下多租户零脚本分表分库读写分离解决方案 ## 介绍 本文ShardinfCore版本x.6.0.20+ 本期主角: - [`ShardingCore`](https://gi ...

  5. css基础06

    精灵图就是只要导入一张照片(这张照片里面有很多很多的小图标和照片),然后通过background-position来移动位置,使网页显示出对应图片或者图标.一般都是负值. 下载然后导入项目里. 不同浏 ...

  6. Changes in GreatSQL 8.0.25-16(2022-5-16)

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 目录 1.新增特性 1.1 新增仲裁节点(投票节点)角色 1.2 新增快速单主模式 1.3 新增MGR网络开销阈值 1.4 ...

  7. HDFS核心原理

    HDFS 读写解析 HDFS 读数据流程 客户端通过 FileSystem 向 NameNode 发起请求下载文件,NameNode 通过查询元数据找到文件所在的 DataNode 地址 挑选一台 D ...

  8. fijkplayer问题反馈:暂停时拖动进度光标,在窗口模式与全屏模式间切换后,进度光标不能及时更新、正常跟进

    fijkplayer-0.8.4很优秀,造福苍生,非常感谢! 使用fijkplayer-0.8.4开发的过程中遇到以下问题,特此记录.提交上传:https://github.com/befovy/fi ...

  9. Blazor预研与实战

    背景 最近一直在搞一件事,就是熟悉Blazor,后期需要将Blazor真正运用到项目内.前期做了一些调研,包括但不限于 Blazor知识学习 组件库生态预研 与现有SPA框架做比对 与WebForm做 ...

  10. Excel 单元格的相对引用和绝对引用

    引用方式 单元格的地址由该单元格所在的行号和列标构成,一个引用代表工作表上的一个或者一组单元格,指明公式中数据所在的位置. 编号 消费记录 价格 1 乒乓球 1 2 火腿肠 2 3 乒乓球 1 4 羽 ...