拋磚引玉
先說結論
- 以不變應萬變
- 一個相對固定的 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
全新構建
# 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
這次, 我們將第三層 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 02Removing 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 7ecbed95c4cdhello
Removing intermediate container 7ecbed95c4cd
---> a1c998781f2e
Step 3/4 : RUN echo "demo best practise 02"
---> Running in e90dae9440c2
demo best practise 02Removing 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 不同, 從根本上來說,他們是不同的鏡像。
總結
Dockerfile 以外的功夫
- 儘量不變的構建環境,保障 Dockerfile 在任意時間構建的時候,可以複用已生成的
緩存層
- 更好的組織架構規劃,同一組或同一技術棧的同事可以用相同的
FROM
基礎層, 以複用在生產場景中的基礎鏡像。
Dockerfile 以內的事項
- 需要保證, 在 Dockerfile 中,越上層的語句越穩定,越不容易變化。
- 在執行
ADD
和COPY
時, 儘量不要以文件夾
作爲對象,因爲文件夾中變化因子更多。儘可能的將變化頻發的文件放在最下層。 - 使用
.dockerignore
忽略不需要的文件,以保證context
的簡潔。