Docker 實作觀察: Layer Cache、Restart 行為、Image 選擇與資源限制
DevOps 學習筆記
Docker 基礎概念的實作觀察紀錄
把 Docker 的幾個核心行為實際跑過一遍, 包含 layer cache 命中邏輯、container restart 與 rebuild 的差異、image 大小比較, 以及 cgroup 資源限制的驗證方式
Legacy Builder vs BuildKit
現代 Docker 使用 BuildKit 作為 build engine, 舊版的 legacy builder 已被標記為 deprecated
|
|
重要: 兩個 builder 的 cache 是分開存放的, 互不共用; 從 legacy 切換到 BuildKit 後, 第一次 build 不會有任何 cache 可以用
BuildKit 的輸出格式比舊版更清楚, 每一步都會標示是否命中 cache:
|
|
Layer Cache 行為
為什麼 Dockerfile 的順序很重要
Docker 的 cache 機制是: 某一層的內容只要有變動, 那一層以下的所有層都會失效, 必須重新執行
這就是為什麼要把「不常變動的東西」放前面:
|
|
|
|
三個實驗的觀察結果
實驗一: 第一次 build
所有層都實際執行, 沒有任何 CACHED, build 時間最長
實驗二: 不改任何東西, 直接再 build
所有層都 CACHED, build 時間從數秒縮短到不到 1 秒, 這就是 cache 的價值
實驗三: 只改程式碼, 不動 go.mod
|
|
go mod download 命中 cache 代表套件沒有重新下載, 這是分開 COPY 的核心原因
Container Restart 行為
Restart 不等於 Rebuild
這是很容易誤解的一個概念, 實驗步驟:
|
|
結論: restart 只是把同一個 process 停掉再重新啟動, container 裡跑的 binary 還是 image build 當時編譯進去的版本
程式碼的修改要生效, 必須走完整的流程:
|
|
為什麼這樣設計?
Container 啟動的瞬間就凍結在那個 image 版本上了, Image 之後怎麼更新, 跑中的 container 都不受影響
這是刻意的設計, 讓 production 環境的更新是可控的, 不會在你不知情的情況下自動改變
Image 大小比較: scratch vs distroless
實際數字
|
|
差距約 7MB
distroless 的層級
|
|
注意: distroless/base 比 distroless/static 更大, 不是更小
怎麼選
關鍵問題是: 你的 server 會不會主動對外發 HTTPS 請求?
「對外發請求」的意思是你的 server 去打別人, 例如:
- 呼叫 Stripe API 收款
- 呼叫 SendGrid 發 email
- 呼叫 Slack 送通知
- 呼叫任何第三方服務
接收外部請求並回 response 不算, 那只是正常的 server 行為, 不需要 CA 憑證
| 情境 | 選擇 |
|---|---|
| server 只接收請求, 不對外呼叫 | scratch |
| server 需要對外發 HTTPS 請求 | distroless/static |
| 語言需要 runtime (Java, Python) | distroless/java / distroless/python |
Dockerfile 差別
|
|
只有 base image 不同, 其他完全一樣
cgroup 資源限制
設定限制
|
|
--cpus="0.5": 最多使用 0.5 顆 CPU core--memory="64m": 最多使用 64MB 記憶體
驗證限制有套上
方法一: docker inspect
|
|
NanoCpus 的換算:
|
|
這個數字直接對應 Linux kernel 的 cgroup 設定, 確認這個值就是確認限制真的有套上去
方法二: docker stats
|
|
|
|
CPU %: 目前實際使用的 CPU 百分比 (idle 時接近 0%)MEM USAGE / LIMIT: 目前用了多少記憶體 / 上限是多少MEM %: 記憶體使用率
--no-stream 的作用: docker stats 預設是持續刷新的畫面 (類似 top), 加上 --no-stream 只印一次快照然後退出
cgroup 的實際意義
就算 container 裡的程式跑瘋掉 (無窮迴圈、memory leak), 它也只能用到設定的上限, 不會影響 host 或其他 container
這是 Linux kernel 的 cgroup 機制在背後運作, Docker 只是把設定指令包裝成簡單的參數
port mapping 注意事項
docker run 時一定要確認 PORTS 欄位有值:
|
|
如果 PORTS 是空的, 代表 -p 8080:8080 沒有套上, 需要停掉重建
整體觀察總結
-
Layer Cache:
- Dockerfile 順序決定 cache 效率
- 不常變動的東西放前面 → 套件下載才能被 cache 住
-
Container Restart:
restart= 重啟同一個 process, binary 不變rebuild= 產生新 image, 程式碼改動才生效
-
Image 選擇:
- scratch → Go 靜態 binary, 最小, 不需對外發 HTTPS
- distroless → 需要 CA 憑證或 runtime
- alpine/ubuntu → 開發除錯用, 不上 production
-
cgroup:
--cpus/--memory設定上限docker inspect看 NanoCpus 確認設定docker stats看即時資源使用狀況
References
- Docker BuildKit documentation — BuildKit 的官方說明, 包含與 legacy builder 的差異
- Docker build caching — layer cache 機制的詳細說明
- Docker resource constraints —
--cpus,--memory等資源限制參數的完整說明 - GoogleContainerTools/distroless — 各種 distroless image 的選擇說明
- Linux man page — cgroups(7) — cgroup 的 kernel 層級文件