1.原文参考

地址: https://blog.fundebug.com/2017/05/15/write-excellent-dockerfile/

2.总结

原文总结如下:
在这里插入图片描述
对于其中"编写.dockerignore文件","合理调整COPY与RUN的顺序"不太好理解,其他的都还挺好理解。 该条本质上是对dockerfile在build时利用缓存的原因。

  1. 实验: 执行相同的docker build 一个是存在无效的大文件, 一个是不存在无效大文件时, 两者耗时巨大差距。 可以通过删除文件或者添加.dockerignore文件声明忽略文件,将那些不相干的文件排除,加速构建时间。在这里插入图片描述
    可以参考官方文档地址:

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context

  1. “合理调整COPY与RUN的顺序”’

其实是巧妙的利用docker build的缓存机制来实现。官方文档是这么描述构建缓存的:

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context

总结几句话: 1.每条指令只要前面的指令缓存失效, 则随后指令构建的镜像都不再使用缓存。 2.对应COPY和ADD文件会检验文件的校验和, 改变则缓存失效。

那么 我们可以总结一个规律, 对于 类似 COPY WORKDIR ENV LABEL 等命令,可以往后放,进行把变化频率小的往前放, 经常可能变化的命令往后放。 因为假设把经常变化的指令放在前面, 根据规定1, 缓存没有命中,则后面都要重新打镜像。

实验截图:
v1 Dockerfile (v1第一次构建应该时间最长 有下载wget.)

FROM alpine

RUN apk update  && apk add wget

CMD ["/bin/sh","-c","echo hello"]

在这里插入图片描述
V2 Dockerfile (v2把label放到run后面 run缓存命中。应该很快 )
在这里插入图片描述

FROM alpine

RUN apk update  && apk add wget

LABEL  name=v2     # 将label命令放在  run之后    此时前面的run 会命中缓存 节约时间,从这里这一行指令往下
#都不会使用缓存cache

CMD ["/bin/sh","-c","echo hello"]

v3 Dockerfile (v3把label放到run前面, label缓存没命中,所以根据规定, 从此往下所有命令的缓存无效。 会比v2慢)

FROM alpine

LABEL  name=v3   #  从这里开始 往后  所有缓存无效。  

RUN apk update  && apk add wget

CMD ["/bin/sh","-c","echo hello"]

实验截图:

在这里插入图片描述

由此可以知道了构建Dockerfile的优化过程还是很重要的。 docker入门的时候要求是,学会使用以及 编辑dockerfile即可。 但是进阶之后,重点要学会优化!!!! 上面只是很简单的demo,构建时间都能差几十倍。 如果是正式项目, 那可能就因为一个简单的label位置, build时间浪费很多。

3.Dockerfile多阶段构建

docker版本在17.04以后提出了称为"多阶段构建"模式。 直接白话进入重点:
1.dockerfile中可以使用多个FROM语句。 FROM语句 还可以加一个自己的别名,用来标明阶段工程。 例如 FROM ubuntu:16.04 as base-container
2.后阶段的镜像构建,可以使用COPY拷贝前面一个阶段镜像内生成的产物。 如文件, 可执行程序等等。
下面是demo演示:
假设场景, 对于一个Java 项目,你想使用一个"编译容器"去编译你的代码变成.class字节码文件,然后把代码从容器拷贝出来,然后再把这个字节码文件放到"生产环境"的一个容器中去运行."编译容器"镜像很大,而且没必要生成,因为你最终想要的产物只是.class文件罢了。最终产物是"生成环境"容器以及.class文件。

1.最原始以及还没出现"多阶段构建"的解决方案(编写一个shell脚本以及2个Dockerfile)
shell脚本大致工作内容如下:

1.  编写编译容器Dockerfile,  把源Java代码拷贝进容器,然后编译,生成在一个目录中。
假设目录路径 /home/java/code.class
2. docker build -t java:build .
3. 运行编译容器
4. docker cp  容器:/home/java/code.class  ./  #把编译好的产物从容器拷贝出来到宿主机上
5. 编写生产环境Dockerfile
6. 将code.class拷贝到生产环境Dockerfile中
7. docker build java:production .  #最终生成  目标镜像
8. rm  code.class #删除宿主机的文件
9. docker rmi java:build  #删除无用(中间状态的构建容器)
10.docker rm -f build容器 

2.Docker多阶段构建解决方案

FROM  java  as build  #1.构建阶段别名
COPY code.java /home/java
WORKDDIR  /home/java
RUN java -c code.java

FROM java as production #2.构建阶段别名
COPY --form=build /home/java/code.class /home/java/code.class  #重点!!! 直接从第一阶段拷贝产物文件
WORKDIR  /home/java
CMD ["java","code"]

#  相对1解决方案  清晰明了   不用bash脚本了

docker build -t java:production .   #直接指挥生成最后一个阶段构建的容器   

#假设想单独生成某个阶段容器
docker build -t java:build  --target=build(构建阶段名称)  .   

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐