说在前面
最近,有小伙伴面试极兔时,遇到一个面试题:
如果优化 Dockerfile?
小伙伴没有回答好,只是提到了减少镜像层数。
一般来说,面试的小伙伴,大部分都会说
- 使用更小的基础镜像, 比如 alpine.
- 减少镜像层数, 比如 使用 && 符号将命令链接起来。
- 给基础镜像打上 安全补丁。
但这些,其实都是单点的优化。优化 Dockerfile 的核心是 合理分层、构建一个精良的基础镜像。
这里给大家做一下系统化、体系化的梳理,使得大家可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”。
也一并把这个题目以及参考答案,收入咱们的 《Java面试宝典》V46版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
注:本文以 PDF 持续更新,最新Java 架构笔记、面试题 的PDF文件.
为什么要优化镜像
首先,回到起点。为啥要优化 镜像?优化镜像的好处是:
- 一个小镜像有什么好处: 分发更快,存储更少,加载更快。
- 镜像臃肿带来了什么问题: 存储过多,分发更慢且浪费带宽更多。
镜像的构成
其次,来看看镜像的构成。从两个维度来看:
- 俯瞰镜像: 就是一个删减版的操作系统。
- 侧看镜像: 由一层层的 layer 堆叠而成
那么问题来了
应该如何优化镜像?
举个例子 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"
ENTRYPOINT [ "/bin/sh" ]
1st build
全新构建
# docker build -t demo:0.0.1 .
Sending build context to Docker daemon 2.048kB
Step 1/4 : FROM nginx:1.15-alpine
---> 9a2868cac230
Step 2/4 : RUN echo "hello"
---> Running in d301b4b3ed55
hello
Removing intermediate container d301b4b3ed55
---> 6dd2a7773bbc
Step 3/4 : RUN echo "demo best practise"
---> Running in e3084037668e
demo best practise
Removing intermediate container e3084037668e
---> 4588ecf9837a
Step 4/4 : ENTRYPOINT [ "/bin/sh" ]
---> Running in d63f460347ff
Removing intermediate container d63f460347ff
---> 77b52d828f21
Successfully built 77b52d828f21
Successfully tagged demo:0.0.1
2nd build
Dockerfile 与 1st build 完全一致, 命令仅修改 build tag , 从 0.0.1 到 0.0.2
# docker build -t demo:0.0.2 .
Sending build context to Docker daemon 4.096kB
Step 1/4 : FROM nginx:1.15-alpine
---> 9a2868cac230
Step 2/4 : RUN echo "hello"
---> Using cache
---> 6dd2a7773bbc
Step 3/4 : RUN echo "demo best practise"
---> Using cache
---> 4588ecf9837a
Step 4/4 : ENTRYPOINT [ "/bin/sh" ]
---> Using cache
---> 77b52d828f21
Successfully built 77b52d828f21
Successfully 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
这次, 我们将Dockerfile 02的 第三层 RUN echo "demo best practise" 变更为 RUN echo "demo best practise 02"
docker build -t demo:0.0.3 .
Sending build context to Docker daemon 4.608kB
Step 1/4 : FROM nginx:1.15-alpine
---> 9a2868cac230
Step 2/4 : RUN echo "hello"
---> Using cache
---> 6dd2a7773bbc
Step 3/4 : RUN echo "demo best practise 02"
---> Running in c55f94e217bd
demo best practise 02
Removing intermediate container c55f94e217bd
---> 46992ea04f49
Step 4/4 : ENTRYPOINT [ "/bin/sh" ]
---> Running in f176830cf445
Removing intermediate container f176830cf445
---> 2e2043b7f3cb
Successfully built 2e2043b7f3cb
Successfully tagged demo:0.0.3
可以看到 ,
- 第二层仍然使用 cache
- 但是第三层已经生成了新的 hash 了
- 虽然第四层的操作没有变更,但是由于上层的镜像已经变化了,所以第四层本身也发生了变化。
注意: 每层在 build 的时候都是依赖于上册 ---> Running in f176830cf445。
4th build
第四次构建, 这次使用 --no-cache 不使用缓存, 模拟在另一台电脑上进行 build 。
# docker build -t demo:0.0.4 --no-cache .
Sending build context to Docker daemon 5.632kB
Step 1/4 : FROM nginx:1.15-alpine
---> 9a2868cac230
Step 2/4 : RUN echo "hello"
---> Running in 7ecbed95c4cd
hello
Removing intermediate container 7ecbed95c4cd
---> a1c998781f2e
Step 3/4 : RUN echo "demo best practise 02"
---> Running in e90dae9440c2
demo best practise 02
Removing intermediate container e90dae9440c2
---> 09bf3b4238b8
Step 4/4 : ENTRYPOINT [ "/bin/sh" ]
---> Running in 2ec19670cb14
Removing intermediate container 2ec19670cb14
---> 9a552fa08f73
Successfully built 9a552fa08f73
Successfully tagged demo:0.0.4
可以看到,
- 虽然和 3rd build 使用的 Dockerfile 相同, 但由于没有缓存,每一层都是重新 build 的。
- 虽然 demo:0.0.3 和 demo:0.0.4 在功能上是一致的。但是 他们的 layer 不同, 从根本上来说,他们是不同的镜像。
结论
1. 合理分层、构建一个精良的基础镜像
- 一个相对固定的 build 环境
- 善用 cache
- 构建 自己的基础镜像:其中就包括了
- a. 安全补丁
b. 权限限制c. 基础库依赖安装d. 等...
2. 精简为美:一屋不扫何以扫天下
- 使用 .dockerignore 保持 context 干净
- 容器镜像环境清理
a. 缓存清理b. multi stage build
提示:以上答案,所包含的技术细节比较多,具体请参见《2023年Java面试宝典》最新版,需要的朋友点赞支持一下吧。