优化Dockerfile最佳实践

原文: https://blog.csdn.net/xyz_dream/article/details/89741751





1.原文参考

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


2.总结

原文总结如下:

在这里插入图片描述

对于其中”编写.dockerignore文件”,”合理调整COPY与RUN的顺序”不太好理解,其他的都还挺好理解。 该条本质上是对dockerfile在build时利用缓存的原因。

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

    可以参考官方文档地址:

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


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

    2. 其实是巧妙的利用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(构建阶段名称)  .