抛砖引玉
先说结论
- 以不变应万变
- 一个相对固定的 build 环境
- 善用 cache
- 构建 自己的基础镜像
2 . 精简为美
- 缓存清理
- multi stage build
- 使用 .dockerignore 保持 context 干净
- 容器镜像环境清理
你需要的了解的参考资料
- docker storage driver: https://docs.docker.com/storage/storagedriver/
- dockerfile best practices: https://docs.docker.com/develop/develop-images/dockerfile_best-practices/
- multi-stage: https://docs.docker.com/develop/develop-images/multistage-build/
为什么要优化镜像
- 一个小镜像有什么好处: 分发更快,存储更少,加载更快。
- 镜像臃肿带来了什么问题: 存储过多,分发更慢且浪费带宽更多。
镜像的构成

- 俯瞰镜像: 就是一个删减版的操作系统。
- 侧看镜像: 由一层层的 layer 堆叠而成
那么问题来了
- 是否层数少的镜像, 就是一个好镜像?
- 在企业应用中, 要怎么去规划和建设 CI中的镜像和构建 ?
- 带集群足够大, 节点足够多的时候, 要怎么快速分发这些镜像 ?
举个例子 docker build
-
Dockerfile v1
v1
FROM nginx:1.15-alpine
RUN echo "hello"
RUN echo "demo best practise"
ENTRYPOINT [ "/bin/sh" ]
-
Dockerfile v2
v2
FROM nginx:1.15-alpine
RUN echo "hello"
RUN echo "demo best practise 02"
ENTRYPOINT [ "/bin/sh" ]
-
1st build
全新构建
1# docker build -t demo:0.0.1 . Sending build context to Docker daemon 2.048kB 2Step 1/4 : FROM nginx:1.15-alpine 3 ---> 9a2868cac230 4Step 2/4 : RUN echo "hello" 5 ---> Running in d301b4b3ed55 6hello 7Removing intermediate container d301b4b3ed55 8 ---> 6dd2a7773bbc 9Step 3/4 : RUN echo "demo best practise" 10 ---> Running in e3084037668e 11demo best practise 12Removing intermediate container e3084037668e 13 ---> 4588ecf9837a 14Step 4/4 : ENTRYPOINT [ "/bin/sh" ] 15 ---> Running in d63f460347ff 16Removing intermediate container d63f460347ff 17 ---> 77b52d828f21 18Successfully built 77b52d828f21 19Successfully tagged demo:0.0.1
- 2nd build
Dockerfile 与 1st build 完全一致, 命令仅修改 build tag , 从 0.0.1 到 0.0.2
1# docker build -t demo:0.0.2 .Sending build context to Docker daemon 4.096kB 2Step 1/4 : FROM nginx:1.15-alpine 3 ---> 9a2868cac230 4Step 2/4 : RUN echo "hello" 5 ---> Using cache 6 ---> 6dd2a7773bbc 7Step 3/4 : RUN echo "demo best practise" 8 ---> Using cache 9 ---> 4588ecf9837a 10Step 4/4 : ENTRYPOINT [ "/bin/sh" ] 11 ---> Using cache 12 ---> 77b52d828f21 13Successfully built 77b52d828f21 14Successfully tagged demo:0.0.2
可以看到,
- 每层 layer 都使用 cache (---> Using cache) ,并未重新构建。
- 我们可以通过 docker image ls |grep demo 看到, demo:0.0.1 与 demo:0.0.2 的 layer hash 是相同。
所以从根本上来说, 这两个镜像就是同一个镜像,虽然都是 build 出来的。
3rd build
这次, 我们将第三层 RUN echo "demo best practise" 变更为 RUN echo "demo best practise 02"
1docker build -t demo:0.0.3 . 2Sending build context to Docker daemon 4.608kB 3Step 1/4 : FROM nginx:1.15-alpine 4 ---> 9a2868cac230 5Step 2/4 : RUN echo "hello" 6 ---> Using cache 7 ---> 6dd2a7773bbc 8Step 3/4 : RUN echo "demo best practise 02" 9 ---> Running in c55f94e217bd 10demo best practise 02Removing intermediate container c55f94e217bd 11 ---> 46992ea04f49 12Step 4/4 : ENTRYPOINT [ "/bin/sh" ] 13 ---> Running in f176830cf445 14Removing intermediate container f176830cf445 15 ---> 2e2043b7f3cb 16Successfully built 2e2043b7f3cb 17Successfully tagged demo:0.0.3
可以看到 ,
-
第二层仍然使用 cache
-
但是第三层已经生成了新的 hash 了
-
虽然第四层的操作没有变更,但是由于上层的镜像已经变化了,所以第四层本身也发生了变化。
注意: 每层在 build 的时候都是依赖于上册 ---> Running in f176830cf445。
4th build
第四次构建, 这次使用 --no-cache 不使用缓存, 模拟在另一台电脑上进行 build 。
1# docker build -t demo:0.0.4 --no-cache . Sending build context to Docker daemon 5.632kB 2Step 1/4 : FROM nginx:1.15-alpine 3 ---> 9a2868cac230 4Step 2/4 : RUN echo "hello" 5 ---> Running in 7ecbed95c4cdhello 6Removing intermediate container 7ecbed95c4cd 7 ---> a1c998781f2e 8Step 3/4 : RUN echo "demo best practise 02" 9 ---> Running in e90dae9440c2 10demo best practise 02Removing intermediate container e90dae9440c2 11 ---> 09bf3b4238b8 12Step 4/4 : ENTRYPOINT [ "/bin/sh" ] 13 ---> Running in 2ec19670cb14 14Removing intermediate container 2ec19670cb14 15 ---> 9a552fa08f73 16Successfully built 9a552fa08f73 17Successfully tagged demo:0.0.4
可以看到,
- 虽然和 3rd build 使用的 Dockerfile 相同, 但由于没有缓存,每一层都是重新 build 的。
- 虽然 demo:0.0.3 和 demo:0.0.4 在功能上是一致的。
但是 他们的 layer 不同, 从根本上来说,他们是不同的镜像。
总结
Dockerfile 以外的功夫
- 尽量不变的构建环境,保障 Dockerfile 在任意时间构建的时候,可以复用已生成的
缓存层 - 更好的组织架构规划,同一组或同一技术栈的同事可以用相同的
FROM基础层, 以复用在生产场景中的基础镜像。
Dockerfile 以内的事项
- 需要保证, 在 Dockerfile 中,越上层的语句越稳定,越不容易变化。
- 在执行
ADD和COPY时, 尽量不要以文件夹作为对象,因为文件夹中变化因子更多。尽可能的将变化频发的文件放在最下层。 - 使用
.dockerignore忽略不需要的文件,以保证context的简洁。