原文: 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时利用缓存的原因。
可以参考官方文档地址:
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#understand-build-context
其实是巧妙的利用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(构建阶段名称) .