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

Java(SpringBoot)项目打包(构建)成`Docker`镜像的几种方式 小简选择的是最后一个,第一个也还行吧,但是Google那个不需要本地有Docker环境,第一个我还是开着魔法(梯子)打包的,最后一个挺好的。看情况选择的,很久没更新啦!越来越懒了,嘿嘿,下篇再见。 阅读详情

来源:http://www.mydlq.club/article/16/

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

前言:

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

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

这里我将对 SpringBoot 项目打包 Docker 镜像如何写 Dockerfile 的探究进行简单叙述。

系统环境:

  • Docker 版本:18.09.3

  • Open JDK 基础镜像版本:openjdk:8u212-b04-jre-slim

  • 测试用的镜像仓库:阿里云 Docker Hub

  • 项目 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 路径对应。

FROM openjdk:8u212-b04-jre-slim
VOLUME /tmp
ADD target/*.jar app.jar
RUN sh -c 'touch /app.jar'
ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
ENV APP_OPTS=""
ENTRYPOINT [ "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:此参数会显示执行过程经过的时间

$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1 .

构建过程

Sending build context to Docker daemon  148.7MB
Step 1/7 : FROM openjdk:8u212-b04-jre-slim
8u212-b04-jre-slim: Pulling from library/openjdk
743f2d6c1f65: Already exists 
b83e581826a6: Pull complete 
04305660f45e: Pull complete 
bbe7020b5561: Pull complete 
Digest: sha256:a5bcd678408a5fe94d13e486d500983ee6fa594940cbbe137670fbb90030456c
Status: Downloaded newer image for openjdk:8u212-b04-jre-slim
 ---> 7c6b62cf60ee
Step 2/7 : VOLUME /tmp
 ---> Running in 13a67ab65d2b
Removing intermediate container 13a67ab65d2b
 ---> 52011f49ddef
Step 3/7 : ADD target/*.jar app.jar
 ---> 26aa41a404fd
Step 4/7 : RUN sh -c 'touch /app.jar'
 ---> Running in 722e7e44e04d
Removing intermediate container 722e7e44e04d
 ---> 7baedb10ec62
Step 5/7 : ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
 ---> Running in 2681d0c5edac
Removing intermediate container 2681d0c5edac
 ---> 5ef4a794b992
Step 6/7 : ENV APP_OPTS=""
 ---> Running in 5c8924a2a49d
Removing intermediate container 5c8924a2a49d
 ---> fba87c19053a
Step 7/7 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
 ---> Running in c4cf97009b3c
Removing intermediate container c4cf97009b3c
 ---> d5f30cdfeb81
Successfully built d5f30cdfeb81
Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1

real    0m13.778s
user    0m0.078s
sys 0m0.153s

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

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

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

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

执行过程

The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
cc1a2376d7c0: Pushed 
2b940d07e9e7: Pushed 
9544e87fb8dc: Pushed 
feb5d0e1e192: Pushed 
8fd22162ddab: Pushed 
6270adb5794c: Pushed 
0.0.1: digest: sha256:dc60d304383b1441941ca4e9abc08db775d7be57ccb7c534c929b34ff064a62f size: 1583

real    0m24.335s
user    0m0.052s
sys 0m0.059s

看到这次在 25s 内完成。扩展:面试官:你简历中写用过docker,能说说容器和镜像的区别吗?

5、拉取镜像

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

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

拉取过程

0.0.1: Pulling from mydlq/springboot
743f2d6c1f65: Already exists 
b83e581826a6: Pull complete 
04305660f45e: Pull complete 
bbe7020b5561: Pull complete 
4847672cbfa5: Pull complete 
b60476972fc4: Pull complete 
Digest: sha256:dc60d304383b1441941ca4e9abc08db775d7be57ccb7c534c929b34ff064a62f
Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1

real    0m27.528s
user    0m0.033s
sys 0m0.192s

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

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

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

(1)、编译

$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2 .

Sending build context to Docker daemon  148.7MB
Step 1/7 : FROM openjdk:8u212-b04-jre-slim
 ---> 7c6b62cf60ee
Step 2/7 : VOLUME /tmp
 ---> Using cache
 ---> 52011f49ddef
Step 3/7 : ADD target/*.jar app.jar
 ---> c67160dd2a23
Step 4/7 : RUN sh -c 'touch /app.jar'
 ---> Running in 474900d843a2
Removing intermediate container 474900d843a2
 ---> 3ce9a8bb2600
Step 5/7 : ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
 ---> Running in f48620b1ad36
Removing intermediate container f48620b1ad36
 ---> 0478f8f14e5b
Step 6/7 : ENV APP_OPTS=""
 ---> Running in 98485fb15fc8
Removing intermediate container 98485fb15fc8
 ---> 0b567c848027
Step 7/7 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
 ---> Running in e32242fc6efe
Removing intermediate container e32242fc6efe
 ---> 7b223b23ebfd
Successfully built 7b223b23ebfd
Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2

real    0m3.190s
user    0m0.039s
sys 0m0.403s

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

(2)、推送

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

The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
d66a2fec30b5: Pushed 
f4da2c7581aa: Pushed 
9544e87fb8dc: Layer already exists 
feb5d0e1e192: Layer already exists 
8fd22162ddab: Layer already exists 
6270adb5794c: Layer already exists 

real    0m20.816s
user    0m0.024s
sys 0m0.081s

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

(3)、拉取

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

0.0.2: Pulling from mydlq/springboot
743f2d6c1f65: Already exists 
b83e581826a6: Already exists 
04305660f45e: Already exists 
bbe7020b5561: Already exists 
d7e364f0d94a: Pull complete 
8d688ada35b1: Pull complete 
Digest: sha256:7c13c40fa92ec2fdc3a8dfdd3232be1be9c1a1a99bf123743ff2a43907ee03dc
Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2

real    0m23.214s
user    0m0.053s
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 使用了一种缓存机制:只要这一行命令不变,那么结果和上一次是一样的,直接使用上一次的结果即可。扩展:终于有人把 Docker 讲清楚了,万字详解!

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

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

2、SpringBoot Docker 镜像的分层

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

三、是什么导致 Jar 包臃肿

从上面实验中了解到之所以每次编译、推送、拉取过程中较为缓慢,原因就是庞大的镜像文件。了解到 Docker 缓存概念后就就产生一种想法,如果不经常改变的文件缓存起来,将常改动的文件不进行缓存。扩展:SpringBoot缓存应用实践

由于 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 中

<build>
    <plugins>
        <!--设置应用 Main 参数启动依赖查找的地址指向外部 lib 文件夹-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <configuration>
                <archive>
                    <manifest>
                        <addClasspath>true</addClasspath>
                        <classpathPrefix>lib/</classpathPrefix>
                    </manifest>
                </archive>
            </configuration>
        </plugin>
        <!--设置 SpringBoot 打包插件不包含任何 Jar 依赖包-->
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                 <includes>
                     <include>
                         <groupId>nothing</groupId>
                         <artifactId>nothing</artifactId>
                     </include>
                 </includes>
            </configuration>
        </plugin>
        <!--设置将 lib 拷贝到应用 Jar 外面-->
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-dependency-plugin</artifactId>
            <executions>
                <execution>
                    <id>copy-dependencies</id>
                    <phase>prepare-package</phase>
                    <goals>
                        <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                        <outputDirectory>${project.build.directory}/lib</outputDirectory>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

执行 Maven 命令打包 Jar

$ mvn clean install

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

然后测试下这个 Jar 文件是否能正常运行

$ 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 一致。

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

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

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

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

(1)、编译

$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1 .

Sending build context to Docker daemon  223.2MB
Step 1/8 : FROM openjdk:8u212-b04-jre-slim
8u212-b04-jre-slim: Pulling from library/openjdk
743f2d6c1f65: Already exists 
b83e581826a6: Pull complete 
04305660f45e: Pull complete 
bbe7020b5561: Pull complete 
Digest: sha256:a5bcd678408a5fe94d13e486d500983ee6fa594940cbbe137670fbb90030456c
Status: Downloaded newer image for openjdk:8u212-b04-jre-slim
 ---> 7c6b62cf60ee
Step 2/8 : VOLUME /tmp
 ---> Running in 529369acab24
Removing intermediate container 529369acab24
 ---> ad689d937118
Step 3/8 : COPY target/lib/ ./lib/
 ---> 029a64c15853
Step 4/8 : ADD target/*.jar app.jar
 ---> 6265a83a1b90
Step 5/8 : RUN sh -c 'touch /app.jar'
 ---> Running in 839032a58e6b
Removing intermediate container 839032a58e6b
 ---> 5d877dc35b2b
Step 6/8 : ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
 ---> Running in 4043994c5fed
Removing intermediate container 4043994c5fed
 ---> 7cf32beb571f
Step 7/8 : ENV APP_OPTS=""
 ---> Running in b7dcfa10458a
Removing intermediate container b7dcfa10458a
 ---> b6b332bcf0e6
Step 8/8 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
 ---> Running in 539093461b59
Removing intermediate container 539093461b59
 ---> d4c095c4ffec
Successfully built d4c095c4ffec
Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1

real    0m22.983s
user    0m0.051s
sys 0m0.540s

(2)、推送

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

The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
c16749205e05: Pushed 
7fef1a146748: Pushed 
a3bae74bbdf2: Pushed 
9544e87fb8dc: Pushed
feb5d0e1e192: Pushed
8fd22162ddab: Pushed
6270adb5794c: Pushed 
0.0.1: digest: sha256:e2f4db740880dbe5338b823112ba9467fedf8b27cd75572611d0d3837c80f157 size: 1789

real    0m30.335s
user    0m0.052s
sys 0m0.059s

(3)、拉取

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

0.0.1: Pulling from mydlq/springboot
743f2d6c1f65: Already exists 
b83e581826a6: Pull complete 
04305660f45e: Pull complete 
bbe7020b5561: Pull complete 
de6c4f15d75b: Pull complete 
7066947b7d89: Pull complete 
e0742de67c75: Pull complete 
Digest: sha256:e2f4db740880dbe5338b823112ba9467fedf8b27cd75572611d0d3837c80f157
Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.1

real    0m36.585s
user    0m0.024s
sys 0m0.092s

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

(1)、编译

$ time docker build -t registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2 .

Sending build context to Docker daemon  223.2MB
Step 1/8 : FROM openjdk:8u212-b04-jre-slim
 ---> 7c6b62cf60ee
Step 2/8 : VOLUME /tmp
 ---> Using cache
 ---> ad689d937118
Step 3/8 : COPY target/lib/ ./lib/
 ---> Using cache
 ---> 029a64c15853
Step 4/8 : ADD target/*.jar app.jar
 ---> 563773953844
Step 5/8 : RUN sh -c 'touch /app.jar'
 ---> Running in 3b9df57802bd
Removing intermediate container 3b9df57802bd
 ---> 706a0d47317f
Step 6/8 : ENV JAVA_OPTS="-Duser.timezone=Asia/Shanghai"
 ---> Running in defda61452bf
Removing intermediate container defda61452bf
 ---> 742c7c926374
Step 7/8 : ENV APP_OPTS=""
 ---> Running in f09b81d054dd
Removing intermediate container f09b81d054dd
 ---> 929ed5f8b12a
Step 8/8 : ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar $APP_OPTS" ]
 ---> Running in 5dc66a8fc1e6
Removing intermediate container 5dc66a8fc1e6
 ---> c4942b10992c
Successfully built c4942b10992c
Successfully tagged registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2

real    0m2.524s
user    0m0.051s
sys 0m0.493s

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

(2)、推送

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

The push refers to repository [registry.cn-beijing.aliyuncs.com/mydlq/springboot]
d719b9540809: Pushed 
d45bf4c5fb92: Pushed 
a3bae74bbdf2: Layer already exists 
9544e87fb8dc: Layer already exists 
feb5d0e1e192: Layer already exists 
8fd22162ddab: Layer already exists 
6270adb5794c: Layer already exists 
0.0.2: digest: sha256:b46d81b153ec64321caaae7ab28da0e362ed7d720a7f0775ea8d1f7bef310d00 size: 1789

real    0m0.168s
user    0m0.016s
sys 0m0.032s

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

(3)、拉取

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

0.0.2: Pulling from mydlq/springboot
743f2d6c1f65: Already exists 
b83e581826a6: Already exists 
04305660f45e: Already exists 
bbe7020b5561: Already exists 
de6c4f15d75b: Already exists 
1c77cc70cc41: Pull complete 
aa5b8cbca568: Pull complete 
Digest: sha256:b46d81b153ec64321caaae7ab28da0e362ed7d720a7f0775ea8d1f7bef310d00
Status: Downloaded newer image for registry.cn-beijing.aliyuncs.com/mydlq/springboot:0.0.2

real    0m1.947s
user    0m0.017s
sys 0m0.042s

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

五、最后总结

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

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

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

(完)

推荐阅读:
面试官:什么是NIO?NIO的原理是什么机制?
两万字深度介绍分布式系统原理!【收藏了~】
2020年最新的常问企业面试题大全以及答案
好文!必须点赞
从零开始:教你将程序打包成 Docker 镜像 阅读详情

相关推荐

java集成Docker-java实现远程镜像、容器创建,服务发布

目前项目中需要实现水利机理模型的容器化部署,使用docker实现模型容器化部署操作,互相隔离,就是一个不错的方案。

weixin_38863607的博客 5005

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

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

qq_32641153的博客 3465

使用Maven构建项目Docker镜像

前言 在前面我们介绍了 Docker是什么?有什么优点?应用场景?_黄嚯嚯-优快云博客 Linux安装Docker步骤_黄嚯嚯-优快云博客 Docker配置使用阿里云加速镜像_黄嚯嚯-优快云博客 准备工作 创建一个基础项目工程 开始之前我们先准备一个Springboot的项目工程 , 就很普通就好 , 确保项目整正常启动即可! 配置 本次构建所使用到的插件是docker-maven-plugin , 该插件提供了两种不同的构建方式 , 两种方式外观差别体现在po...

黄嚯嚯 5559

springboot jar以及docker镜像太大问题解决方案

springboot jar docker

weixin_60748184的博客 3302

【Docker】Springboot项目打成docker镜像并生成容器

确保docker环境正常运行,不论是远程的还是本地的 确保springboot项目能正常运行 将springboot项目打包成jar包 编写Dockerfile文件 使用docker build命令生成镜像 使用docker run命令生成容器

喜欢分享些自己折腾的技术笔记 2385

【微服务】springboot 构建docker镜像多模式使用详解

springboot 构建镜像多种模式使用详解

congge 6010

docker java镜像_SpringBoot 项目构建 Docker 镜像调优实践

点击上方“Java知音”,选择“置顶公众号”技术文章第一时间送达!作者:超级小豆丁http://www.mydlq.club/article/16/PS:已经在生产实践中验证,解决在生产环境下,网速带宽小,每次推拉镜像影响线上服务问题,按本文方式构建镜像,除了第一次拉取、推送、构建镜像慢,第二、三…次都是几百K大小传输,速度非常快,构建、打包、推送几秒内完成。前言:以前的 SpringC...

weixin_39933026的博客 376

V2签名预装失败原因及解决方案

GOOGLE最新的GTS要求targetSdkVersion 大于30的APK必须使用V2及以上签名。 但实际在预装过程中,GTS测试会失败。 后面发现这是由于预装方式的原因造成的。原来的预装方式是使用APK,指定APK使用预签名的方式。Android.mk如下: LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := DKTest LOCAL_MODULE_CLASS := APPS LOCAL_MODULE_TAGS

zyfzhangyafei的专栏 6305

docker commit构建镜像的优缺点_SpringBoot 项目构建 Docker 镜像调优实践

点击上方“Java知音”,选择“置顶公众号”技术文章第一时间送达!作者:超级小豆丁http://www.mydlq.club/article/16/PS:已经在生产实践中验证,解决在生产环境下,网速带宽小,每次推拉镜像影响线上服务问题,按本文方式构建镜像,除了第一次拉取、推送、构建镜像慢,第二、三…次都是几百K大小传输,速度非常快,构建、打包、推送几秒内完成。前言:以前的 SpringC...

weixin_35765686的博客 328

SAP期初库存导入 (1)

LSMW

IT养家 7192

PID算法(位置式pid算法和增量式pid算法)

PID 算法是闭环控制系统中常用的算法,PID 分别是 Proportion(比例)、Integral(积分)、Differential(微分)的首字母缩写。它是一种结合比例、积分和微分三个环节于一体的闭环控。

m0_57678852的博客 2万+

docker拉取远程私库的镜像_SpringBoot 项目构建 Docker 镜像调优实践

作者:超级小豆丁来源:http://www.mydlq.club/article/16/PS:已经在生产实践中验证,解决在生产环境下,网速带宽小,每次推拉镜像影响线上服务问题,按本文方式构建镜像,除了第一次拉取、推送、构建镜像慢,第二、三…次都是几百K大小传输,速度非常快,构建、打包、推送几秒内完成。前言:以前的 SpringCloud 微服务时代以 “Jar包” 为服务的基础,每个服务都打成 J...

weixin_42131618的博客 519

【RF】Receiver Function接收函数:从理论到实践的深度解析

本文深入解析地震学中的接收函数(Receiver Function, RF)技术,从理论原理到实践操作。文章将RF比作地球的“听诊器”,详细阐述了其通过反褶积剥离震源影响、揭示地下结构(如莫霍面)的核心机制,并提供了使用Python和rf库进行数据处理、计算、动校正与叠加的完整实战流程,同时探讨了其应用、局限性与常见问题的解决方案。

Jerry的专栏 122

【docker系列】构建JDK及SpringBoot项目镜像

作为java程序员,通常情况下不会使用OpenJDK作为生产环境的JDK。然而Oracle官方是不提供标准镜像的,目前各个docker仓库中提供的JDK都是openjdk,如果希望使用oracle JDK需要我们自己来构建镜像。 我们本节就来说明一下如何自定义Oracle JDK镜像,并在此基础上构建Spring Boot项目镜像。

字母哥博客 2257

别再手动更新Excel了!用Jira看板管理你的个人项目(附保姆级配置流程)

本文详细介绍了如何利用Jira看板替代Excel进行个人项目管理,提供从注册到配置的保姆级教程。通过极简看板模式、自动化规则和数据分析功能,帮助用户实现任务可视化、协作高效化和提醒自动化,大幅提升个人工作效率。特别适合需要管理复杂个人项目的用户。

weixin_42699679的博客 229

DeepSeek赋能公文写作:从高效成文到精准传情的全维度指南

摘要:AI赋能公文写作的精准化实践 公文写作长期面临框架混乱、素材匮乏、表达不规范、修改低效等痛点。DeepSeek等AI工具通过"精准指令+分步赋能"模式实现突破:首先明确公文的目的性、逻辑性和规范性特征;其次采用包含场景、类型、目的等要素的指令模板;再分四步实施:框架搭建确保层次分明,内容填充解决素材问题,表达优化适配不同语体,合规校验把关格式规范。典型案例显示,AI能将数据罗列转化为"数据+成果+价值"的生动表述,使公文既规范又具感染力。这种AI赋能的写作方法显

通俗易懂学IT 233

Docker:SpringBoot项目创建Docker镜像并推送到阿里云容器镜像仓库

SpringBoot项目创建Docker镜像并推送到阿里云容器镜像仓库

扒鸡斯坦 2190

0基础学习diffusion_model扩散模型【易理解的公式推导】Denoising Diffusion Probabilistic Models

去噪扩散概率模型。本文是笔者在学习扩散模型时的一些笔记与心得,在公式推导过程中能够保证自己是一步一步去推导并且理解了的。概述是我认为比较重要的部分能够帮助理解公式的推导,具体的公式推导在每一小节中进行。1、扩散过程:如上图所示,扩散过程为从右到左X0→XTX0​→XT​的过程,表示对图片逐渐加噪,且Xt1X_{t+1}Xt1​是在XtX_{t}Xt​加躁得到的,其只受XtX_{t}Xt​的影响,因此扩散过程是一个马尔科夫过程。X0X_0。

zik的博客 2402

【Docker】Docker部署SpringBoot项目

使用Docker、DockerCompose部署SpringBoot、nginx、静态资源的代码示例。

qq_38924779的博客 9637

【DDR3 控制器设计】(1)MIG IP 核的详解与配置

2:1 比例的用户接口数据总线宽度是 MIG 内存接口宽度的 4 倍,比如 MIG 中配置的数据位宽是 16 位,而 DDR 双边采样,所以用户接口是 MIG 内存接口的 4 倍即数据位宽为 64 位,同理可以得到如果设置为 4:1 则是 8 倍的数据位宽即数据位宽为 128 位。这里选择不使用OFF。以下为 MIG 和 DDR 之间的连接框图,可以看到框图的左边是用户接口,中间是 MIG 核,右边是需要控制的 DDR 接口,用户只需要去配置使用 MIG 核,就可以对 DDR 进行控制读写等操作。

记录学习历程 5360

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

上一篇: Java 的 JSP 已经被淘汰了吗?
下一篇: Java实现文件批量导入导出实践(兼容xls,xlsx)
Java思维导图
博客等级 码龄10年 306粉丝 71原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值