Docker 從入門到實戰
關於本書
Docker 是個劃時代的開源專案,它徹底釋放了計算虛擬化的威力,極大提高了應用的維護效率,降低了雲端計算應用開發的成本!使用 Docker,可以讓應用的部署、測試和分發都變得前所未有的高效和輕鬆!
無論是應用開發者、運維人員、還是其他資訊科技從業人員,都有必要認識和掌握 Docker,節約有限的生命。
本書既適用於具備基礎 Linux 知識的 Docker 初學者,也希望可供理解原理和實現的高階使用者參考。同時,書中給出的實踐案例,可供在進行實際部署時借鑑。
內容特色
- 入門基礎:第 1 ~ 6 章為基礎內容,幫助深入理解 Docker 的基本概念 (映像檔、容器、倉庫) 和核心操作。
- 進階應用:第 7 ~ 11 章涵蓋 Dockerfile 指令詳解、資料與網路管理、Buildx、Compose 等高階配置和管理操作。
- 深入原理:第 12 ~ 17 章介紹其底層實現技術,深入探討容器編排體系 (Kubernetes、Etcd),並延伸涉及容器與雲端計算及其它關鍵生態專案 (Fedora CoreOS、Podman 等)。
- 實戰擴充套件:第 18 ~ 21 章重點討論容器安全防護機制、監控與日誌聚合系統 (Prometheus、ELK),並展示作業系統、CI/CD 自動化構建等典型實踐案例。
五分鐘快速上手
“5分鐘執行第一個容器”——跟隨以下步驟快速體驗 Docker:
- 安裝 Docker(第3章):根據作業系統完成 Docker 的安裝與驗證
- 第一個容器(第1章
1.1):快速體驗構建映像檔與啟動容器的完整流程 - 互動式容器(第5章):執行
docker run -it ubuntu bash,進入容器內部與系統互動 - 映像檔與倉庫(第2章、第4章、第6章):理解核心概念,並學會拉取、使用與管理映像檔和倉庫
- 自定義映像檔(第7章):學習如何編寫 Dockerfile 建立自己的映像檔
學習路線圖
graph LR
Start[Docker 學習入口] --> Ch1[第1章:Docker 簡介]
Ch1 --> Role1["運維新手<br/>第1-4章"]
Ch1 --> Role2["開發者<br/>第1-3章 → 第5-8章"]
Ch1 --> Role3["DevOps 工程師<br/>第1章 → 第9-14章 → 第18章"]
Ch1 --> Role4["架構師<br/>第1章 → 第15-21章"]
Role1 --> End1["掌握基本操作"]
Role2 --> End2["構建與部署應用"]
Role3 --> End3["自動化與運維"]
Role4 --> End4["設計容器方案"]
| 讀者角色 | 學習重點 | 核心成果 |
|---|---|---|
| 運維新手 | 第1-4章 | 掌握容器的基本概念與操作 |
| 開發者 | 第1-3章 → 第5-8章 | 學會容器化應用的構建與部署 |
| DevOps 工程師 | 第1章 → 第9-14章 → 第18章 | 實現容器編排與自動化部署流程 |
| 架構師 | 第1章 → 第15-21章 | 設計高可用、高效能的容器基礎設施 |
線上閱讀
本書線上閱讀,可直接訪問 GitBook。也可訪問 GitHub 倉庫目錄 或 映像檔站點。
下載離線版本
本書提供 PDF 版本供離線閱讀,可前往 GitHub Releases 頁面下載最新版本。
如需獲取預設分支自動更新的預覽版,可直接下載 docker_practice.pdf。該檔案會隨主線更新覆蓋,不代表正式釋出版本。
本地閱讀
先安裝 mdPress:
brew tap yeasy/tap && brew install mdpress
mdpress serve
或使用 Docker 映像檔一條命令啟動:
docker run -it --rm -p 4000:80 ccr.ccs.tencentyun.com/dockerpracticesig/docker_practice:vuepress
社群交流
- GitHub Discussions(技術問答、交流)
- GitHub Issues(內容錯誤、建議)
推薦閱讀
本書是技術叢書的一部分。以下書籍與本書形成互補:
| 書名 | 與本書的關係 |
|---|---|
| 《前線部署工程實踐指南》 | 講容器、Kubernetes、GitOps、IaC、供應鏈、安全、觀測與 SLO 如何組合成可交付、可審計、可運營的部署工程體系 |
| 《智慧體 Harness 工程指南》 | Agent 基礎設施中的容器化部署與隔離 |
| 《大模型安全權威指南》 | 容器安全與 AI 系統安全的交叉實踐 |
| 《區塊鏈技術指南》 | 區塊鏈節點的容器化部署 |
參與貢獻
歡迎參與專案維護。
進階學習
《Docker 技術入門與實戰》已更新到第 4 版,講解最新容器技術棧知識,歡迎大家閱讀並反饋建議。京東圖書 | 天貓圖書
支援鼓勵
歡迎鼓勵專案一杯 coffee~
Star History
許可證
本書採用 CC BY-NC-SA 4.0 許可證。
您可以自由分享和演繹,但需署名、非商業使用、相同方式共享。
修訂記錄
修訂記錄
- 1.9.2 2026-05-16
- 重新整理 Docker 生態說明,更新 Fedora CoreOS、Podman、containerd 等章節中的維護狀態
-
修復映像檔事實、演示 TLS 金鑰等後續審閱發現的問題
-
1.9.1 2026-05-08
- 使用
browser-actions/setup-chrome替代 Ubuntu runner 上不穩定的 Chromium snap 安裝 -
修正 Docker Engine 29 日期、TLS 協議、healthcheck 與互動式除錯示例
-
1.9.0 2026-05-02
- 更新 Docker Engine 29、nginx、MySQL 8.4 LTS、Node.js 22 LTS 與 Kubernetes 相關版本說明
- 補充 ipvlan、nftables、Gateway API、Docker Scout、映像檔安全與供應鏈安全內容
-
修復 Docker Hub 限流、etcdctl API、Compose healthcheck、Docker Debug 等時效性內容
-
1.8.0 2026-04-27
- 補全 Dockerfile 指令參考與多處章節編號、標題層級、程式碼塊和表格格式
-
增加預覽 PDF 自動釋出流程,修復 mdpress 埠和匯出相關配置
-
1.7.5 2026-04-05
-
將失效的 AtomHub 映像檔替換為可用映像檔源
-
1.7.4 2026-03-31
-
修復標題層級格式
-
1.7.3 2026-03-29
-
修復 Wikipedia URL 編碼
-
1.7.2 2026-03-28
- 修正 macOS、Windows、Compose 與 Kubernetes 章節中的時效性內容和錯誤前提
- 收縮越界網路內容,補充 bind mount、tmpfs 與埠對映的關鍵限制說明
- 統一 numbered section 的標題層級,清理正文末尾分散的參考資料小節
-
補充生成物忽略規則,避免
.mdpress與本地 HTML 匯出誤提交 -
1.7.1 2026-03-28
- 對齊附錄首頁與目錄結構,補全學習路線入口
- 重組資源連結頁,統一官方一手入口
-
完善附錄二導航頁,提升熱門映像檔查閱體驗
-
1.7.0 2026-03-25
- 精簡 CI 流程,移除遺留的 vuepress 構建,統一使用 mdpress
- 升級 etcd 叢集示例從 v3.4.0 到 v3.5.17
- 更新 npm 映像檔為 npmmirror.com,PHP 升級到 8.3
- 移除 Compose 已廢棄的 version 欄位
-
升級所有 CI Actions 到最新版本
-
1.6.1 2026-02-28
- 修正資料卷
--mount與-v的行為差異及資料卷管理說明 - 補充 Docker Hub 限流機制說明,區分 pull rate limit 與 abuse rate limit
- 完善安全許可權警告,強化使用者加入 docker 組等同於 root 的風險意識
-
增補 Docker Engine v29 containerd image store 與 BuildKit provenance attestations 預設行為說明
-
1.6.0 2026-02-20
- 全面統一使用
docker compose(V2) 為預設標準,提供 V1 遷移說明 - 修復全書大量排版錯誤,建立附錄與正文的雙向索引與引用
-
更新 Kubernetes 至 1.35 相容說明及執行時環境提示
-
1.5.4 2026-02-15
- 移除 combine.py
-
修復若干問題
-
1.5.3 2026-02-15
- 修復 CI 流程中的圖片引用路徑錯誤
- 修復 CODEOWNERS 檔案路徑匹配問題
-
更新專案配置版本號
-
1.5.0 2026-02-05
- 全面重構章節目錄結構 (01-15)
- 支援 Docker Engine v29.x
-
最佳化文件圖片引用路徑
-
1.4.0 2026-01-11
- 全面支援 Docker Engine v29 新版本
- 更新 Docker Compose 至 v2.40.x
- 更新 Kubernetes 相關章節至 1.35 版本
- BuildKit 已成為預設穩定構建器,移除實驗特性說明
- 新增 Docker Scout、Docker Init 相關內容
- 更新映像檔加速器配置
- 新增 CentOS EOL 警告,推薦使用 Rocky Linux/AlmaLinux
-
擴充安全章節和底層架構章節內容
-
1.3.0 2021-12-31
- 全面支援 Docker v20.10 新版本
- 新增 Docker Compose v2
-
Docker Hub 自動構建轉為付費功能
-
1.2.0 2020-12-20
-
錯誤修復
-
1.1.0 2019-12-31
- 全面支援 Docker v19.03 新版本
- 增加
BuildKit - 增加
docker buildx命令使用說明 - 增加
docker manifest命令使用說明 -
移除
Ubuntu 14.04Debian 8Debian 7 -
1.0.0: 2018-12-31
- 全面支援 Docker v18.x 新版本
- 新增如何除錯 Docker
-
錯誤修正
-
0.9.0: 2017-12-31
-
對 v1.13.x 舊版本的最後支援
-
0.9.0-rc2: 2017-12-10
-
增加 Docker 中文資源連結
- 增加介紹基於 Docker 的 CI/CD 工具
Drone - 增加
docker secret相關內容 - 增加
docker config相關內容 -
增加
LinuxKit相關內容 -
更新
CoreOS章節 -
更新
etcd章節,基於 3.x 版本 -
刪除
Docker Compose中的links指令 -
替換
docker daemon命令為dockerd - 替換
docker ps命令為docker container ls -
替換
docker images命令為docker image ls -
修改
安裝 Docker一節中部分文字表述 -
移除歷史遺留檔案和錯誤的檔案
- 最佳化文字排版
- 調整目錄結構
- 修復內容邏輯錯誤
-
修復
404連結 -
0.9.0-rc1: 2017-11-29
-
根據最新版本 (v17.09) 修訂內容
-
增加
Dockerfile多階段構建 (multistage builds)Docker 17.05新增特性 - 增加
docker exec子命令介紹 - 增加
docker管理子命令containerimagenetworkvolume介紹 - 增加
樹莓派單片電腦安裝 Docker -
增加 Docker 儲存驅動
OverlayFS相關內容 -
更新
Docker CEv17.x安裝說明 - 更新
Docker 網路一節 - 更新
Docker Machine基於 0.13.0 版本 -
更新
Docker Compose基於 3 檔案格式 -
刪除
Docker Swarm相關內容,替換為Swarm modeDocker 1.12.0新增特性 -
刪除
docker run--link引數 -
精簡
Docker Registry一節 -
替換
docker run-v引數為--mount -
修復
404連結 - 最佳化文字排版
-
增加離線閱讀功能
-
0.8.0: 2017-01-08
-
修正文字內容
- 根據最新版本 (1.12) 修訂安裝使用
-
補充附錄章節
-
0.7.0: 2016-06-12
-
根據最新版本進行命令調整
-
修正若干文字描述
-
0.6.0: 2015-12-24
-
補充 Machine 專案
-
修正若干 bug
-
0.5.0: 2015-06-29
-
新增 Compose 專案
- 新增 Machine 專案
- 新增 Swarm 專案
- 完善 Kubernetes 專案內容
-
新增 Mesos 專案內容
-
0.4.0: 2015-05-08
-
新增 Etcd 專案
- 新增 Fig 專案
- 新增 CoreOS 專案
-
新增 Kubernetes 專案
-
0.3.0: 2014-11-25
-
完成倉庫章節
- 重寫安全章節
- 修正底層實現章節的架構、名稱空間、控制組、檔案系統、容器格式等內容
- 新增對常見倉庫和映像檔的介紹
- 新增 Dockerfile 的介紹
- 重新校訂中英文混排格式
- 修訂文字表達
-
釋出繁體版本分支:zh-Hant
-
0.2.0: 2014-09-18
-
對照官方文件重寫介紹、基本概念、安裝、映像檔、容器、倉庫、資料管理、網路等章節
- 新增底層實現章節
- 新增命令查詢和資源連結章節
-
其它修正
-
0.1.0: 2014-09-05
-
新增基本內容
- 修正錯別字和表達不通順的地方
如何貢獻
如何貢獻
領取或建立新的 Issue,如 issue 235,新增自己為 Assignee。
在 GitHub 上 fork 到自己的倉庫,如 docker_user/docker_practice,然後 clone 到本地,並設定使用者資訊。
$ git clone [email protected]:docker_user/docker_practice.git
$ cd docker_practice
修改程式碼後提交,並推送到自己的倉庫,注意修改提交訊息為對應 Issue 號和描述。
# Update the content
$ git commit -a -s
# In commit msg dialog, add content like "Fix issue #235: describe ur change"
$ git push
在 GitHub 上提交 Pull Request,新增標籤,並邀請維護者進行 Review。
定期使用專案倉庫內容更新自己倉庫內容。
$ git remote add upstream https://github.com/yeasy/docker_practice
$ git fetch upstream
$ git rebase upstream/master
$ git push -f origin master
排版規範
本開源書籍遵循中文排版指南規範。
第一章 Docker 簡介
第一章 Docker 簡介
本章將帶領你進入 Docker 的世界。
版本提示:本書內容及示例基於 Docker Engine v29.x 及以上版本。值得注意的是,自 Docker Engine v29 起,官方在全新安裝情境下 預設啟用
containerd image store作為映像檔儲存後端(取代傳統 classic store 路徑下的 graph driver 體系)。這項底層革新極大增強了 Docker 對多架構映像檔(Multi-platform)以及軟體供應鏈安全後設資料(Attestations, SBOM, Provenance)的本地支援原生性。
本章內容
- 快速上手
-
透過一個簡單的 Web 應用例子,帶你快速體驗 Docker 的核心流程:構建映像檔、執行容器。
- 介紹 Docker 的起源、發展歷程以及其背後的核心技術 (Cgroups,Namespaces,UnionFS,以及
containerd引擎的演進)。 -
瞭解 Docker 是如何改變軟體交付方式的。
- 對比傳統虛擬機器技術,闡述 Docker 在啟動速度、資源利用率、交付效率等方面的巨大優勢。
- 探討 Docker 在 DevOps、微服務架構中的關鍵作用。
學習目標
透過本章的學習,你將能夠:
- 理解 Docker 的核心概念與架構。
- 明白 Docker 解決了現代軟體開發與運維中的哪些痛點。
- 建立起對容器技術的初步認知,為後續的實戰操作打下基礎。
好吧,讓我們帶著問題開始這神奇之旅。
1.1 快速上手
版本說明:本節示例基於 Docker v29.x 編寫。示例中使用的
nginx:alpine映像檔標籤為演示用途,請查閱 Docker Hub - nginx 確認最新可用版本。
本節將透過一個簡單的 Web 應用例子,帶你快速體驗 Docker 的核心流程:構建映像檔、執行容器。
為什麼選擇 Nginx + HTML 作為入門例子?
在學習 Docker 之前,我們先來理解為什麼這個例子是最適合初學者的。Docker 的核心價值在於一致性交付——無論你在本地、雲端還是他人的機器上執行容器,應用的行為都是完全一致的。這個 Nginx + 靜態 HTML 的例子之所以被廣泛採用,是因為它展現了 Docker 工作流的三個核心階段:
- 映像檔定義(Image Layer):透過 Dockerfile 描述如何把應用打包成一個自包含的單元
- 映像檔構建(Build):執行
docker build,Docker 根據 Dockerfile 逐層構建映像檔 - 容器執行(Runtime):透過
docker run啟動容器例項,應用真正開始提供服務
Nginx 是一個輕量級、使用廣泛的 Web 伺服器,學習完這個例子後,你可以輕鬆擴充套件到部署 Node.js、Python、Go 等任何語言的應用。
1.1.1 準備程式碼
建立一個名為 hello-docker 的資料夾,並在其中建立一個 index.html 檔案:
<h1>Hello, Docker!</h1>
1.1.2 編寫 Dockerfile
在同級目錄下建立一個名為 Dockerfile (無字尾) 的檔案:
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/index.html
1.1.3 構建映像檔
開啟終端,進入該目錄,執行構建命令:
$ docker build -t my-hello-world .
docker build:構建命令-t my-hello-world:給映像檔起個名字 (標籤).:指定上下文路徑為當前目錄
1.1.4 執行容器
使用剛才構建的映像檔啟動一個容器:
$ docker run -d -p 8080:80 my-hello-world
docker run:執行命令-d:後臺執行-p 8080:80:將宿主機的 8080 埠對映到容器的 80 埠
1.1.5 訪問測試
開啟瀏覽器訪問 http://localhost:8080,你應該能看到 “Hello, Docker!”。
1.1.6 清理
停止並刪除容器:
# 檢視正在執行的容器 ID
$ docker ps
# 停止容器
$ docker stop <CONTAINER_ID>
# 刪除容器
$ docker rm <CONTAINER_ID>
恭喜!你已經完成了第一次 Docker 實戰。接下來請閱讀 Docker 核心概念做深入瞭解。
1.2 什麼是 Docker
Docker 是徹底改變了軟體開發和交付方式的革命性技術。本節將從核心概念、與傳統虛擬機器的對比、技術基礎以及歷史生態等多個維度,帶你深入理解什麼是 Docker。
1.2.1 一句話理解 Docker
Docker 是一種輕量級的虛擬化技術,它讓應用程式及其依賴環境可以被打包成一個標準化的單元,在任何地方都能一致地執行。 如果用一個生活中的類比:Docker 之於軟體,就像集裝箱之於貨物。
在集裝箱發明之前,貨物的運輸是一件麻煩的事情——不同的貨物需要不同的包裝、不同的裝卸方式,換一種運輸工具就要重新裝卸。集裝箱的出現改變了這一切:無論裡面裝的是什麼,集裝箱的外形是標準的,可以用同樣的方式裝卸、堆放和運輸。
Docker 做的事情類似:無論你的應用是用 Python、Java、Node.js 還是其他語言寫的,無論它需要什麼樣的依賴庫和環境,一旦被打包成 Docker 映像檔,就可以用同樣的方式在任何支援 Docker 的機器上執行。
1.2.2 Docker 的核心價值
筆者認為,Docker 解決的是軟體開發中最古老的問題之一:“在我機器上明明能跑啊!”
flowchart LR
subgraph Dev ["開發環境"]
direction TB
A["Python 3.14<br/>Ubuntu 24.04<br/>特定版本的庫"] --> B["執行正常"]
end
subgraph Prod ["生產環境"]
direction TB
C["Python 3.11<br/>Ubuntu 22.04<br/>不同版本的庫"] --> D["執行失敗!"]
end
A -.->|不一致| C
有了 Docker:
flowchart LR
subgraph Dev ["開發環境"]
direction TB
A["Docker 映像檔<br/>(包含所有依賴)"] --> B["執行正常"]
end
subgraph Prod ["生產環境"]
direction TB
C["同一個映像檔<br/>(完全一致)"] --> D["執行正常!"]
end
A == 一致 ==> C
1.2.3 Docker vs 虛擬機器
很多人第一次接觸 Docker 時會問:“這不就是虛擬機器嗎?” 答案是:不是,而且差別很大。
傳統虛擬機器
傳統虛擬機器技術是虛擬出一套完整的硬體,在其上執行一個完整的作業系統,再在該系統上執行應用:

Docker 容器
而 Docker 容器內的應用直接執行於宿主的核心,容器內沒有自己的核心,也沒有進行硬體虛擬:

關鍵區別
| 特性 | Docker 容器 | 傳統虛擬機器 |
|---|---|---|
| 啟動速度 | 秒級 | 分鐘級 |
| 資源佔用 | MB 級別 | GB 級別 |
| 效能 | 接近原生 | 有明顯損耗 |
| 隔離級別 | 程序級隔離 | 完全隔離 |
| 單機數量 | 可執行上千個 | 通常幾十個 |
筆者經常用這個類比來解釋:虛擬機器像是每個應用都住在一棟獨立的房子裡 (有自己的地基、水電系統),而容器像是大家住在同一棟公寓樓裡的不同房間 (共享地基和水電系統,但各自獨立)。
1.2.4 Docker 的技術基礎
Docker 使用 Go 語言開發,基於 Linux 核心的以下技術:
如果你對這些底層技術感興趣,可以閱讀本書的底層實現章節。
Docker 架構演進
Docker 的底層實現經歷了多次演進:
flowchart LR
subgraph Timeline
direction LR
LXC["LXC<br/>(2013)"] --> libcontainer["libcontainer<br/>(2014)"]
libcontainer --> runC["runC<br/>(2015)"]
runC --> containerd["containerd + runC<br/>(現在)"]
runC --> OCI["OCI<br/>標準化"]
end
- LXC (2013):Docker 最初基於 Linux Containers
- libcontainer (2014,Docker 0.9):Docker 自研的容器執行時
- runC (2015,Docker 1.11 整合):捐獻給 OCI 的標準容器執行時
- containerd:高階容器執行時,管理容器生命週期

runc是一個 Linux 命令列工具,用於根據 OCI 容器執行時規範建立和執行容器。
containerd是一個守護程式,它管理容器生命週期,提供了在一個節點上執行容器和管理映像檔的最小功能集。
1.2.5 Docker 的歷史與生態
Docker 最初是 dotCloud 公司創始人 Solomon Hykes 在法國期間發起的一個公司內部專案,於 2013 年 3 月以 Apache 2.0 授權協議開源。
Docker 的發展歷程:
- 2013 年 3 月:開源釋出
- 2013 年底:dotCloud 公司改名為 Docker,Inc。
- 2015 年:成立開放容器聯盟 (OCI),推動容器標準化
- 至今:GitHub 專案超過 7 萬星標
Docker 的成功推動了整個容器生態的發展,催生了 Kubernetes、Podman 等眾多相關專案。筆者認為,Docker 最大的貢獻不僅是技術本身,更是它 讓容器技術從系統管理員的工具變成了每個開發者都能使用的標準工具。
1.3 為什麼要用 Docker
在回答 “為什麼用 Docker” 之前,筆者想先問一個問題:你有沒有經歷過這些情境?
1.3.1 沒有 Docker 的世界
在 Docker 出現之前,軟體開發和運維面臨著諸多棘手的問題。我們先來看看以下三個典型的痛點情境。
情境一:“在我電腦上明明能跑”
週五下午 5:00
├── 開發者:程式碼寫完了,本地測試透過,提交!🎉
├── 週一早上 9:00
│ └── 測試:"這個功能在測試環境跑不起來"
└── 開發者:"不可能,在我電腦上明明能跑啊……"
筆者統計過,這個問題通常由以下原因導致:
- Python/Node/Java 版本不一致
- 依賴庫版本不一致
- 作業系統配置不一致
- 某些環境變數沒有設定
- “哦,忘了說我本地裝了個 XXX”
情境二:環境配置的噩夢
新同事入職
├── Day 1:領電腦,配環境
├── Day 2:繼續配環境,遇到問題
├── Day 3:換種方法配環境
├── Day 4:問老同事怎麼配的,他也忘了
└── Day 5:終於能跑起來了!但不知道為什麼……
情境三:伺服器遷移的恐懼
運維:"我們需要把服務遷移到新伺服器"
開發:"舊伺服器上的配置文件在哪?"
運維:"當時是一個已經離職的同事配的……"
所有人:😱
1.3.2 Docker 如何解決這些問題
Docker 的出現為上述問題提供了完美的解決方案。它透過 “一次構建,到處執行” 的核心理念,從根本上改變了軟體交付的方式。
核心理念:一次構建,到處執行
flowchart LR
dev["開發團隊"] -->|建立| img["Docker 映像檔"]
img -->|測試團隊驗證| test["測試團隊"]
test -- "有問題<br/>反饋修改和更新" --> dev
test -- "沒問題<br/>釋出" --> prod["生產環境"]
1.3.3 Docker 的核心優勢
除了解決上述痛點,Docker 還擁有諸多顯著的技術優勢,包括環境一致性、秒級啟動、高效的資源利用等。
1. 環境一致性
Docker 映像檔包含了應用執行所需的 一切:程式碼、執行時、系統工具、庫、配置。這意味著:
- ✅ 開發環境和生產環境完全一致
- ✅ 不會再有 “在我機器上能跑” 的問題
- ✅ 新人入職,一條命令就能啟動開發環境
## 新同事入職第一天
$ git clone https://github.com/company/project.git
$ docker compose up
## 完整的開發環境就準備好了
...
2. 秒級啟動
傳統虛擬機器啟動需要幾分鐘 (引導作業系統),而 Docker 容器啟動通常只需要 幾秒甚至幾百毫秒。
筆者實測資料:
| 啟動內容 | 虛擬機器 | Docker 容器 |
|---|---|---|
| 空系統 | ~60 秒 | ~0.5 秒 |
| MySQL | ~90 秒 | ~3 秒 |
| 完整 Web 應用 | ~120 秒 | ~5 秒 |
這個差異對以下情境尤為重要:
- CI/CD 流水線:每次構建節省幾分鐘,一天累積下來就是幾小時
- 彈性擴容:流量高峰時能快速啟動更多例項
- 開發體驗:快速重啟服務進行除錯
3. 資源效率
Docker 容器共享宿主機核心,無需為每個應用執行完整的作業系統。以一臺 64GB 記憶體的物理伺服器為例:
- 傳統虛擬機器方案:每個虛擬機器都需要執行完整的作業系統(每個額外佔用如 2GB 記憶體),產生大量資源開銷,實際可用於應用的記憶體可能只有約 18GB。
- Docker 方案:容器直接共享宿主機系統,只需付出很少的基礎開銷(OS 及引擎約 4GB),即可將約 60GB 的記憶體全部用於實際應用。
flowchart TD
subgraph VM ["傳統虛擬機器方案 ❌"]
direction TB
Server1["物理伺服器 (64GB 記憶體)"]
subgraph VMs ["可用應用記憶體: 約 18GB"]
direction LR
VM1["VM 1: 應用 1<br/>(含 2GB OS)"]
VM2["VM 2: 應用 2<br/>(含 2GB OS)"]
VM3["VM 3: 應用 3<br/>(含 2GB OS)"]
end
Server1 --- VMs
end
subgraph Docker ["Docker 方案 ✅"]
direction TB
Server2["物理伺服器 (64GB 記憶體)<br/>含約 4GB OS及引擎配置"]
subgraph Containers ["可用應用記憶體: 約 60GB"]
direction LR
C1["容器 1: 應用 1<br/>(按需分配)"]
C2["容器 2: 應用 2<br/>(按需分配)"]
C3["容器 3: 應用 3<br/>(按需分配)"]
end
Server2 --- Containers
end
4. 持續交付和部署
Docker 完美契合 DevOps 的工作流程:
flowchart LR
A["程式碼提交<br/>(Git push)"] --> B["自動構建映像檔<br/>(docker build)"]
B --> C["自動測試<br/>(容器內執行測試)"]
C --> D["自動部署<br/>(容器滾動更新)"]
使用 Dockerfile 定義映像檔構建過程,使得:
- 構建過程 可重複、可追溯
- 任何人都能從程式碼重建完全相同的映像檔
- 配合 GitHub Actions 等 CI 系統實現自動化
5. 輕鬆遷移
Docker 可以在幾乎任何平臺上執行:
- ✅ 本地開發機 (macOS、Windows、Linux)
- ✅ 公有云 (AWS、Azure、GCP、阿里雲、騰訊雲)
- ✅ 私有云和自建資料中心
- ✅ 邊緣裝置和 IoT
同一個映像檔,在任何地方執行結果都一致。 這讓應用遷移變得前所未有的簡單。
6. 微服務架構的基石
現代微服務架構幾乎都依賴容器技術。Docker 讓你可以:
- 隔離服務:每個服務執行在獨立容器中,互不干擾
- 獨立擴充套件:哪個服務負載高,就單獨擴充套件哪個
- 獨立部署:更新一個服務不影響其他服務
- 技術多樣:不同服務可以用不同語言和框架
flowchart TD
subgraph Microservices ["微服務架構示例"]
direction TB
subgraph AppLayer ["應用層"]
direction LR
Frontend["前端容器<br/>(Node.js)"]
API["API 容器<br/>(Python)"]
Worker["Worker 容器<br/>(Go)"]
end
Redis["Redis 容器"]
DB["PostgreSQL 容器"]
Frontend --> API
API --> Redis
API --> DB
Worker --> Redis
Worker --> DB
end
1.3.4 Docker 不適合的情境
筆者認為,技術選型要客觀。Docker 並非銀彈,以下情境可能不太適合:
- 需要完全隔離的情境:容器共享宿主機核心,隔離性不如虛擬機器。如果需要執行不受信任的程式碼,虛擬機器可能更安全。
- 需要特殊核心的情境:容器使用宿主機核心。如果應用需要特定版本的核心或核心模組,可能需要虛擬機器。
- Windows 原生應用:雖然 Docker 支援 Windows 容器,但生態不如 Linux 容器成熟。傳統 Windows 應用的容器化仍有挑戰。
- 桌面應用:Docker 主要面向伺服器端應用。桌面 GUI 應用的容器化雖然可行,但通常得不償失。
1.3.5 與傳統虛擬機器的對比總結
關於容器與虛擬機器的詳細特性對比,請參閱 1.2.3 Docker vs 虛擬機器 中的對比表。總結來說:
- 效能差異:虛擬機器通常有 5-20% 的效能損耗,而容器接近原生效能。
- 最佳情境:Docker 容器適合微服務、CI/CD、開發環境;虛擬機器適合多租戶、高安全需求情境。
本章小結
- Docker 是一種輕量級虛擬化技術,核心價值是 環境一致性
- 與虛擬機器相比,Docker 更輕量、更快速、資源利用率更高
- Docker 基於 Linux 核心的 Namespace、Cgroups 和 Union FS 技術
- Docker 推動了容器技術的標準化 (OCI) 和生態發展
Docker 的核心價值可以用一句話概括:讓應用的開發、測試、部署保持一致,同時極大提高資源利用效率。 筆者認為,對於現代軟體開發者來說,Docker 已經不是 “要不要學” 的問題,而是 必備技能。無論你是前端、後端、運維還是全棧開發者,掌握 Docker 都能讓你的工作更高效。
第二章 基本概念
第二章 基本概念
版本說明:本章內容及示例基於 Docker v29.x 編寫。映像檔標籤相關示例請查閱官方文件以確認最新可用版本。
Docker 包括三個基本概念:
- 映像檔 (
Image):Docker 映像檔是一個特殊的檔案系統,除了提供容器執行時所需的程式、庫、資源、配置等檔案外,還包含了一些為執行時準備的一些配置引數 (如匿名卷、環境變數、使用者等)。映像檔不包含任何動態資料,其內容在構建之後也不會被改變。 - 容器 (
Container):映像檔 (Image) 和容器 (Container) 的關係,就像是物件導向程式設計中的類和例項一樣,映像檔是靜態的定義,容器是映像檔執行時的實體。容器可以被建立、啟動、停止、刪除、暫停等。 - 倉庫 (
Repository):映像檔構建完成後,可以很容易的在當前宿主機上執行,但是,如果需要在其它伺服器上使用這個映像檔,我們就需要一個集中的儲存、分發映像檔的服務,Docker Registry 就是這樣的服務。
理解了這三個概念,就理解了 Docker 的整個生命週期。
2.1 映像檔
版本說明:本節示例基於 Docker v29.x 編寫。示例中使用的映像檔標籤(如
ubuntu:24.04、nginx:latest)為演示用途,建議查閱 Docker Hub 或相應映像檔的官方頁面確認最新可用版本。
Docker 映像檔作為容器執行的基石,其設計理念和實現機制至關重要。本節將深入探討映像檔的本質、與作業系統的關係、內容構成以及核心的分層儲存機制。
2.1.1 一句話理解映像檔
Docker 映像檔是一個只讀的模板,包含了執行應用所需的一切:程式碼、執行時、庫、環境變數和配置檔案。 如果用一個類比:映像檔就像是一張光碟或 ISO 檔案。你可以用同一張光碟在不同電腦上安裝系統,而光碟本身不會被修改。同樣,一個映像檔可以建立多個容器,而映像檔本身保持不變。
2.1.2 映像檔與作業系統的關係
我們都知道,作業系統分為 核心 和 使用者空間:
flowchart TD
subgraph UserSpace ["使用者空間"]
direction TB
App["應用程式、工具、庫、配置檔案...<br/>(這部分被打包成 Docker 映像檔)"]
end
subgraph KernelSpace ["Linux 核心"]
direction TB
Kernel["容器共享宿主機的核心"]
end
UserSpace --- KernelSpace
對於 Linux 而言,核心啟動後會掛載 root 檔案系統來提供使用者空間支援。Docker 映像檔 本質上就是一個 root 檔案系統。
例如,官方映像檔 ubuntu:24.04 包含了一套完整的 Ubuntu 24.04 最小系統的 root 檔案系統——但 不包含 Linux 核心 (因為容器共享宿主機的核心)。
2.1.3 映像檔包含什麼?
Docker 映像檔是一個特殊的檔案系統,包含:
| 內容型別 | 示例 |
|---|---|
| 程式檔案 | 應用二進位制檔案、Python/Node 直譯器 |
| 庫檔案 | libc、OpenSSL、各種依賴庫 |
| 配置檔案 | nginx.conf、my.cnf 等 |
| 環境變數 | PATH、LANG 等預設值 |
| 後設資料 | 啟動命令、暴露埠、資料卷定義 |
關鍵特性:
- ✅ 映像檔是 只讀 的
- ✅ 映像檔 不包含 動態資料
- ✅ 映像檔構建後 內容不會改變
2.1.4 分層儲存:映像檔的核心設計
映像檔的分層儲存機制是 Docker 最具創新性的特性之一。透過 Union FS 技術,Docker 能夠高效地構建和管理映像檔。
為什麼需要分層?
筆者認為,分層儲存是 Docker 最巧妙的設計之一。
假設你有三個應用,都基於 Ubuntu 執行:
flowchart TD
subgraph Traditional ["傳統方式(不分層)總計: 1.5GB ❌"]
direction LR
AppA_Trad["App A<br/>Ubuntu 500MB"]
AppB_Trad["App B<br/>Ubuntu 500MB"]
AppC_Trad["App C<br/>Ubuntu 500MB"]
end
subgraph DockerLayered ["Docker 分層方式 總計: 620MB ✅"]
direction TB
subgraph Apps ["應用層"]
direction LR
AppA["App A 50MB"]
AppB["App B 30MB"]
AppC["App C 40MB"]
end
Ubuntu["Ubuntu<br/>(共享)500MB"]
AppA --> Ubuntu
AppB --> Ubuntu
AppC --> Ubuntu
end
分層是如何工作的?
筆者用一個更貼近最佳實務的 Dockerfile 來解釋分層:
FROM ubuntu:24.04 # 第 1 層:基礎系統(約 78MB)
RUN apt-get update && apt-get install -y nginx # 第 2 層:更新索引並安裝 nginx
COPY app.conf /etc/nginx/ # 第 3 層:複製配置檔案
這裡特意把 apt-get update 和 apt-get install 放在同一個 RUN 裡,避免構建快取導致包索引過期。
構建後的映像檔結構:
flowchart TD
Layer3["第 3 層: COPY app.conf (只讀)<br/>← 最新新增的層"]
Layer2["第 2 層: nginx 安裝檔案與包索引 (只讀)"]
Layer1["第 1 層: Ubuntu 基礎系統 (只讀)<br/>← 基礎映像檔層"]
Layer3 --> Layer2 --> Layer1
每一層的特點:
- 只讀:構建完成後不可修改
- 可共享:多個映像檔可以共享相同的層
- 有快取:未變化的層不會重新構建
分層儲存的 “陷阱”
⚠️ 筆者特別提醒:理解這一點可以幫你避免構建出臃腫的映像檔。關鍵原理:每一層的檔案變化會被記錄,但 刪除操作只是標記,不會真正減小映像檔體積。
## 錯誤示範 ❌
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y build-essential # 安裝編譯工具(約 200MB)
RUN make && make install # 編譯應用
RUN apt-get remove build-essential # 試圖刪除編譯工具
## 結果:映像檔仍然包含 200MB 的編譯工具!
## 正確做法 ✅
FROM ubuntu:24.04
RUN apt-get update && \
apt-get install -y build-essential && \
make && make install && \
apt-get remove -y build-essential && \
apt-get autoremove -y && \
rm -rf /var/lib/apt/lists/*
## 在同一層完成安裝、使用、清理
2.1.5 檢視映像檔的構建歷史
## 檢視映像檔的歷史(每層的構建記錄)
$ docker history nginx:latest
IMAGE CREATED CREATED BY SIZE
a6bd71f48f68 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
...
需要注意:docker history 展示的是映像檔的構建歷史。像 CMD、ENTRYPOINT、EXPOSE、STOPSIGNAL 這類 0B 項主要是映像檔配置後設資料,並不等同於新增了可見的檔案系統內容;真正佔用空間的通常是 RUN、COPY、ADD 等帶來的檔案變化。
2.1.6 映像檔的標識
Docker 映像檔有多種標識方式:
1. 映像檔名稱和標籤
格式:[倉庫地址/]倉庫名[:標籤]
## 完整格式
registry.example.com/myproject/myapp:v1.2.3
## 簡寫(使用 Docker Hub)
nginx:1.28
ubuntu:24.04
## 省略標籤(預設使用 latest)
nginx # 等同於 nginx:latest
2. 映像檔 ID:Content-Addressable 標識
每個映像檔有一個基於內容計算的唯一 ID:
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx latest a6bd71f48f68 2 weeks ago 187MB
ubuntu 24.04 ca2b0f26964c 3 weeks ago 78.1MB
3. 映像檔摘要
更精確的標識,基於映像檔內容的 SHA256 雜湊:
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68
💡 筆者建議:在生產環境使用映像檔摘要而非標籤,因為標籤可以被覆蓋,但摘要是不可變的。
2.1.7 映像檔的來源
Docker 映像檔可以透過以下方式獲取:
| 方式 | 說明 | 示例 |
|---|---|---|
| 從 Registry 拉取 | 最常用的方式 | docker pull nginx |
| 從 Dockerfile 構建 | 自定義映像檔 | docker build -t myapp . |
| 從容器提交 | 儲存容器狀態 (不推薦) | docker commit |
| 從檔案匯入 | 離線傳輸 | docker load < image.tar |
2.2 容器
容器是 Docker 技術的核心,是應用實際執行的載體。本節將從容器的本質、與虛擬機器的區別、儲存層機制以及生命週期管理等方面,全面解析 Docker 容器。
2.2.1 一句話理解容器
容器是映像檔的執行例項。如果把映像檔比作程式,那麼容器就是程序。 用物件導向程式設計的術語來說:映像檔是類 (Class),容器是物件 (Instance)。
- 一個映像檔可以建立多個容器
- 每個容器相互獨立,互不影響
- 容器可以被建立、啟動、停止、刪除、暫停
2.2.2 容器的本質
💡 筆者認為,理解這一點是理解 Docker 的關鍵:容器的本質是一個特殊的程序。
flowchart TD
subgraph NormalProcess ["普通程序"]
direction TB
N1["• 共享系統資源<br/>• 共享網路<br/>• 共享檔案系統"]
end
subgraph ContainerProcess ["容器程序 (執行在宿主機核心上)"]
direction TB
C1["• 獨立程序空間<br/>• 獨立網路環境<br/>• 獨立檔案系統<br/>• 獨立使用者空間"]
end
這種隔離主要透過 Linux 核心的 Namespace 實現,資源限制通常與 cgroups 配合。具體表現為:
- 程序空間:容器看不到宿主機上的其他程序。
- 網路:在預設網路模式下,容器通常擁有獨立的網路名稱空間,並可分配獨立 IP;使用
host或container:等模式時則例外。 - 檔案系統:容器擁有獨立的 root 目錄。
- 使用者:預設情況下,容器內的
root仍是uid 0,但通常只擁有受限 capabilities;如果啟用userns-remap或 rootless 等機制,還會進一步對映為宿主機上的低許可權使用者。
2.2.3 容器 vs 虛擬機器:核心區別
很多初學者會混淆容器和虛擬機器。筆者用一張圖來說明:
flowchart TD
subgraph VM ["虛擬機器 (每個 VM 執行完整 OS)"]
direction TB
subgraph VMApp ["應用層"]
VA[App A] & VB[App B]
end
subgraph VMGuest ["Guest OS (完整系統)"]
G1[Guest OS] & G2[Guest OS]
end
V[Hypervisor]
VMH[Host OS]
VMHW[Hardware]
VMApp --> VMGuest --> V --> VMH --> VMHW
end
subgraph Container ["容器 (所有容器共享宿主機核心)"]
direction TB
subgraph CApp ["應用層"]
CA[App A] & CB[App B]
end
subgraph CContainer ["隔離層"]
CC1[Container 僅應用] & CC2[Container 僅應用]
end
CE[Docker Engine]
CH[Host OS]
CHW[Hardware]
CApp --> CContainer --> CE --> CH --> CHW
end
| 特性 | 容器 | 虛擬機器 |
|---|---|---|
| 隔離級別 | 作業系統級 (Namespaces/cgroups) | 硬體虛擬化級 (Hypervisor) |
| 啟動時間 | 通常更快 | 通常更慢 |
| 資源佔用 | 通常更低 | 通常更高 |
| 執行開銷 | 通常更接近原生 | 通常有更高虛擬化開銷 |
| 核心 | 共享宿主機核心 | 各自獨立核心 |
2.2.4 容器的儲存層
理解容器的儲存層機制對於資料的持久化和映像檔的最佳化至關重要。本節將介紹容器的可寫層以及 Copy-on-Write 機制。
映像檔層 + 容器層
當容器執行時,Docker 會在映像檔的只讀層之上建立一個 可寫層 (容器儲存層):
flowchart TD
ContainerLayer["容器儲存層(可讀寫)<br/>容器執行時建立,記錄檔案變化"]
ImageLayerN["映像檔第 N 層(只讀)"]
ImageLayerN1["映像檔第 N-1 層(只讀)"]
Dots["..."]
ImageLayer1["映像檔第 1 層(只讀)<br/>基礎映像檔層"]
ContainerLayer --> ImageLayerN --> ImageLayerN1 --> Dots --> ImageLayer1
Copy-on-Write:寫時複製
當容器需要修改映像檔層中的檔案時:
- Docker 將該檔案 複製 到容器儲存層
- 在容器層中進行修改
- 原始映像檔層保持不變
讀取檔案:直接從映像檔層讀取(共享,高效)
修改檔案:複製到容器層,然後修改(只有這個容器能看到修改)
⚠️ 容器儲存層的生命週期
筆者特別強調:這是新手最容易踩的坑!容器儲存層與容器生命週期繫結。容器刪除,資料就沒了!
## 建立容器,寫入資料
$ docker run -it ubuntu bash
root@abc123:/# echo "important data" > /data.txt
root@abc123:/# exit
## 刪除容器
$ docker rm abc123
## 資料丟了!沒有任何辦法恢復!
正確的資料持久化方式
按照 Docker 最佳實務,容器儲存層應該保持 無狀態。需要持久化的資料應該使用:
| 方式 | 說明 | 適用情境 |
|---|---|---|
| 資料卷 (Volume) | Docker 管理的儲存 | 資料庫、應用資料 |
| 繫結掛載 (Bind Mount) | 掛載宿主機目錄 | 開發時共享程式碼 |
## 使用資料卷(推薦)
$ docker run -v mydata:/var/lib/mysql mysql
## 使用繫結掛載
$ docker run -v /host/path:/container/path nginx
這些位置的讀寫 會跳過容器儲存層,直接寫入宿主機,效能更好,也不會隨容器刪除而丟失。
2.2.5 容器的生命週期
掌握容器的生命週期對於管理和除錯 Docker 應用非常重要。如圖 2-1 所示,這裡先聚焦最常見的建立、執行、暫停、停止和刪除流轉;Docker CLI 中還可能看到 restarting、removing、dead 等狀態。
stateDiagram-v2
direction TB
[*] --> Created : docker create
Created --> Running : docker start
Running --> Stopped : docker stop
Running --> Paused : docker pause
Paused --> Running : docker unpause
Created --> Deleted : docker rm
Stopped --> Deleted : docker rm
Paused --> Deleted : docker rm
Deleted --> [*]
圖 2-1:容器生命週期狀態流轉圖
常用生命週期命令如下:
## 建立並啟動容器(最常用)
$ docker run nginx
## 分步操作
$ docker create nginx # 建立容器(不啟動)
$ docker start abc123 # 啟動容器
## 停止容器
$ docker stop abc123 # 優雅停止(傳送 SIGTERM,等待後傳送 SIGKILL)
$ docker kill abc123 # 強制停止(直接傳送 SIGKILL)
## 暫停/恢復(不常用,但有時有用)
$ docker pause abc123 # 暫停容器內所有程序
$ docker unpause abc123 # 恢復
## 刪除容器
$ docker rm abc123 # 刪除已停止的容器
$ docker rm -f abc123 # 強制刪除執行中的容器
2.2.6 容器與程序的關係
核心概念:容器的生命週期 = 主程序 (PID 1) 的生命週期
## 主程序執行,容器執行
## 主程序退出,容器停止
這就是為什麼:
## 這個容器會立即退出(bash 沒有輸入就退出了)
$ docker run ubuntu
## 這個容器會持續執行(官方 nginx 映像檔讓 nginx 在前臺執行)
$ docker run nginx
官方 nginx 映像檔預設使用 nginx -g 'daemon off;',讓主程序保持在前臺執行,這樣容器才會持續處於執行狀態。
詳細解釋請參考後臺執行章節。
2.2.7 容器的隔離性
Docker 容器透過以下 Namespace 實現隔離:
| Namespace | 隔離內容 | 效果 |
|---|---|---|
| PID | 程序 ID | 容器內 PID 1 是應用程序,看不到宿主機其他程序 |
| NET | 網路 | 獨立的網路棧、IP 地址、埠 |
| MNT | 檔案系統 | 獨立的根目錄和掛載點 |
| UTS | 主機名 | 獨立的主機名和域名 |
| IPC | 程序間通訊 | 獨立的訊號量、訊息佇列 |
| USER | 使用者 | 獨立的使用者和組 ID |
想深入瞭解?請閱讀底層實現 - 名稱空間。
2.3 倉庫
版本說明:本節示例基於 Docker v29.x 和常見映像檔版本編寫。示例中的版本號(如
nginx:1.28、mysql:8.4、mysql:5.7等)為演示用途。實際使用時請訪問 Docker Hub 官方頁面 或相應映像檔的釋出頁確認最新可用版本和標籤。
Docker Registry 是映像檔分發和管理的核心元件。本節將介紹 Registry 的基本概念、公共和私有服務的選擇,以及映像檔的安全管理。
2.3.1 一句話理解 Registry
Docker Registry 是儲存和分發 Docker 映像檔的服務,類似於程式碼的 GitHub 或包管理的 npm。
映像檔構建完成後,可以在當前機器上執行。但如果需要在其他伺服器上使用這個映像檔,就需要一個集中的儲存和分發服務——這就是 Docker Registry。
2.3.2 核心概念
要熟練使用 Docker Registry,首先需要理清它與倉庫 (Repository)、標籤 (Tag) 之間的關係。
Registry、倉庫、標籤的關係
Docker Registry 中可以包含多個 Repository,每個 Repository 可以包含多個 Tag。如圖 2-2 所示,它們之間具有清晰的層級關係。
flowchart TB
subgraph Registry ["Docker Registry(如 Docker Hub)"]
direction TB
subgraph RepoNginx ["Repository(倉庫): nginx"]
direction LR
N1(":latest (tag)")
N2(":1.28 (tag)")
N3(":1.26 (tag)")
N4(":alpine (tag)")
N5("...")
N1 ~~~ N2 ~~~ N3 ~~~ N4 ~~~ N5
end
subgraph RepoMysql ["Repository(倉庫): mysql"]
direction LR
M1(":latest")
M2(":8.0")
M3(":5.7")
M4("...")
M1 ~~~ M2 ~~~ M3 ~~~ M4
end
RepoNginx ~~~ RepoMysql
end
圖 2-2:Registry、Repository 與 Tag 的層級關係
相關基本概念具體如下:
| 概念 | 說明 | 示例 |
|---|---|---|
| Registry | 儲存映像檔的服務 | Docker Hub、ghcr.io |
| Repository (倉庫) | 同一軟體的映像檔集合 | nginx、mysql、mycompany/myapp |
| Tag (標籤) | 倉庫內的版本標識 | latest、1.28、alpine |
映像檔的完整名稱
一個完整的 Docker 映像檔名稱由 Registry 地址、使用者名稱/組織名、倉庫名和標籤組成。瞭解其結構有助於我們更準確地定位映像檔。基本格式如下:
[registry 地址/][使用者名稱/]倉庫名[:標籤]
示例:
## 完整格式
registry.example.com/mycompany/myapp:v1.2.3
│ │ │ │
│ │ │ └── 標籤
│ │ └── 倉庫名
│ └── 使用者名稱/組織名
└── Registry 地址
## Docker Hub 官方映像檔(省略 registry 和使用者名稱)
nginx:1.28
ubuntu:24.04
## Docker Hub 使用者映像檔
jwilder/nginx-proxy:latest
## 其他 Registry
ghcr.io/username/myapp:v1.0
us-west1-docker.pkg.dev/my-project/my-repo/myapp:v1.0
💡 筆者提示:如果不指定 Registry 地址,預設使用 Docker Hub。如果不指定標籤,預設使用
latest。
2.3.3 公共 Registry 服務
公共 Registry 服務為開發者提供了便捷的映像檔獲取途徑。其中最著名的是 Docker Hub。
預設的 Docker Hub
Docker Hub 是最大的公共 Registry,也是 Docker 的預設 Registry。
特點:
- 擁有大量官方映像檔 (nginx、mysql、redis 等)
- 免費賬戶可以建立公開倉庫
- 免費個人賬戶可建立 1 個私有倉庫;更高套餐支援更多私有倉庫
## 從 Docker Hub 拉取映像檔
$ docker pull nginx # 官方映像檔
$ docker pull bitnami/redis # 第三方映像檔
## 推送映像檔到 Docker Hub
$ docker login
$ docker push username/myapp:v1.0
其他公共 Registry
除了 Docker Hub,還有以下幾個常見的公共 Registry:
| Registry | 地址 | 說明 |
|---|---|---|
| GitHub Container Registry | ghcr.io | GitHub 提供,與 GitHub Actions 整合好 |
| Google Artifact Registry | LOCATION-docker.pkg.dev | Google Cloud 當前推薦;也支援遷移後的 gcr.io 相容域名 |
| Quay.io | quay.io | Red Hat 提供 |
| 阿里雲容器映像檔服務 | registry.cn-*.aliyuncs.com | 國內訪問快 |
| 騰訊雲容器映像檔服務 | ccr.ccs.tencentyun.com | 國內訪問快 |
Google 的 Container Registry 已廢棄並完成下線,當前應優先使用 Artifact Registry;如果已經完成遷移,部分 gcr.io 域名請求會被相容到 Artifact Registry。
2.3.4 映像檔加速器
由於網路原因,在國內直接訪問 Docker Hub 可能會很慢。可以配置 映像檔加速器 (Registry Mirror) 來加速下載。配置示例如下:
// /etc/docker/daemon.json
{
"registry-mirrors": [
"https://your-accelerator-url"
]
}
詳細配置方法請參考映像檔加速器章節。
⚠️ 筆者提醒:映像檔加速器的可用性經常變化,使用前建議先測試是否可用。
2.3.5 私有 Registry
出於安全和隱私的考慮,企業往往需要搭建自己的私有 Registry。以下是幾種常見的搭建方案。
官方 Registry 映像檔
Docker 官方提供了 registry 映像檔,可以快速搭建私有 Registry:
## 啟動一個本地 Registry
$ docker run -d -p 5000:5000 --name registry registry:2
## 推送映像檔到本地 Registry
$ docker tag myapp:v1.0 localhost:5000/myapp:v1.0
$ docker push localhost:5000/myapp:v1.0
## 從本地 Registry 拉取
$ docker pull localhost:5000/myapp:v1.0
企業級解決方案
官方 Registry 功能較為基礎,企業環境常用以下方案:
| 方案 | 特點 |
|---|---|
| Harbor | CNCF 專案,功能全面 (使用者管理、漏洞掃描、映像檔簽名) |
| Nexus Repository | 支援多種製品型別 (Docker、Maven、npm 等) |
| 雲廠商服務 | 阿里雲 ACR、騰訊雲 TCR、AWS ECR 等 |
筆者建議:
- 小團隊:可以先用官方 Registry,夠用即可
- 中大型團隊:推薦 Harbor,功能完善且開源免費
- 已使用雲服務:直接用雲廠商的 Registry 服務更省心
2.3.6 映像檔的推送和拉取
掌握映像檔的推送 (Push) 和拉取 (Pull) 是使用 Docker Registry 的基本功。
完整工作流程
如圖 2-3 所示,映像檔從開發環境構建後推送到 Registry,再由生產環境拉取並執行。
開發者機器 Registry 生產伺服器
│ │ │
│ docker build │ │
│ 構建映像檔 │ │
│ │ │
│ docker push ─────────────▶ │
│ 推送映像檔 │ 儲存映像檔 │
│ │ │
│ │ ◀───────────── docker pull │
│ │ 拉取映像檔 │
│ │ │
│ │ docker run │
│ │ 執行容器 │
圖 2-3:映像檔構建、推送與拉取流程
常用命令
## 登入 Registry
$ docker login # 登入 Docker Hub
$ docker login registry.example.com # 登入其他 Registry
## 拉取映像檔
$ docker pull nginx:1.28
## 標記映像檔(準備推送)
$ docker tag myapp:latest registry.example.com/myteam/myapp:v1.0
## 推送映像檔
$ docker push registry.example.com/myteam/myapp:v1.0
## 登出
$ docker logout
2.3.7 映像檔的安全性
在使用公共映像檔或維護私有映像檔時,安全性是不容忽視的重要環節。
使用官方映像檔
Docker Hub 的官方映像檔 (標有 “Official Image” 標識) 經過 Docker 團隊稽核,相對更安全。示例如下:
## 官方映像檔示例
nginx # ✅ 官方
mysql # ✅ 官方
redis # ✅ 官方
## 第三方映像檔(需要自行評估可信度)
bitnami/redis # ⚠️ 需要評估
someuser/myapp # ⚠️ 需要評估
映像檔簽名
當前更推薦使用 Sigstore / Notation 體系進行映像檔簽名與驗證。Docker Content Trust (DCT) 已進入棄用階段:2025 年 8 月 8 日起最早的 DCT 簽名證書開始過期,2025 年 9 月 30 日起不能在新 Registry 啟用 DCT,2028 年 3 月 31 日將完全刪除此功能。不建議作為新專案方案。
注意:Cosign 預設會把簽名推送回映像檔所在倉庫,請使用你有推送許可權的映像檔地址。
## 準備一個你有寫許可權的映像檔地址
$ export IMAGE=<你的倉庫名>/nginx:1.28
$ docker pull nginx:1.28
$ docker tag nginx:1.28 $IMAGE
$ docker push $IMAGE
## 生成簽名金鑰(會生成 cosign.key / cosign.pub)
$ cosign generate-key-pair
## 使用 Cosign 簽名與驗證
$ cosign sign --key cosign.key $IMAGE
$ cosign verify --key cosign.pub $IMAGE
漏洞掃描
## 使用 Docker Scout 掃描映像檔漏洞
$ docker scout cves nginx:latest
## 使用 Trivy(開源工具)
$ trivy image nginx:latest
本章小結
本章介紹了 Docker 的三個核心概念:映像檔、容器和倉庫。
| 概念 | 要點 |
|---|---|
| 映像檔是什麼 | 只讀的應用模板,包含執行所需的一切 |
| 分層儲存 | 多層疊加,共享基礎層,節省空間 |
| 只讀特性 | 構建後不可修改,保證一致性 |
| 層的陷阱 | 刪除操作只是標記,不減小體積 |
| 容器是什麼 | 映像檔的執行例項,本質是隔離的程序 |
| 容器 vs 虛擬機器 | 共享核心,更輕量,但隔離性較弱 |
| 儲存層 | 可寫層隨容器刪除而消失 |
| 資料持久化 | 使用 Volume 或 Bind Mount |
| 生命週期 | 與主程序 (PID 1) 繫結 |
| Registry | 儲存和分發映像檔的服務 |
| 倉庫 (Repository) | 同一軟體的映像檔集合 |
| 標籤 (Tag) | 版本標識,預設為 latest |
| Docker Hub | 預設的公共 Registry |
| 私有 Registry | 企業內部使用,推薦 Harbor |
現在你已經瞭解了 Docker 的三個核心概念:映像檔、容器 和倉庫。接下來,讓我們開始 安裝 Docker,動手實踐!
第三章 安裝 Docker
第三章 安裝 Docker
Docker Engine 主要提供 stable 和 test 兩個更新頻道;test.docker.com 對應測試頻道,適合預釋出驗證,不建議直接用於生產環境。
官方網站上有各種環境下的安裝指南,這裡主要介紹 Docker 在 Linux、Windows 10/11 和 macOS 上的安裝。
安裝方式選擇指南
在開始安裝前,筆者建議你根據以下決策樹選擇最合適的安裝方式:
生產環境 vs 開發環境
生產環境(伺服器部署):
- 優先使用官方 APT/YUM 源安裝(Ubuntu、Debian、Fedora、CentOS)
- 優勢:獲得官方安全更新、長期技術支援、版本管理清晰
- 安裝步驟稍多一些,但這種“麻煩”是值得的——它為你的生產系統爭取了穩定性和可維護性
開發環境(本地開發機、測試伺服器):
- 使用指令碼自動安裝或包管理器直接安裝
- 如果你想快速上手,官方指令碼(
get.docker.com)是最便捷的選擇 - 國內使用者注意:這一步一定要選對映像檔源,否則網路卡頓會嚴重影響體驗
國內使用者的網路最佳化建議
值得注意的是,國內直接訪問 Docker 官方源速度較慢,建議:
- 安裝過程:使用阿里雲、騰訊雲等國內映像檔源
- 映像檔拉取:安裝完成後配置 Docker 映像檔加速器(詳見 3.9 映像檔加速器),這一步對日常開發的體驗提升最明顯
特殊情境
- Raspberry Pi/ARM 平臺:見 3.5 Raspberry Pi
- 離線環境:見 3.6 Linux 離線安裝
- macOS/Windows:Docker Desktop 是官方推薦的一站式解決方案
- 需要實驗特性:見 3.10 開啟實驗特性
詳細安裝指南
3.1 Ubuntu
Ubuntu 是 Docker 最常用的執行環境之一。本節將介紹如何在 Ubuntu 系統上安裝 Docker,並配置國內映像檔加速。
為什麼推薦 APT 源安裝而不是指令碼?
雖然 Docker 官方提供了便捷的安裝指令碼(get.docker.com),但筆者在生產環境中強烈推薦透過 Docker 官方 APT 倉庫安裝,原因如下:
- 版本管理:透過 APT 倉庫安裝後,可以像管理其他系統軟體包一樣升級、回滾和鎖定版本
- 安全更新:Docker 官方倉庫會持續釋出新版本和安全修復,適合長期維護
- 一致性:團隊更容易鎖定同一版本,避免“在我的機器上可以執行”的問題
- 解除安裝乾淨:APT 包管理系統會負責清理所有相關檔案,指令碼安裝的清理往往不夠徹底
如果你只是想快速嘗試 Docker,指令碼安裝沒有問題;但一旦涉及持久運維,APT 源是更成熟的選擇。
警告:切勿在沒有配置 Docker APT 源的情況下直接使用 apt 命令安裝 Docker。
3.1.1 準備工作
在開始安裝之前,我們需要確認系統版本是否滿足要求,並清理可能存在的舊版本。
系統要求
根據 Docker 官方安裝文件,當前受支援的 Ubuntu 64 位版本包括(具體以官方 安裝文件 為準):
- Ubuntu Resolute Raccoon 26.04 (LTS)
- Ubuntu Questing Quokka 25.10
- Ubuntu Noble 24.04 (LTS)
- Ubuntu Jammy 22.04 (LTS)
警告:Ubuntu 20.04 LTS 已不在 Docker 當前支援列表中,不建議用於新部署。對於仍在執行 20.04 的生產系統,應儘快升級到 22.04 LTS 或 24.04 LTS;若短期內無法遷移,可透過 Ubuntu Pro 獲取作業系統層面的擴充套件安全維護(ESM),但這並不改變 Docker 官方支援矩陣。
在 Ubuntu LTS 版本上,目前 Docker 支援 amd64、arm64、armhf、ppc64el、s390x 等 5 個平臺;而非 LTS 版本支援的平臺通常較少。同時,LTS 版本會獲得 5 年的升級維護支援,這樣的系統會獲得更長期的安全保障,因此在生產環境中推薦使用 LTS 版本。
解除安裝舊版本
Docker 官方建議先解除安裝可能衝突的非官方軟體包:
$ for pkg in docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc;
do
sudo apt remove $pkg;
done
3.1.2 使用 APT 安裝
先安裝基礎依賴,並準備 Docker 官方金鑰目錄:
$ sudo apt update
$ sudo apt install ca-certificates curl
$ sudo install -m 0755 -d /etc/apt/keyrings
如果企業內網已經維護了受信任的軟體包映像檔,可在後續步驟中替換 URIs 的域名;預設建議優先以 Docker 官方倉庫為準。
為了確認所下載軟體包的合法性,需要新增倉庫簽名金鑰:
$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
$ sudo chmod a+r /etc/apt/keyrings/docker.asc
然後向 apt 新增 Docker 倉庫:
$ sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
如果需要測試頻道,可將
Components: stable改為test,或改用test.docker.com指令碼在測試環境驗證。
更新 APT 快取,並安裝 Docker Engine 及常用 CLI 外掛:
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
3.1.3 使用指令碼自動安裝
在測試或開發環境中,Docker 官方提供了便捷安裝指令碼,但官方明確不建議把它作為生產環境的標準安裝方式。
在真正執行前,建議先用 --dry-run 預覽指令碼動作:
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh ./get-docker.sh --dry-run
# 若需要測試頻道:
# curl -fsSL https://test.docker.com -o test-docker.sh
# sudo sh ./test-docker.sh
確認無誤後,再執行 sudo sh ./get-docker.sh 安裝穩定版。
3.1.4 啟動 Docker
$ sudo systemctl enable --now docker
3.1.5 建立 docker 使用者組
預設情況下,docker 命令會使用 Unix socket 與 Docker 引擎通訊。而只有 root 使用者和 docker 組的使用者才可以訪問 Docker 引擎的 Unix socket。出於安全考慮,一般 Linux 系統上不會直接使用 root 使用者。因此,更好的做法是將需要使用 docker 的使用者加入 docker 使用者組。
⚠️ 安全警告:
docker使用者組等同於root許可權將使用者加入
docker組免去了每次執行docker命令時輸入sudo的繁瑣,但這也意味著該使用者可以輕易獲取主機的最高 root 許可權(例如透過掛載根目錄執行容器)。 如果你在一個多使用者共享的生產系統上配置,切勿隨意將普通使用者加入此組。此時,更安全的替代方案是使用官方提供的 Rootless 模式 (Rootless mode),它允許在沒有任何 root 許可權的情況下執行 Docker 守護程序和容器。
建立 docker 組:
$ sudo groupadd docker
將當前使用者加入 docker 組:
$ sudo usermod -aG docker $USER
退出當前終端並重新登入,進行如下測試。
3.1.6 測試 Docker 是否安裝正確
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
若能正常輸出以上資訊,則說明安裝成功。
3.1.7 映像檔加速
如果在使用過程中發現拉取 Docker 映像檔十分緩慢,可以配置 Docker 國內映像檔加速。
3.2 Debian
Debian 以其穩定性著稱,是 Docker 的理想宿主系統。本節將指導你在 Debian 上完成 Docker 的安裝。
APT 源安裝的必要性
與 Ubuntu 類似,Debian 使用者在安裝 Docker 時同樣應該優先選擇 Docker 官方 APT 倉庫。對於伺服器環境,這種方式更利於版本控制、升級和長期維護。
警告:切勿在沒有配置 Docker APT 源的情況下直接使用 apt 命令安裝 Docker。
3.2.1 準備工作
安裝前請仔細檢查 Debian 版本支援情況,並解除安裝舊版本以避免衝突。
系統要求
Docker 支援以下版本的 Debian 作業系統(具體以官方 安裝文件 為準):
- Debian Trixie 13 (stable)
- Debian Bookworm 12 (oldstable,全面支援至 2026 年 6 月 10 日,LTS 至 2028 年 6 月 30 日)
- Debian Bullseye 11 (oldoldstable,LTS 支援至 2026 年 8 月 31 日)
注意:Debian Bullseye 11 將於 2026 年 8 月底結束長期支援。建議新部署使用 Bookworm 12 或 Trixie 13。
解除安裝舊版本
Docker 官方建議先解除安裝可能衝突的非官方軟體包:
$ for pkg in docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc; do
sudo apt remove $pkg
done
3.2.2 使用 APT 安裝
先安裝基礎依賴,並準備金鑰目錄:
$ sudo apt update
$ sudo apt install ca-certificates curl
$ sudo install -m 0755 -d /etc/apt/keyrings
如果公司內網維護了受信任映像檔站,可在後續倉庫地址中替換域名;預設建議優先使用 Docker 官方倉庫。
為了確認所下載軟體包的合法性,需要新增軟體源的 GPG 金鑰。
$ sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
$ sudo chmod a+r /etc/apt/keyrings/docker.asc
然後,我們需要向 sources.list 中新增 Docker 軟體源:
在一些基於 Debian 的 Linux 發行版中,
$(. /etc/os-release && echo "$VERSION_CODENAME")可能不會返回 Debian 官方倉庫使用的代號,例如 Kali Linux 等衍生發行版。此時請手動替換為對應的 Debian 代號,例如bookworm。
$ sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
如果使用 Kali 等衍生發行版,請把
Suites對應的版本代號替換為對映到的 Debian 代號,例如bookworm。如果需要測試頻道,可將Components: stable改為test。
更新 APT 快取,並安裝 Docker Engine 及常用 CLI 外掛。
$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
3.2.3 使用指令碼自動安裝
在測試或開發環境中,Docker 官方提供了便捷安裝指令碼,但官方明確不建議把它作為生產環境的標準安裝方式。
在真正執行前,建議先用 --dry-run 預覽指令碼動作:
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh ./get-docker.sh --dry-run
# 若需要測試頻道:
# curl -fsSL https://test.docker.com -o test-docker.sh
# sudo sh ./test-docker.sh
確認無誤後,再執行 sudo sh ./get-docker.sh 安裝穩定版。
3.2.4 啟動 Docker
$ sudo systemctl enable --now docker
3.2.5 建立 docker 使用者組
預設情況下,docker 命令會使用 Unix socket 與 Docker 引擎通訊。而只有 root 使用者和 docker 組的使用者才可以訪問 Docker 引擎的 Unix socket。出於安全考慮,一般 Linux 系統上不會直接使用 root 使用者。因此,更好的做法是將需要使用 docker 的使用者加入 docker 使用者組。
⚠️ 安全警告:
docker使用者組等同於root許可權將使用者加入
docker組免去了每次執行docker命令時輸入sudo的繁瑣,但這也意味著該使用者可以輕易獲取主機的最高 root 許可權(例如透過掛載根目錄執行容器)。 如果你在一個多使用者共享的生產系統上配置,切勿隨意將普通使用者加入此組。此時,更安全的替代方案是使用官方提供的 Rootless 模式 (Rootless mode),它允許在沒有任何 root 許可權的情況下執行 Docker 守護程序和容器。
建立 docker 組:
$ sudo groupadd docker
將當前使用者加入 docker 組:
$ sudo usermod -aG docker $USER
退出當前終端並重新登入,進行如下測試。
3.2.6 測試 Docker 是否安裝正確
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
若能正常輸出以上資訊,則說明安裝成功。
3.2.7 映像檔加速
如果在使用過程中發現拉取 Docker 映像檔十分緩慢,可以配置 Docker 國內映像檔加速。
3.3 Fedora
Fedora 作為技術前瞻的 Linux 發行版,對 Docker 有著良好的支援。本節介紹在 Fedora 上的安裝步驟。
YUM/DNF 源安裝的策略建議
Fedora 的快速釋出週期(每 6 個月釋出新版本)決定了它的使用者群體多為開發者和技術愛好者。雖然透過 DNF 可以直接安裝 Docker,但筆者建議仍然透過 Docker 官方 YUM 源進行安裝,原因是:Fedora 官方倉庫的 Docker 版本往往滯後,而官方源能確保你獲得最新的 Docker 功能和安全補丁。特別是在開發環境需要用到最新 Docker 特性時,這一點顯得尤為重要。
警告:切勿在沒有配置 Docker dnf 源的情況下直接使用 dnf 命令安裝 Docker。
3.3.1 準備工作
確保你的 Fedora 版本在支援列表中,並清理舊版本。
系統要求
根據 Docker 官方安裝文件,當前受支援的 Fedora 版本包括(具體以官方 安裝文件 為準):
- Fedora 44
- Fedora 43
- Fedora 42
解除安裝舊版本
舊版本的 Docker 稱為 docker 或者 docker-engine,使用以下命令解除安裝舊版本:
$ sudo dnf remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine
3.3.2 使用 dnf 安裝
使用 dnf 包管理器安裝是推薦的方式,便於後續的更新和管理。
執行以下命令安裝依賴包:
$ sudo dnf -y install dnf-plugins-core
預設建議優先使用 Docker 官方倉庫;如果企業內網維護了受信任映像檔站,可自行替換倉庫 URL。
執行下面的命令新增 dnf 軟體源:
$ sudo dnf config-manager \
--add-repo \
https://download.docker.com/linux/fedora/docker-ce.repo
如果需要測試版本的 Docker 請使用以下命令:
$ sudo dnf config-manager --set-enabled docker-ce-test
你也可以禁用測試版本的 Docker
$ sudo dnf config-manager --set-disabled docker-ce-test
安裝 Docker
更新 dnf 軟體源快取,並安裝 Docker Engine 及常用 CLI 外掛。
$ sudo dnf update
$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
你也可以使用以下命令安裝指定版本的 Docker
$ dnf list docker-ce --showduplicates | sort -r
docker-ce.x86_64 3:29.4.0-1.fc42 docker-ce-stable
$ sudo dnf -y install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING>
3.3.3 使用指令碼自動安裝
在測試或開發環境中,Docker 官方提供了便捷安裝指令碼,但官方明確不建議把它作為生產環境的標準安裝方式。
在真正執行前,建議先用 --dry-run 預覽指令碼動作:
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh ./get-docker.sh --dry-run
# 若需要測試頻道:
# curl -fsSL https://test.docker.com -o test-docker.sh
# sudo sh ./test-docker.sh
確認無誤後,再執行 sudo sh ./get-docker.sh 安裝穩定版。
3.3.4 啟動 Docker
$ sudo systemctl enable --now docker
3.3.5 建立 docker 使用者組
預設情況下,docker 命令會使用 Unix socket 與 Docker 引擎通訊。而只有 root 使用者和 docker 組的使用者才可以訪問 Docker 引擎的 Unix socket。出於安全考慮,一般 Linux 系統上不會直接使用 root 使用者。因此,更好的做法是將需要使用 docker 的使用者加入 docker 使用者組。
⚠️ 安全警告:
docker使用者組等同於root許可權將使用者加入
docker組免去了每次執行docker命令時輸入sudo的繁瑣,但這也意味著該使用者可以輕易獲取主機的最高 root 許可權(例如透過掛載根目錄執行容器)。 如果你在一個多使用者共享的生產系統上配置,切勿隨意將普通使用者加入此組。此時,更安全的替代方案是使用官方提供的 Rootless 模式 (Rootless mode),它允許在沒有任何 root 許可權的情況下執行 Docker 守護程序和容器。
建立 docker 組:
$ sudo groupadd docker
將當前使用者加入 docker 組:
$ sudo usermod -aG docker $USER
退出當前終端並重新登入,進行如下測試。
3.3.6 測試 Docker 是否安裝正確
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
若能正常輸出以上資訊,則說明安裝成功。
3.3.7 映像檔加速
如果在使用過程中發現拉取 Docker 映像檔十分緩慢,可以配置 Docker 國內映像檔加速。
3.4 CentOS
CentOS (及其替代品 Rocky Linux、AlmaLinux) 是企業級伺服器常用的作業系統。本節介紹在這些系統上安裝 Docker 的步驟。
企業級部署的版本選擇
值得注意的是,CentOS 8 已停止維護,CentOS 7 已停止支援。如果你正在規劃新的生產部署,強烈建議選擇 Rocky Linux 或 AlmaLinux——這兩個專案是由社群維護的 CentOS 替代品,延續了 CentOS 的企業級特性,同時提供了更長的生命週期承諾。選擇穩定的基礎系統,才能為 Docker 的長期運維奠定堅實基礎。
警告:切勿在沒有配置 Docker YUM 源的情況下直接使用 yum 命令安裝 Docker。
3.4.1 準備工作
安裝前請確認系統版本和核心版本滿足 Docker 的執行要求。
系統要求
嚴重警告:CentOS 7 已於 2024 年 6 月 30 日結束所有支援,不再接收任何安全更新。CentOS 8 已於 2021 年 12 月 31 日停止維護。強烈建議新專案使用 Rocky Linux 或 AlmaLinux 替代,這兩個專案由社群維護,提供長期支援承諾。
Docker 官方當前安裝文件覆蓋的 CentOS 平臺為 CentOS Stream 9 和 CentOS Stream 10(具體以官方 安裝文件 為準)。Rocky Linux、AlmaLinux 等 RHEL 相容發行版通常可以沿用相近步驟,但建議先在測試環境驗證倉庫與依賴是否匹配。
對於 Rocky Linux、AlmaLinux 或 CentOS Stream,推薦使用 dnf 包管理器。
解除安裝舊版本
舊版本的 Docker 稱為 docker 或者 docker-engine,使用以下命令解除安裝舊版本:
$ sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine \
docker-ce-cli \
containerd.io
3.4.2 使用 yum 安裝
使用 yum/dnf 安裝是管理 Docker 生命週期的標準方式。
執行以下命令安裝依賴包:
$ sudo dnf install -y dnf-plugins-core
預設建議優先使用 Docker 官方倉庫;如果企業內網維護了受信任映像檔站,可自行替換倉庫 URL。
執行下面的命令新增 yum 軟體源:
$ sudo dnf config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
如果需要測試版本的 Docker 請執行以下命令:
$ sudo dnf config-manager --set-enabled docker-ce-test
安裝 Docker
更新 dnf 軟體源快取,並安裝 Docker Engine 及常用 CLI 外掛。
$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
3.4.3 防火牆注意事項
Docker 官方提醒:如果主機使用 ufw 或 firewalld 管理防火牆,容器暴露出的埠可能繞過現有規則;同時 Docker 僅相容 iptables-nft 和 iptables-legacy,不支援直接由 nft 建立的規則集。
因此,生產環境中更穩妥的做法是:
- 明確使用
iptables-nft或iptables-legacy維護規則; - 需要限制容器流量時,優先把自定義規則寫入
DOCKER-USER鏈; - 在變更防火牆策略前,先用測試容器驗證埠暴露和東西向流量是否符合預期。
3.4.4 使用指令碼自動安裝
在測試或開發環境中,Docker 官方提供了便捷安裝指令碼,但官方明確不建議把它作為生產環境的標準安裝方式。
在真正執行前,建議先用 --dry-run 預覽指令碼動作:
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh ./get-docker.sh --dry-run
# 若需要測試頻道:
# curl -fsSL https://test.docker.com -o test-docker.sh
# sudo sh ./test-docker.sh
確認無誤後,再執行 sudo sh ./get-docker.sh 安裝穩定版。
3.4.5 啟動 Docker
$ sudo systemctl enable --now docker
3.4.6 建立 docker 使用者組
預設情況下,docker 命令會使用 Unix socket 與 Docker 引擎通訊。而只有 root 使用者和 docker 組的使用者才可以訪問 Docker 引擎的 Unix socket。出於安全考慮,一般 Linux 系統上不會直接使用 root 使用者。因此,更好的做法是將需要使用 docker 的使用者加入 docker 使用者組。
⚠️ 安全警告:
docker使用者組等同於root許可權將使用者加入
docker組免去了每次執行docker命令時輸入sudo的繁瑣,但這也意味著該使用者可以輕易獲取主機的最高 root 許可權(例如透過掛載根目錄執行容器)。 如果你在一個多使用者共享的生產系統上配置,切勿隨意將普通使用者加入此組。此時,更安全的替代方案是使用官方提供的 Rootless 模式 (Rootless mode),它允許在沒有任何 root 許可權的情況下執行 Docker 守護程序和容器。
建立 docker 組:
$ sudo groupadd docker
將當前使用者加入 docker 組:
$ sudo usermod -aG docker $USER
退出當前終端並重新登入,進行如下測試。
3.4.7 測試 Docker 是否安裝正確
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
若能正常輸出以上資訊,則說明安裝成功。
3.4.8 映像檔加速
如果在使用過程中發現拉取 Docker 映像檔十分緩慢,可以配置 Docker 國內映像檔加速。
3.4.9 新增核心引數
如果在 CentOS 使用 Docker 看到下面的這些警告資訊:
WARNING: bridge-nf-call-iptables is disabled
WARNING: bridge-nf-call-ip6tables is disabled
請新增核心配置引數以啟用這些功能。
$ sudo tee -a /etc/sysctl.conf <<-EOF
net.bridge.bridge-nf-call-ip6tables = 1
net.bridge.bridge-nf-call-iptables = 1
EOF
然後重新載入 sysctl.conf 即可
$ sudo sysctl -p
3.5 Raspberry Pi
樹莓派等 ARM 架構裝置在物聯網和邊緣計算領域應用廣泛。本節介紹如何在樹莓派上安裝 Docker。
樹莓派執行 Docker 的現實考慮
樹莓派是 Docker 在邊緣計算的一個優秀用例。但在安裝前,筆者建議你瞭解幾個實際情況:
效能特性:
- Raspberry Pi 4 及以上才能流暢執行 Docker 和基本容器
- Raspberry Pi Zero 2 可以執行,但效能受限
- 磁碟 I/O 是主要瓶頸(特別是使用 microSD 卡時)
映像檔可用性:
- 並非所有 Docker 映像檔都有 ARM 版本(arm64 或 armv7)
- 官方映像檔通常提供多架構支援,但第三方映像檔可能沒有
- 某些依賴 Intel 特定指令的應用無法在 ARM 上執行
儲存和記憶體:
- 容器映像檔會佔用較多儲存空間,128GB microSD 卡建議最多執行 3-4 箇中等大小的容器
- 512MB 或 1GB 記憶體的樹莓派執行多個容器會非常吃力
實踐建議:樹莓派 Docker 部署最適合輕量級應用——單個微服務、監控代理、Web 伺服器等。複雜多容器應用還是應該部署在效能更強的硬體上。
警告:切勿在沒有配置 Docker APT 源的情況下直接使用 apt 命令安裝 Docker。
3.5.1 系統要求
Docker 對 ARM 架構有著良好的支援。
Docker 可以執行在多種 CPU 架構上,但本小節只聚焦 Raspberry Pi OS 32-bit (armhf) 的官方安裝流程;如果你使用的是 64 位 Raspberry Pi OS,請直接參考 Debian arm64 安裝說明。
Docker 官方目前單獨提供的是 Raspberry Pi OS 32-bit (armhf) 安裝頁(具體以官方 安裝文件 為準),支援情況如下:
- Raspberry Pi OS Bookworm
- Raspberry Pi OS Bullseye
重要提醒:Docker Engine v28 是官方最後一個支援 Raspberry Pi OS 32-bit (armhf) 的大版本。從 v29 開始,32 位 Raspberry Pi OS 不再提供新主版本軟體包。若你使用的是 64 位 Raspberry Pi OS,請直接參考 Debian
arm64安裝方式。
注:Raspberry Pi OS 由樹莓派的開發與維護機構樹莓派基金會官方支援,並推薦用作樹莓派的首選系統,其基於 Debian。
3.5.2 使用 APT 安裝
推薦使用 APT 包管理器進行安裝,以確保版本的穩定性和安全性。
先安裝基礎依賴,並準備金鑰目錄:
$ sudo apt-get update
$ sudo apt-get install \
ca-certificates \
curl \
gnupg
預設建議優先使用 Docker 官方倉庫;如果企業內網維護了受信任映像檔站,可自行替換倉庫 URL。
為了確認所下載軟體包的合法性,需要新增軟體源的 GPG 金鑰。
$ sudo install -m 0755 -d /etc/apt/keyrings
$ sudo curl -fsSL https://download.docker.com/linux/raspbian/gpg -o /etc/apt/keyrings/docker.asc
$ sudo chmod a+r /etc/apt/keyrings/docker.asc
然後,我們需要向 sources.list 中新增 Docker 軟體源:
$ sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/raspbian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
以上命令會新增穩定版本的 Docker APT 源,如果需要測試版本的 Docker 請將 stable 改為 test。
報錯解決辦法
在 Raspberry Pi OS Bullseye/Bookworm 中,如果你使用 add-apt-repository 新增源,可能會出現如下報錯。官方當前更推薦的做法是不要繼續回退到舊式單行 deb 配置,而是直接使用上面的 docker.sources 檔案方式寫入倉庫:
Traceback (most recent call last):
File "/usr/bin/add-apt-repository", line 95, in <module>
sp = SoftwareProperties(options=options)
File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 109, in __init__
self.reload_sourceslist()
File "/usr/lib/python3/dist-packages/softwareproperties/SoftwareProperties.py", line 599, in reload_sourceslist
self.distro.get_sources(self.sourceslist)
File "/usr/lib/python3/dist-packages/aptsources/distro.py", line 91, in get_sources
raise NoDistroTemplateException(
aptsources.distro.NoDistroTemplateException: Error: could not find a distribution template for Raspbian/bullseye
如果之前已經寫入了錯誤的源配置,建議先刪除舊條目,再重新執行本節前面的 docker.asc + docker.sources 兩步。
安裝 Docker
更新 apt 軟體包快取,並安裝 Docker Engine 及常用 CLI 外掛。
$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
3.5.3 使用指令碼自動安裝
在測試或開發環境中,Docker 官方提供了便捷安裝指令碼,但官方明確不建議把它作為生產環境的標準安裝方式。
在真正執行前,建議先用 --dry-run 預覽指令碼動作:
$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh ./get-docker.sh --dry-run
# 若需要測試頻道:
# curl -fsSL https://test.docker.com -o test-docker.sh
# sudo sh ./test-docker.sh
確認無誤後,再執行 sudo sh ./get-docker.sh 安裝穩定版。如果你執行的是 64 位 Raspberry Pi OS,更推薦直接遷移到 Debian arm64 安裝路徑,而不是繼續停留在 32 位 armhf 流程上。
3.5.4 啟動 Docker
$ sudo systemctl enable --now docker
3.5.5 建立 docker 使用者組
預設情況下,docker 命令會使用 Unix socket 與 Docker 引擎通訊。而只有 root 使用者和 docker 組的使用者才可以訪問 Docker 引擎的 Unix socket。出於安全考慮,一般 Linux 系統上不會直接使用 root 使用者。因此,更好的做法是將需要使用 docker 的使用者加入 docker 使用者組。
⚠️ 安全警告:
docker使用者組等同於root許可權將使用者加入
docker組免去了每次執行docker命令時輸入sudo的繁瑣,但這也意味著該使用者可以輕易獲取主機的最高 root 許可權(例如透過掛載根目錄執行容器)。 如果你在一個多使用者共享的生產系統上配置,切勿隨意將普通使用者加入此組。此時,更安全的替代方案是使用官方提供的 Rootless 模式 (Rootless mode),它允許在沒有任何 root 許可權的情況下執行 Docker 守護程序和容器。
建立 docker 組:
$ sudo groupadd docker
將當前使用者加入 docker 組:
$ sudo usermod -aG docker $USER
退出當前終端並重新登入,進行如下測試。
3.5.6 測試 Docker 是否安裝正確
$ docker run --rm hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4ee5c797bcd7: Pull complete
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm32v7)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
若能正常輸出以上資訊,則說明安裝成功。
注意:ARM 平臺不能使用 x86 映像檔,檢視 Raspberry Pi OS 可使用映像檔請訪問 arm32v7 或者 arm64v8。
3.5.7 映像檔加速
如果在使用過程中發現拉取 Docker 映像檔十分緩慢,可以配置 Docker 國內映像檔加速。
3.6 Linux 離線安裝
生產環境中一般都是沒有公網資源的,本文介紹如何在生產伺服器上離線部署 Docker
括號內的字母表示該操作需要在哪些伺服器上執行

3.6.1 CentOS/Rocky/AlmaLinux 離線安裝 Docker
在無法連線外網的安全環境中,離線安裝是唯一的選擇。本節介紹如何在 RHEL 系發行版中進行離線安裝。
注意:Docker 官方當前支援的 RHEL 相容平臺基線已是 CentOS Stream 9/10(具體以官方 安裝文件 為準)。下面的離線示例建議統一按
el9軟體包和dnf流程準備,Rocky Linux 9、AlmaLinux 9 也可先在測試環境按相同思路驗證。
3.6.1.1 本地 RPM 檔案安裝:推薦
推薦這種方式,是因為在生產環境中一般會選定某個指定的 Docker 軟體版本使用。
查詢可用的軟體版本
$ sudo dnf -y install dnf-plugins-core
$ sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
$ sudo dnf list docker-ce --showduplicates | sort -r
docker-ce.x86_64 3:29.4.0-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.3.1-1.el9 docker-ce-stable
docker-ce.x86_64 3:29.3.0-1.el9 docker-ce-stable
下載到指定資料夾
sudo dnf install --downloadonly --downloaddir=/tmp/docker_offline_install/ \
docker-ce-<VERSION_STRING> \
docker-ce-cli-<VERSION_STRING> \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin
下載完成後,把 /tmp/docker_offline_install/ 目錄中的全部 RPM 檔案複製到離線目標伺服器。
在目標伺服器進入資料夾後安裝
- 離線安裝時,不要使用
rpm --nodeps --force跳過依賴檢查;應先把完整依賴包集複製到目標伺服器,再讓dnf從本地 RPM 安裝。
$ sudo dnf install ./*.rpm
鎖定軟體版本(可選)
下載鎖定版本軟體
可參考下文的網路源搭建
$ sudo dnf install 'dnf-command(versionlock)'
鎖定軟體版本
$ sudo dnf versionlock add docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
檢視鎖定列表
$ sudo dnf versionlock list
鎖定後無法再更新
$ sudo dnf upgrade docker-ce
解鎖指定軟體
$ sudo dnf versionlock delete docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
解鎖所有軟體
$ sudo dnf versionlock clear
3.6.1.2 本地倉庫伺服器搭建安裝 Docker
掛載 ISO 映像檔搭建本地 File 源
## 刪除其他網路源
$ sudo rm -f /etc/yum.repos.d/*
## 掛載光碟或者iso映像檔
$ sudo mount /dev/cdrom /mnt
## 新增本地源
$ sudo tee /etc/yum.repos.d/local-base.repo <<EOF
[local_base]
name=local_base
baseurl=file:///mnt
enabled=1
gpgcheck=0
EOF
## 測試剛才的本地源,安裝createrepo軟體
$ sudo dnf clean all
$ sudo dnf install -y createrepo_c httpd
根據本地檔案搭建 BaseOS/AppStream 網路源
## 安裝apache 伺服器
## 新建centos目錄
$ sudo mkdir -p /var/www/html/base
## 複製光碟內的檔案到剛才新建的目錄
$ sudo cp -R /mnt/* /var/www/html/base/
$ sudo createrepo_c /var/www/html/base/
$ sudo systemctl enable --now httpd
下載 Docker CE 倉庫內容
在有網路的伺服器上同步 Docker CE RPM 包:
$ sudo dnf -y install dnf-plugins-core
$ sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
$ mkdir -p /tmp/docker-ce
$ sudo dnf reposync --repo=docker-ce-stable --download-path=/tmp/docker-ce
建立倉庫索引
把下載的 docker-ce 資料夾複製到離線的伺服器
## 把 docker-ce 資料夾複製到 /var/www/html/docker-ce
$ sudo createrepo_c /var/www/html/docker-ce/
DNF 用戶端設定
$ sudo rm -f /etc/yum.repos.d/*
$ sudo tee /etc/yum.repos.d/local-files.repo <<EOF
[local_base]
name=local_base
## 改成倉庫伺服器地址
baseurl=http://x.x.x.x/base
enabled=1
gpgcheck=0
proxy=_none_
[docker-ce-stable]
name=docker-ce-stable
## 改成倉庫伺服器地址
baseurl=http://x.x.x.x/docker-ce
enabled=1
gpgcheck=0
proxy=_none_
EOF
安裝 Docker
$ sudo dnf makecache
$ sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
$ sudo systemctl enable --now docker
$ sudo docker run hello-world
3.7 macOS
Mac 使用者的特殊考慮
macOS 上沒有原生 Linux 核心,Docker 需要執行在一個輕量級虛擬機器中。Docker Desktop 完全封裝了這個複雜性,讓 Mac 使用者可以像 Linux 使用者一樣使用 Docker。但有幾點需要特別瞭解:
效能特性:
- Apple Silicon(M 系列晶片)比 Intel Mac 的效能更好,且擁有原生支援
- 檔案 I/O 效能:macOS 與容器之間的卷掛載效能不如 Linux(這是虛擬化的代價)
- 記憶體使用:Docker Desktop 本身會消耗一定記憶體用於虛擬機器管理
許可考慮:
- 小型企業(少於 250 名員工且年收入低於 1000 萬美元)、個人使用、教育和非商業開源專案可免費使用
- 超出上述範圍的商業用途需要付費訂閱
3.7.1 系統要求
Docker Desktop for Mac 支援當前版本及前兩個主要版本的 macOS(具體以官方 安裝文件 為準),並且至少需要 4 GB 記憶體。對於 Apple Silicon 機型,若需要相容部分 Intel 命令列工具,官方建議安裝 Rosetta 2。
3.7.2 安裝
[!WARNING] 商業許可限制:Docker Desktop 對小型企業(少於 250 名員工且年收入低於 1000 萬美元)、個人使用、教育和非商業開源專案仍然免費。超出上述範圍的商業用途需要付費訂閱。企業使用者請注意合規風險。
Docker Desktop 為 Mac 使用者提供了標準的圖形化安裝體驗。官方當前主要提供 DMG 互動安裝和命令列/企業部署安裝;這裡先介紹最常見的 DMG 安裝方式。
手動下載安裝
如果需要手動下載,可直接使用 Docker Desktop for Mac Intel 版 安裝包。
如果你的電腦搭載的是 Apple Silicon 晶片(
arm64架構),請使用 Docker Desktop for Mac Apple Silicon 版。
如同 macOS 其它軟體一樣,安裝也非常簡單,雙擊下載的 .dmg 檔案,然後將那隻叫 Moby 的鯨魚圖示拖拽到 Application 資料夾即可 (其間需要輸入使用者密碼)。

3.7.3 執行
從應用中找到 Docker 圖示並點選執行。

執行之後,會在右上角選單欄看到多了一個鯨魚圖示,這個圖示表明瞭 Docker 的執行狀態。

每次點選鯨魚圖示會彈出操作選單。

之後,你可以在終端透過命令檢查安裝後的 Docker 版本。
$ docker version
$ docker info
如果 docker version 和 docker info 都能正常返回資訊,就可以嘗試執行一個 Nginx 伺服器:
$ docker run -d -p 80:80 --name webserver nginx
服務執行後,可以訪問 http://localhost。如果看到了 Welcome to nginx!,就說明 Docker Desktop for Mac 安裝成功了。

要停止並刪除 Nginx 容器,執行下面的命令:
$ docker stop webserver
$ docker rm webserver
3.7.4 替代容器執行時
Docker Desktop 並非 macOS 上執行容器的唯一選擇。以下兩個工具也廣泛使用,各有側重:
| 特性 | Docker Desktop | OrbStack | Colima |
|---|---|---|---|
| 啟動速度 | 較慢(約 10–30 秒) | 極快(約 2 秒) | 中等(約 5–10 秒) |
| 空閒記憶體佔用 | 4–6 GB | 200–300 MB | 約 400 MB |
| 圖形介面 | 完整 GUI | 輕量 GUI | 僅命令列 |
| Apple Silicon | 支援 | 原生最佳化 | 支援 |
| 商業許可 | 大型企業需付費 | 個人免費,商業付費 | MIT 開源 |
| Kubernetes | 內建 | 內建 | 透過 k3s 支援 |
OrbStack:以極低的資源佔用和接近原生的 I/O 效能著稱。對於 Apple Silicon 機型上需要頻繁構建映像檔的開發者,體驗提升尤為明顯。個人和教育用途免費。
Colima:完全開源的命令列方案,底層基於 Lima 虛擬機器。適合偏好終端工作流、不需要 GUI 的開發者,或企業中希望避免商業許可限制的團隊。
上述三種工具均相容標準的
dockerCLI 命令。從 Docker Desktop 切換到 OrbStack 或 Colima 時,已有的映像檔和容器配置通常可以平滑遷移。
3.7.5 映像檔加速
如果在使用過程中發現拉取 Docker 映像檔十分緩慢,可以配置 Docker 國內映像檔加速。
3.8 Windows 10/11
在 Windows 平臺上,Docker Desktop 提供了完整的 Docker 開發環境。本節介紹在 Windows 10/11 上的安裝和配置。
Windows 上的 Docker:執行原理理解
與 macOS 類似,Windows 也沒有原生 Linux 容器支援。Docker Desktop for Windows 有兩種執行後端可選:
WSL 2(Windows Subsystem for Linux 2) - 推薦:
- 利用 Hyper-V 虛擬化執行真正的 Linux 核心
- 效能更好,檔案系統整合更深
- 現代 Windows 10/11 的標準選擇
- 支援在 Linux 和 Windows 之間的無縫檔案訪問
Hyper-V - 傳統方案:
- 純虛擬化方式
- 效能略低於 WSL 2
- 在某些企業網路環境下仍被使用
實踐建議:WSL 2 和 Hyper-V 在功能上都能滿足 Docker Desktop 的日常開發需求,選擇哪種後端應以機器能力、企業策略和你的工作流為準;如果系統只滿足其中一種後端的前置條件,安裝器才會自動選擇可用的那一種。
3.8.1 系統要求
Docker Desktop for Windows 支援 Docker 官方文件列出的受支援 Windows 10/11 64 位版本(具體以官方 安裝文件 為準)。若使用 WSL 2 後端,需要啟用 WSL 2,並滿足官方要求的 WSL 2.1.5 或更高版本;若使用 Hyper-V 後端,則需要啟用 Hyper-V 和 Containers 功能。Windows 10 64 位支援 Enterprise、Pro 和 Education 22H2(build 19045),Windows 11 64 位支援 Enterprise、Pro 和 Education 23H2(build 22631)或更高版本,且官方建議主機至少具備 8 GB 記憶體。
3.8.2 安裝
[!WARNING] 商業許可限制:Docker Desktop 對小型企業(少於 250 名員工且年收入少於 1000 萬美元)、個人使用、教育和非商業開源專案仍然免費。對於其他商業用途,以及政府機構使用,需要付費訂閱。企業使用者請注意合規風險。
手動下載安裝
官方當前提供三個主要入口:
- Docker Desktop for Windows x86_64 安裝包
- Docker Desktop for Windows Microsoft Store 版本
- Docker Desktop for Windows Arm 早期訪問版
下載好對應安裝包後,雙擊 Docker Desktop Installer.exe 開始安裝。
使用winget安裝
$ winget install Docker.DockerDesktop
3.8.3 在 WSL2 執行 Docker
若你的環境使用 WSL 2 後端,請先確認 wsl --version 滿足 Docker 官方的版本要求,並按 Docker Desktop 的 WSL 說明啟用對應功能。
3.8.4 執行
在 Windows 搜尋欄輸入 Docker 點選 Docker Desktop 開始執行。

Docker 啟動之後會在 Windows 工作列出現鯨魚圖示。

等待片刻,當鯨魚圖示靜止時,說明 Docker 啟動成功,之後你可以開啟 PowerShell 使用 Docker。
推薦使用 Windows Terminal 在終端使用 Docker。
3.8.5 映像檔加速
如果在使用過程中發現拉取 Docker 映像檔十分緩慢,可以配置 Docker 國內映像檔加速。
3.9 映像檔加速器
國內從 Docker Hub 拉取映像檔有時會遇到困難,此時可以配置映像檔加速器。
⚠️ 注意:映像檔加速器的可用性經常變化。配置前請先訪問 docker-practice/docker-registry-cn-mirror-test 檢視各映像檔站的實時狀態。
3.9.1 推薦配置方案
針對不同的使用情境,我們推薦以下幾種映像檔加速配置方案,以確保最佳的拉取速度。
⚠️ 重要提示:國內大多數 Docker Hub 加速服務已於 2024 年中旬關閉(包括阿里雲、騰訊雲、網易雲、百度雲等)。如下推薦的映像檔源可用性因人而異,建議先測試可用性再配置。
- 雲伺服器使用者:優先使用所在雲平臺提供的內部加速器(見本頁末尾“雲服務商”部分)
- 本地開發使用者:優先使用自建 pull-through cache;如果必須依賴社群映像檔,請先用上面的測試倉庫確認實時可用性
- 代理方案:如有條件,可配置 HTTP 代理直接訪問 Docker Hub
更穩妥的長期方案是使用自己控制的 pull-through cache / registry mirror,或者優先使用雲廠商提供的內網映像檔。下文統一用 https://<your-registry-mirror> 作為佔位符;如果你選擇第三方公共站,請先確認可用性、服務條款和快取策略。
3.9.2 Ubuntu 22.04+、Debian 12+、Rocky/Alma/CentOS Stream 9+
目前主流 Linux 發行版均已使用 systemd 進行服務管理,這裡介紹如何在使用 systemd 的 Linux 發行版中配置映像檔加速器。
請首先執行以下命令,檢視是否在 docker.service 檔案中配置過映像檔地址。
$ systemctl cat docker | grep '\-\-registry\-mirror'
如果該命令有輸出,那麼請執行 $ systemctl cat docker 檢視 ExecStart= 出現的位置,修改對應的檔案內容去掉 --registry-mirror 引數及其值,並按接下來的步驟進行配置。
如果以上命令沒有任何輸出,那麼就可以在 /etc/docker/daemon.json 中寫入如下內容 (如果檔案不存在請新建該檔案):
{
"registry-mirrors": [
"https://<your-registry-mirror>"
]
}
注意,一定要保證該檔案符合 json 規範,否則 Docker 將不能啟動。
之後重新啟動服務。
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
3.9.3 Windows 10/11
對於使用 Windows 10/11 的使用者,在工作列托盤 Docker 圖示內開啟 Settings,在左側導航選單選擇 Docker Engine,在右側像下邊一樣編輯 JSON 檔案,之後點選 Apply & Restart 儲存後 Docker 就會重啟並應用配置的映像檔地址了。
{
"registry-mirrors": [
"https://<your-registry-mirror>"
]
}
3.9.4 macOS
對於使用 macOS 的使用者,在工作列點選 Docker Desktop 應用圖示 -> Settings...,在左側導航選單選擇 Docker Engine,在右側像下邊一樣編輯 json 檔案。修改完成之後,點選 Apply & restart 按鈕,Docker 就會重啟並應用配置的映像檔地址了。
{
"registry-mirrors": [
"https://<your-registry-mirror>"
]
}
3.9.5 檢查加速器是否生效
執行 $ docker info,如果從結果中看到了如下內容,說明配置成功。
Registry Mirrors:
https://<your-registry-mirror>/
3.9.6 Kubernetes 官方映像檔地址遷移
可以登入 阿里雲容器映像檔服務,在 映像檔中心 -> 映像檔搜尋 中查詢。
Kubernetes 社群已將官方映像檔地址從 k8s.gcr.io 遷移到 registry.k8s.io。建議優先使用新地址。
一般情況下有如下對應關係:
$ docker pull registry.k8s.io/xxx
3.9.7 已停止服務的映像檔列表
以下映像檔源已停止服務,新增無用的映像檔加速器會拖慢拉取速度,請從配置中刪除:
- https://hub.atomgit.com (已於 2024 年底關閉)
- https://registry.cn-hangzhou.aliyuncs.com (阿里雲 Docker 加速已於 2024 年關閉)
- https://dockerhub.azk8s.cn (已轉為私有)
- https://reg-mirror.qiniu.com (已停止服務)
- https://registry.docker-cn.com (已停止服務)
- https://hub-mirror.c.163.com (網易雲映像檔已於 2024 年關閉)
- https://mirror.baidubce.com (百度雲映像檔已停止)
建議 watch(頁面右上角) 映像檔測試倉庫 這個 GitHub 倉庫,我們會持續更新各映像檔源的可用狀態。
3.9.8 雲服務商
某些雲服務商提供了 僅供內部 訪問的映像檔服務,當您的 Docker 執行在雲平臺時可以選擇它們。
3.10 開啟實驗特性
一些 docker 命令或功能僅當 實驗特性 開啟時才能使用,請按照以下方法進行設定。
3.10.1 Docker CLI 的實驗特性
CLI 的實驗特性通常包含仍在開發中的新功能。幸運的是,在較新版本中這些特性已經更加易用。
從 v20.10 及更高版本開始,Docker CLI 所有實驗特性的命令均預設開啟,無需再進行配置或設定系統環境變數。
3.10.2 開啟 dockerd 的實驗特性
編輯 /etc/docker/daemon.json,新增如下條目
{
"experimental": true
}
儲存後重啟 Docker daemon:
$ sudo systemctl restart docker
然後執行下面的命令驗證伺服器端實驗特性已經生效:
$ docker version
若輸出中的 Server / Engine 部分出現 Experimental: true,說明 daemon 端實驗特性已經啟用。
本章小結
Docker 支援在多種平臺上安裝和使用,選擇合適的安裝方式是順利使用 Docker 的第一步。
| 平臺 | 推薦方式 | 說明 |
|---|---|---|
| Ubuntu/Debian | 官方 APT 倉庫 | 最完善的支援,推薦首選 |
| CentOS/Fedora | 官方 DNF/YUM 倉庫 | 注意驗證防火牆與 iptables 相容性 |
| macOS | Docker Desktop | 圖形化安裝,預設整合 Compose |
| Windows 10/11 | Docker Desktop(WSL 2 或 Hyper-V) | 按機器能力與企業策略選擇後端 |
| Raspberry Pi | 官方 APT 倉庫或 Debian arm64 方案 |
32 位系統已停止接收 v29+ 新主版本 |
| 離線環境 | 二進位制包安裝 | 適用於無法聯網的伺服器 |
安裝後驗證
安裝完成後,執行以下命令驗證 Docker 是否正常工作:
$ docker version
$ docker run --rm hello-world
延伸閱讀
- 映像檔加速器:解決國內拉取映像檔慢的問題
- 開啟實驗特性:使用最新功能
- Docker Hub:官方映像檔倉庫
第四章 使用映像檔
第四章 使用映像檔
在之前的介紹中,我們知道映像檔是 Docker 的三大元件之一。
Docker 執行容器前需要本地存在對應的映像檔,如果本地不存在該映像檔,Docker 會從映像檔倉庫下載該映像檔。
本章內容
本章將介紹更多關於映像檔的內容,包括:
版本提示:映像檔儲存後端的變遷
在 Docker Engine v29 及後續版本中,Docker 在全新安裝情境預設啟用 containerd image store(替代傳統 classic store 路徑)。這一底層架構級別的變遷,意味著 Docker 解鎖了對 OCI Image Index 和 Attestations(例如原生的 provenance 來源證明與 SBOM 軟體物料清單)的全量本地支援。 讀者在執行類似
docker buildx build --provenance=mode=min --sbom=true甚至使用後續審查工具(如docker buildx imagetools inspect)時,其後設資料能夠與映像檔資料一併完好地管理於本地儲存系統中,為供應鏈安全驗證補齊了最後一塊拼圖。
4.1 獲取映像檔
從 Docker 映像檔倉庫獲取映像檔可謂是 Docker 運作的第一步。本節將介紹如何使用 docker pull 命令下載映像檔,以及如何理解下載過程。
版本號最佳實務
- 永遠指定版本號:避免使用
latest標籤,應指定具體的版本(如ubuntu:24.04、nginx:1.28),以確保映像檔內容穩定一致。- 在生產環境使用摘要:優先使用映像檔摘要(SHA256)而非標籤,如
nginx@sha256:abc123...,因為摘要不可變。- 定期評估依賴:即使指定了版本號,仍應定期檢查依賴的基礎映像檔是否有安全更新。
4.1.1 docker pull 命令
從映像檔倉庫獲取映像檔的命令是 docker pull:
docker pull [選項] [Registry地址/]倉庫名[:標籤]
映像檔名稱格式
Docker 映像檔名稱由 Registry 地址、使用者名稱、倉庫名和標籤組成。其標準格式如下:
docker.io / library / ubuntu : 24.04
────┬──── ───┬─── ──┬─── ──┬──
│ │ │ │
Registry地址 使用者名稱 倉庫名 標籤
(可省略) (可省略)
| 組成部分 | 說明 | 預設值 |
|---|---|---|
| Registry 地址 | 映像檔倉庫地址 | docker.io (Docker Hub) |
| 使用者名稱 | 映像檔所屬使用者/組織 | library (官方映像檔) |
| 倉庫名 | 映像檔名稱 | 必須指定 |
| 標籤 | 版本標識 | latest |
示例
## 完整格式
$ docker pull docker.io/library/ubuntu:24.04
## 省略 Registry(預設 Docker Hub)
$ docker pull library/ubuntu:24.04
## 省略 library(官方映像檔)
$ docker pull ubuntu:24.04
## 省略標籤(預設 latest)
$ docker pull ubuntu
## 拉取第三方映像檔
$ docker pull bitnami/redis:latest
## 從其他 Registry 拉取
$ docker pull ghcr.io/username/myapp:v1.0
4.1.2 下載過程解析
當我們執行 docker pull 命令時,Docker 會輸出詳細的下載進度。讓我們以 ubuntu:24.04 為例來解析這些資訊。
$ docker pull ubuntu:24.04
24.04: Pulling from library/ubuntu
92dc2a97ff99: Pull complete
be13a9d27eb8: Pull complete
c8299583700a: Pull complete
Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
Status: Downloaded newer image for ubuntu:24.04
docker.io/library/ubuntu:24.04
輸出解讀
| 輸出內容 | 說明 |
|---|---|
Pulling from library/ubuntu |
正在從官方 ubuntu 倉庫拉取 |
92dc2a97ff99: Pull complete |
各層的下載狀態 (顯示層 ID 前 12 位) |
Digest: sha256:... |
映像檔內容的唯一摘要 |
docker.io/library/ubuntu:24.04 |
映像檔的完整名稱 |
分層下載
從輸出可以看到,映像檔是 分層下載 的:
flowchart TD
subgraph Image ["ubuntu:24.04 映像檔"]
direction TB
L3["第3層 c8299583700a<br/>(已存在,跳過下載)"]
L2["第2層 be13a9d27eb8<br/>(下載中... 完成)"]
L1["第1層 92dc2a97ff99<br/>(下載中... 完成)"]
L3 --- L2 --- L1
end
如果本地已有相同的層,Docker 會跳過下載,節省頻寬和時間。
4.1.3 常用選項
docker pull 命令支援多種選項來滿足不同的下載需求,例如下載所有標籤、指定平臺架構等。
| 選項 | 說明 | 示例 |
|---|---|---|
--all-tags, -a |
拉取所有標籤 | docker pull -a ubuntu |
--platform |
指定平臺架構 | docker pull --platform linux/arm64 nginx |
--quiet, -q |
靜默模式 | docker pull -q nginx |
指定平臺
在 Apple Silicon Mac 上拉取 x86 映像檔:
$ docker pull --platform linux/amd64 nginx
4.1.4 拉取後執行
拉取映像檔後,可以基於它啟動容器:
## 拉取映像檔
$ docker pull ubuntu:24.04
## 執行容器
$ docker run -it --rm ubuntu:24.04 bash
root@e7009c6ce357:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
...
root@e7009c6ce357:/# exit
本例使用
ubuntu:24.04這樣的具體版本標籤是最佳實務。若無特殊需求,避免使用docker pull ubuntu或ubuntu:latest,因為映像檔內容可能在某個時刻發生變化。
引數說明:
| 引數 | 說明 |
|---|---|
-it |
互動式終端模式 |
--rm |
退出後自動刪除容器 |
bash |
啟動命令 |
💡
docker run在需要時會自動pull映像檔,因此通常不需要單獨執行docker pull。
4.1.5 映像檔加速
從 Docker Hub 下載可能較慢。可以配置映像檔加速器:
// /etc/docker/daemon.json (Linux)
// ~/.docker/daemon.json (Docker Desktop)
{
"registry-mirrors": [
"https://your-accelerator-url"
]
}
配置後重啟 Docker:
$ sudo systemctl restart docker # Linux
## 或在 Docker Desktop 中重啟
詳見映像檔加速器章節。
4.1.6 驗證映像檔完整性
為了確保下載的映像檔沒有被篡改且內容一致,我們可以校驗映像檔的摘要 (Digest)。
檢視映像檔摘要
$ docker images --digests ubuntu
REPOSITORY TAG DIGEST IMAGE ID
ubuntu 24.04 sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26 ca2b0f26964c
使用摘要拉取
用摘要拉取可確保獲取完全相同的映像檔:
$ docker pull ubuntu@sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
筆者建議:生產環境使用摘要而非標籤,因為標籤可能被覆蓋,摘要則是不可變的。
4.1.7 常見問題
在使用 docker pull 過程中,可能會遇到下載速度慢、映像檔不存在或磁碟空間不足等問題。以下是一些常見問題的排查思路。
Q:下載速度很慢
- 配置映像檔加速器
- 檢查網路連線
- 嘗試拉取更小的映像檔版本 (如
alpine變體)
Q:提示映像檔不存在
Error: pull access denied, repository does not exist
可能原因:
- 映像檔名拼寫錯誤
- 私有映像檔未登入 (需要
docker login) - 映像檔確實不存在
Q:磁碟空間不足
## 清理未使用的映像檔
$ docker image prune
## 清理所有未使用資源
$ docker system prune
4.2 列出映像檔
在下載了映像檔後,我們可以使用 docker image ls 命令列出本地主機上的映像檔。
4.2.1 基本用法
檢視本地已下載的映像檔:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183MB
nginx latest 05a60462f8ba 5 days ago 181MB
ubuntu 24.04 329ed837d508 3 days ago 78MB
ubuntu noble 329ed837d508 3 days ago 78MB
💡
docker images是docker image ls的簡寫,兩者等效。
4.2.2 輸出欄位說明
docker image ls 命令預設輸出的列表包含倉庫名、標籤、映像檔 ID、建立時間和佔用空間等資訊。
| 欄位 | 說明 |
|---|---|
| REPOSITORY | 倉庫名 |
| TAG | 標籤 (版本) |
| IMAGE ID | 映像檔唯一標識 (短 ID,前 12 位) |
| CREATED | 建立時間 |
| SIZE | 本地佔用空間 |
同一映像檔多個標籤
注意上面的 ubuntu:24.04 和 ubuntu:noble 擁有相同的 IMAGE ID——它們是同一個映像檔的不同標籤,只佔用一份儲存空間。
版本說明:
ubuntu:24.04是具體版本號,ubuntu:noble是釋出代號(Ubuntu 24.04 的代號)。在 Dockerfile 中應優先使用版本號(如ubuntu:24.04)而非釋出代號,因為版本號在將來更易理解。
4.2.3 理解映像檔大小
Docker 映像檔的大小可能與我們通常理解的檔案大小有所不同,這涉及到分層儲存的概念。
本地大小 vs Hub 顯示大小
| 位置 | 顯示大小 | 說明 |
|---|---|---|
| Docker Hub | 29MB | 壓縮後的網路傳輸大小 |
| docker image ls | 78MB | 本地解壓後的實際大小 |
實際磁碟佔用
由於映像檔是分層儲存,不同映像檔可能共享相同的層:
ubuntu:24.04 nginx:latest redis:latest
│ │ │
└───────┬───────┘ │
▼ │
共享基礎層 ◄───────────────────┘
因此,docker image ls 中各映像檔大小之和 > 實際磁碟佔用。
檢視實際空間佔用
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 15 3 2.5GB 1.8GB (72%)
Containers 5 2 100MB 80MB (80%)
Local Volumes 8 2 500MB 400MB (80%)
Build Cache 0 0 0B 0B
4.2.4 過濾映像檔
隨著本地映像檔數量的增加,我們需要更有效的方式來查詢特定的映像檔。Docker 提供了多種過濾方式。
按倉庫名過濾
## 列出所有 ubuntu 映像檔
$ docker images ubuntu
REPOSITORY TAG IMAGE ID SIZE
ubuntu 24.04 329ed837d508 78MB
ubuntu noble 329ed837d508 78MB
ubuntu 22.04 a1b2c3d4e5f6 72MB
按倉庫名和標籤過濾
$ docker images ubuntu:24.04
REPOSITORY TAG IMAGE ID SIZE
ubuntu 24.04 329ed837d508 78MB
使用過濾器 --filter
| 過濾條件 | 說明 | 示例 |
|---|---|---|
dangling=true |
虛懸映像檔 | -f dangling=true |
before=映像檔 |
在某映像檔之前建立 | -f before=nginx:latest |
since=映像檔 |
在某映像檔之後建立 | -f since=nginx:latest |
label=key=value |
按 LABEL 過濾 | -f label=version=1.0 |
reference=pattern |
按名稱模式 | -f reference='*:latest' |
## 列出 nginx 之後建立的映像檔
$ docker images -f since=nginx:latest
## 列出所有帶 latest 標籤的映像檔
$ docker images -f reference='*:latest'
## 列出帶特定 LABEL 的映像檔
$ docker images -f [email protected]
4.2.5 虛懸映像檔
在映像檔列表裡,你可能會看到一些倉庫名和標籤都為 <none> 的映像檔,這類映像檔被稱為虛懸映像檔。
什麼是虛懸映像檔
倉庫名和標籤都顯示為 <none> 的映像檔:
$ docker images
REPOSITORY TAG IMAGE ID SIZE
<none> <none> 00285df0df87 342MB
產生原因
- 映像檔重新構建:新映像檔使用了舊映像檔的標籤,舊映像檔標籤被移除
- docker pull 更新:拉取更新版本時,舊版本失去標籤
處理虛懸映像檔
## 列出虛懸映像檔
$ docker images -f dangling=true
## 刪除虛懸映像檔
$ docker image prune
4.2.6 中間層映像檔
除了虛懸映像檔,docker image ls 預設列出的只是頂層映像檔。還有一種映像檔是為了加速映像檔構建、重複利用資源而存在的中間層映像檔。
檢視所有映像檔:包含中間層
$ docker images -a
會顯示很多無標籤映像檔——這些是構建過程中產生的中間層,被其他映像檔依賴。
⚠️ 不要刪除中間層映像檔。它們是其他映像檔的依賴,刪除會導致上層映像檔無法使用。刪除頂層映像檔時會自動清理不再需要的中間層。
4.2.7 格式化輸出
為了配合指令碼使用或展示更關注的資訊,我們可以使用 --format 引數來自定義輸出格式。
只輸出 ID
$ docker images -q
5f515359c7f8
05a60462f8ba
329ed837d508
常用於配合其他命令:
## 刪除所有映像檔
$ docker rmi $(docker images -q)
## 刪除所有 redis 映像檔
$ docker rmi $(docker images -q redis)
顯示完整 ID
$ docker images --no-trunc
顯示摘要
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
自定義格式
使用 Go 模板語法自定義輸出:
## 只顯示 ID 和倉庫名
$ docker images --format "{{.ID}}: {{.Repository}}"
5f515359c7f8: redis
05a60462f8ba: nginx
329ed837d508: ubuntu
## 表格形式(帶標題)
$ docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
REPOSITORY TAG SIZE
redis latest 183MB
nginx latest 181MB
ubuntu 24.04 78MB
可用模板欄位
| 欄位 | 說明 |
|---|---|
.ID |
映像檔 ID |
.Repository |
倉庫名 |
.Tag |
標籤 |
.Digest |
摘要 |
.CreatedSince |
建立後經過的時間 |
.CreatedAt |
建立時間 |
.Size |
大小 |
4.2.8 常用命令組合
## 列出所有映像檔及其大小,按大小排序(需要系統 sort 命令)
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | sort -h
## 查詢大於 500MB 的映像檔
$ docker images --format "{{.Size}}\t{{.Repository}}:{{.Tag}}" | grep -E "^[0-9]+GB|^[5-9][0-9]{2}MB"
## 匯出映像檔列表
$ docker images --format "{{.Repository}}:{{.Tag}}" > images.txt
4.3 刪除本地映像檔
當不再需要某個映像檔時,我們可以將其刪除以釋放儲存空間。本節介紹刪除映像檔的常用方法。
4.3.1 基本用法
使用 docker image rm 刪除本地映像檔:
$ docker image rm [選項] <映像檔1> [<映像檔2> ...]
💡
docker rmi是docker image rm的簡寫,兩者等效。
4.3.2 映像檔標識方式
刪除映像檔時,可以使用多種方式指定映像檔:
| 方式 | 說明 | 示例 |
|---|---|---|
| 短 ID | ID 的前幾位 (通常 3-4 位) | docker rmi 501 |
| 長 ID | 完整的映像檔 ID | docker rmi 501ad78535f0... |
| 映像檔名:標籤 | 倉庫名和標籤 | docker rmi redis:7.0 |
| 映像檔摘要 | 精確的內容摘要 | docker rmi nginx@sha256:... |
版本提示:建議使用 映像檔名:標籤 的方式刪除,特別是當需要明確清理特定版本的映像檔時。例如
docker rmi redis:7.0比docker rmi redis:latest更清晰且安全。
使用短 ID 刪除
$ docker image ls
REPOSITORY TAG IMAGE ID SIZE
redis alpine 501ad78535f0 30MB
nginx latest e43d811ce2f4 142MB
## 只需輸入足夠區分的前幾位
$ docker rmi 501
Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
使用映像檔名刪除
$ docker rmi redis:alpine
Untagged: redis:alpine
Deleted: sha256:501ad78535f0...
使用摘要刪除
摘要刪除最精確,適用於 CI/CD 情境:
## 檢視映像檔摘要
$ docker images --digests
REPOSITORY TAG DIGEST IMAGE ID
nginx latest sha256:b4f0e0bdeb5... e43d811ce2f4
## 使用摘要刪除
$ docker rmi nginx@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
4.3.3 理解輸出資訊
執行刪除命令後,Docker 會輸出一系列的操作記錄,理解這些資訊有助於我們掌握映像檔刪除的機制。
刪除映像檔時會看到兩類資訊:Untagged 和 Deleted
$ docker rmi redis:alpine
Untagged: redis:alpine
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
Untagged vs Deleted
| 操作 | 含義 |
|---|---|
| Untagged | 移除映像檔的標籤 |
| Deleted | 刪除映像檔的儲存層 |
刪除流程
Docker 會檢測映像檔是否有容器依賴或其他標籤指向,只有在確認為無用資源時才會真正刪除儲存層。
flowchart TD
Start(["docker rmi redis:alpine"]) --> Step1
subgraph Process ["刪除流程"]
direction TB
Step1["1. Untag:移除 redis:alpine 標籤"] --> Step2
Step2{"2. 檢查是否還有其他標籤指向此映像檔"}
Step2 -- "有" --> Keep1["只 Untag,不刪除"]
Step2 -- "無" --> Step3
Step3{"3. 檢查是否有容器依賴"}
Step3 -- "有" --> Error["報錯,無法刪除"]
Step3 -- "無" --> Step4
Step4{"4. 從上到下逐層刪除,檢查每層是否被其他映像檔使用"}
Step4 -- "被使用" --> Keep2["保留該層"]
Step4 -- "未使用" --> Delete["Deleted (刪除該層)"]
end
4.3.4 批次刪除
手動一個一個刪除映像檔非常繁瑣,Docker 提供了 image prune 命令和 shell 組合命令來實現批次清理。
刪除所有虛懸映像檔
虛懸映像檔 (dangling):沒有標籤的映像檔,通常是舊版本被新版本覆蓋後產生的
## 檢視虛懸映像檔
$ docker images -f dangling=true
## 刪除虛懸映像檔
$ docker image prune
## 不提示確認
$ docker image prune -f
刪除所有未使用的映像檔
## 刪除所有沒有被容器使用的映像檔
$ docker image prune -a
## 保留最近 24 小時的
$ docker image prune -a --filter "until=24h"
按條件刪除
## 刪除所有 redis 映像檔
$ docker rmi $(docker images -q redis)
## 刪除 mongo:8.0 之前的所有映像檔
$ docker rmi $(docker images -q -f before=mongo:8.0)
## 刪除某個時間之前的映像檔
$ docker image prune -a --filter "until=168h" # 7天前
4.3.5 刪除失敗的常見原因
在刪除映像檔時,Docker 可能會提示錯誤並拒絕執行。這通常是為了防止誤刪正在使用的資源。
原因一:有容器依賴
$ docker rmi nginx
Error: conflict: unable to remove repository reference "nginx"
(must force) - container abc123 is using its referenced image
解決方案:
## 方案1:先刪除依賴的容器
$ docker rm abc123
$ docker rmi nginx
## 方案2:強制刪除映像檔(容器仍可執行,但無法再建立新容器)
$ docker rmi -f nginx
原因二:多個標籤指向同一映像檔
$ docker images
REPOSITORY TAG IMAGE ID
ubuntu 24.04 ca2b0f26964c
ubuntu latest ca2b0f26964c # 同一個映像檔
$ docker rmi ubuntu:24.04
Untagged: ubuntu:24.04
## 只是移除標籤,映像檔仍存在(因為還有 ubuntu:latest 指向它)
當同一個映像檔有多個標籤時,docker rmi 只是刪除指定的標籤,不會刪除映像檔本身。
原因三:被其他映像檔依賴:中間層
$ docker rmi some_base_image
Error: image has dependent child images
中間層映像檔被其他映像檔依賴,無法刪除。需要先刪除依賴它的映像檔。
4.3.6 常用過濾條件
| 過濾條件 | 說明 | 示例 |
|---|---|---|
dangling=true |
虛懸映像檔 | -f dangling=true |
before=映像檔 |
在某映像檔之前 | -f before=mongo:3.2 |
since=映像檔 |
在某映像檔之後 | -f since=mongo:3.2 |
label=key=value |
按標籤過濾 | -f label=version=1.0 |
reference=pattern |
按名稱模式 | -f reference='*:latest' |
4.3.7 清理策略
針對不同的環境 (開發環境 vs 生產環境),我們應該採用不同的映像檔清理策略。
開發環境
## 定期清理虛懸映像檔
$ docker image prune -f
## 一鍵清理所有未使用資源
$ docker system prune -a
CI/CD 環境
## 只保留最近使用的映像檔
$ docker image prune -a --filter "until=72h" -f
檢視空間佔用
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 15 3 2.5GB 1.8GB (72%)
Containers 5 2 100MB 80MB (80%)
Local Volumes 8 2 500MB 400MB (80%)
Build Cache 0 0 0B 0B
4.4 利用 commit 理解映像檔構成
注意:如果你是初學者,可以暫時跳過後面的內容,直接學習容器一節。
docker commit 除了幫助理解映像檔分層之外,在少數情境下也可用於留存現場,例如事後分析被入侵容器的狀態;但是,日常定製映像檔不應依賴 docker commit,而應使用下一節介紹的 Dockerfile。
映像檔是容器的基礎,每次執行 docker run 的時候都會指定哪個映像檔作為容器執行的基礎。在之前的例子中,我們所使用的都是來自於 Docker Hub 的映像檔。直接使用這些映像檔是可以滿足一定的需求,而當這些映像檔無法直接滿足需求時,我們就需要定製這些映像檔。接下來的幾節就將講解如何定製映像檔。
回顧一下之前我們學到的知識,映像檔是多層儲存,每一層是在前一層的基礎上進行的修改;而容器同樣也是多層儲存,它以映像檔層為只讀基礎,並在最上方增加一層供執行時寫入的容器層。
現在讓我們以定製一個 Web 伺服器為例子,來講解映像檔是如何構建的。
版本提示:以下示例中
nginx映像檔使用預設latest標籤。生產環境建議指定具體版本號(如nginx:1.28),以避免映像檔更新帶來的不相容性。
$ docker run --name webserver -d -p 8080:80 nginx
這條命令會用 nginx 映像檔啟動一個容器,命名為 webserver。其中,-p 8080:80 表示把宿主機的 8080 埠對映到容器內的 80 埠,這樣我們就可以用瀏覽器去訪問這個 nginx 伺服器。
如果是在本機執行的 Docker,那麼可以直接訪問:http://localhost:8080;如果是在虛擬機器、雲伺服器上安裝的 Docker,則需要將 localhost 換為虛擬機器地址或者實際雲伺服器地址,並保留 8080 埠。
直接用瀏覽器訪問的話,我們會看到預設的 Nginx 歡迎頁面。

現在,假設我們非常不喜歡這個歡迎頁面,我們希望改成歡迎 Docker 的文字,我們可以使用 docker exec 命令進入容器,修改其內容。
$ docker exec -it webserver bash
root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit
我們以互動式終端方式進入 webserver 容器,並執行了 bash 命令,也就是獲得一個可操作的 Shell。
然後,我們用 <h1>Hello, Docker!</h1> 覆蓋了 /usr/share/nginx/html/index.html 的內容。
現在我們再重新整理瀏覽器的話,會發現內容被改變了。

我們修改了容器的檔案,也就是改動了容器的儲存層。我們可以透過 docker diff 命令看到具體的改動。
$ docker diff webserver
C /root
A /root/.bash_history
C /run
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp
其中,A 表示新增(Added),C 表示變更(Changed),D 表示刪除(Deleted)。
現在我們定製好了變化,我們希望能將其儲存下來形成映像檔。
要知道,當我們執行一個容器的時候 (如果不使用卷的話),我們做的任何檔案修改都會被記錄於容器儲存層裡。而 Docker 提供了一個 docker commit 命令,可以將容器的儲存層儲存下來成為映像檔。換句話說,就是在原有映像檔的基礎上,再疊加上容器的儲存層,並構成新的映像檔。以後我們執行這個新映像檔的時候,就會擁有原有容器最後的檔案變化。
docker commit 的語法格式為:
docker commit [選項] <容器ID或容器名> [<倉庫名>[:<標籤>]]
我們可以用下面的命令將容器儲存為映像檔。預設情況下,docker commit 會在提交時暫停容器程序,以降低資料損壞的風險;如果確實不希望暫停,可以顯式指定 --no-pause:
$ docker commit \
--author "Tao Wang <[email protected]>" \
--message "修改了預設網頁" \
webserver \
nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214
其中 --author 是指定修改的作者,而 --message 則是記錄本次修改的內容。這點和 git 版本控制相似,不過這裡這些資訊可以省略留空。
我們可以在 docker image ls 中看到這個新定製的映像檔。下面的輸出僅為示例,標籤、建立時間和大小會隨著映像檔版本和本地環境不同而變化:
$ docker image ls nginx
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v2 07e334659748 9 seconds ago 181.5 MB
nginx 1.30 05a60462f8ba 12 days ago 181.5 MB
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB
版本說明:上面示例中
nginx:1.30代表 1.30 系列的最新 patch 版本。在實際應用中應根據需求選擇確切的版本號,而不是盲目使用latest。
我們還可以用 docker history 具體檢視映像檔內的歷史記錄。例如先執行 docker history nginx:v2,再對比 docker history nginx:latest,就能看到我們剛剛提交出來的新層。
$ docker history nginx:v2
IMAGE CREATED CREATED BY SIZE COMMENT
07e334659748 54 seconds ago nginx -g daemon off; 95 B 修改了預設網頁
e43d811ce2f4 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 443/tcp 80/tcp 0 B
<missing> 4 weeks ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx/ 22 B
<missing> 4 weeks ago /bin/sh -c apt-key adv --keyserver hkp://pgp. 58.46 MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.27.0-1 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) MAINTAINER NGINX Docker Ma 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:23aa4f893e3288698c 123 MB
新的映像檔定製好後,我們可以來執行這個映像檔。
$ docker run --name web2 -d -p 81:80 nginx:v2
這裡我們將新容器命名為 web2,並把宿主機的 81 埠對映到容器的 80 埠。訪問 http://localhost:81 後,看到的內容應該和之前修改後的 webserver 一樣。
至此,我們第一次完成了定製映像檔,使用的是 docker commit 命令,手動操作給舊的映像檔新增了新的一層,形成新的映像檔,對映像檔多層儲存應該有了更直觀的感覺。
4.4.1 慎用 docker commit
使用 docker commit 命令雖然可以比較直觀地幫助理解映像檔分層儲存的概念,但它不應作為常規定製映像檔的方式。
首先,如果仔細觀察之前的 docker diff webserver 的結果,你會發現除了真正想要修改的 /usr/share/nginx/html/index.html 檔案外,由於命令的執行,還有很多檔案被改動或新增了。這還僅僅是最簡單的操作,如果是安裝軟體包、編譯構建,那會有大量的無關內容被新增進來,將會導致映像檔極為臃腫。
此外,使用 docker commit 意味著所有對映像檔的操作都是黑箱操作,生成的映像檔也被稱為 黑箱映像檔,換句話說,就是除了製作映像檔的人知道執行過什麼命令、怎麼生成的映像檔,別人根本無從得知。而且,即使是這個製作映像檔的人,過一段時間後也無法記清具體的操作。這種黑箱映像檔的維護工作是非常痛苦的。
而且,回顧之前提及的映像檔所使用的分層儲存的概念,除當前層外,之前的每一層都不會發生改變。換句話說,任何修改的結果僅僅是在當前層進行標記、新增、修改,而不會改動上一層。如果使用 docker commit 製作映像檔,以及後期繼續修改,那麼每一次修改都會讓映像檔再膨脹一層;即使某些檔案在更高層裡被刪除了,它們仍然存在於更低層中,只是在最終檢視裡被隱藏而已。因此,日常定製映像檔應使用下一節介紹的 Dockerfile,把構建過程寫成可重複執行、便於審查的文字。
4.5 使用 Dockerfile 定製映像檔
從剛才的 docker commit 的學習中,我們可以瞭解到,映像檔的定製實際上就是定製每一層所新增的配置、檔案。如果我們可以把每一層修改、安裝、構建、操作的命令都寫入一個指令碼,用這個指令碼來構建、定製映像檔,那麼之前提及的無法重複、映像檔構建不透明、體積難以控制等問題就會更容易解決。這個指令碼就是 Dockerfile。
Dockerfile 是一個文字檔案,其內包含了一條條的 指令 (Instruction)。其中,會修改檔案系統的指令通常會建立新層;而 LABEL、CMD 這類只修改映像檔後設資料的指令,則不會新增檔案系統層。每一條指令的內容,都是在描述該映像檔應當如何構建。
4.5.1 使用 docker init 快速建立:推薦
Docker 提供了 docker init 命令,可以根據專案型別自動生成 Dockerfile、.dockerignore、compose.yaml 和 README.Docker.md 等檔案:
$ docker init
該命令會互動式地詢問專案型別(支援 Go、Node.js、Python、Rust、Java、ASP.NET Core、PHP with Apache 等),並生成可作為起點的配置檔案。對於新專案,這是一個很好的起步方式,但生成後的內容仍應結合專案實際情況繼續調整。
4.5.2 手動建立 Dockerfile
還以之前定製 nginx 映像檔為例,這次我們使用 Dockerfile 來定製。
在一個空白目錄中,建立一個文字檔案,並命名為 Dockerfile:
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile
其內容為:
版本提示:下面示例中
FROM nginx使用的是latest標籤。在實際應用中應使用明確的版本號(如FROM nginx:1.28),以確保 Dockerfile 的可重現性和穩定性。
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
這個 Dockerfile 很簡單,一共就兩行。涉及到了兩條指令,FROM 和 RUN。
4.5.3 FROM 指定基礎映像檔
所謂定製映像檔,那一定是以一個映像檔為基礎,在其上進行定製。就像我們之前執行了一個 nginx 映像檔的容器,再進行修改一樣,基礎映像檔是必須指定的。而 FROM 就是指定 基礎映像檔,因此一個 Dockerfile 中 FROM 是必備的指令,並且必須是第一條指令。
版本號最佳實務:在
FROM指令中 務必指定具體版本號(如FROM ubuntu:24.04或FROM python:3.12-slim)而非FROM ubuntu或FROM python:latest。這樣可以確保 Dockerfile 在不同時間、不同環境下構建出的映像檔內容一致,避免因基礎映像檔更新導致的不可預期的變化。
在 Docker Hub 上有非常多的高質量的官方映像檔,有可以直接拿來使用的服務類的映像檔,如 nginx、redis、mongo、mysql、httpd、php、tomcat 等;也有一些方便開發、構建、執行各種語言應用的映像檔,如 node、openjdk、python、ruby、golang 等。可以在其中尋找一個最符合我們最終目標的映像檔為基礎映像檔進行定製。
如果沒有找到對應服務的映像檔,官方映像檔中還提供了一些更為基礎的作業系統映像檔,如 ubuntu、debian、centos、fedora、alpine 等,這些作業系統的軟體庫為我們提供了更廣闊的擴充套件空間。
除了選擇現有映像檔為基礎映像檔外,Docker 還存在一個特殊的映像檔,名為 scratch。這個映像檔是虛擬的概念,並不實際存在,它表示一個空白的映像檔。
FROM scratch
...
如果你以 scratch 為基礎映像檔的話,意味著你不以任何映像檔為基礎,接下來所寫的指令將作為映像檔第一層開始存在。
不以任何系統為基礎,直接將可執行檔案複製進映像檔的做法並不罕見,對於 Linux 下靜態編譯的程式來說,並不需要有作業系統提供執行時支援,所需的一切庫都已經在可執行檔案裡了,因此直接 FROM scratch 會讓映像檔體積更加小巧。使用 Go 語言開發的應用很多會使用這種方式來製作映像檔,這也是有人認為 Go 是特別適合容器微服務架構的語言的原因之一。
4.5.4 RUN 執行命令
RUN 指令是用來執行命令列命令的。由於命令列的強大能力,RUN 指令在定製映像檔時是最常用的指令之一。其格式有兩種:
- shell 格式:
RUN <命令>,就像直接在命令列中輸入的命令一樣。剛才寫的 Dockerfile 中的RUN指令就是這種格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
- exec 格式:
RUN ["可執行檔案", "引數1", "引數2"],這更像是函式呼叫中的格式。
在會修改檔案系統的指令裡,RUN 是最典型的一類。每一個 RUN 的行為,都可以類比為我們剛才手工建立映像檔的過程:先基於當前結果啟動一個臨時構建環境,在其上執行這些命令,再把這一步產生的檔案系統變化儲存為新的結果層。
注意
每一個
RUN指令都會產生一個新的映像檔層。為了減少映像檔體積和層數,我們通常會將多個命令合併到一個RUN指令中執行。更多關於
RUN指令的詳細用法、最佳實務 (如清理快取、使用 pipefail 等) 及Union FS的層數限制等內容,請參閱 第七章 Dockerfile 指令詳解 中的 RUN 指令 小節。
要想編寫優秀的 Dockerfile,必須瞭解每一條指令的作用和副作用。在 第七章 Dockerfile 指令詳解 中,我們將對 COPY,ADD,CMD,ENTRYPOINT 等指令進行詳細講解。
4.5.5 構建映像檔
好了,讓我們再回到之前定製的 nginx 映像檔的 Dockerfile 來。現在我們明白了這個 Dockerfile 的內容,那麼讓我們來構建這個映像檔吧。
在 Dockerfile 檔案所在目錄執行:
$ docker build -t nginx:v3 .
在當前版本的 Docker 中,docker build 預設會透過 Buildx 呼叫 BuildKit,因此你更常看到的是 [+] Building ... 這類輸出。為了幫助理解“每一步如何形成映像檔歷史”,下面仍展示一種較容易閱讀的經典輸出形式:
Sending build context to Docker daemon 2.048 kB
Step 1 : FROM nginx
---> e43d811ce2f4
Step 2 : RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
---> Running in 9cdc27646c7b
---> 44aa4490ce2c
Removing intermediate container 9cdc27646c7b
Successfully built 44aa4490ce2c
從命令的輸出結果中,我們可以清晰的看到映像檔的構建過程。在 Step 2 中,如同我們之前所說的那樣,RUN 指令啟動了一個容器 9cdc27646c7b,執行了所要求的命令,並最後提交了這一層 44aa4490ce2c,隨後刪除了所用到的這個容器 9cdc27646c7b。
這裡我們使用了 docker build 命令進行映像檔構建。其格式為:
docker build [選項] <上下文路徑/URL/->
在這裡我們指定了最終映像檔的名稱 -t nginx:v3,構建成功後,我們可以像之前執行 nginx:v2 那樣來執行這個映像檔,其結果會和 nginx:v2 一樣。
4.5.6 映像檔構建上下文
如果注意,會看到 docker build 命令最後有一個 .。. 表示當前目錄,而 Dockerfile 就在當前目錄,因此不少初學者以為這個路徑是在指定 Dockerfile 所在路徑,這麼理解其實是不準確的。如果對應上面的命令格式,你可能會發現,這是在指定 上下文路徑。那麼什麼是上下文呢?
首先要理解 docker build 的工作原理。今天的 docker build 預設會透過 Buildx 向 BuildKit 後端發起構建請求;無論後端執行在本機還是遠端,位置引數指定的都是 構建上下文,也就是構建器可以訪問到的檔案集合。
當我們進行映像檔構建的時候,並非所有定製都會透過 RUN 指令完成,經常還需要把本地檔案複製進映像檔,比如透過 COPY 指令、ADD 指令等。因此,構建器必須能夠訪問這些檔案,而它能訪問的範圍正是你傳給 docker build 的那個上下文。
如果上下文是本地目錄,那麼這個目錄中的檔案和子目錄就會成為可用輸入;如果上下文是遠端 Git 倉庫或 tar 包,那麼構建器會直接獲取對應內容。對於本地目錄,BuildKit 會按需讀取構建過程中真正需要的檔案,而不是讓 Dockerfile 任意訪問宿主機上的任意路徑。
如果在 Dockerfile 中這麼寫:
COPY ./package.json /app/
這並不是要複製執行 docker build 命令所在的目錄下的 package.json,也不是複製 Dockerfile 所在目錄下的 package.json,而是複製 上下文 (context) 目錄下的 package.json。
因此,COPY 這類指令中的原始檔路徑都應該以構建上下文為基準來理解。對於 legacy builder,像 COPY ../package.json /app 這樣的寫法會直接報錯;而在 BuildKit 下,前導的越界 ../ 會被剝離並重新解釋為上下文內路徑。無論是哪種情況,構建器都無法讀取上下文之外的宿主機檔案;如果真的需要那些檔案,應該先把它們放進上下文目錄,或重新選擇合適的上下文。
現在就可以理解剛才的命令 docker build -t nginx:v3 . 中的這個 .,實際上是在指定上下文目錄,而不是單純指定 Dockerfile 所在目錄。
如果觀察 docker build 的經典輸出,或 BuildKit 輸出中的 transferring context 提示,我們其實都能看到上下文傳輸的過程:
$ docker build -t nginx:v3 .
Sending build context to Docker daemon 2.048 kB
...
理解構建上下文對於映像檔構建是很重要的,避免犯一些不應該的錯誤。比如有些初學者在發現需要的檔案不在上下文裡後,乾脆把上下文切到硬碟根目錄去構建。這樣做即使在 BuildKit 下也會讓可見上下文變得過大,並且在使用 COPY . .、ADD . /app 之類寫法時,仍可能觸發大規模上下文傳輸,導致構建緩慢甚至失敗。這顯然是使用錯誤。
一般來說,應該會將 Dockerfile 置於一個空目錄下,或者專案根目錄下。如果該目錄下沒有所需檔案,那麼應該把所需檔案複製一份過來。如果目錄下有些東西確實不希望構建時傳給 Docker 引擎,那麼可以用 .gitignore 一樣的語法寫一個 .dockerignore,該檔案是用於剔除不需要作為上下文傳遞給 Docker 引擎的。
那麼為什麼會有人誤以為 . 是指定 Dockerfile 所在目錄呢?這是因為在預設情況下,如果不額外指定 Dockerfile 的話,會將上下文目錄下的名為 Dockerfile 的檔案作為 Dockerfile。
這只是預設行為,實際上 Dockerfile 的檔名並不要求必須為 Dockerfile,而且並不要求必須位於上下文目錄中,比如可以用 -f ../Dockerfile.php 引數指定某個檔案作為 Dockerfile。
當然,一般大家習慣性的會使用預設的檔名 Dockerfile,以及會將其置於映像檔構建上下文目錄中。
4.5.7 其它 docker build 的用法
直接用 Git repo 進行構建
或許你已經注意到了,docker build 還支援從 URL 構建,也就是直接把遠端 Git 倉庫作為上下文。傳統寫法可以使用 URL 片段 #ref:dir,例如:
$ docker build https://github.com/user/myrepo.git#mybranch:docker
這行命令表示:把 Git 倉庫作為構建上下文,使用 mybranch 分支中的 docker/ 子目錄來構建。在較新的 Buildx 中,也可以改用結構更清晰的查詢引數寫法,例如 ?branch=mybranch&subdir=docker。
用給定的 tar 壓縮包構建
$ docker build http://server/context.tar.gz
如果所給出的 URL 不是個 Git repo,而是個 tar 壓縮包,那麼 Docker 引擎會下載這個包,並自動解壓縮,以其作為上下文,開始構建。
從標準輸入中讀取 Dockerfile 進行構建
docker build - < Dockerfile
或
cat Dockerfile | docker build -
如果標準輸入傳入的是文字檔案,則將其視為 Dockerfile,並開始構建。這種形式由於直接從標準輸入中讀取 Dockerfile 的內容,它沒有上下文,因此不可以像其他方法那樣可以將本地檔案 COPY 進映像檔之類的事情。
從標準輸入中讀取上下文壓縮包進行構建
$ docker build - < context.tar.gz
如果發現標準輸入的檔案格式是 gzip、bzip2 以及 xz 的話,將會使其為上下文壓縮包,直接將其展開,將裡面視為上下文,並開始構建。
4.6 其它製作映像檔的方式
除了標準的使用 Dockerfile 生成映像檔的方法外,由於各種特殊需求和歷史原因,還提供了一些其它方法用以生成映像檔。
4.6.1 從 rootfs 壓縮包匯入
格式:docker import [選項] <檔案>|<URL>|- [<倉庫名>[:<標籤>]]
壓縮包可以是本地檔案、遠端 Web 檔案,甚至是從標準輸入中得到。壓縮包將會在映像檔 / 目錄展開,並直接作為映像檔第一層提交。
比如我們想要建立一個 OpenVZ 的 Ubuntu 16.04 模板的映像檔:
版本提示:
noble對應 Ubuntu 24.04 LTS。實際用於生產環境時,應選擇仍在安全維護期內的發行版,並按團隊的基礎映像檔更新策略定期重建。
$ docker import \
http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz \
openvz/ubuntu:16.04
Downloading from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
sha256:412b8fc3e3f786dca0197834a698932b9c51b69bd8cf49e100c35d38c9879213
這條命令自動下載了 ubuntu-16.04-x86_64.tar.gz 檔案,並且作為根檔案系統展開匯入,並儲存為映像檔 openvz/ubuntu:16.04。
匯入成功後,我們可以用 docker image ls 看到這個匯入的映像檔:
$ docker image ls openvz/ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
openvz/ubuntu 16.04 412b8fc3e3f7 55 seconds ago 505MB
如果我們檢視其歷史的話,會看到描述中有匯入的檔案連結:
$ docker history openvz/ubuntu:16.04
IMAGE CREATED CREATED BY SIZE COMMENT
f477a6e18e98 About a minute ago 214.9 MB Imported from http://download.openvz.org/template/precreated/ubuntu-16.04-x86_64.tar.gz
4.6.2 Docker 映像檔的匯入和匯出 docker save 和 docker load
Docker 還提供了 docker save 和 docker load 命令,用以將映像檔儲存為一個檔案,然後傳輸到另一個位置上,再載入進來。這是在沒有 Docker Registry 時的做法,現在已經不推薦,映像檔遷移應該直接使用 Docker Registry,無論是直接使用 Docker Hub 還是使用內網私有 Registry 都可以。
儲存映像檔
使用 docker save 命令可以將映像檔儲存為歸檔檔案。
比如我們希望儲存這個 alpine 映像檔。
$ docker image ls alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest baa5d63471ea 5 weeks ago 4.803 MB
版本提示:
alpine:latest為最新版本的 Alpine Linux。如果需要特定版本號(如alpine:3.20),可以明確指定以確保可重現性。 儲存映像檔的命令為:
$ docker save alpine -o filename
$ file filename
filename: POSIX tar archive
這裡的 filename 可以為任意名稱甚至任意字尾名,但檔案的本質都是歸檔檔案
注意:如果同名則會覆蓋 (沒有警告)
若使用 gzip 壓縮:
$ docker save alpine | gzip > alpine-latest.tar.gz
然後我們將 alpine-latest.tar.gz 檔案複製到了到了另一個機器上,可以用下面這個命令載入映像檔:
$ docker load -i alpine-latest.tar.gz
Loaded image: alpine:latest
如果我們結合這兩個命令以及 ssh 甚至 pv 的話,利用 Linux 強大的管道,我們可以寫一個命令完成從一個機器將映像檔遷移到另一個機器,並且帶進度條的功能:
docker save <映像檔名> | bzip2 | pv | ssh <使用者名稱>@<主機名> 'cat | docker load'
4.7 實現原理
Docker 映像檔是怎麼實現增量的修改和維護的?為什麼容器啟動如此之快?這一切都歸功於 Docker 的映像檔分層儲存設計。
4.7.1 映像檔與分層儲存
在之前的章節中,我們一直強調映像檔包含作業系統完整的 root 檔案系統,其體積往往是龐大的。因此在 Docker 設計時,就充分利用 Union FS 的技術,將其設計為分層儲存的架構。
Docker 映像檔並不是一個單純的檔案,而是由一組檔案系統疊加構成的。
最底層的映像檔稱為 基礎映像檔 (Base Image),通常是各種 Linux 發行版的 root 檔案系統,如 Ubuntu、Debian、CentOS 等。
當我們在基礎映像檔之上構建新的映像檔時 (例如安裝了 Nginx),Docker 並不是複製一份基礎映像檔,而是在基礎映像檔之上,新建一個層 (Layer),並在該層中僅記錄為了安裝 Nginx 而發生的檔案變更 (新增、修改、刪除)。
這種分層儲存結構使得映像檔的複用、分發變得非常高效:
- 複用:如果多個映像檔都基於同一個基礎映像檔 (例如都基於
ubuntu:24.04),那麼宿主機只需要下載一份ubuntu:24.04,所有映像檔都可以共享它。 - 輕量分發:映像檔可以複用已有層,只傳輸和儲存新增差異層;不過映像檔是否足夠小,仍然取決於基礎映像檔和新增內容本身。
4.7.2 容器層與讀寫
我們要理解的一個關鍵概念是:映像檔的每一層都是隻讀的 (Read-only)。
那麼,既然映像檔只讀,容器為什麼能寫檔案呢?
當容器啟動時,Docker 會在映像檔的最上層,新增一個新的 可寫層 (Writable Layer),通常被稱為 容器層。
flowchart TD
subgraph Container ["執行中的容器"]
direction TB
L4["容器層 (可寫, Writable Container Layer)"]
L3["映像檔層 (只讀, Read-only Image Layer)"]
L2["映像檔層 (只讀, Read-only Image Layer)"]
L1["基礎映像檔層 (只讀, Base Image Layer)"]
L4 --- L3 --- L2 --- L1
end
Note["所有的寫操作都在容器層這裡"] -.-> L4
- 讀取檔案:當容器需要讀取檔案時,Docker 會從最上層 (容器層) 開始向下層 (映像檔層) 尋找,直到找到該檔案為止。
- 修改檔案:當容器需要修改某個檔案時,Docker 會從下層映像檔中將該檔案複製到上層的容器層,然後對副本進行修改。這被稱為 寫時複製 (Copy-on-Write,CoW) 策略。
- 刪除檔案:當容器刪除某個檔案時,Docker 並不是真的去下層刪除它 (因為下層是隻讀的),而是在容器層建立一個特殊的 “白障 (Whiteout)” 檔案,用來標記該檔案已被刪除,從而在容器檢視中隱藏它。
這就是為什麼:
- 容器刪除後資料會丟失:因為所有的資料修改都儲存在最上層的容器層中,容器銷燬時,這個層也就隨之銷燬了。(除非使用了資料卷,詳見資料管理)。
- 映像檔不可變:無論我們在容器裡刪除了多少檔案,基礎映像檔的體積並不會減小,因為它們依然存在於底層的只讀層中。
4.7.3 內容定址與映像檔 ID
Docker 映像檔的每一層都有一個唯一的 ID,這個 ID 是根據該層的內容計算出來的雜湊值 (SHA256)。這意味著:
- 內容即 ID:只要層的內容有一丁點變化,ID 就會變。
- 安全性:確保了映像檔內容的完整性,下載過程中如果資料損壞,ID 校驗就會失敗。
- 去重:如果兩個不同的映像檔 (甚至是不同來源的映像檔) 包含相同的層 (ID 相同),Docker 引擎在本地只會儲存一份,絕不重複下載。
4.7.4 聯合檔案系統
Docker 使用聯合檔案系統 (Union FS) 與寫時複製思路來實現這種分層掛載。傳統的實現方式常見於 overlay2、aufs、btrfs、zfs 等儲存驅動;而在 Docker Engine 29.0 及之後的全新安裝中,預設映像檔後端已經變為 containerd image store,它使用 snapshotter 來管理這些層。
版本背景:Docker Engine 29.0.0 釋出於 2025 年 11 月 10 日,是一個重要版本分界點。Docker Engine 29.0 及之後的全新安裝預設使用 containerd image store;從更早版本升級的 daemon 會繼續使用 legacy graph driver,直到顯式啟用 containerd image store。Docker Desktop 4.34 及之後也預設啟用 containerd image store,實際環境仍應以當前配置為準。
雖然底層實現細節不同,但它們都遵循上述的 分層 + CoW 模型;因此,無論你看到的是 overlay2 還是 containerd snapshotter,理解映像檔層、容器層和寫時複製的方式都是一樣重要的。
想要深入瞭解 Overlay2 等檔案系統的具體實現原理,包括 WorkDir、UpperDir、LowerDir 等底層細節,請閱讀 第十二章 底層實現 中的 聯合檔案系統 章節。
本章小結
本章介紹了 Docker 映像檔的獲取、列出、刪除以及構建方式。
| 操作 | 命令 |
|---|---|
| 拉取映像檔 | docker pull 映像檔名:標籤 |
| 拉取所有標籤 | docker pull -a 映像檔名 |
| 指定平臺 | docker pull --platform linux/amd64 映像檔名 |
| 用摘要拉取 | docker pull 映像檔名@sha256:... |
| 列出所有映像檔 | docker images |
| 按倉庫名過濾 | docker images nginx |
| 列出虛懸映像檔 | docker images -f dangling=true |
| 只輸出 ID | docker images -q |
| 顯示摘要 | docker images --digests |
| 自定義格式 | docker images --format "..." |
| 檢視空間佔用 | docker system df |
| 刪除指定映像檔 | docker rmi 映像檔名:標籤 |
| 強制刪除 | docker rmi -f 映像檔名 |
| 刪除虛懸映像檔 | docker image prune |
| 刪除未使用映像檔 | docker image prune -a |
| 批次刪除 | docker rmi $(docker images -q -f ...) |
延伸閱讀
- 獲取映像檔:從 Registry 拉取映像檔
- 列出映像檔:檢視和過濾映像檔
- 刪除映像檔:清理本地映像檔
- 映像檔加速器:加速映像檔下載
- Docker Hub:官方映像檔倉庫
- 映像檔:理解映像檔概念
- 刪除容器:清理容器
- 資料卷:清理資料卷
第五章 操作容器
第五章 操作容器
容器是 Docker 又一核心概念。
簡單的說,容器是獨立執行的一個或一組應用,以及它們的執行態環境。對應的,虛擬機器可以理解為模擬執行的一整套作業系統 (提供了執行態環境和其他系統環境) 和跑在上面的應用。
本章將具體介紹如何來管理一個容器,包括建立、啟動和停止等。
版本號說明
本章示例涉及多個 Docker 映像檔,遵循以下版本號最佳實務:
- 官方映像檔(如
ubuntu、nginx、mysql):使用具體大版本號(如ubuntu:24.04、mysql:8.4)而非latest,確保示例的可重複性 - 映像檔標籤約定:
latest或v1.0.0等:帶標籤的自定義映像檔,示例中指定具體版本24.04、8.4:官方映像檔的穩定版本分支-
生產環境建議:指定確切版本號(如
nginx:1.28.0、mysql:8.4.4)而非僅大版本號 - 守護態執行
- 終止容器
- 進入容器
- 匯出和匯入容器
- 刪除容器
5.1 啟動
本節將詳細介紹 Docker 容器的啟動方式,包括新建啟動和重新啟動已停止的容器。
5.1.1 啟動方式概述
啟動容器有兩種方式:
- 新建並啟動:基於映像檔建立新容器
- 重新啟動:將已終止的容器重新執行
由於 Docker 容器非常輕量,實際使用中常常是隨時刪除和新建容器,而不是反覆重啟同一個容器。
5.1.2 新建並啟動
基本語法
docker run [選項] 映像檔 [命令] [引數...]
最簡單的例子
輸出 “Hello World” 後容器自動終止:
$ docker run ubuntu:24.04 /bin/echo 'Hello world'
Hello world
這與直接執行 /bin/echo 'Hello world' 幾乎沒有區別,但實際上已經啟動了一個完整的 Ubuntu 容器來執行這條命令。
版本說明:示例使用
ubuntu:24.04,這是最新 LTS 版本。如需其他版本,可替換為ubuntu:22.04、ubuntu:20.04等。
互動式容器
啟動一個可以互動的 bash 終端:
$ docker run -it ubuntu:24.04 /bin/bash
root@af8bae53bdd3:/#
引數說明:
| 引數 | 作用 |
|---|---|
-i |
保持標準輸入 (stdin) 開啟,允許輸入 |
-t |
分配偽終端 (pseudo-TTY),提供終端介面 |
-it |
兩者組合使用,獲得互動式終端 |
在互動模式下可以執行命令:
root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@af8bae53bdd3:/# exit # 退出容器
5.1.3 docker run 的完整流程
執行 docker run 時,Docker 在後臺完成以下操作:
flowchart TD
Cmd["docker run ubuntu:24.04 /bin/echo 'Hello'"] --> Step1
Step1{"1. 檢查本地是否有 ubuntu:24.04 映像檔"}
Step1 -- 有 --> Step1_Yes["使用本地映像檔"]
Step1 -- 無 --> Step1_No["從 Registry 下載"]
Step1_Yes --> Step2
Step1_No --> Step2
Step2["2. 建立容器<br/>• 基於映像檔的只讀層<br/>• 新增一層可讀寫層(容器儲存層)"] --> Step3
Step3["3. 配置網路<br/>• 建立虛擬網絡卡<br/>• 分配 IP 地址<br/>• 連線到 Docker 網橋"] --> Step4
Step4["4. 啟動容器,執行指定命令"] --> Step5
Step5["5. 命令執行完畢,容器停止"]
5.1.4 常用啟動選項
基礎選項
| 選項 | 說明 | 示例 |
|---|---|---|
-d |
後臺執行 (detach) | docker run -d nginx:latest |
-it |
互動式終端 | docker run -it ubuntu:24.04 bash |
--name |
指定容器名稱 | docker run --name myapp nginx:latest |
--rm |
退出後自動刪除容器 | docker run --rm ubuntu:24.04 echo hi |
埠對映
## 將容器的 80 埠對映到宿主機的 8080 埠
$ docker run -d -p 8080:80 nginx:latest
## 隨機對映埠
$ docker run -d -P nginx:latest
## 只繫結到 localhost
$ docker run -d -p 127.0.0.1:8080:80 nginx:latest
資料卷掛載
## 掛載命名卷
$ docker run -v mydata:/data nginx:latest
## 掛載宿主機目錄
$ docker run -v /host/path:/container/path nginx:latest
## 只讀掛載
$ docker run -v /host/path:/container/path:ro nginx:latest
環境變數
## 設定單個環境變數
$ docker run -e MYSQL_ROOT_PASSWORD=secret mysql
## 從檔案載入環境變數
$ docker run --env-file .env myapp
資源限制
## 限制記憶體
$ docker run -m 512m nginx:latest
## 限制 CPU
$ docker run --cpus=1.5 nginx:latest
5.1.5 啟動已終止容器
使用 docker start 重新啟動已停止的容器:
## 檢視所有容器(包括已停止的)
$ docker ps -a
CONTAINER ID IMAGE STATUS NAMES
af8bae53bdd3 ubuntu Exited (0) 2 minutes ago myubuntu
## 重新啟動
$ docker start myubuntu
## 啟動並附加終端
$ docker start -ai myubuntu
5.1.6 容器內程序的特點
容器內只執行指定的應用程式及其必需資源:
root@ba267838cc1b:/# ps
PID TTY TIME CMD
1 ? 00:00:00 bash
11 ? 00:00:00 ps
可見容器中僅執行了 bash 程序。這種特點使得 Docker 對資源的利用率極高。
💡 筆者提示:容器內的 PID 1 程序很重要——它是容器的主程序,該程序退出則容器停止。詳見後臺執行章節。
5.1.7 常見問題
Q:容器啟動後立即退出
原因:主程序執行完畢或無法保持執行
## 這個容器會立即退出(echo 執行完就結束了)
$ docker run ubuntu:24.04 echo "hello"
## 解決:使用能持續執行的命令
$ docker run -d nginx:latest # nginx 是持續執行的服務
詳細解釋見後臺執行。
Q:無法連線容器內的服務
原因:未正確對映埠
## 錯誤:沒有 -p 引數,外部無法訪問
$ docker run -d nginx:latest
## 正確:對映埠
$ docker run -d -p 80:80 nginx:latest
Q:容器內修改的檔案丟失
原因:未使用資料卷,資料儲存在容器儲存層
## 使用資料卷持久化
$ docker run -v mydata:/app/data myapp
詳見資料管理。
5.2 守護態執行
在生產環境中,我們通常需要容器持續執行,不受終端關閉的影響。本節將深入講解如何讓容器在後臺執行,以及理解容器生命週期的核心概念。
5.2.1 核心概念:前臺 vs 後臺
當你在終端執行一個程式時,有兩種模式:
- 前臺執行:程式佔用當前終端,輸出直接顯示,關閉終端程式就停止
- 後臺執行:程式在後臺執行,不佔用終端,終端關閉也不影響程式
Docker 容器預設是 前臺執行 的。使用 -d (detach) 引數可以讓容器在後臺執行。
5.2.2 基本使用
前臺執行:預設
$ docker run ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
容器會把輸出的結果 (STDOUT) 列印到宿主機上面。此時:
- 終端被佔用,無法執行其他命令
- 按
Ctrl+C會終止容器 - 關閉終端視窗,容器也會停止
後臺執行:使用 -d 引數
$ docker run -d ubuntu:24.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
使用 -d 引數後:
- 容器在後臺執行
- 返回容器的完整 ID
- 終端立即釋放,可以繼續執行其他命令
- 輸出不會直接顯示 (需要用
docker logs檢視)
5.2.3 深入理解:容器為什麼會 “立即退出”?
這是初學者最常遇到的困惑。 理解這個問題,你就理解了 Docker 的核心設計理念。
很多人嘗試這樣啟動容器:
$ docker run -d ubuntu:24.04
然後用 docker ps 檢視,發現容器根本不在執行!這是為什麼?
核心原理:容器的生命週期與主程序繫結
flowchart TD
subgraph Lifecycle ["Docker 容器的生命週期 = 容器內 PID 1 程序的生命週期"]
direction LR
Start["主程序啟動"] --> Run["容器執行"]
Exit["主程序退出"] --> Stop["容器停止"]
end
當你執行 docker run -d ubuntu:24.04 時:
- 容器啟動
- 沒有指定命令,預設執行
/bin/bash - 但沒有互動式終端 (沒有
-it引數),bash 發現沒有輸入源 - bash 立即退出
- 主程序退出,容器停止
關鍵理解:
- ❌
-d引數 不是 讓容器 “一直執行” - ✅
-d引數是讓容器 “在後臺執行”,能執行多久取決於主程序
常見的 “立即退出” 情境
| 情境 | 原因 | 解決方案 |
|---|---|---|
docker run -d ubuntu |
預設 bash 無輸入立即退出 | 指定長期執行的命令 |
docker run -d nginx 後改了配置 |
配置錯誤導致 nginx 啟動失敗 | 檢視 docker logs |
| 自定義映像檔容器啟動即退 | Dockerfile 的 CMD 執行完畢 | 確保 CMD 是前臺程序 |
5.2.4 檢視後臺容器
檢視執行中的容器
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77b2dc01fe0f ubuntu:24.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright
檢視容器輸出日誌
$ docker container logs 77b2dc01fe0f
hello world
hello world
hello world
...
實時檢視日誌 (類似 tail -f):
$ docker container logs -f 77b2dc01fe0f
檢視已停止的容器
$ docker container ls -a
加上 -a 引數可以看到所有容器,包括已停止的。這對於除錯 “容器啟動即退出” 的問題非常有用。
5.2.5 最佳實務
1. 長期執行的服務使用 -d
## Web 伺服器
$ docker run -d -p 80:80 nginx:latest
## 資料庫
$ docker run -d -p 3306:3306 mysql:8.4
## 快取服務
$ docker run -d -p 6379:6379 redis:latest
版本說明:示例使用常見的標籤如
latest或穩定大版本號如mysql:8.4。具體版本可根據需求調整,生產環境建議明確指定版本號(如mysql:8.4.4)而非使用latest。
2. 除錯時先用前臺模式
當容器啟動有問題時,去掉 -d 引數 可以直接看到輸出和錯誤:
## 有問題的容器,先前臺執行看看發生了什麼
$ docker run myimage:v1.0.0
3. 使用 --rm 自動清理
對於一次性任務,使用 --rm 引數讓容器退出後自動刪除:
$ docker run --rm ubuntu:24.04 echo "Hello, World!"
Hello, World!
## 容器執行完後自動刪除
...
4. 配合日誌檢視
## 後臺啟動
$ docker run -d --name myapp myimage:v1.0.0
## 檢視最近 100 行日誌
$ docker logs --tail 100 myapp
## 實時跟蹤日誌
$ docker logs -f myapp
## 檢視帶時間戳的日誌
$ docker logs -t myapp
5.2.6 常見問題排查
Q:容器啟動後立即退出
- 檢視退出狀態碼: ```bash $ docker ps -a --filter "name=mycontainer" # 檢視 STATUS 列,如 “Exited (1)” 表示異常退出
```
-
檢視容器日誌:
bash $ docker logs mycontainer -
以互動模式除錯:
bash # /bin/sh 覆蓋映像檔原本的啟動命令,避免容器再次崩潰退出 # 進入 shell 後可手動執行原啟動命令,定位具體報錯原因 $ docker run -it myimage:v1.0.0 /bin/sh
Q:容器在後臺執行但無法訪問服務
-
檢查埠對映:
bash $ docker port mycontainer -
檢查容器內服務狀態:
bash $ docker exec mycontainer ps aux
Q:如何讓已經在後臺執行的容器回到前臺?
使用 docker attach:
$ docker attach mycontainer
注意:
attach會連線到容器的主程序。如果主程序不是互動式的,你可能只能看到輸出。使用Ctrl+PCtrl+Q可以安全退出而不停止容器。
5.2.7 延伸閱讀
- 進入容器:如何進入正在執行的容器執行命令
- 容器日誌:生產環境的日誌管理最佳實務
- HEALTHCHECK 健康檢查:自動檢測容器內服務是否正常
- Docker Compose:管理多個後臺容器的更好方式
5.3 終止
本節將介紹如何終止一個執行中的容器,以及幾種不同的終止方式及其區別。
5.3.1 終止方式概述
終止容器有三種方式:
| 方式 | 命令 | 說明 |
|---|---|---|
| 優雅停止 | docker stop |
先發 SIGTERM,超時後發 SIGKILL |
| 強制停止 | docker kill |
直接發 SIGKILL |
| 自動終止 | - | 容器主程序退出時自動停止 |
5.3.2 docker stop:推薦
docker stop 基本用法
$ docker stop 容器名或ID
工作原理
flowchart TD
cmd["docker stop mycontainer"] --> A["1. 傳送 SIGTERM 訊號給容器主程序 (PID 1)"]
A --> B["2. 等待容器優雅退出 (預設 10 秒)"]
B --> C["3. 如果超時仍未退出,傳送 SIGKILL 強制終止"]
自定義超時時間
## 等待 30 秒後強制終止
$ docker stop -t 30 mycontainer
## 立即傳送 SIGKILL(相當於 docker kill)
$ docker stop -t 0 mycontainer
停止多個容器
## 停止多個指定容器
$ docker stop container1 container2 container3
## 停止所有執行中的容器
$ docker stop $(docker ps -q)
5.3.3 docker kill
基本用法
$ docker kill 容器名或ID
與 stop 的區別
| 命令 | 訊號 | 使用情境 |
|---|---|---|
docker stop |
SIGTERM → SIGKILL | 正常停止,讓應用優雅退出 |
docker kill |
SIGKILL | 應用無響應,強制終止 |
傳送自定義訊號
## 傳送 SIGHUP(讓程序重新載入配置)
$ docker kill -s HUP mycontainer
## 傳送 SIGTERM
$ docker kill -s TERM mycontainer
5.3.4 容器自動終止
容器的生命週期與主程序繫結。主程序退出時,容器自動停止:
## 主程序是互動式 bash
$ docker run -it ubuntu bash
root@abc123:/# exit # 退出 bash → 容器停止
## 主程序執行完畢
$ docker run ubuntu echo "Hello" # echo 執行完 → 容器停止
5.3.5 檢視已停止的容器
$ docker ps -a
CONTAINER ID IMAGE COMMAND STATUS NAMES
ba267838cc1b ubuntu "/bin/bash" Exited (0) 2 minutes ago myubuntu
c5d3a5e8f7b2 nginx "nginx" Up 5 minutes mynginx
STATUS 欄位說明:
| 狀態 | 說明 |
|---|---|
Up X minutes |
執行中 |
Exited (0) |
正常退出 (退出碼 0) |
Exited (1) |
異常退出 (非零退出碼) |
Exited (137) |
被 SIGKILL 終止 (128 + 9) |
Exited (143) |
被 SIGTERM 終止 (128 + 15) |
5.3.6 重新啟動容器
啟動已停止的容器
$ docker start 容器名或ID
## 啟動並附加終端
$ docker start -ai 容器名
重啟執行中的容器
## 先停止再啟動
$ docker restart 容器名
## 自定義停止超時
$ docker restart -t 30 容器名
5.3.7 生命週期狀態圖
stateDiagram-v2
direction TB
[*] --> Created : docker create
Created --> Running : docker start
Running --> Stopped : docker stop
Running --> Paused : docker pause
Paused --> Running : docker unpause
Created --> Deleted : docker rm
Stopped --> Deleted : docker rm
Paused --> Deleted : docker rm
Deleted --> [*]
5.3.8 批次操作
停止所有容器
$ docker stop $(docker ps -q)
刪除所有已停止的容器
$ docker container prune
停止並刪除所有容器
$ docker stop $(docker ps -q) && docker container prune -f
5.3.9 常見問題
Q:容器停止很慢
原因:應用沒有正確處理 SIGTERM 訊號,需要等待超時後強制終止。
解決方案:
- 在應用中正確處理 SIGTERM
- 使用
docker stop -t 0立即終止 - 檢查 Dockerfile 中的
STOPSIGNAL配置
Q:如何讓容器優雅退出
確保容器主程序正確處理訊號:
## Dockerfile 示例
FROM node:22-alpine
...
## 使用 exec 形式確保訊號能傳遞給 node 程序
CMD ["node", "server.js"]
版本說明:示例使用
node:22-alpine,這是一個精簡的 Node.js 22 版本映像檔。可根據需求替換為其他版本(如node:24-alpine、node:latest)。
Q:容器無法停止
## 強制終止
$ docker kill 容器名
## 如果仍無法停止,檢查系統資源
$ docker inspect 容器名
5.4 進入容器
5.4.1 為什麼需要進入容器
使用 -d 引數啟動容器後,容器在後臺執行。以下情境需要進入容器內部操作:
| 情境 | 示例 |
|---|---|
| 除錯問題 | 檢視日誌、檢查配置、排查錯誤 |
| 臨時操作 | 執行資料庫遷移、清理快取 |
| 檢查狀態 | 檢視程序、網路連線、檔案系統 |
| 開發測試 | 互動式測試命令、驗證環境 |
5.4.2 兩種進入方式
Docker 提供兩種進入容器的命令:
| 命令 | 推薦程度 | 特點 |
|---|---|---|
docker exec |
✅ 推薦 | 啟動新程序,退出不影響容器 |
docker attach |
⚠️ 謹慎使用 | 附加到主程序,退出可能停止容器 |
5.4.3 docker exec:推薦
docker exec 基本用法
## 進入容器並啟動互動式 shell
$ docker exec -it 容器名 /bin/bash
## 或使用 sh(適用於 Alpine 等精簡映像檔)
$ docker exec -it 容器名 /bin/sh
引數說明
| 引數 | 作用 |
|---|---|
-i |
保持標準輸入開啟 (interactive) |
-t |
分配偽終端 (TTY) |
-it |
兩者組合,獲得完整互動體驗 |
-u |
指定使用者 (如 -u root) |
-w |
指定工作目錄 |
-e |
設定環境變數 |
docker exec 示例
## 啟動一個後臺容器
$ docker run -dit --name myubuntu ubuntu
69d137adef7a...
## 進入容器(互動式 shell)
$ docker exec -it myubuntu bash
root@69d137adef7a:/# ls
bin boot dev etc home lib ...
root@69d137adef7a:/# exit
## 容器仍在執行!
$ docker ps
CONTAINER ID IMAGE STATUS NAMES
69d137adef7a ubuntu Up 2 minutes myubuntu
執行單條命令
不進入互動模式,直接執行命令:
## 檢視容器內程序
$ docker exec myubuntu ps aux
## 檢視配置檔案
$ docker exec myubuntu cat /etc/nginx/nginx.conf
## 以 root 使用者執行
$ docker exec -u root myubuntu apt update
只用 -i 不用 -t 的區別
## 只用 -i:可以執行命令,但沒有提示符
$ docker exec -i myubuntu bash
ls # 輸入命令
bin # 輸出結果
boot
dev
...
## 用 -it:有完整的終端體驗
$ docker exec -it myubuntu bash
root@69d137adef7a:/# # 有提示符
💡 通常使用
-it組合。只有在指令碼中需要透過管道傳入命令時才只用-i。
5.4.4 docker attach:謹慎使用
docker attach 基本用法
$ docker attach 容器名
工作原理
attach 會附加到容器的 主程序 (PID 1) 的標準輸入輸出:
flowchart LR
subgraph Container ["容器"]
direction TB
subgraph Process ["主程序"]
P1["PID 1: /bin/bash<br>(你的輸入直接傳送到主程序)"]
end
end
Attach["docker attach"] -->|"附加到這裡"| P1
docker attach 示例
## 啟動容器
$ docker run -dit --name myubuntu ubuntu
243c32535da7...
## 附加到容器
$ docker attach myubuntu
root@243c32535da7:/#
⚠️ 重要警告
從 attach 會話中輸入 exit 或按 Ctrl+D 會導致容器停止!
$ docker attach myubuntu
root@243c32535da7:/# exit # 這會停止容器!
$ docker ps
CONTAINER ID IMAGE STATUS NAMES
243c32535da7 ubuntu Exited (0) 2 seconds ago myubuntu
原因:attach 附加到主程序,退出主程序就等於退出容器。
安全退出 attach
使用 Ctrl+P 然後 Ctrl+Q 可以從 attach 會話中 分離,而不停止容器:
$ docker attach myubuntu
root@243c32535da7:/#
## 按 Ctrl+P 然後 Ctrl+Q
read escape sequence
$ docker ps # 容器仍在執行
CONTAINER ID IMAGE STATUS NAMES
243c32535da7 ubuntu Up 5 minutes myubuntu
5.4.5 exec vs attach 對比
| 特性 | docker exec | docker attach |
|---|---|---|
| 工作方式 | 在容器內啟動新程序 | 附加到主程序 |
| 退出影響 | 不影響容器 | 可能停止容器 |
| 多終端 | 可以開多個 | 共享同一個會話 |
| 適用情境 | 除錯、臨時操作 | 檢視主程序輸出 |
| 推薦程度 | ✅ 推薦 | ⚠️ 特殊情境使用 |
flowchart LR
subgraph Exec ["docker exec"]
direction TB
subgraph Container1 ["容器"]
E_PID1["PID 1: nginx"]
E_PID50["PID 50: bash"]
end
NewProc["新程序"] -- 附加到 --> E_PID50
end
subgraph Attach ["docker attach"]
direction TB
subgraph Container2 ["容器"]
A_PID1["PID 1: bash"]
end
MainProc["附加到主程序"] --> A_PID1
end
note1["退出 bash 不影響 nginx"]
note2["退出 bash 容器停止"]
Container1 -.-> note1
Container2 -.-> note2
5.4.6 最佳實務
1. 首選 docker exec
## 進入容器除錯
$ docker exec -it myapp bash
## 檢視日誌
$ docker exec myapp tail -f /var/log/app.log
## 執行資料庫遷移
$ docker exec myapp python manage.py migrate
2. 生產環境避免進入容器
筆者建議:生產環境應儘量避免進入容器直接操作,而是透過:
- 日誌系統檢視日誌 (如
docker logs或集中式日誌) - 監控系統檢視狀態
- 重新部署而非手動修改
3. 無 shell 映像檔的處理
某些精簡映像檔 (如基於 scratch 或 distroless) 沒有 shell:
## 這會失敗
$ docker exec -it myapp bash
OCI runtime exec failed: exec failed: unable to start container process: exec: "bash": executable file not found
## 解決方案:使用除錯容器(需要 Docker Desktop Pro/Team/Business 訂閱)
$ docker debug myapp
> 注意:docker debug 是 Docker Desktop 4.33+ 提供的功能,需要 Pro、Team 或 Business 訂閱。它會附加一個包含常用除錯工具(vim、curl、htop 等)的工具箱到目標容器,即使目標映像檔基於 scratch 也能使用。
5.4.7 常見問題
Q:exec 進入後看不到其他終端的操作
這是正常的。exec 啟動的是獨立程序,多個 exec 會話互不影響。
Q:容器沒有 bash
嘗試使用 sh:
$ docker exec -it myapp /bin/sh
Q:需要 root 許可權
$ docker exec -u root -it myapp bash
5.5 匯出和匯入
當我們需要遷移容器或者備份容器時,可以使用 Docker 的匯入和匯出功能。本節將介紹這兩個命令的使用方法。
5.5.1 匯出容器
如果要匯出本地某個容器,可以使用 docker export 命令。
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7691a814370e ubuntu:24.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
$ docker export 7691a814370e > ubuntu.tar
這樣將匯出容器快照到本地檔案。
版本說明:匯出的容器快照不包含映像檔版本歷史,匯入時可以自定義標籤和版本號。
5.5.2 匯入容器快照
可以使用 docker import 從容器快照檔案中再匯入為映像檔,例如
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB
此外,也可以透過指定 URL 或者某個目錄來匯入,例如
$ docker import http://example.com/exampleimage.tgz example/imagerepo
注:使用者既可以使用 docker load 來匯入映像檔儲存檔案到本地映像檔庫,也可以使用 docker import 來匯入一個容器快照到本地映像檔庫。這兩者的區別在於容器快照檔案將丟棄所有的歷史記錄和後設資料資訊 (即僅儲存容器當時的快照狀態),而映像檔儲存檔案將儲存完整記錄,體積也要大。此外,從容器快照檔案匯入時可以重新指定標籤等後設資料資訊。
5.6 刪除
隨著容器的建立和停止,系統中會積累大量的容器。本節將介紹如何刪除不再需要的容器,以及如何清理所有停止的容器。
5.6.1 基本用法
使用 docker rm 刪除已停止的容器:
$ docker rm 容器名或ID
💡
docker rm是docker container rm的簡寫,兩者等效。
5.6.2 刪除選項
| 選項 | 說明 | 示例 |
|---|---|---|
| 無引數 | 刪除已停止的容器 | docker rm mycontainer |
-f |
強制刪除執行中的容器 | docker rm -f mycontainer |
-v |
同時刪除關聯的匿名卷 | docker rm -v mycontainer |
刪除已停止的容器
$ docker rm mycontainer
mycontainer
強制刪除執行中的容器
## 不加 -f 會報錯
$ docker rm running_container
Error: cannot remove running container
## 加 -f 強制刪除
$ docker rm -f running_container
running_container
⚠️ 強制刪除會向容器傳送 SIGKILL 訊號,可能導致資料丟失。建議先
docker stop優雅停止。
刪除容器及其資料卷
## 刪除容器時同時刪除其匿名卷
$ docker rm -v mycontainer
注意:只刪除匿名卷,命名卷不會被刪除。
5.6.3 批次刪除
刪除所有已停止的容器
## 方式一:使用 prune 命令(推薦)
$ docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N] y
Deleted Containers:
abc123...
def456...
Total reclaimed space: 150MB
## 方式二:不提示確認
$ docker container prune -f
刪除所有容器:包括執行中的
## 先停止所有容器,再刪除
$ docker stop $(docker ps -q)
$ docker rm $(docker ps -aq)
## 或者直接強制刪除
$ docker rm -f $(docker ps -aq)
按條件刪除
## 刪除所有已退出的容器
$ docker rm $(docker ps -aq -f status=exited)
## 刪除名稱包含 "test" 的容器
$ docker rm $(docker ps -aq -f name=test)
## 刪除 24 小時前建立的容器
$ docker container prune --filter "until=24h"
5.6.4 常用過濾條件
docker ps 的過濾條件可以配合 rm 使用:
| 過濾條件 | 說明 | 示例 |
|---|---|---|
status=exited |
已退出的容器 | -f status=exited |
status=created |
已建立未啟動 | -f status=created |
name=xxx |
名稱匹配 | -f name=myapp |
ancestor=xxx |
基於某映像檔建立 | -f ancestor=nginx |
before=xxx |
在某容器之前建立 | -f before=mycontainer |
since=xxx |
在某容器之後建立 | -f since=mycontainer |
示例
## 刪除所有基於 nginx 映像檔的容器
$ docker rm $(docker ps -aq -f ancestor=nginx)
## 刪除所有建立後未啟動的容器
$ docker rm $(docker ps -aq -f status=created)
5.6.5 容器與映像檔的依賴關係
有容器依賴的映像檔無法刪除。
## 嘗試刪除有容器依賴的映像檔
$ docker image rm nginx
Error: image is being used by stopped container abc123
## 需要先刪除依賴該映像檔的容器
$ docker rm abc123
$ docker image rm nginx
5.6.6 清理策略建議
開發環境
## 定期清理已停止的容器
$ docker container prune -f
## 一鍵清理所有未使用資源
$ docker system prune -f
生產環境
## 使用 --rm 引數執行臨時容器
$ docker run --rm ubuntu echo "Hello"
## 容器退出後自動刪除
## 定期清理(設定保留時間)
$ docker container prune --filter "until=168h" # 保留 7 天內的
完整清理指令碼
#!/bin/bash
## cleanup.sh - Docker 資源清理指令碼
echo "清理已停止的容器..."
docker container prune -f
echo "清理未使用的映像檔..."
docker image prune -f
echo "清理未使用的資料卷..."
docker volume prune -f
echo "清理未使用的網路..."
docker network prune -f
echo "清理完成!"
docker system df
5.6.7 常見問題
Q:容器無法刪除
Error: container is running
解決:先停止容器,或使用 -f 強制刪除
$ docker stop mycontainer
$ docker rm mycontainer
## 或
$ docker rm -f mycontainer
Q:刪除後磁碟空間沒釋放
可能原因:
- 容器的資料卷未刪除 (使用
-v引數) - 映像檔未刪除
- 構建快取未清理
解決:
## 檢視空間佔用
$ docker system df
## 完整清理
$ docker system prune -a --volumes
本章小結
本章介紹了 Docker 容器的啟動、停止、進入和刪除等核心操作。
| 操作 | 命令 | 說明 |
|---|---|---|
| 新建並執行 | docker run |
最常用的啟動方式 |
| 互動式啟動 | docker run -it |
用於除錯或臨時操作 |
| 後臺執行 | docker run -d |
用於服務類應用 |
| 啟動已停止的容器 | docker start |
重用已有容器 |
| 優雅停止 | docker stop |
先 SIGTERM,超時後 SIGKILL |
| 強制停止 | docker kill |
直接 SIGKILL |
| 重啟 | docker restart |
停止後立即啟動 |
| 停止全部 | docker stop $(docker ps -q) |
停止所有執行中容器 |
| 進入容器除錯 | docker exec -it 容器名 bash |
推薦方式 |
| 執行單條命令 | docker exec 容器名 命令 |
不進入互動模式 |
| 檢視主程序輸出 | docker attach 容器名 |
慎用,退出可能停止容器 |
| 刪除已停止容器 | docker rm 容器名 |
需先停止 |
| 強制刪除執行中容器 | docker rm -f 容器名 |
直接刪除 |
| 刪除容器及匿名卷 | docker rm -v 容器名 |
同時清理匿名卷 |
| 清理所有已停止容器 | docker container prune |
批次清理 |
延伸閱讀
第六章 訪問倉庫
第六章 訪問倉庫
倉庫 (Repository) 是集中存放映像檔的地方。
一個容易混淆的概念是註冊伺服器 (Registry)。實際上註冊伺服器是管理倉庫的具體伺服器,每個伺服器上可以有多個倉庫,而每個倉庫下面有多個映像檔。從這方面來說,倉庫可以被認為是一個具體的專案或目錄。例如對於倉庫地址 docker.io/ubuntu 來說,docker.io 是註冊伺服器地址,ubuntu 是倉庫名。
大部分時候,並不需要嚴格區分這兩者的概念。
版本號說明
本章涉及的 Registry 和相關工具版本說明:
- Docker Registry:使用
registry:2推薦版本,已停止維護的registry:1不建議使用 - Nexus 3:建議指定具體版本(如
sonatype/nexus3:3.69)而非latest,避免自動升級帶來的相容性問題 - 映像檔標籤規範:
- 生產環境推送至倉庫時應明確指定版本號(如
myapp:v1.0.0) - 避免依賴
latest標籤,因為其含義存在歧義且易導致版本混淆
為什麼需要私有倉庫?
在討論具體的安裝和配置前,讓我們先理解:什麼時候你應該建設私有倉庫?
開發團隊(有專利程式碼、不能公開):
- 需要私有倉庫儲存內部映像檔
- 涉及訪問控制和審計
- 強烈推薦使用託管方案(如 Harbor 或雲廠商提供的映像檔倉庫)
開源專案或個人學習:
- Docker Hub 公開倉庫足夠
- 無需自建私有倉庫的成本
企業級部署:
- 需要高可用、備份、災難恢復
- 推薦使用專業級方案(Nexus 3、Harbor)而非簡單的 Registry
本章涵蓋的方案從簡到複雜:
- Docker Registry:最小化部署(適合簡單情境)
- 私有倉庫高階配置:新增認證、HTTPS 等生產必需項
- Nexus 3:企業級完整解決方案,支援許可權管理、備份等
本章內容
6.1 Docker Hub
6.1.1 什麼是 Docker Hub
Docker Hub 是 Docker 的中央映像檔倉庫,透過它您可以輕鬆地分享和獲取 Docker 映像檔。
Docker Hub 是 Docker 官方維護的公共映像檔倉庫,也是全球最大的容器映像檔庫。
它提供了:
- 官方映像檔:由 Docker 官方和軟體廠商 (如 Nginx,MySQL,Node.js) 維護的高質量映像檔。
- 個人/組織倉庫:使用者可以上傳自己的映像檔。
- 自動構建:與 GitHub/Bitbucket 整合 (需付費)。
- Webhooks:映像檔更新時觸發回撥。
6.1.2 核心功能
1. 搜尋映像檔
我們可以透過 docker search 命令來查詢官方倉庫中的映像檔,並利用 docker pull 命令來將它下載到本地。
除了網頁搜尋,也可以使用命令列:
$ docker search centos
NAME DESCRIPTION STARS OFFICIAL
centos The official build of CentOS. 7000+ [OK]
技巧:始終優先使用
OFFICIAL標記為[OK]的映像檔,安全性更有保障。
2. 拉取映像檔
$ docker pull nginx:alpine
3. 推送映像檔
需要先登入:
$ docker login
## 預設情況下,不帶其它引數進行 docker login 會自動走 Device Code Web Flow (瀏覽器認證)
## 若在非互動 CI 環境中,推薦結合 --username 與 --password-stdin 引數使用
...
打標籤並推送:
## 1. 標記映像檔
$ docker tag myapp:v1 username/myapp:v1
## 2. 推送
$ docker push username/myapp:v1
6.1.3 限制與配額
映像檔拉取限制
Docker Hub 對不同型別使用者實施拉取速率限制(基於 6 小時週期):
| 使用者型別 | 限制 |
|---|---|
| 匿名使用者 (未登入) | 每 6 小時 100 次請求 |
| 免費賬戶 (已登入) | 每 6 小時 200 次請求 |
| Pro/Team/Business 賬戶 | 無限制(公平使用政策) |
注意:Docker 曾計劃於 2025 年 4 月調整拉取限制策略,但在 2025 年 2 月宣佈取消該計劃。目前付費訂閱使用者享有無限制拉取額度,匿名使用者和免費賬戶的限制保持不變。建議在 CI/CD 環境中始終配置
docker login以獲得更高的拉取額度。
濫用限流
除了上述針對特定賬號拉取映像檔數量的 Pull Rate Limit 之外,Docker Hub 對所有使用者(包含已認證及付費使用者)還實施了 濫用保護限流 (Abuse Rate Limiting)。它是根據網路出口 IP (IPv4 或 IPv6 /64 子網) 計算整體請求頻率,門檻值動態觸發(通常為每分鐘數千級別請求)。
兩類的差異與排查方法:
- Pull Rate Limit:針對拉取量達到上限。報錯返回
429 Too Many Requests,並且 HTTP 返回體/CLI 錯誤提示中會帶有明確的toomanyrequests: You have reached your pull rate limit提示,常附有賬戶升級連結。 - Abuse Rate Limit:防範介面頻率打擊。報錯僅返回簡化的
429 Too Many Requests。這一限流不分付費與否,常發生在“多終端共享出口 IP”的企業區域網或者第三方雲 CI 服務(如 GitHub Actions 等)中,即使你已正常配置docker login也依舊可能觸發。
提示:如果在 CI/CD 等環境遇到 429 錯誤,建議: 1. 先甄別具體是哪類限流:普通的 pull rate limit 只要在 CI 中配置
docker login(並使用有效賬號) 就能解除匿名限制。 2. 如果是 Abuse 頻控導致,應考慮搭建私有倉庫作為拉取快取代理 (Registry pull-through cache),避免頻繁直接請求官方 Hub。 3. 使用國內映像檔加速器。
6.1.4 安全最佳實務
1. 啟用 2FA:雙因素認證
為了保護您的 Docker Hub 賬號安全,我們建議採取以下措施。
在 Account Settings -> Security 中啟用 2FA,保護賬號安全。啟用後,CLI 登入需要使用 Access Token 而非密碼。
2. 使用 Access Token
⚠️ 警告:絕不要在指令碼或 CI/CD 系統中,直接使用
-p引數傳遞密碼或 Token (類似docker login -p xxx)!這會導致憑證直接暴露在系統的命令歷史、程序列表和終端輸出中。
- 在 Docker Hub -> Account Settings -> Security -> Access Tokens 建立 Token (PAT)。
- 將 Token 透過標準輸入 (stdin) 安全傳遞給 Docker:
$ echo "dckr_pat_xxxxxxx" | docker login --username username --password-stdin
3. 關注映像檔漏洞
Docker Hub 提供 Docker Scout 安全掃描功能。官方映像檔的漏洞掃描結果對所有使用者免費可見。Docker Scout 的持續掃描功能在免費層可以覆蓋 1 個私有倉庫,付費使用者可以掃描更多倉庫。在映像檔標籤頁可以看到漏洞掃描結果。
6.1.5 Webhooks
當映像檔被推送時,可以自動觸發 HTTP 回撥 (例如通知 CI 系統部署)。
配置方法: 倉庫頁面 -> Webhooks -> Create Webhook。
6.1.6 自動構建
⚠️ 目前僅限付費使用者 (Pro/Team) 使用。
連結 GitHub/Bitbucket 倉庫後,當程式碼有提交或打標籤時,Docker Hub 會自動執行構建。這保證了映像檔總是與程式碼同步,且由可信的官方環境構建。
6.2 私有倉庫
有時候使用 Docker Hub 這樣的公共倉庫可能不方便,使用者可以建立一個本地倉庫供私人使用。
本節介紹如何使用本地倉庫。
Docker Registry 是官方提供的工具,可以用於構建私有的映像檔倉庫。本文內容基於 distribution/distribution v2.x 版本。
6.2.1 安裝執行 docker-registry
容器執行
如果您需要搭建私有倉庫,可以透過官方提供的 registry 映像檔快速部署。
你可以使用官方 registry 映像檔來執行。
$ docker run -d -p 5000:5000 --restart=always --name registry registry:2
版本說明:使用
registry:2表示 Docker Registry 2.x 版本,這是當前推薦的版本。舊版本 Registry 1.x 已停止維護,不建議使用。 這將使用官方的registry映像檔來啟動私有倉庫。預設情況下,倉庫會被建立在容器的/var/lib/registry目錄下。你可以透過-v引數來將映像檔檔案存放在本地的指定路徑。例如下面的例子將上傳的映像檔放到本地的/opt/data/registry目錄。
$ docker run -d \
-p 5000:5000 \
-v /opt/data/registry:/var/lib/registry \
registry:2
6.2.2 在私有倉庫上傳、搜尋、下載映像檔
建立好私有倉庫之後,就可以使用 docker tag 來標記一個映像檔,然後推送它到倉庫。例如私有倉庫地址為 127.0.0.1:5000。
先在本機檢視已有的映像檔。
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
使用 docker tag 將 ubuntu:latest 這個映像檔標記為 127.0.0.1:5000/ubuntu:latest。
格式為 docker tag IMAGE[:TAG] [REGISTRY_HOST[:REGISTRY_PORT]/]REPOSITORY[:TAG]。
$ docker tag ubuntu:latest 127.0.0.1:5000/ubuntu:latest
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
ubuntu latest ba5877dc9bec 6 weeks ago 192.7 MB
127.0.0.1:5000/ubuntu:latest latest ba5877dc9bec 6 weeks ago 192.7 MB
使用 docker push 上傳標記的映像檔。
$ docker push 127.0.0.1:5000/ubuntu:latest
The push refers to repository [127.0.0.1:5000/ubuntu]
373a30c24545: Pushed
a9148f5200b0: Pushed
cdd3de0940ab: Pushed
fc56279bbb33: Pushed
b38367233d37: Pushed
2aebd096e0e2: Pushed
latest: digest: sha256:fe4277621f10b5026266932ddf760f5a756d2facd505a94d2da12f4f52f71f5a size: 1568
用 curl 檢視倉庫中的映像檔。
$ curl 127.0.0.1:5000/v2/_catalog
{"repositories":["ubuntu"]}
這裡可以看到 {"repositories":["ubuntu"]},表明映像檔已經被成功上傳了。
先刪除已有映像檔,再嘗試從私有倉庫中下載這個映像檔。
$ docker image rm 127.0.0.1:5000/ubuntu:latest
$ docker pull 127.0.0.1:5000/ubuntu:latest
Pulling repository 127.0.0.1:5000/ubuntu:latest
ba5877dc9bec: Download complete
511136ea3c5a: Download complete
9bad880da3d2: Download complete
25f11f5fb0cb: Download complete
ebc34468f71d: Download complete
2318d26665ef: Download complete
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
127.0.0.1:5000/ubuntu:latest latest ba5877dc9bec 6 weeks ago 192.7 MB
6.2.3 配置非 https 倉庫地址
如果你不想使用 127.0.0.1:5000 作為倉庫地址,比如想讓本網段的其他主機也能把映像檔推送到私有倉庫。你就得把例如 192.168.199.100:5000 這樣的內網地址作為私有倉庫地址,這時你會發現無法成功推送映像檔。
這是因為 Docker 預設不允許非 HTTPS 方式推送映像檔。我們可以透過 Docker 的配置選項來取消這個限制,或者檢視下一節配置能夠透過 HTTPS 訪問的私有倉庫。
Linux
預設情況下,Docker 強制使用 HTTPS 協議推送映像檔。如果您搭建的私有倉庫是 HTTP 協議,需要進行如下配置。
對於使用 systemd 的系統,請在 /etc/docker/daemon.json 中寫入如下內容 (如果檔案不存在請新建該檔案)
{
"registry-mirrors": [
"https://docker.your-mirror.example.com"
],
"insecure-registries": [
"192.168.199.100:5000"
]
}
注意:該檔案必須符合
json規範,否則 Docker 將不能啟動。映像檔加速器地址請替換為實際可用的源,具體配置參見 3.9 映像檔加速器。
6.2.4 其他
對於 Docker Desktop for Windows、Docker Desktop for Mac 在設定中的 Docker Engine 中進行編輯,增加和上邊一樣的字串即可。
6.3 私有倉庫高階配置
上一節我們搭建了一個具有基礎功能的私有倉庫,本小節我們來使用 Docker Compose 搭建一個擁有許可權認證、TLS 的私有倉庫。
新建一個資料夾,以下步驟均在該資料夾中進行。
6.3.1 準備站點證書
如果你擁有一個域名,國內各大雲服務商均提供免費的站點證書。你也可以使用 openssl 自行簽發證書。
這裡假設我們將要搭建的私有倉庫地址為 docker.domain.com,下面我們介紹使用 openssl 自行簽發 docker.domain.com 的站點 SSL 證書。
第一步建立 CA 私鑰。
$ openssl genrsa -out "root-ca.key" 4096
第二步利用私鑰建立 CA 根證書請求檔案。
$ openssl req \
-new -key "root-ca.key" \
-out "root-ca.csr" -sha256 \
-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=Your Company Name Docker Registry CA'
以上命令中
-subj引數裡的/C表示國家,如CN;/ST表示省;/L表示城市或者地區;/O表示組織名;/CN通用名稱。
第三步配置 CA 根證書,新建 root-ca.cnf。
[root_ca]
basicConstraints = critical,CA:TRUE,pathlen:1
keyUsage = critical, nonRepudiation, cRLSign, keyCertSign
subjectKeyIdentifier=hash
第四步簽發根證書。
$ openssl x509 -req -days 3650 -in "root-ca.csr" \
-signkey "root-ca.key" -sha256 -out "root-ca.crt" \
-extfile "root-ca.cnf" -extensions \
root_ca
第五步生成站點 SSL 私鑰。
$ openssl genrsa -out "docker.domain.com.key" 4096
第六步使用私鑰生成證書請求檔案。
$ openssl req -new -key "docker.domain.com.key" -out "site.csr" -sha256 \
-subj '/C=CN/ST=Shanxi/L=Datong/O=Your Company Name/CN=docker.domain.com'
第七步配置證書,新建 site.cnf 檔案。
[server]
authorityKeyIdentifier=keyid,issuer
basicConstraints = critical,CA:FALSE
extendedKeyUsage=serverAuth
keyUsage = critical, digitalSignature, keyEncipherment
subjectAltName = DNS:docker.domain.com, IP:127.0.0.1
subjectKeyIdentifier=hash
第八步簽署站點 SSL 證書。
$ openssl x509 -req -days 750 -in "site.csr" -sha256 \
-CA "root-ca.crt" -CAkey "root-ca.key" -CAcreateserial \
-out "docker.domain.com.crt" -extfile "site.cnf" -extensions server
這樣已經擁有了 docker.domain.com 的網站 SSL 私鑰 docker.domain.com.key 和 SSL 證書 docker.domain.com.crt 及 CA 根證書 root-ca.crt。
新建 ssl 資料夾並將 docker.domain.com.key docker.domain.com.crt root-ca.crt 這三個檔案移入,刪除其他檔案。
安全提示:這些私鑰和證書應在本地或受控部署環境中生成,不要提交進 Git 倉庫。示例目錄只保留佔位說明,執行前請按上面的步驟重新生成。
6.3.2 配置私有倉庫
私有倉庫預設的配置檔案位於 /etc/docker/registry/config.yml,我們先在本地編輯 config.yml,之後掛載到容器中。
log:
accesslog:
disabled: true
level: debug
formatter: text
fields:
service: registry
environment: staging
storage:
delete:
enabled: true
cache:
blobdescriptor: inmemory
filesystem:
rootdirectory: /var/lib/registry
auth:
htpasswd:
realm: basic-realm
path: /etc/docker/registry/auth/nginx.htpasswd
http:
addr: :443
host: https://docker.domain.com
headers:
X-Content-Type-Options: [nosniff]
http2:
disabled: false
tls:
certificate: /etc/docker/registry/ssl/docker.domain.com.crt
key: /etc/docker/registry/ssl/docker.domain.com.key
health:
storagedriver:
enabled: true
interval: 10s
threshold: 3
6.3.3 生成 http 認證檔案
$ mkdir auth
$ docker run --rm \
--entrypoint htpasswd \
httpd:2.4-alpine \
-Bbn username password > auth/nginx.htpasswd
將上面的
usernamepassword替換為你自己的使用者名稱和密碼。安全提示:上述命令會將密碼明文暴露在 shell 歷史記錄和程序列表中。生產環境建議使用互動式方式輸入密碼(不帶
-b引數),或透過環境變數/檔案傳入。版本說明:使用
httpd:2.4-alpine基於 Apache 2.4 的精簡映像檔。如需其他版本,可替換為httpd:latest或指定具體版本號如httpd:2.4.58-alpine。
6.3.4 編輯 Docker Compose 配置
編輯 compose.yaml (或 docker-compose.yml) 配置如下:
services:
registry:
image: registry:2
ports:
- "443:443"
volumes:
- ./:/etc/docker/registry
- registry-data:/var/lib/registry
volumes:
registry-data:
版本說明:Compose 配置中明確指定
registry:2版本。生產環境建議固定版本號(如registry:2.8.3)而非使用latest,以保證部署的可重複性。
6.3.5 修改 Hosts 檔案
編輯 /etc/hosts
127.0.0.1 docker.domain.com
6.3.6 啟動
$ docker compose up -d
這樣我們就搭建好了一個具有許可權認證、TLS 的私有倉庫,接下來我們測試其功能是否正常。
6.3.7 測試私有倉庫功能
由於自行簽發的 CA 根證書不被系統信任,所以我們需要將 CA 根證書 ssl/root-ca.crt 移入 /etc/docker/certs.d/docker.domain.com 資料夾中。
$ sudo mkdir -p /etc/docker/certs.d/docker.domain.com
$ sudo cp ssl/root-ca.crt /etc/docker/certs.d/docker.domain.com/ca.crt
登入到私有倉庫。
$ docker login docker.domain.com
嘗試推送、拉取映像檔。
$ docker pull ubuntu:24.04
$ docker tag ubuntu:24.04 docker.domain.com/username/ubuntu:24.04
$ docker push docker.domain.com/username/ubuntu:24.04
$ docker image rm docker.domain.com/username/ubuntu:24.04
$ docker pull docker.domain.com/username/ubuntu:24.04
版本說明:示例使用
ubuntu:24.04,這是最新 LTS 版本。推送到私有倉庫時保持了原有的版本標籤,建議生產環境明確指定映像檔版本號。 如果我們退出登入,嘗試推送映像檔。
$ docker logout docker.domain.com
$ docker push docker.domain.com/username/ubuntu:24.04
no basic auth credentials
發現會提示沒有登入,不能將映像檔推送到私有倉庫中。
6.3.8 注意事項
如果你本機佔用了 443 埠,你可以配置 Nginx 代理,這裡不再贅述。
6.4 Nexus 3
使用 Docker 官方的 Registry 建立的倉庫面臨一些維護問題。比如某些映像檔刪除以後空間預設是不會回收的,需要一些命令去回收空間然後重啟 Registry。在企業中把內部的一些工具包放入 Nexus 中是比較常見的做法,最新版本 Nexus3.x 全面支援 Docker 的私有映像檔。所以使用 Nexus 3 一個軟體來管理 Docker,Maven,Yum,PyPI 等是一個明智的選擇。
6.4.1 啟動 Nexus 容器
$ docker run -d --name nexus3 --restart=always \
-p 8081:8081 \
--mount src=nexus-data,target=/nexus-data \
sonatype/nexus3:3.69
版本說明:使用
sonatype/nexus3:3.69指定穩定的 Nexus 版本。使用具體版本號而非latest可以避免意外升級帶來的不相容問題。 首次執行需等待 3-5 分鐘,你可以使用docker logs nexus3 -f檢視日誌:
$ docker logs nexus3 -f
2021-03-11 15:31:21,990+0000 INFO [jetty-main-1] *SYSTEM org.sonatype.nexus.bootstrap.jetty.JettyServer -
-------------------------------------------------
Started Sonatype Nexus OSS 3.69.0
-------------------------------------------------
版本說明:上述日誌中顯示的是 Nexus 3.69 版本的啟動日誌。具體版本號會隨著容器映像檔版本而不同。 如果你看到以上內容,說明
Nexus已經啟動成功,你可以使用瀏覽器開啟http://YourIP:8081訪問Nexus了。
首次執行請透過以下命令獲取初始密碼:
$ docker exec nexus3 cat /nexus-data/admin.password
9266139e-41a2-4abb-92ec-e4142a3532cb
首次啟動 Nexus 的預設賬號是 admin,密碼則是上邊命令獲取到的,點選右上角登入,首次登入需更改初始密碼。
登入之後可以點選頁面上方的齒輪按鈕按照下面的方法進行設定。
6.4.2 建立倉庫
建立一個私有倉庫的方法:Repository->Repositories 點選右邊選單 Create repository 選擇 docker (hosted)
- Name:倉庫的名稱
- HTTP:倉庫單獨的訪問埠 (例如:5001)
- Hosted -> Deployment policy:請選擇 Allow redeploy 否則無法上傳 Docker 映像檔。
其它的倉庫建立方法請各位自己摸索,還可以建立一個 docker (proxy) 型別的倉庫連結到 DockerHub 上。再建立一個 docker (group) 型別的倉庫把剛才的 hosted 與 proxy 新增在一起。主機在訪問的時候預設下載私有倉庫中的映像檔,如果沒有將連結到 DockerHub 中下載並快取到 Nexus 中。
6.4.3 新增訪問許可權
選單 Security->Realms 把 Docker Bearer Token Realm 移到右邊的框中儲存。
新增使用者規則:選單 Security->Roles->Create role 在 Privileges 選項搜尋 docker 把相應的規則移動到右邊的框中然後儲存。
新增使用者:選單 Security->Users->Create local user 在 Roles 選項中選中剛才建立的規則移動到右邊的視窗儲存。
6.4.4 NGINX 反向代理配置
證書的生成請參見 私有倉庫認證 裡面證書生成一節。
NGINX 示例配置如下
upstream register
{
server "YourHostName OR IP":5001; #埠為上面新增私有映像檔倉庫時設定的 HTTP 選項的埠號
check interval=3000 rise=2 fall=10 timeout=1000 type=http;
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
check_http_expect_alive http_4xx;
}
server {
server_name YourDomainName;#如果沒有 DNS 伺服器做解析,請刪除此選項使用本機 IP 地址訪問
listen 443 ssl;
ssl_certificate key/example.crt;
ssl_certificate_key key/example.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
large_client_header_buffers 4 32k;
client_max_body_size 300m;
client_body_buffer_size 512k;
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
proxy_buffer_size 128k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 512k;
location / {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://register;
proxy_read_timeout 900s;
}
error_page 500 502 503 504 /50x.html;
}
6.4.5 Docker 主機訪問映像檔倉庫
如果不啟用 SSL 加密可以透過前面章節的方法新增非 https 倉庫地址到 Docker 的配置檔案中然後重啟 Docker。
使用 SSL 加密以後程式需要訪問就不能採用修改配置的方式了。具體方法如下:
$ openssl s_client -showcerts -connect YourDomainName OR HostIP:443 </dev/null 2>/dev/null|openssl x509 -outform PEM >ca.crt
$ cat ca.crt | sudo tee -a /etc/ssl/certs/ca-certificates.crt
$ systemctl restart docker
使用 docker login YourDomainName OR HostIP 進行測試,使用者名稱密碼填寫上面 Nexus 中設定的。
第七章 Dockerfile 指令詳解
第七章 Dockerfile 指令詳解
什麼是 Dockerfile
Dockerfile 是一個文字檔案,其內包含了一條條的 指令 (Instruction),每一條指令構建一層,因此每一條指令的內容,就是描述該層應當如何構建。
在第四章中,我們透過 docker commit 學習了映像檔的構成。但是,手動 commit 只能作為臨時修補,並不適合作為生產環境映像檔的構建方式。
使用 Dockerfile 構建映像檔有以下優勢:
- 自動化:可以透過
docker build命令自動構建映像檔。 - 可重複性:由於 Dockerfile 是文字檔案,可以確保每次構建的結果一致。
- 版本控制:Dockerfile 可以納入版本控制系統 (如 Git),便於追蹤變更。
- 透明性:任何人都可以透過閱讀 Dockerfile 瞭解映像檔的構建過程。
Dockerfile 編寫哲學
在深入每個指令的細節之前,筆者想強調一個至關重要的原則:Dockerfile 不是指令碼,而是映像檔的“設計圖”。這個區別決定了你如何思考每條指令的作用。
相比編寫 Bash 指令碼的思維(“按順序執行這些命令”),Dockerfile 的思維應該是(“這一層映像檔應該如何構建,下一層如何分層”)。這個思維轉變會影響你的決策:
- 合併命令:一個
RUN apt-get update && apt-get install ...應該寫在一起,而不是分開成多個RUN指令,因為它們是同一個“層”的邏輯 - 選擇合適的指令:
COPYvsADD、CMDvsENTRYPOINT這些選擇不是隨意的,而是根據映像檔分層的語義來決定的 - 最佳化映像檔大小:最後才清理快取、刪除臨時檔案,讓這些“瘦身”操作在同一層完成
這個章節將詳細介紹各個指令。在學習指令語法時,請始終思考:“這個指令為什麼要以這樣的方式工作?如果我是 Docker,我應該如何設計它?”
Dockerfile 基本結構
Dockerfile 一般分為四部分:基礎映像檔資訊、維護者資訊、映像檔操作指令和容器啟動時執行指令。
指令詳解
本章將詳細講解 Dockerfile 中的各個指令:
- RUN 執行命令
- COPY 複製檔案
- ADD 更高階的複製檔案
- CMD 容器啟動命令
- ENTRYPOINT 入口點
- ENV 設定環境變數
- ARG 構建引數
- VOLUME 定義匿名卷
- EXPOSE 暴露埠
- WORKDIR 指定工作目錄
- USER 指定當前使用者
- HEALTHCHECK 健康檢查
- ONBUILD 為他人作嫁衣裳
- LABEL 為映像檔新增後設資料
- SHELL 指令
高階特性
本章還將介紹 Dockerfile 的高階特性:
參考與最佳實務
此外,我們還將介紹 Dockerfile 的最佳實務和常見問題。
使用 Dockerfile 構建映像檔
構建映像檔的基本命令格式為:
docker build [選項] <上下文路徑/URL/->
例如,在 Dockerfile 所在目錄執行:
docker build -t my-image:1.0 .
關於版本號最佳實務
本章中的 Dockerfile 示例使用的基礎映像檔標籤遵循以下原則:
- 通用標籤(如
ubuntu:24.04、alpine、nginx):保持原樣,無需修改 - 基礎映像檔版本號(如
node:22、python:3.12):使用主或次版本號而非完整版本號(patch),這樣可以自動獲取最新的補丁版本,確保獲得安全更新 - 避免:不建議使用
latest標籤和完整的 patch 版本號(如20.10.0)作為基礎映像檔,因為這會導致構建的不可重現性或安全風險
讀者在使用這些示例時,應根據實際生產環境需求選擇合適的版本號。
更多關於 docker build 的用法,我們在實戰中會結合具體指令進行演示。
7.1 RUN 執行命令
何時使用 RUN
RUN 是 Dockerfile 中最常用的指令,主要用於在映像檔構建階段執行命令來修改映像檔。具體來說:
- 安裝依賴:
RUN apt-get install nginx - 編譯程式:
RUN gcc -o app main.c - 下載檔案:
RUN curl -O https://example.com/file.tar.gz - 配置系統:
RUN mkdir -p /app/data
理解 RUN 的核心是理解映像檔分層:每一個 RUN 都會在當前層之上建立新的一層,這會影響映像檔大小。因此,合理使用 RUN(特別是合併多個 RUN)是構建輕量級映像檔的關鍵。
7.1.1 基本語法
RUN <command>
RUN ["executable", "param1", "param2"]
RUN 指令是 Dockerfile 中最常用的指令之一。它在 當前映像檔層 之上建立一個新層,執行指定的命令,並提交結果。
7.1.2 兩種格式對比
1. Shell 格式
RUN apt-get update
- 特點:預設透過
/bin/sh -c執行。 - 優勢:可以使用環境變數、管道、重定向等 Shell 特性。
- 示例:
docker RUN echo "Hello" > /test.txt
2. Exec 格式
RUN ["apt-get", "update"]
- 特點:直接呼叫可執行檔案,不經過 Shell。
- 優勢:避免 Shell 字串解析問題,適用於引數中包含特殊字元的情況。
- 注意:無法使用
$VAR環境變數替換 (除非顯式呼叫 shell)。
7.1.3 常見最佳實務
1. 組合命令:減少層數
每一個 RUN 指令都會新建一層映像檔。為了減少映像檔體積和層數,應使用 && 連線命令。
❌ 糟糕的寫法 (建立 3 層):
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
✅ 推薦寫法 (建立 1 層):
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
2. 清理快取
在安裝完軟體後,立即清除快取,可以顯著減小映像檔體積。
-
Debian/Ubuntu:
docker RUN apt-get update && apt-get install -y package-bar \ && rm -rf /var/lib/apt/lists/* -
Alpine:
docker RUN apk add --no-cache package-bar
3. 使用 set -e 和 pipefail
預設情況下,管道命令 cmd1 | cmd2 的返回值由最後一個命令 (cmd2) 決定,即使前面的命令失敗了,整個 RUN 仍可能視為成功。
❌ 隱蔽的錯誤:
## 如果下載失敗,gzip 可能會報錯,但如果不影響後續,構建可能繼續
RUN wget http://error-url | gzip -d > file
✅ 推薦寫法:
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN wget http://url | gzip -d > file
7.1.4 常見問題
Q:為什麼 RUN cd /app 不生效?
RUN cd /app
RUN touch hello.txt
結果:hello.txt 會出現在根目錄 /,而不是 /app。原因:每個 RUN 都在一個新的 Shell/容器環境中執行。cd 隻影響當前 RUN 的環境。解決:使用 WORKDIR 指令。
WORKDIR /app
RUN touch hello.txt
Q:環境變數不生效?
RUN export MY_VAR=hello
RUN echo $MY_VAR
結果:輸出為空。原因:同上,環境變數只在當前 RUN 有效。解決:使用 ENV 指令,或在同一行 RUN 中匯出。
ENV MY_VAR=hello
RUN echo $MY_VAR
7.1.5 高階技巧
1. 使用 BuildKit 的掛載快取
BuildKit 支援在 RUN 指令中使用 --mount 掛載快取,加速構建。
## 快取 apt 包
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y gcc
## 快取 Go 模組
RUN --mount=type=cache,target=/go/pkg/mod \
go build -o app
2. 掛載金鑰
安全地使用 SSH 金鑰或 Token,而不將其記錄在映像檔中。
RUN --mount=type=secret,id=mysecret \
cat /run/secrets/mysecret
3. Heredoc 語法
BuildKit 支援使用 heredoc 語法編寫多行指令碼,無需行末反斜槓 \ 連線:
RUN <<EOF
apt-get update
apt-get install -y \
build-essential \
curl \
git
rm -rf /var/lib/apt/lists/*
EOF
優勢:
- 可讀性更強,不需要在每行末尾新增
\ - 避免在指令碼中轉義引號
- 支援多個 heredoc 塊,可指定不同的 Shell
RUN <<EOF
echo "使用預設 /bin/sh"
EOF
RUN <<'EOF'
#!/bin/bash
set -euo pipefail
echo "使用 bash"
wget http://example.com/file.tar.gz
EOF
使用 heredoc 需要在 Dockerfile 首行宣告 BuildKit 語法版本:
# syntax=docker/dockerfile:1
7.2 COPY 複製檔案
何時使用 COPY
COPY 是在構建映像檔時,將構建上下文(Dockerfile 所在目錄及其子目錄)中的檔案或目錄複製到容器內的指令。它是處理應用程式碼、配置檔案最常用的方式。
典型情境:
- 複製應用原始碼:
COPY . /app - 複製配置檔案:
COPY nginx.conf /etc/nginx/nginx.conf - 複製靜態資源:
COPY public /app/public
為什麼 COPY 比 ADD 更值得推薦? 筆者建議在 90% 的情況下使用 COPY。原因是 COPY 的語義更清晰——它就是簡單地複製檔案。而 ADD 有額外的功能(自動解壓、支援 URL),這些功能往往會帶來意外的行為。除非你明確需要 ADD 的自動解壓功能,否則用 COPY。詳見 7.3 ADD 指令 中的詳細對比。
7.2.1 基本語法
COPY [選項] <源路徑>... <目標路徑>
COPY [選項] ["<源路徑1>", "<源路徑2>", ... "<目標路徑>"]
COPY 指令將構建上下文中的檔案或目錄複製到映像檔內。
7.2.2 基本用法
複製單個檔案
## 複製檔案到指定目錄
COPY package.json /app/
## 複製檔案並重新命名
COPY config.json /app/settings.json
複製多個檔案
## 複製多個指定檔案
COPY package.json package-lock.json /app/
## 使用萬用字元
COPY *.json /app/
COPY src/*.js /app/src/
複製目錄
## 複製整個目錄的內容(不是目錄本身)
COPY src/ /app/src/
⚠️ 注意:複製目錄時,複製的是目錄的 內容,不包含目錄本身。
構建上下文: 映像檔內:
src/ /app/src/
├── index.js → ├── index.js
└── utils.js └── utils.js
7.2.3 萬用字元規則
COPY 支援 Go 的 filepath.Match 萬用字元規則:
| 萬用字元 | 說明 | 示例 |
|---|---|---|
* |
匹配任意字元序列 | *.json |
? |
匹配單個字元 | config?.json |
[abc] |
匹配括號內任一字元 | [abc].txt |
[a-z] |
匹配範圍內字元 | file[0-9].txt |
COPY hom* /mydir/ # home.txt, homework.md 等
COPY hom?.txt /mydir/ # home.txt, homy.txt 等
COPY app[0-9].js /app/ # app0.js ~ app9.js
7.2.4 目標路徑
絕對路徑
COPY app.js /usr/src/app/
相對路徑:基於 WORKDIR
WORKDIR /app
COPY package.json ./ # 複製到 /app/package.json
COPY src/ ./src/ # 複製到 /app/src/
自動建立目錄
如果目標目錄不存在,Docker 會自動建立:
## /app/config/ 不存在也會自動建立
COPY settings.json /app/config/
7.2.5 修改檔案所有者
使用 --chown 選項設定檔案的使用者和組:
## 使用使用者名稱和組名
COPY --chown=node:node package.json /app/
## 使用 UID 和 GID
COPY --chown=1000:1000 . /app/
## 只指定使用者
COPY --chown=node . /app/
💡 結合
USER指令使用,確保應用以非 root 使用者執行。
7.2.6 保留檔案後設資料
COPY 會保留原始檔的後設資料:
- 讀、寫、執行許可權
- 修改時間
這對於指令碼檔案特別重要:
## start.sh 的可執行許可權會被保留
COPY start.sh /app/
7.2.7 COPY vs ADD
| 特性 | COPY | ADD |
|---|---|---|
| 複製本地檔案 | ✅ | ✅ |
| 自動解壓 tar | ❌ | ✅ |
| 支援 URL | ❌ | ✅ (不推薦) |
| 推薦程度 | ✅ 推薦 | ⚠️ 特殊情境使用 |
## 推薦:使用 COPY
COPY app.tar.gz /app/
RUN tar -xzf /app/app.tar.gz
## ADD 會自動解壓(行為不明顯,不推薦)
ADD app.tar.gz /app/
筆者建議:除非需要自動解壓 tar 檔案,否則始終使用 COPY。明確的行為比隱式的魔法更好。
7.2.8 多階段構建中的 COPY
從其他構建階段複製
## 構建階段
FROM node:22 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
## 生產階段
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
使用 --link 最佳化快取
## 使用 --link 後,檔案以獨立層新增,不依賴前序指令
COPY --link --from=builder /app/dist /usr/share/nginx/html
--link 的優勢:
- 更高效利用構建快取
- 並行化構建過程
- 加速多階段構建
⚠️ 注意:使用
--link需要滿足以下條件: - 啟用 BuildKit(Docker Engine 23.0+ 預設啟用) - Dockerfile 語法版本 1.4 或更高(在首行宣告# syntax=docker/dockerfile:1) - 目標路徑在前序指令中應不存在或為空目錄
7.2.9 dockerignore
使用 .dockerignore 排除不需要複製的檔案:
## .dockerignore
node_modules
.git
.env
*.log
Dockerfile
.dockerignore
這可以:
- 減小構建上下文大小
- 加速構建
- 避免複製敏感檔案
7.2.10 最佳實務
1. 利用快取,先複製依賴檔案
## ✅ 好:先複製依賴定義,再安裝,最後複製程式碼
COPY package.json package-lock.json ./
RUN npm install
COPY . .
## ❌ 差:一次性複製所有檔案,程式碼變更會導致重新 npm install
COPY . .
RUN npm install
2. 使用 .dockerignore
## 確保 node_modules 不被複制
COPY . .
## .dockerignore 中應包含 node_modules
...
3. 明確複製路徑
## ✅ 好:明確的路徑
COPY src/ /app/src/
COPY package.json /app/
## ❌ 差:過於寬泛
COPY . .
🔥 踩坑實錄
某公司在最佳化 Node.js 應用的 Docker 映像檔時,發現構建出來的映像檔體積超過了 2GB,遠遠超過生產部署的需求。排查發現,Dockerfile 中使用了
COPY . .把整個構建上下文複製進映像檔,導致node_modules/、.git/目錄和大量測試資料全部被打包進映像檔。最初他們沒有建立.dockerignore檔案,預設會複製所有檔案。解決方案是新增一個.dockerignore檔案,排除這些不必要的目錄,使映像檔縮小到了 200MB。這個教訓深刻地說明了:.dockerignore和.gitignore一樣重要,應該在專案初始化時就建立,而不是等到出現問題時才想起來。建議的標準做法是先複製package.json和package-lock.json安裝依賴,再複製應用程式碼,同時在.dockerignore中明確列出node_modules、.git、test 目錄等。
7.3 ADD 更高階的複製檔案
何時使用 ADD?何時用 COPY?
在開始前,讓我們直言不諱:在大多數情況下,你應該使用 COPY,而不是 ADD。
ADD 在 COPY 基礎上增加了兩個額外功能,但這些功能往往引入複雜性而非便利:
- 自動解壓 tar 壓縮包(有時你想複製一個 .tar.gz 本身,而 ADD 會意外地解壓它)
- 支援從 URL 下載檔案(這個功能由於網路不穩定已被廣泛認為是反模式)
實踐中的建議:除非你明確需要自動解壓功能(比如官方基礎映像檔構建根檔案系統),否則始終使用 COPY。原因很簡單——顯式優於隱式。你的 Dockerfile 在 6 個月後被接手維護時,清晰的意圖會讓團隊少走很多彎路。
7.3.1 基本語法
ADD [選項] <源路徑>... <目標路徑>
ADD [選項] ["<源路徑>", ... "<目標路徑>"]
ADD 在 COPY 基礎上增加了兩個功能:
- 自動解壓 tar 壓縮包
- 支援從 URL 下載檔案 (不推薦)
7.3.2 ADD vs COPY 詳細對比
| 特性 | COPY | ADD |
|---|---|---|
| 複製本地檔案 | ✅ | ✅ |
| 自動解壓 tar | ❌ | ✅ |
| 支援 URL | ❌ | ✅ (不推薦) |
| 行為可預測性 | ✅ 高 | ⚠️ 低 |
| 推薦程度 | ✅ 優先使用 | 僅解壓情境 |
筆者建議:除非需要自動解壓 tar 檔案,否則始終使用 COPY。明確的行為比隱式的魔法更好。
7.3.3 自動解壓功能
基本用法:自動解壓本地 tar
## 自動解壓 tar.gz 到目標目錄
ADD app.tar.gz /app/
ADD 會識別並解壓以下格式:
.tar.tar.gz/.tgz.tar.bz2/.tbz2.tar.xz/.txz
實際應用
官方基礎映像檔通常使用 ADD 解壓根檔案系統:
FROM scratch
ADD ubuntu-noble-core-cloudimg-amd64-root.tar.gz /
解壓過程
ADD app.tar.gz /app/
│
├─ 識別 .tar.gz 格式
├─ 自動解壓
└─ 內容放入 /app/
app.tar.gz 包含: /app/ 目錄結果:
├── src/ ├── src/
│ └── main.py │ └── main.py
└── config.json └── config.json
7.3.4 URL 下載功能:不推薦
基本用法
## 從 URL 下載檔案
ADD https://example.com/app.zip /app/app.zip
為什麼不推薦
| 問題 | 說明 |
|---|---|
| 許可權固定 | 下載的檔案許可權為 600,通常需要額外 RUN 修改 |
| 不會解壓 | URL 下載的壓縮包不會自動解壓 |
| 快取問題 | URL 內容變化時不會重新下載 |
| 層數增加 | 需要額外 RUN 清理 |
推薦替代方案
## ❌ 不推薦:使用 ADD 下載
ADD https://example.com/app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && rm /tmp/app.tar.gz
## ✅ 推薦:使用 RUN + curl
RUN curl -fsSL https://example.com/app.tar.gz | tar -xz -C /app
優勢:
- 一條 RUN 完成下載、解壓、清理
- 減少映像檔層數
- 更清晰的構建意圖
7.3.5 修改檔案所有者
ADD --chown=node:node app.tar.gz /app/
ADD --chown=1000:1000 files/ /app/
7.3.6 何時使用 ADD
✅ 適合使用 ADD
## 解壓本地 tar 檔案
FROM scratch
ADD rootfs.tar.gz /
## 解壓應用包
ADD dist.tar.gz /app/
❌ 不適合使用 ADD
## 複製普通檔案(用 COPY)
ADD package.json /app/ # ❌
COPY package.json /app/ # ✅
## 下載檔案(用 RUN + curl)
ADD https://example.com/file / # ❌
RUN curl -fsSL ... -o /file # ✅
## 需要保留 tar 不解壓(用 COPY)
ADD archive.tar.gz /archives/ # ❌ 會解壓
COPY archive.tar.gz /archives/ # ✅ 保持原樣
7.3.7 快取行為
ADD 可能導致構建快取失效:
## 如果 app.tar.gz 內容變化,此層及後續層都需重建
ADD app.tar.gz /app/
RUN npm install
最佳化建議:
## 先複製依賴檔案
COPY package*.json /app/
RUN npm install
## 再新增應用程式碼
ADD app.tar.gz /app/
7.3.8 最佳實務
1. 預設使用 COPY
## ✅ 大多數情境使用 COPY
COPY . /app/
2. 僅在需要解壓時使用 ADD
## ✅ 自動解壓情境
ADD app.tar.gz /app/
3. 不要用 ADD 下載檔案
## ❌ 避免
ADD https://example.com/file.tar.gz /tmp/
## ✅ 推薦
RUN curl -fsSL https://example.com/file.tar.gz | tar -xz -C /app
4. 解壓後清理
## 如果需要控制解壓過程
COPY app.tar.gz /tmp/
RUN tar -xzf /tmp/app.tar.gz -C /app && \
rm /tmp/app.tar.gz
7.4 CMD 容器啟動命令
何時使用 CMD,何時使用 ENTRYPOINT?
在深入 CMD 的細節之前,我們需要理解一個關鍵問題:CMD 和 ENTRYPOINT 應該在什麼時候使用?
這是 Dockerfile 使用中最常見的困惑之一。簡單的答案是:
- CMD:定義容器的“預設命令”。如果使用者在
docker run時提供命令,CMD 會被覆蓋 - ENTRYPOINT:定義容器的“入口指令碼”。通常用於啟動應用的某個特定部分
決策樹:
- 你的容器是否有不可變的啟動邏輯? 比如需要先做一些初始化工作,然後才執行應用 → 使用 ENTRYPOINT
- 使用者經常會在
docker run時傳入不同的命令嗎? → 使用 CMD(讓使用者靈活覆蓋) - 大多數情況下,你希望容器始終以相同的方式啟動? → 使用 ENTRYPOINT
更多細節見 7.5 ENTRYPOINT 指令。
7.4.1 什麼是 CMD
CMD 指令用於指定容器啟動時預設執行的命令。它定義了容器的 “主程序”。
核心概念:容器的生命週期 = 主程序的生命週期。CMD 指定的命令就是這個主程序。
7.4.2 語法格式
CMD 有三種格式:
| 格式 | 語法 | 推薦程度 |
|---|---|---|
| exec 格式 | CMD ["可執行檔案", "引數1", "引數2"] |
✅推薦 |
| shell 格式 | CMD 命令 引數1 引數2 |
⚠️ 簡單情境 |
| 引數格式 | CMD ["引數1", "引數2"] |
配合 ENTRYPOINT |
exec 格式:推薦
CMD ["nginx", "-g", "daemon off;"]
CMD ["python", "app.py"]
CMD ["node", "server.js"]
優點:
- 直接執行指定程式,是容器的 PID 1
- 正確接收訊號 (如 SIGTERM)
- 無需 shell 解析
shell 格式
CMD echo "Hello World"
CMD nginx -g "daemon off;"
實際執行:會被包裝為 sh -c
## 你寫的
CMD echo $HOME
## 實際執行的
CMD ["sh", "-c", "echo $HOME"]
優點:可以使用環境變數、管道等 shell 特性
缺點:主程序是 sh,訊號無法正確傳遞給應用
7.4.3 exec 格式 vs shell 格式
| 特性 | exec 格式 | shell 格式 |
|---|---|---|
| 主程序 | 指定的程式 | /bin/sh |
| 訊號傳遞 | ✅ 正確 | ❌ 無法傳遞 |
| 環境變數 | ❌ 需要 shell 包裝 | ✅ 自動解析 |
| 推薦使用 | ✅ 大多數情境 | 需要 shell 特性時 |
訊號傳遞問題示例
## ❌ shell 格式:docker stop 會超時
CMD node server.js
## 實際是 sh -c "node server.js"
## SIGTERM 發給 sh,不會傳遞給 node
## ✅ exec 格式:docker stop 正常工作
CMD ["node", "server.js"]
## SIGTERM 直接發給 node
...
7.4.4 執行時覆蓋 CMD
docker run 後的命令會覆蓋 Dockerfile 中的 CMD:
## ubuntu 預設 CMD 是 /bin/bash
$ docker run -it ubuntu # 進入 bash
$ docker run ubuntu cat /etc/os-release # 覆蓋為 cat 命令
Dockerfile: docker run 命令:
CMD ["/bin/bash"] + cat /etc/os-release
│ │
└───────► 被覆蓋 ◄───────┘
↓
執行: cat /etc/os-release
7.4.5 經典錯誤:容器立即退出
錯誤示例
## ❌ 容器啟動後立即退出
CMD service nginx start
原因分析
1. CMD service nginx start
↓ 被轉換為
2. CMD ["sh", "-c", "service nginx start"]
↓
3. sh 啟動,執行 service 命令
↓
4. service 命令將 nginx 放到後臺
↓
5. service 命令結束,sh 退出
↓
6. 容器主程序(sh)退出 → 容器停止
正確做法
## ✅ 讓 nginx 在前臺執行
CMD ["nginx", "-g", "daemon off;"]
7.4.6 CMD vs ENTRYPOINT
| 指令 | 用途 | 執行時行為 |
|---|---|---|
| CMD | 預設命令 | docker run 引數會 覆蓋 它 |
| ENTRYPOINT | 入口點 | docker run 引數會 追加 到它後面 |
單獨使用 CMD
## Dockerfile
CMD ["curl", "-s", "http://example.com"]
$ docker run myimage # 執行預設命令
$ docker run myimage curl -v ... # 完全覆蓋
搭配 ENTRYPOINT
## Dockerfile
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
$ docker run myimage # curl -s http://example.com
$ docker run myimage http://other.com # curl -s http://other.com(引數覆蓋)
詳見 ENTRYPOINT 入口點章節。
7.4.7 最佳實務
1. 優先使用 exec 格式
## ✅ 推薦
CMD ["python", "app.py"]
## ⚠️ 僅在需要 shell 特性時使用
CMD ["sh", "-c", "echo $PATH && python app.py"]
2. 確保應用在前臺執行
## ✅ 前臺執行
CMD ["nginx", "-g", "daemon off;"]
CMD ["apache2ctl", "-D", "FOREGROUND"]
CMD ["java", "-jar", "app.jar"]
## ❌ 不要使用後臺服務命令
CMD service nginx start
CMD systemctl start nginx
3. 使用雙引號
## ✅ 正確:雙引號
CMD ["node", "server.js"]
## ❌ 錯誤:單引號(JSON 不支援)
CMD ['node', 'server.js']
4. 配合 ENTRYPOINT 使用
## 用於可配置引數的情境
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"]
## 執行時可以覆蓋埠
$ docker run myapp --port 9000
...
7.4.8 常見問題
Q:CMD 可以寫多個嗎?
不可以。多個 CMD 只有最後一個生效:
CMD ["echo", "first"]
CMD ["echo", "second"] # 只有這個生效
Q:如何在 CMD 中使用環境變數?
## 方法1:使用 shell 格式
CMD echo "Port is $PORT"
## 方法2:顯式使用 sh -c
CMD ["sh", "-c", "echo Port is $PORT"]
Q:為什麼我的容器不響應 Ctrl+C?
可能是使用了 shell 格式,訊號被 sh 吃掉了:
## ❌ 訊號無法傳遞
CMD python app.py
## ✅ 訊號正確傳遞
CMD ["python", "app.py"]
7.5 ENTRYPOINT 入口點
何時使用 ENTRYPOINT:從“容器”到“命令”
如果說 CMD 是“容器中的預設程式”,那麼 ENTRYPOINT 就是“把容器變成一個命令”。這個思維轉變決定了你何時使用 ENTRYPOINT。
使用 ENTRYPOINT 的典型情境:
-
命令列工具:你想讓映像檔像
curl或wget一樣使用dockerfile ENTRYPOINT ["curl"] # docker run myimage http://example.com → curl http://example.com -
應用啟動指令碼:你有一個初始化指令碼,需要接收命令列引數
dockerfile ENTRYPOINT ["/app/entrypoint.sh"] # docker run myimage --debug → /app/entrypoint.sh --debug -
與 CMD 結合:ENTRYPOINT 定義入口,CMD 定義預設引數
dockerfile ENTRYPOINT ["python", "app.py"] CMD ["--port", "8000"] # docker run myimage → python app.py --port 8000 # docker run myimage --port 9000 → python app.py --port 9000
對比 CMD:如果沒有這些“把容器當命令用”的需求,通常使用 CMD 就足夠了。
7.5.1 什麼是 ENTRYPOINT
ENTRYPOINT 指定容器啟動時執行的入口程式。與 CMD 不同,ENTRYPOINT 定義的命令不會被 docker run 的引數覆蓋,而是 接收這些引數。
核心作用:讓映像檔像一個可執行程式一樣使用,
docker run的引數作為這個程式的引數。
7.5.2 語法格式
| 格式 | 語法 | 推薦程度 |
|---|---|---|
| exec 格式 | ENTRYPOINT ["可執行檔案", "引數1"] |
✅推薦 |
| shell 格式 | ENTRYPOINT 命令 引數 |
⚠️ 不推薦 |
## exec 格式(推薦)
ENTRYPOINT ["nginx", "-g", "daemon off;"]
## shell 格式(不推薦)
ENTRYPOINT nginx -g "daemon off;"
7.5.3 ENTRYPOINT vs CMD
核心區別
| 特性 | ENTRYPOINT | CMD |
|---|---|---|
| 定位 | 固定的入口程式 | 預設引數 |
| docker run 引數 | 追加為引數 | 完全覆蓋 |
| 覆蓋方式 | --entrypoint |
直接指定命令 |
| 適用情境 | 把映像檔當命令用 | 提供預設行為 |
行為對比
## 只用 CMD
CMD ["curl", "-s", "http://example.com"]
$ docker run myimage # curl -s http://example.com
$ docker run myimage -v # 執行 -v(錯誤!)
$ docker run myimage curl -v ... # curl -v ...(完全替換)
## 只用 ENTRYPOINT
ENTRYPOINT ["curl", "-s"]
$ docker run myimage # curl -s(缺引數)
$ docker run myimage http://example.com # curl -s http://example.com ✓
## ENTRYPOINT + CMD 組合(推薦)
ENTRYPOINT ["curl", "-s"]
CMD ["http://example.com"]
$ docker run myimage # curl -s http://example.com(預設)
$ docker run myimage http://other.com # curl -s http://other.com ✓
$ docker run myimage -v http://other.com # curl -s -v http://other.com ✓
7.5.4 情境一:讓映像檔像命令一樣使用
需求:啟動前準備
建立一個查詢公網 IP 的 “命令” 映像檔。
使用 CMD 的問題
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
CMD ["curl", "-s", "http://myip.ipip.net"]
$ docker run myip # ✓ 正常工作
當前 IP:61.148.226.66
$ docker run myip -i # ✗ 錯誤!
exec: "-i": executable file not found
## -i 替換了整個 CMD,被當作可執行檔案
...
使用 ENTRYPOINT 解決
FROM ubuntu:24.04
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
$ docker run myip # ✓ 正常工作
當前 IP:61.148.226.66
$ docker run myip -i # ✓ 新增 -i 引數
HTTP/1.1 200 OK
...
當前 IP:61.148.226.66
互動圖示
ENTRYPOINT ["curl", "-s", "http://myip.ipip.net"]
│
docker run myip -i
│
▼
curl -s http://myip.ipip.net -i
└─────────────────────────────┘
ENTRYPOINT + docker run 引數
7.5.5 情境二:啟動前的準備工作
需求
在啟動主服務前執行初始化指令碼 (如資料庫遷移、許可權設定)。
實現方式
# 建議使用 redis:7 或 redis:latest,具體版本號根據生產需求選擇
FROM redis:7-alpine
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]
docker-entrypoint.sh:
#!/bin/sh
set -e
## 準備工作
echo "Initializing..."
## 如果第一個引數是 redis-server,以 redis 使用者執行
if [ "$1" = 'redis-server' ]; then
chown -R redis:redis /data
exec gosu redis "$@"
fi
## 其他命令直接執行
exec "$@"
工作流程
docker run redis docker run redis bash
│ │
▼ ▼
docker-entrypoint.sh redis-server docker-entrypoint.sh bash
│ │
├─ 初始化 ├─ 初始化
├─ chown -R redis:redis /data │
└─ exec gosu redis redis-server └─ exec bash
(以 redis 使用者執行) (以 root 使用者執行)
關鍵點
- exec “$@”:用傳入的引數替換當前程序,確保訊號正確傳遞
- 條件判斷:根據 CMD 不同執行不同邏輯
- 使用者切換:使用
gosu切換使用者 (比su更適合容器)
7.5.6 情境三:帶引數的應用
# 建議使用 python:3.12 或 python:3,具體版本號根據應用相容性需求選擇
FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
ENTRYPOINT ["python", "app.py"]
CMD ["--host", "0.0.0.0", "--port", "8080"]
## 使用預設引數
$ docker run myapp
## 執行: python app.py --host 0.0.0.0 --port 8080
## 覆蓋引數
$ docker run myapp --host 0.0.0.0 --port 9000
## 執行: python app.py --host 0.0.0.0 --port 9000
## 完全不同的引數
$ docker run myapp --help
## 執行: python app.py --help
...
7.5.7 覆蓋 ENTRYPOINT
使用 --entrypoint 引數覆蓋:
## 正常執行
$ docker run myimage
## 覆蓋 ENTRYPOINT 進入 shell 除錯
$ docker run --entrypoint /bin/sh myimage
## 覆蓋 ENTRYPOINT 並傳入引數
$ docker run --entrypoint /bin/cat myimage /etc/os-release
7.5.8 ENTRYPOINT 與 CMD 組合表
| ENTRYPOINT | CMD | 最終執行命令 |
|---|---|---|
| 無 | 無 | 無 (容器無法啟動) |
| 無 | ["cmd", "p1"] |
cmd p1 |
["ep", "p1"] |
無 | ep p1 |
["ep", "p1"] |
["cmd", "p2"] |
ep p1 cmd p2 |
ep p1 (shell) |
["cmd", "p2"] |
/bin/sh -c "ep p1" (CMD 被忽略) |
⚠️ 注意:shell 格式的 ENTRYPOINT 會忽略 CMD!
7.5.9 最佳實務
1. 使用 exec 格式
## ✅ 推薦
ENTRYPOINT ["python", "app.py"]
## ❌ 避免 shell 格式
ENTRYPOINT python app.py
2. 提供有意義的預設引數
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]
3. 入口指令碼使用 exec
#!/bin/sh
## 準備工作...
## 使用 exec 替換當前程序
exec "$@"
4. 處理訊號
確保 ENTRYPOINT 指令碼能正確傳遞訊號:
#!/bin/bash
trap 'kill -TERM $PID' TERM INT
## 啟動應用
app "$@" &
PID=$!
## 等待應用退出
wait $PID
7.6 ENV 設定環境變數
7.6.1 基本語法
## 格式一:單個變數
ENV <key> <value>
## 格式二:多個變數(推薦)
ENV <key1>=<value1> <key2>=<value2> ...
7.6.2 基本用法
設定單個變數
ENV NODE_VERSION 20
ENV APP_ENV production
設定多個變數
ENV NODE_VERSION=20 \
APP_ENV=production \
APP_NAME="My Application"
💡 包含空格的值用雙引號括起來。
7.6.3 環境變數的作用
1. 後續指令中使用
ENV NODE_VERSION=20.10.0
## 在 RUN 中使用
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz \
| tar -xJ -C /usr/local --strip-components=1
## 在 WORKDIR 中使用
ENV APP_HOME=/app
WORKDIR $APP_HOME
## 在 COPY 中使用
COPY . $APP_HOME
2. 容器執行時使用
ENV DATABASE_URL=postgres://localhost/mydb
應用程式碼中可以讀取:
import os
db_url = os.environ.get('DATABASE_URL')
const dbUrl = process.env.DATABASE_URL;
7.6.4 支援環境變數的指令
以下指令可以使用 $變數名 或 ${變數名} 格式:
| 指令 | 示例 |
|---|---|
RUN |
RUN echo $VERSION |
CMD |
CMD ["sh", "-c", "echo $HOME"] |
ENTRYPOINT |
同上 |
COPY |
COPY . $APP_HOME |
ADD |
ADD app.tar.gz $APP_HOME |
WORKDIR |
WORKDIR $APP_HOME |
EXPOSE |
EXPOSE $PORT |
VOLUME |
VOLUME $DATA_DIR |
USER |
USER $USERNAME |
LABEL |
LABEL version=$VERSION |
FROM |
FROM node:$NODE_VERSION |
7.6.5 執行時覆蓋
使用 -e 或 --env 覆蓋 Dockerfile 中定義的環境變數:
## 覆蓋單個變數
$ docker run -e APP_ENV=development myimage
## 覆蓋多個變數
$ docker run -e APP_ENV=development -e DEBUG=true myimage
## 從環境變數檔案讀取
$ docker run --env-file .env myimage
.env 檔案格式
## .env
APP_ENV=development
DEBUG=true
DATABASE_URL=postgres://localhost/mydb
7.6.6 ENV vs ARG
| 特性 | ENV | ARG |
|---|---|---|
| 生效時間 | 構建時 + 執行時 | 僅構建時 |
| 永續性 | 寫入映像檔,執行時可用 | 構建後消失 |
| 覆蓋方式 | docker run -e |
docker build --build-arg |
| 適用情境 | 應用配置 | 構建引數 (如版本號) |
組合使用
## ARG 接收構建時引數
ARG NODE_VERSION=20
## ENV 儲存到執行時
ENV NODE_VERSION=$NODE_VERSION
## 後續指令使用
RUN curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/...
## 構建時指定版本
$ docker build --build-arg NODE_VERSION=18 -t myapp .
7.6.7 最佳實務
1. 統一管理版本號
## ✅ 好:版本集中管理
ENV NGINX_VERSION=1.30 \
NODE_VERSION=22 \
PYTHON_VERSION=3.12
RUN apt-get install nginx=${NGINX_VERSION}
## ❌ 差:版本分散在各處
RUN apt-get install nginx=1.30
2. 不要儲存敏感資訊
## ❌ 錯誤:密碼寫入映像檔
ENV DB_PASSWORD=secret123
## ✅ 正確:執行時傳入
## docker run -e DB_PASSWORD=xxx myimage
...
3. 為應用提供合理預設值
ENV APP_ENV=production \
APP_PORT=8080 \
LOG_LEVEL=info
4. 使用有意義的變數名
## ✅ 好:清晰的命名
ENV REDIS_HOST=localhost \
REDIS_PORT=6379
## ❌ 差:模糊的命名
ENV HOST=localhost \
PORT=6379
7.6.8 常見問題
Q:環境變數在 CMD 中不展開
exec 格式不會自動展開環境變數:
## ❌ 不會展開 $PORT
CMD ["python", "app.py", "--port", "$PORT"]
## ✅ 使用 shell 格式或顯式呼叫 sh
CMD ["sh", "-c", "python app.py --port $PORT"]
Q:如何檢視容器的環境變數
$ docker inspect mycontainer --format '{{json .Config.Env}}'
$ docker exec mycontainer env
Q:多行 ENV 還是多個 ENV
## ✅ 推薦:減少層數
ENV VAR1=value1 \
VAR2=value2 \
VAR3=value3
## ⚠️ 多個 ENV 會建立多層
ENV VAR1=value1
ENV VAR2=value2
ENV VAR3=value3
7.7 ARG 構建引數
7.7.1 基本語法
ARG <引數名>[=<預設值>]
ARG 指令定義構建時的變數,可以在 docker build 時透過 --build-arg 傳入。
7.7.2 ARG vs ENV
| 特性 | ARG | ENV |
|---|---|---|
| 生效時間 | 僅構建時 | 構建時 + 執行時 |
| 永續性 | 構建後消失 | 寫入映像檔 |
| 覆蓋方式 | docker build --build-arg |
docker run -e |
| 適用情境 | 構建引數 (版本號等) | 應用配置 |
| 可見性 | docker history 可見 |
docker inspect 可見 |
構建時 執行時
├─ ARG VERSION=1.0 │ (ARG 已消失)
├─ ENV APP_ENV=prod │ APP_ENV=prod(仍存在)
└─ RUN echo $VERSION │
⚠️ 安全提示:不要用 ARG 傳遞密碼等敏感資訊,
docker history可以檢視所有 ARG 值。
7.7.3 基本用法
定義和使用
## 定義有預設值的 ARG
## 建議使用主版本號,如 20 而非 20.10.0,這樣可以自動獲取最新的補丁版本
ARG NODE_VERSION=20
## 使用 ARG
FROM node:${NODE_VERSION}-alpine
RUN echo "Using Node.js $NODE_VERSION"
構建時覆蓋
## 使用預設值
$ docker build -t myapp .
## 覆蓋預設值
$ docker build --build-arg NODE_VERSION=18 -t myapp .
7.7.4 ARG 的作用域
FROM 之前的 ARG
## FROM 之前的 ARG 只能用於 FROM 指令
ARG REGISTRY=docker.io
ARG IMAGE_NAME=node
FROM ${REGISTRY}/${IMAGE_NAME}:20
## ❌ 這裡無法使用上面的 ARG
RUN echo $REGISTRY # 輸出空
FROM 之後重新宣告
ARG NODE_VERSION=20
FROM node:${NODE_VERSION}-alpine
## 需要再次宣告才能使用
ARG NODE_VERSION
RUN echo "Node version: $NODE_VERSION"
多階段構建中的 ARG
ARG BASE_VERSION=alpine
FROM node:22-${BASE_VERSION} AS builder
## 需要重新宣告
ARG NODE_VERSION=20
RUN echo "Building with Node $NODE_VERSION"
FROM node:22-${BASE_VERSION}
## 每個階段都需要重新宣告
ARG NODE_VERSION=20
RUN echo "Running with Node $NODE_VERSION"
7.7.5 常見使用情境
1. 控制基礎映像檔版本
# 推薦使用主版本號(如 3),或指定次版本號(如 3.19),避免指定完整補丁版本
ARG ALPINE_VERSION=3
FROM alpine:${ALPINE_VERSION}
$ docker build --build-arg ALPINE_VERSION=3.19 .
2. 設定軟體版本
# 使用次版本號 (1.30) 而非完整版本號 (1.30.0),以便自動更新到最新補丁版本
ARG NGINX_VERSION=1.30
RUN curl -fsSL https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz | tar -xz
3. 配置構建環境
ARG BUILD_ENV=production
ARG ENABLE_DEBUG=false
RUN if [ "$ENABLE_DEBUG" = "true" ]; then \
npm install --include=dev; \
else \
npm install --production; \
fi
4. 配置私有倉庫
ARG NPM_TOKEN
RUN echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc && \
npm install && \
rm ~/.npmrc
## 構建時傳入 token
$ docker build --build-arg NPM_TOKEN=xxx .
7.7.6 將 ARG 傳遞給 ENV
如果需要在執行時使用 ARG 的值:
ARG VERSION=1.0.0
## 將 ARG 傳遞給 ENV
ENV APP_VERSION=$VERSION
## 執行時可用
CMD echo "App version: $APP_VERSION"
7.7.7 預定義 ARG
Docker 提供了一些預定義的 ARG,無需宣告即可使用:
| ARG | 說明 |
|---|---|
HTTP_PROXY |
HTTP 代理 |
HTTPS_PROXY |
HTTPS 代理 |
NO_PROXY |
不使用代理的地址 |
FTP_PROXY |
FTP 代理 |
## 構建時使用代理
$ docker build --build-arg HTTP_PROXY=http://proxy:8080 .
7.7.8 最佳實務
1. 為 ARG 提供合理預設值
## ✅ 好:有預設值,建議使用主或次版本號而非完整版本號
ARG NODE_VERSION=20
## ⚠️ 需要每次傳入
ARG NODE_VERSION
2. 不要用 ARG 儲存敏感資訊
## ❌ 錯誤:密碼會被記錄在映像檔歷史中
ARG DB_PASSWORD
RUN echo "password=$DB_PASSWORD" > /app/.env
## ✅ 正確:使用 secrets 或執行時環境變數
...
3. 使用 ARG 提高構建靈活性
# 推薦使用次版本號標籤,允許Docker自動拉取最新的補丁版本
ARG BASE_IMAGE=python:3.12-slim
FROM ${BASE_IMAGE}
## 可以構建不同基礎映像檔的版本
## docker build --build-arg BASE_IMAGE=python:3-slim .
...
7.8 VOLUME 定義匿名卷
7.8.1 基本語法
VOLUME ["/路徑1", "/路徑2"]
VOLUME /路徑
VOLUME 指令建立掛載點,並標記為外部掛載的卷。
7.8.2 為什麼使用 VOLUME
核心原則:容器儲存層應該保持無狀態,任何執行時資料都應該儲存在卷中。
flowchart LR
subgraph NoVolume ["沒有 VOLUME:"]
direction TB
subgraph Container1 ["容器儲存層"]
direction TB
Files["資料庫檔案 (問題)<br/>日誌檔案<br/>上傳檔案"]
end
Result1["容器刪除 = 資料丟失"]
Container1 ~~~ Result1
end
subgraph UseVolume ["使用 VOLUME:"]
direction TB
Container2["容器儲存層<br/>(只讀/無狀態)"]
subgraph Volume ["資料卷"]
Data["持久化資料 (安全)"]
end
Container2 --> Volume
Result2["容器刪除,資料保留"]
Volume ~~~ Result2
end
7.8.3 基本用法
定義單個卷
FROM mysql:8.4
VOLUME /var/lib/mysql
定義多個卷
FROM myapp
VOLUME ["/data", "/logs", "/config"]
7.8.4 VOLUME 的行為
1. 自動建立匿名卷
如果執行時未指定掛載,Docker 會自動建立匿名卷:
$ docker run mysql:8.4
$ docker volume ls
DRIVER VOLUME NAME
local a1b2c3d4e5f6... # 自動建立的匿名卷
2. 可被命名卷覆蓋
## 使用命名卷替代匿名卷
$ docker run -v mysql_data:/var/lib/mysql mysql:8.4
3. 可被 Bind Mount 覆蓋
## 使用宿主機目錄替代
$ docker run -v /my/data:/var/lib/mysql mysql:8.4
7.8.5 VOLUME 在構建時的特殊行為
⚠️ 重要:VOLUME 之後對該目錄的修改會被丟棄!
FROM ubuntu
VOLUME /data
## ❌ 這個檔案不會出現在映像檔中!
RUN echo "hello" > /data/test.txt
原因:在構建過程中,VOLUME 指令會為該目錄建立一個臨時的匿名卷。後續 RUN 指令對該目錄的寫入實際發生在這個臨時卷中,而非映像檔層。當該 RUN 指令結束後,臨時卷被丟棄,因此寫入的內容不會儲存到最終映像檔中。注意:這與容器執行時建立的匿名卷是不同的——執行時建立的卷會在容器生命週期內持續存在。
正確做法
FROM ubuntu
## ✅ 先寫入檔案
RUN mkdir -p /data && echo "hello" > /data/test.txt
## 再宣告 VOLUME
VOLUME /data
7.8.6 常見使用情境
資料庫持久化
# 建議使用 postgres:16 或 postgres:latest,具體版本號根據資料庫相容性需求選擇
FROM postgres:16
VOLUME /var/lib/postgresql/data
日誌目錄
FROM nginx
VOLUME /var/log/nginx
上傳檔案目錄
FROM myapp
VOLUME /app/uploads
7.8.7 檢視 VOLUME 定義
## 檢視映像檔定義的 VOLUME
$ docker inspect mysql:8.4 --format '{{json .Config.Volumes}}' | jq
{
"/var/lib/mysql": {}
}
## 檢視容器掛載的卷
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
7.8.8 VOLUME vs docker run -v
| 特性 | Dockerfile VOLUME | docker run -v |
|---|---|---|
| 定義時機 | 映像檔構建時 | 容器執行時 |
| 預設行為 | 建立匿名卷 | 可指定命名卷或路徑 |
| 靈活性 | 低 (固定路徑) | 高 (可任意指定) |
| 適用情境 | 定義必須持久化的路徑 | 靈活的資料管理 |
7.8.9 在 Compose 中
在 Compose 中配置如下:
services:
db:
# 建議使用 postgres:16 或其他具體版本,避免 latest
image: postgres:16
volumes:
# 命名卷(推薦)
- postgres_data:/var/lib/postgresql/data
# Bind Mount
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
volumes:
postgres_data: # 宣告命名卷
7.8.10 安全注意事項
匿名卷可能導致資料丟失
## 使用 --rm 執行的容器,匿名卷會在容器刪除時一起刪除
$ docker run --rm mysql:8.4
## 容器停止後,資料丟失!
...
解決:始終使用命名卷
$ docker run -v mysql_data:/var/lib/mysql mysql:8.4
7.8.11 最佳實務
1. 定義必須持久化的路徑
## 資料庫必須使用卷
FROM postgres:16
VOLUME /var/lib/postgresql/data
2. 不要在 VOLUME 後修改目錄
## ❌ 避免
VOLUME /app/data
RUN cp init-data.json /app/data/
## ✅ 正確
RUN mkdir -p /app/data && cp init-data.json /app/data/
VOLUME /app/data
3. 文件中說明 VOLUME 用途
## 持久化使用者上傳的檔案
VOLUME /app/uploads
## 持久化資料庫資料
VOLUME /var/lib/mysql
7.9 EXPOSE 暴露埠
7.9.1 基本語法
EXPOSE <埠> [<埠>/<協議>...]
EXPOSE 宣告容器執行時提供服務的埠。這是一個 文件性質的宣告,告訴使用者容器會監聽哪些埠。
7.9.2 基本用法
## 宣告單個埠
EXPOSE 80
## 宣告多個埠
EXPOSE 80 443
## 宣告 TCP 和 UDP 埠
EXPOSE 80/tcp
EXPOSE 53/udp
7.9.3 EXPOSE 的作用
1. 文件說明
告訴映像檔使用者,容器將在哪些埠提供服務:
## 使用者一看就知道這是 web 應用
EXPOSE 80 443
## 檢視映像檔暴露的埠
$ docker inspect nginx --format '{{.Config.ExposedPorts}}'
map[80/tcp:{}]
2. 配合 -P 使用
使用 docker run -P 時,Docker 會自動對映 EXPOSE 的埠到宿主機隨機埠:
## Dockerfile
EXPOSE 80
$ docker run -P nginx
$ docker port $(docker ps -q)
80/tcp -> 0.0.0.0:32768
7.9.4 EXPOSE vs -p
| 特性 | EXPOSE | -p |
|---|---|---|
| 位置 | Dockerfile | docker run 命令 |
| 作用 | 宣告/文件 | 實際埠對映 |
| 是否必需 | 否 | 是 (外部訪問時) |
| 對映發生時 | 不發生 | 執行時發生 |
flowchart TD
Expose["EXPOSE 80<br/>僅宣告意圖"]
Run["docker run -p<br/>實際埠對映<br/>宿主機 ←→ 容器"]
Expose ~~~ Run
沒有 EXPOSE 也能 -p
## 即使沒有 EXPOSE,也可以使用 -p
FROM nginx
## 沒有 EXPOSE
...
## 仍然可以對映埠
$ docker run -p 8080:80 mynginx
7.9.5 常見誤解
誤解:EXPOSE 會開啟埠
## ❌ 錯誤理解:這不會讓容器可從外部訪問
EXPOSE 80
EXPOSE 不會:
- 自動進行埠對映
- 讓服務可從外部訪問
- 在容器啟動時開啟埠監聽
EXPOSE 只是後設資料宣告。容器是否實際監聽該埠,取決於容器內的應用。
正確理解
## Dockerfile
FROM nginx
EXPOSE 80 # 1. 宣告:這個容器會在 80 埠提供服務
## 執行:需要 -p 才能從外部訪問
$ docker run -p 8080:80 nginx # 2. 對映:宿主機 8080 → 容器 80
7.9.6 最佳實務
1. 總是宣告應用使用的埠
## Web 服務
FROM nginx
EXPOSE 80 443
## 資料庫
FROM postgres
EXPOSE 5432
## Redis
FROM redis
EXPOSE 6379
2. 使用明確的協議
## 預設是 TCP
EXPOSE 80
## 明確指定 UDP
EXPOSE 53/udp
## 同時支援 TCP 和 UDP
EXPOSE 53/tcp 53/udp
3. 與應用實際埠保持一致
## ✅ 好:EXPOSE 與應用埠一致
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]
## ❌ 差:EXPOSE 與應用埠不一致(誤導)
EXPOSE 80
CMD ["node", "server.js"] # 實際監聽 3000
7.9.7 使用環境變數
ARG PORT=80
EXPOSE $PORT
7.9.8 在 Compose 中
在 Compose 中配置如下:
services:
web:
build: .
ports:
- "8080:80" # 對映埠(類似 -p)
expose:
- "80" # 僅宣告(類似 EXPOSE)
expose 在 Compose 中僅用於容器間通訊的文件說明,不進行埠對映。
7.10 WORKDIR 指定工作目錄
7.10.1 基本語法
WORKDIR <工作目錄路徑>
WORKDIR 指定後續指令的工作目錄。如果目錄不存在,Docker 會自動建立。
7.10.2 基本用法
WORKDIR /app
RUN pwd # 輸出 /app
RUN echo "hello" > world.txt # 建立 /app/world.txt
COPY . . # 複製到 /app/
7.10.3 為什麼需要 WORKDIR
常見錯誤
## ❌ 錯誤:cd 在下一個 RUN 中無效
RUN cd /app
RUN echo "hello" > world.txt # 檔案在根目錄!
原因分析
RUN cd /app
↓
啟動容器 → cd /app(僅記憶體變化)→ 提交映像檔層 → 容器銷燬
│
↓ 工作目錄未改變!
RUN echo "hello" > world.txt
↓
啟動新容器(工作目錄在 /)→ 建立 /world.txt
每個 RUN 都在新容器中執行,前一個 RUN 的記憶體狀態 (包括工作目錄) 不會保留。
正確做法
## ✅ 正確:使用 WORKDIR
WORKDIR /app
RUN echo "hello" > world.txt # 建立 /app/world.txt
7.10.4 相對路徑
WORKDIR 支援相對路徑,基於上一個 WORKDIR:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd # 輸出 /a/b/c
7.10.5 使用環境變數
ENV APP_HOME=/app
WORKDIR $APP_HOME
RUN pwd # 輸出 /app
7.10.6 多階段構建中的 WORKDIR
## 構建階段
## 建議使用 node:22 或 node: 等具體版本標籤,避免使用 latest
FROM node:22 AS builder
WORKDIR /build
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
## 生產階段
## 建議使用 nginx:alpine 或其他具體版本
FROM nginx:alpine
WORKDIR /usr/share/nginx/html
COPY --from=builder /build/dist .
7.10.7 最佳實務
1. 儘早設定 WORKDIR
# 建議使用 node:22 等主/次版本號標籤
FROM node:22
WORKDIR /app # 儘早設定
COPY package*.json ./
RUN npm install
COPY . .
CMD ["node", "server.js"]
2. 使用絕對路徑
## ✅ 推薦:絕對路徑,意圖明確
WORKDIR /app
## ⚠️ 避免:相對路徑可能造成混淆
WORKDIR app
3. 不要用 RUN cd
## ❌ 避免
RUN cd /app && echo "hello" > world.txt
## ✅ 推薦
WORKDIR /app
RUN echo "hello" > world.txt
4. 適時重置 WORKDIR
WORKDIR /app
## ... 應用相關操作 ...
WORKDIR /data
## ... 資料相關操作 ...
...
7.10.8 與其他指令的關係
| 指令 | WORKDIR 的影響 |
|---|---|
RUN |
在 WORKDIR 中執行命令 |
CMD |
在 WORKDIR 中啟動 |
ENTRYPOINT |
在 WORKDIR 中啟動 |
COPY |
相對目標路徑基於 WORKDIR |
ADD |
相對目標路徑基於 WORKDIR |
WORKDIR /app
RUN pwd # /app
COPY . . # 複製到 /app
CMD ["./start.sh"] # /app/start.sh
7.10.9 執行時覆蓋
使用 -w 引數覆蓋工作目錄:
$ docker run -w /tmp myimage pwd
/tmp
7.11 USER 指定當前使用者
7.11.1 基本語法
USER <使用者名稱>[:<使用者組>]
USER <UID>[:<GID>]
USER 指令切換後續指令 (RUN、CMD、ENTRYPOINT) 的執行使用者。
7.11.2 為什麼要使用 USER
筆者強調:以非 root 使用者執行容器是最重要的安全實踐之一。
flowchart LR
subgraph Root ["root 使用者執行的風險:"]
direction TB
R_C["容器內 root"] -- 可能逃逸 --> R_H["宿主機 root"]
R_C -- 漏洞利用 --> R_Control["完全控制宿主機"]
end
subgraph NonRoot ["非 root 使用者執行:"]
direction TB
NR_C["容器內普通使用者"] -- 逃逸後 --> NR_H["宿主機普通使用者"]
NR_C -- 許可權受限,危害降低 --> NR_Safe["無法控制系統"]
end
7.11.3 基本用法
建立並切換使用者
FROM node:22-alpine
## 1. 建立使用者和組
RUN addgroup -g 1001 appgroup && \
adduser -u 1001 -G appgroup -D appuser
## 2. 設定目錄許可權
WORKDIR /app
COPY --chown=appuser:appgroup . .
## 3. 切換使用者
USER appuser
## 4. 後續命令以 appuser 身份執行
CMD ["node", "server.js"]
使用 UID/GID
## 也可以使用數字
USER 1001:1001
7.11.4 使用者必須已存在
USER 指令只能切換到 已存在 的使用者:
## ❌ 錯誤:使用者不存在
USER nonexistent
## Error: unable to find user nonexistent
## ✅ 正確:先建立使用者
RUN useradd -r -s /bin/false appuser
USER appuser
建立使用者的方式
Debian/Ubuntu:
RUN groupadd -r appgroup && \
useradd -r -g appgroup appuser
Alpine:
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S -G appgroup appuser
| 選項 | 說明 |
|---|---|
-r (useradd) / -S (adduser) |
建立系統使用者 |
-g |
指定主組 |
-G |
指定附加組 |
-u |
指定 UID |
-s /bin/false |
禁用登入 shell |
7.11.5 執行時切換使用者
使用 gosu:推薦
在 ENTRYPOINT 指令碼中切換使用者時,不要使用 su 或 sudo,應使用 gosu:
FROM debian:bookworm
## 建立使用者
RUN groupadd -r redis && useradd -r -g redis redis
## 安裝 gosu
RUN apt-get update && apt-get install -y gosu && rm -rf /var/lib/apt/lists/*
COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["redis-server"]
docker-entrypoint.sh:
#!/bin/bash
set -e
## 以 root 執行初始化
chown -R redis:redis /data
## 用 gosu 切換到 redis 使用者執行服務
exec gosu redis "$@"
為什麼不用 su/sudo
| 問題 | su/sudo | gosu |
|---|---|---|
| TTY 要求 | 需要 | 不需要 |
| 訊號傳遞 | 不正確 | 正確 |
| 子程序 | 是 | exec 替換 |
| 容器中使用 | ❌ | ✅ |
7.11.6 執行時覆蓋使用者
使用 -u 或 --user 引數:
## 以指定使用者執行
$ docker run -u 1001:1001 myimage
## 以 root 執行(除錯時)
$ docker run -u root myimage
7.11.7 檔案許可權處理
切換使用者後,確保應用有權訪問檔案:
FROM node:22-alpine
## 建立使用者
RUN adduser -D -u 1001 appuser
WORKDIR /app
## 方式1:使用 --chown
COPY --chown=appuser:appuser . .
## 方式2:手動 chown(減少層數)
## COPY . .
## RUN chown -R appuser:appuser /app
USER appuser
CMD ["node", "server.js"]
7.11.8 最佳實務
1. 始終使用非 root 使用者
## ✅ 推薦
RUN adduser -D appuser
USER appuser
CMD ["myapp"]
## ❌ 避免
CMD ["myapp"] # 以 root 執行
2. 使用固定 UID/GID
便於在宿主機和容器間共享檔案:
## 使用常見的非 root UID
RUN addgroup -g 1000 -S appgroup && \
adduser -u 1000 -S -G appgroup appuser
USER 1000:1000
3. 多階段構建中的 USER
## 構建階段可以用 root
FROM node:22 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
## 生產階段用非 root
FROM node:22-alpine
RUN adduser -D appuser
WORKDIR /app
COPY --from=builder --chown=appuser:appuser /app/dist .
USER appuser
CMD ["node", "server.js"]
7.11.9 常見問題
Q:許可權被拒絕
permission denied: '/app/data.log'
解決:確保目錄許可權正確
RUN mkdir -p /app/data && chown appuser:appuser /app/data
Q:無法繫結低於 1024 的埠
非 root 使用者無法繫結 80、443 等埠。
解決:
- 使用高階口 (如 8080)
- 在執行時對映埠:
docker run -p 80:8080
7.12 HEALTHCHECK 健康檢查
7.12.1 基本語法
HEALTHCHECK [選項] CMD <命令>
HEALTHCHECK NONE
HEALTHCHECK 指令告訴 Docker 如何判斷容器狀態是否正常。這是保障服務高可用的重要機制。
7.12.2 為什麼需要 HEALTHCHECK
在沒有 HEALTHCHECK 之前,Docker 只能透過 程序退出碼 來判斷容器狀態。問題情境:
- Web 服務死鎖,無法響應請求,但程序仍在執行
- 資料庫正在啟動中,尚未準備好接受連線
- 應用陷入死迴圈,CPU 爆滿但程序存活
引入 HEALTHCHECK 後: Docker 定期執行指定的檢查命令,根據返回值判斷容器是否 “健康”。
容器狀態轉換:
Starting ──成功──> Healthy ──失敗N次──> Unhealthy
▲ │
└──────成功──────┘
7.12.3 基本用法
Web 服務檢查
# 注:nginx 映像檔推薦使用具體的版本標籤(如 nginx:1.28-alpine)
FROM nginx
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD curl -fs http://localhost/ || exit 1
命令返回值
0:成功 (healthy)1:失敗 (unhealthy)2:保留值 (不使用)
常用選項
| 選項 | 說明 | 預設值 |
|---|---|---|
--interval |
兩次檢查的間隔 | 30s |
--timeout |
檢查命令的超時時間 | 30s |
--start-period |
啟動緩衝期 (期間失敗不計入次數) | 0s |
--retries |
連續失敗多少次標記為 unhealthy | 3 |
7.12.4 遮蔽健康檢查
如果基礎映像檔定義了 HEALTHCHECK,但你不想使用它:
FROM my-base-image
HEALTHCHECK NONE
7.12.5 常見檢查指令碼
HTTP 服務
使用 curl 或 wget:
## 使用 curl
HEALTHCHECK CMD curl -f http://localhost/ || exit 1
## 使用 wget(Alpine 預設包含)
HEALTHCHECK CMD wget -q --spider http://localhost/ || exit 1
資料庫
## MySQL
HEALTHCHECK CMD mysqladmin ping -h localhost || exit 1
## Redis
HEALTHCHECK CMD redis-cli ping || exit 1
自定義指令碼
COPY healthcheck.sh /usr/local/bin/
HEALTHCHECK CMD ["healthcheck.sh"]
7.12.6 在 Compose 中使用
可以在 compose.yaml (或 docker-compose.yml) 中覆蓋或定義健康檢查:
services:
web:
image: nginx
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
start_period: 40s
帶健康檢查的依賴啟動:
services:
web:
depends_on:
db:
condition: service_healthy # 等待 db 變健康才啟動 web
db:
image: mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
7.12.7 檢視健康狀態
## 檢視容器狀態(包含健康資訊)
$ docker ps
CONTAINER ID STATUS
abc123 Up 1 minute (healthy)
def456 Up 2 minutes (unhealthy)
## 檢視詳細健康日誌
$ docker inspect --format '{{json .State.Health}}' mycontainer | jq
{
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "...",
"End": "...",
"ExitCode": 0,
"Output": "..."
}
]
}
7.12.8 最佳實務
1. 避免副作用
健康檢查會被頻繁執行,不要在檢查指令碼中進行寫操作或消耗大量資源的操作。
2. 使用輕量級工具
優先使用映像檔中已有的工具 (如 wget),避免為了健康檢查安裝龐大的依賴 (如 curl)。
3. 設定合理的 Start Period
應用啟動可能需要時間 (如 Java 應用)。設定 --start-period 可以防止在啟動階段因檢查失敗而誤判。
## 給應用 1 分鐘啟動時間
HEALTHCHECK --start-period=60s CMD curl -f http://localhost/ || exit 1
4. 只檢查核心依賴
健康檢查應主要關注 當前服務 是否可用,而不是檢查其下游依賴 (資料庫等)。下游依賴的檢查應由應用邏輯處理。
5. 與容器編排平臺的關係
不同平臺對 HEALTHCHECK 的支援有所不同:
- Docker Compose:直接使用 Dockerfile 中定義的 HEALTHCHECK,也可在
compose.yaml中覆蓋 - Docker Swarm:使用 HEALTHCHECK 進行服務健康判斷和滾動更新決策
- Kubernetes:忽略 Dockerfile 中的 HEALTHCHECK,使用自己的
livenessProbe和readinessProbe機制
如果應用同時部署在 Docker Compose 和 Kubernetes 環境中,建議在 Dockerfile 中保留 HEALTHCHECK(供 Compose 使用),同時在 Kubernetes 的 Pod 配置中定義對應的探針。
7.13 ONBUILD 為他人作嫁衣裳
7.13.1 基本語法
ONBUILD <其它指令>
ONBUILD 是一個特殊的指令,它後面跟的是其它指令 (如 RUN,COPY 等),這些指令 在當前映像檔構建時不會執行,只有當以當前映像檔為基礎映像檔去構建下一級映像檔時才會被執行。
7.13.2 為什麼需要 ONBUILD
ONBUILD 主要用於製作 語言棧基礎映像檔 或 框架基礎映像檔。
情境:維護 Node.js 專案
假設你有多個 Node.js 專案,它們的構建流程都一樣:
- 建立目錄
- 複製
package.json - 執行
npm install - 複製原始碼
- 啟動應用
如果不使用 ONBUILD,每個專案的 Dockerfile 都要重複這些步驟,且透過 COPY 複製檔案時,基礎映像檔無法預知子專案的檔名。
使用 ONBUILD 的解決方案
基礎映像檔 (my-node-base):
FROM node:22-alpine
WORKDIR /app
## 這些指令將在子映像檔構建時執行
ONBUILD COPY package*.json ./
ONBUILD RUN npm install
ONBUILD COPY . .
CMD ["npm", "start"]
子專案 Dockerfile:
FROM my-node-base
## 只需要一行!
## 構建時會自動執行 COPY 和 RUN
...
7.13.3 執行機制
基礎映像檔構建:
Dockerfile (含 ONBUILD) ──build──> 基礎映像檔 (記錄了 ONBUILD 觸發器)
(指令未執行)
子映像檔構建:
FROM 基礎映像檔 ──build──> 讀取基礎映像檔觸發器 ──> 執行觸發器指令 ──> 繼續執行子 Dockerfile
7.13.4 常見使用情境
1. 自動處理依賴安裝
## Python 基礎映像檔
ONBUILD COPY requirements.txt ./
ONBUILD RUN pip install -r requirements.txt
2. 自動編譯程式碼
## Go 基礎映像檔
ONBUILD COPY . .
ONBUILD RUN go build -o app main.go
3. 處理靜態資源
## Nginx 靜態網站基礎映像檔
ONBUILD COPY dist/ /usr/share/nginx/html/
7.13.5 注意事項
1. 繼承性限制
ONBUILD 指令 只會繼承一次。
- 映像檔 A (含 ONBUILD)
- 映像檔 B (FROM A) -> 觸發 ONBUILD
- 映像檔 C (FROM B) -> 不會 再次觸發 ONBUILD
2. 構建上下文
子映像檔構建時,ONBUILD COPY . . 中的 . 指的是 子專案 的構建上下文,而不是基礎映像檔的上下文。
3. 不允許級聯
ONBUILD ONBUILD 是非法的。你不能寫 ONBUILD ONBUILD COPY ...。
4. 可能會導致構建失敗
由於 ONBUILD 實際上是在子映像檔中執行指令,如果子專案的上下文不滿足要求 (例如缺少 package.json),會導致子映像檔構建失敗,且錯誤資訊可能比較隱晦。
7.13.6 最佳實務
1. 命名規範
建議在映像檔標籤中新增 -onbuild 字尾,明確告知使用者該映像檔包含觸發器。
node:22-onbuild
python:3.12-onbuild
2. 避免執行耗時操作
儘量不要在 ONBUILD 中執行過於耗時或不確定的操作 (如更新系統軟體),這會讓子映像檔構建變得緩慢且不可控。
3. 清理工作
如果 ONBUILD 指令產生了臨時檔案,最好在同一個指令鏈中清理,或者提供機制讓子映像檔清理。
7.14 LABEL 為映像檔新增後設資料
7.14.1 基本語法
LABEL <key>=<value> <key>=<value> ...
LABEL 指令以鍵值對的形式給映像檔新增後設資料。這些資料不會影響映像檔的功能,但可以幫助使用者理解映像檔,或被自動化工具使用。
7.14.2 為什麼需要 LABEL
- 版本管理:記錄版本號、構建時間、Git Commit ID
- 聯絡資訊:維護者郵箱、文件地址、支援渠道
- 自動化工具:CI/CD 工具可以讀取標籤觸發操作
- 許可證資訊:宣告開源協議
7.14.3 基本用法
定義單個標籤
LABEL version="1.0"
LABEL description="這是一個 Web 應用伺服器"
定義多個標籤:推薦
LABEL maintainer="[email protected]" \
version="1.2.0" \
description="My App Description" \
org.opencontainers.image.authors="Yeasy"
💡 包含空格的值需要用引號括起來。
7.14.4 常用標籤規範
為了標準和互操作性,推薦使用 OCI Image Format Specification 定義的標準標籤:
| 標籤 Key | 說明 | 示例 |
|---|---|---|
org.opencontainers.image.created |
構建時間(RFC 3339) | 2024-01-01T00:00:00Z |
org.opencontainers.image.authors |
作者/維護者 | [email protected] |
org.opencontainers.image.url |
專案主頁 | https://example.com |
org.opencontainers.image.documentation |
文件地址 | https://example.com/docs |
org.opencontainers.image.source |
原始碼倉庫 | https://github.com/user/repo |
org.opencontainers.image.version |
版本號 | 1.0.0 |
org.opencontainers.image.licenses |
許可證 | MIT |
org.opencontainers.image.title |
映像檔標題 | My App |
org.opencontainers.image.description |
描述 | Production ready web server |
示例
LABEL org.opencontainers.image.authors="yeasy" \
org.opencontainers.image.documentation="https://yeasy.gitbooks.io" \
org.opencontainers.image.source="https://github.com/yeasy/docker_practice" \
org.opencontainers.image.licenses="MIT"
7.14.5 MAINTAINER 指令:已廢棄
舊版本的 Dockerfile 中常看到 MAINTAINER 指令:
## ❌ 已棄用
MAINTAINER [email protected]
現在推薦使用 LABEL:
## ✅ 推薦
LABEL maintainer="[email protected]"
## 或
LABEL org.opencontainers.image.authors="[email protected]"
7.14.6 動態標籤
配合 ARG 使用,可以在構建時動態注入標籤:
ARG BUILD_DATE
ARG VCS_REF
LABEL org.opencontainers.image.created=$BUILD_DATE \
org.opencontainers.image.revision=$VCS_REF
構建命令:
$ docker build \
--build-arg BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ') \
--build-arg VCS_REF=$(git rev-parse --short HEAD) \
.
7.14.7 檢視標籤
docker inspect
檢視映像檔的標籤資訊:
$ docker inspect nginx --format '{{json .Config.Labels}}' | jq
{
"maintainer": "NGINX Docker Maintainers <[email protected]>"
}
過濾器
可以使用標籤過濾映像檔:
## 列出作者是 yeasy 的所有映像檔
$ docker images --filter "label=org.opencontainers.image.authors=yeasy"
## 刪除所有帶有特定標籤的映像檔
$ docker rmi $(docker images -q --filter "label=stage=builder")
7.15 SHELL 指令
7.15.1 基本語法
SHELL ["executable", "parameters"]
SHELL 指令允許覆蓋 Docker 預設的 shell。
- Linux 預設:
["/bin/sh", "-c"] - Windows 預設:
["cmd", "/S", "/C"]
該指令會影響後續的 RUN,CMD,ENTRYPOINT 指令 (當它們使用 shell 格式時)。
7.15.2 為什麼要用 SHELL 指令
1. 使用 bash 特性
預設的 /bin/sh (通常是 dash 或 alpine 的 ash) 功能有限。如果你需要使用 bash 的特有功能 (如陣列、{} 擴充套件、pipefail 等),可以切換 shell。
FROM ubuntu:24.04
## 切換到 bash
SHELL ["/bin/bash", "-c"]
## 現在可以使用 bash 特性了
RUN echo {a..z}
2. 增強錯誤處理
預設情況下,管道命令 cmd1 | cmd2 只要 cmd2 成功,整個指令就視為成功。這可能掩蓋構建錯誤。
## ❌ 這裡的 wget 失敗了,但構建繼續(因為 tar 成功了)
RUN wget -O - https://invalid-url | tar xz
使用 SHELL 啟用 pipefail:
## ✅ 啟用 pipefail
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
## 如果 wget 失敗,整個 RUN 就會失敗
RUN wget -O - https://invalid-url | tar xz
3. Windows 環境
在 Windows 容器中,經常需要在 cmd 和 powershell 之間切換。
FROM mcr.microsoft.com/windows/servercore:ltsc2022
## 預設是 cmd
RUN echo Default shell is cmd
## 切換到 powershell
SHELL ["powershell", "-command"]
RUN Write-Host "Hello from PowerShell"
## 切回 cmd
SHELL ["cmd", "/S", "/C"]
7.15.3 作用範圍
SHELL 指令可以出現多次,每次隻影響其後的指令:
FROM ubuntu:24.04
## 使用預設 sh
RUN echo "Using sh"
SHELL ["/bin/bash", "-c"]
## 使用 bash
RUN echo "Using bash"
SHELL ["/bin/sh", "-c"]
## 回到 sh
RUN echo "Using sh again"
7.15.4 對其他指令的影響
SHELL 影響的是所有使用 shell 格式 的指令:
| 指令格式 | 是否受 SHELL 影響 |
|---|---|
RUN command |
✅ 是 |
RUN ["exec", "param"] |
❌ 否 |
CMD command |
✅ 是 |
CMD ["exec", "param"] |
❌ 否 |
ENTRYPOINT command |
✅ 是 |
ENTRYPOINT ["exec", "param"] |
❌ 否 |
7.15.5 最佳實務
1. 推薦開啟 pipefail
對於使用 bash 的映像檔,強烈建議開啟 pipefail,以確保構建過程中的錯誤能被及時捕獲。
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
2. 明確意圖
如果由於指令碼需求必須更改 shell,最好在 Dockerfile 中顯式宣告,而不是依賴預設行為。
3. 儘量保持一致
避免在 Dockerfile 中頻繁切換 SHELL,這會使構建過程難以理解和除錯。儘量在頭部定義一次即可。
7.16 參考文件
官方文件
-
Dockerfile官方參考手冊:https://docs.docker.com/engine/reference/builder/ -
Dockerfile最佳實務指南:https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ -
Docker官方映像檔Dockerfile庫:https://github.com/docker-library/docs
常用指令總結
Dockerfile 中的常用指令包括:
- FROM: 指定基礎映像檔,必須是第一條指令
- RUN: 在映像檔中執行命令,用於安裝軟體包等
- COPY: 複製檔案到映像檔中
- ADD: 更高階的複製檔案(支援 URL 和自動解壓)
- CMD: 容器預設執行的命令
- ENTRYPOINT: 容器啟動時的入口點
- ENV: 設定環境變數
- ARG: 構建時的引數變數
- VOLUME: 定義匿名卷掛載點
- EXPOSE: 宣告容器監聽的埠
- WORKDIR: 設定工作目錄
- USER: 指定執行容器時的使用者
- HEALTHCHECK: 配置容器健康檢查
- ONBUILD: 設定觸發器指令,在子映像檔構建時執行
- LABEL: 為映像檔新增後設資料標籤
- SHELL: 指定 RUN 等指令使用的 shell
最佳實務建議
- 使用具體的基礎映像檔版本標籤而非 latest
- 最小化映像檔層數,合併 RUN 指令
- 使用 .dockerignore 檔案排除不必要的檔案
- 安裝必要的軟體包後清理快取
- 使用多階段構建減小最終映像檔體積
- 避免以 root 身份執行容器應用
相關資源
- Docker 官方映像檔庫:https://hub.docker.com/
- Docker 映像檔構建最佳實務:https://docs.docker.com/build/building/best-practices/
7.17 多階段構建
在 Docker 17.05 版本之前,我們構建 Docker 映像檔時,通常會採用兩種方式:
7.17.1 全部放入一個 Dockerfile
一種方式是將所有的構建過程包含在一個 Dockerfile 中,包括專案及其依賴庫的編譯、測試、打包等流程,這裡可能會帶來的一些問題:
-
映像檔層次多,映像檔體積較大,部署時間變長
-
原始碼存在洩露的風險
例如,編寫 app.go 檔案,該程式輸出 Hello World!
package main
import "fmt"
func main(){
fmt.Printf("Hello World!");
}
編寫 Dockerfile.one 檔案
FROM golang:alpine
RUN apk --no-cache add git ca-certificates
WORKDIR /go/src/github.com/go/helloworld/
COPY app.go .
RUN go mod init helloworld \
&& go get github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root
WORKDIR /root/
CMD ["./app"]
構建映像檔
$ docker build -t go/helloworld:1 -f Dockerfile.one .
7.17.2 分散到多個 Dockerfile
另一種方式,就是我們事先在一個 Dockerfile 將專案及其依賴庫編譯測試打包好後,再將其複製到執行環境中,這種方式需要我們編寫兩個 Dockerfile 和一些編譯指令碼才能將其兩個階段自動整合起來,這種方式雖然可以很好地規避第一種方式存在的風險,但明顯部署過程較複雜。
例如,編寫 Dockerfile.build 檔案
FROM golang:alpine
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld
COPY app.go .
RUN go mod init helloworld \
&& go get github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
編寫 Dockerfile.copy 檔案
FROM alpine:3
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]
新建 build.sh
#!/bin/sh
echo Building go/helloworld:build
docker build -t go/helloworld:build . -f Dockerfile.build
docker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extract
echo Building go/helloworld:2
docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app
現在執行指令碼即可構建映像檔
$ chmod +x build.sh
$ ./build.sh
對比兩種方式生成的映像檔大小
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
7.17.3 使用多階段構建
為解決以上問題,Docker v17.05 開始支援多階段構建 (multistage builds)。使用多階段構建我們就可以很容易解決前面提到的問題,並且只需要編寫一個 Dockerfile:
例如,編寫 Dockerfile 檔案
FROM golang:alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go mod init helloworld \
&& go get github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:3 as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /go/src/github.com/go/helloworld/app .
CMD ["./app"]
構建映像檔
$ docker build -t go/helloworld:3 .
對比三個映像檔大小
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 3 d6911ed9c846 7 seconds ago 6.47MB
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
很明顯使用多階段構建的映像檔體積小,同時也完美解決了上邊提到的問題。
Go Modules 最佳實務:上述示例為簡化演示在 Dockerfile 中臨時執行
go mod init。在實際專案中,通常已在程式碼倉庫中維護好go.mod和go.sum檔案。推薦的 Dockerfile 寫法是先複製這兩個檔案並執行go mod download以利用 Docker 層快取,再複製原始碼並構建:
docker COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o app .
7.17.4 只構建某一階段的映像檔
我們可以使用 as 來為某一階段命名,例如
FROM golang:alpine as builder
例如當我們只想構建 builder 階段的映像檔時,增加 --target=builder 引數即可
$ docker build --target builder -t username/imagename:tag .
7.17.5 構建時從其他映像檔複製檔案
上面例子中我們使用 COPY --from=builder /go/src/github.com/go/helloworld/app . 透過已命名階段(as builder)從上一階段的映像檔中複製檔案(命名引用比 --from=0 這種位置索引更易讀,也是當前官方推薦的最佳實務);我們也可以複製任意映像檔中的檔案。
COPY --from=nginx:1.28-alpine /etc/nginx/nginx.conf /nginx.conf
7.18 實戰多階段構建 Laravel 映像檔
本節適用於 PHP 開發者閱讀。
Laravel基於 8.x 版本,各個版本的檔案結構可能會有差異,請根據實際自行修改。
7.18.1 準備
新建一個 Laravel 專案或在已有的 Laravel 專案根目錄下新建 Dockerfile .dockerignore laravel.conf 檔案。
在 .dockerignore 檔案中寫入以下內容。
.idea/
.git/
vendor/
node_modules/
public/js/
public/css/
public/mix-manifest.json
yarn-error.log
bootstrap/cache/*
storage/
## 自行新增其他需要排除的檔案,例如 .env.* 檔案
...
在 laravel.conf 檔案中寫入 nginx 配置。
server {
listen 80 default_server;
root /app/laravel/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ .*\.php(\/.*)*$ {
fastcgi_pass laravel:9000;
include fastcgi.conf;
# fastcgi_connect_timeout 300;
# fastcgi_send_timeout 300;
# fastcgi_read_timeout 300;
}
}
7.18.2 前端構建
第一階段進行前端構建。
# 注:node 映像檔推薦使用具體的版本標籤(如 node:22-alpine)
FROM node:22-alpine as frontend
COPY package.json /app/
RUN set -x ; cd /app \
&& npm install --registry=https://registry.npmmirror.com
COPY webpack.mix.js webpack.config.js tailwind.config.js /app/
COPY resources/ /app/resources/
RUN set -x ; cd /app \
&& touch artisan \
&& mkdir -p public \
&& npm run production
7.18.3 安裝 Composer 依賴
第二階段安裝 Composer 依賴。
# 注:composer 映像檔推薦使用具體的版本標籤(如 composer:2.x)
FROM composer:2 as composer
COPY database/ /app/database/
COPY composer.json composer.lock /app/
RUN set -x ; cd /app \
&& composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \
&& composer install \
--ignore-platform-reqs \
--no-interaction \
--no-plugins \
--no-scripts \
--prefer-dist
7.18.4 整合以上階段所生成的檔案
第三階段對以上階段生成的檔案進行整合。
# 注:php 映像檔版本號已包含 major.minor(8.3),生產環境可根據需要調整
FROM php:8.3-fpm-alpine as laravel
ARG LARAVEL_PATH=/app/laravel
COPY --from=composer /app/vendor/ ${LARAVEL_PATH}/vendor/
COPY . ${LARAVEL_PATH}
COPY --from=frontend /app/public/js/ ${LARAVEL_PATH}/public/js/
COPY --from=frontend /app/public/css/ ${LARAVEL_PATH}/public/css/
COPY --from=frontend /app/public/mix-manifest.json ${LARAVEL_PATH}/public/mix-manifest.json
RUN set -x ; cd ${LARAVEL_PATH} \
&& mkdir -p storage \
&& mkdir -p storage/framework/cache \
&& mkdir -p storage/framework/sessions \
&& mkdir -p storage/framework/testing \
&& mkdir -p storage/framework/views \
&& mkdir -p storage/logs \
&& chmod -R 777 storage \
&& php artisan package:discover
7.18.5 最後一個階段構建 NGINX 映像檔
# 注:nginx 映像檔推薦使用具體的版本標籤(如 nginx:1.28-alpine)
FROM nginx:1.28-alpine as nginx
ARG LARAVEL_PATH=/app/laravel
COPY laravel.conf /etc/nginx/conf.d/
COPY --from=laravel ${LARAVEL_PATH}/public ${LARAVEL_PATH}/public
7.18.6 構建 Laravel 及 Nginx 映像檔
使用 docker build 命令構建映像檔。
$ docker build -t my/laravel --target=laravel .
$ docker build -t my/nginx --target=nginx .
7.18.7 啟動容器並測試
新建 Docker 網路
$ docker network create laravel
啟動 laravel 容器,--name=laravel 引數設定的名字必須與 nginx 配置檔案中的 fastcgi_pass laravel:9000; 一致
$ docker run -dit --rm --name=laravel --network=laravel my/laravel
啟動 nginx 容器
$ docker run -dit --rm --network=laravel -p 8080:80 my/nginx
瀏覽器訪問 127.0.0.1:8080 可以看到 Laravel 專案首頁。
也許 Laravel 專案依賴其他外部服務,例如 redis、MySQL,請自行啟動這些服務之後再進行測試,本小節不再贅述。
7.18.8 生產環境最佳化
本小節內容為了方便測試,將配置檔案直接放到了映像檔中,實際在使用時 建議 將配置檔案作為 config 或 secret 掛載到容器中,請讀者自行學習 Kubernetes 的相關內容。
由於篇幅所限本小節只是簡單列出,更多內容可以參考 khs1994-docker/laravel-demo 專案。
7.18.9 附錄
完整的 Dockerfile 檔案如下。
# 注:生產環境推薦使用具體的版本標籤,如 node:22-alpine、composer:2.x、php:8.3-fpm-alpine、nginx:1.28-alpine
FROM node:22-alpine as frontend
COPY package.json /app/
RUN set -x ; cd /app \
&& npm install --registry=https://registry.npmmirror.com
COPY webpack.mix.js webpack.config.js tailwind.config.js /app/
COPY resources/ /app/resources/
RUN set -x ; cd /app \
&& touch artisan \
&& mkdir -p public \
&& npm run production
FROM composer:2 as composer
COPY database/ /app/database/
COPY composer.json composer.lock /app/
RUN set -x ; cd /app \
&& composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/ \
&& composer install \
--ignore-platform-reqs \
--no-interaction \
--no-plugins \
--no-scripts \
--prefer-dist
FROM php:8.3-fpm-alpine as laravel
ARG LARAVEL_PATH=/app/laravel
COPY --from=composer /app/vendor/ ${LARAVEL_PATH}/vendor/
COPY . ${LARAVEL_PATH}
COPY --from=frontend /app/public/js/ ${LARAVEL_PATH}/public/js/
COPY --from=frontend /app/public/css/ ${LARAVEL_PATH}/public/css/
COPY --from=frontend /app/public/mix-manifest.json ${LARAVEL_PATH}/public/mix-manifest.json
RUN set -x ; cd ${LARAVEL_PATH} \
&& mkdir -p storage \
&& mkdir -p storage/framework/cache \
&& mkdir -p storage/framework/sessions \
&& mkdir -p storage/framework/testing \
&& mkdir -p storage/framework/views \
&& mkdir -p storage/logs \
&& chmod -R 777 storage \
&& php artisan package:discover
FROM nginx:1.28-alpine as nginx
ARG LARAVEL_PATH=/app/laravel
COPY laravel.conf /etc/nginx/conf.d/
COPY --from=laravel ${LARAVEL_PATH}/public ${LARAVEL_PATH}/public
本章小結
本章詳細介紹了 Dockerfile 的所有核心指令,以下是各指令要點的速查表。
| 指令 | 作用 | 關鍵要點 |
|---|---|---|
| FROM | 指定基礎映像檔 | 必須是第一條指令 |
| RUN | 在新層執行命令 | 合併命令、清理快取以減小體積 |
| COPY | 複製檔案 | 優先使用,支援 --from |
| ADD | 更高階的複製 | 自動解壓 tar,不推薦用於下載 |
| CMD | 容器啟動預設命令 | 可被 docker run 引數覆蓋 |
| ENTRYPOINT | 容器入口點 | 固定啟動命令,CMD 作為預設引數 |
| ENV | 設定環境變數 | 構建時 + 執行時均生效 |
| ARG | 構建引數 | 僅構建時生效,FROM 後需重新宣告 |
| VOLUME | 定義匿名卷 | VOLUME 之後的修改會丟失 |
| EXPOSE | 宣告埠 | 僅文件作用,不自動對映 |
| WORKDIR | 指定工作目錄 | 替代 RUN cd,目錄不存在會自動建立 |
| USER | 指定執行使用者 | 使用者必須已存在,推薦 gosu |
| HEALTHCHECK | 健康檢查 | 支援 starting/healthy/unhealthy 狀態 |
| ONBUILD | 延遲執行指令 | 只繼承一次,不可級聯 |
| LABEL | 新增後設資料 | 推薦 OCI 標準標籤,替代 MAINTAINER |
| SHELL | 更改預設 shell | 推薦 ["/bin/bash", "-o", "pipefail", "-c"] |
生產映像檔快速檢查清單
在將映像檔推向生產之前,建議逐條過一遍以下清單:
- [ ] 基礎映像檔選擇了最小化版本(如
alpine、distroless) - [ ] 使用了多階段構建,最終映像檔不含編譯工具鏈
- [ ] 以非 root 使用者執行(
USER指令) - [ ]
COPY優先於ADD,且僅複製必要檔案 - [ ]
RUN指令合併了apt-get update && install && rm -rf /var/lib/apt/lists/* - [ ] 設定了
HEALTHCHECK - [ ] 使用了
.dockerignore排除.git、node_modules等無關檔案 - [ ] 映像檔標籤使用了具體版本號或 commit hash,而非
latest
更完整的編寫指南見附錄:Dockerfile 最佳實務。
延伸閱讀
- 使用 Dockerfile 定製映像檔:Dockerfile 入門
- 多階段構建:最佳化映像檔大小
- Dockerfile 最佳實務:編寫指南
- 安全:容器安全實踐
- Compose 模板檔案:Compose 中的配置
第八章 資料管理
第八章 資料管理
如圖 8-1 所示,Docker 資料管理主要圍繞三類掛載方式展開。
圖 8-1:Docker 資料掛載型別示意圖
這一章介紹如何在 Docker 內部以及容器之間管理資料,在容器中管理資料主要有以下幾種方式:
8.1 資料卷
8.1.1 為什麼需要資料卷
容器的儲存層有一個關鍵問題:容器刪除後,資料就沒了。
flowchart LR
Run[容器執行] --> Write[寫入資料]
Write --> Delete[容器刪除]
Delete -->|資料都在容器 writable 層| Lost[DATA LOST! ❌]
資料卷 (Volume) 解決了這個問題,它的生命週期獨立於容器。
8.1.2 資料卷的特性
| 特性 | 說明 |
|---|---|
| 持久化 | 容器刪除後資料仍然保留 |
| 共享 | 多個容器可以掛載同一個資料卷 |
| 即時生效 | 對資料卷的修改立即可見 |
| 不影響映像檔 | 資料卷中的資料不會打包進映像檔 |
| 效能更好 | 繞過 UnionFS,直接讀寫 |
8.1.3 資料卷 vs 容器儲存層
容器儲存層:不推薦儲存重要資料
graph TD
subgraph Container [容器]
Writable[容器儲存層<br>Writable]
Image[映像檔層<br>ReadOnly]
Writable --- Image
end
Lifecycle[生命週期 = 容器生命週期] -.-> Container
Delete[容器刪除] -->|導致| DataLost[資料丟失 ❌]
資料卷:推薦
graph TD
subgraph Container [容器]
AppDir["/app/data"]
end
subgraph Volume [資料卷 my-data]
Data[持久化資料]
end
AppDir == 掛載 ==> Volume
Delete[容器刪除] -.->|不會影響| Volume
8.1.4 資料卷基本操作
建立資料卷
$ docker volume create my-vol
列出所有資料卷
$ docker volume ls
DRIVER VOLUME NAME
local my-vol
local postgres_data
local redis_data
檢視資料卷詳情
$ docker volume inspect my-vol
[
{
"CreatedAt": "2026-01-15T10:00:00Z",
"Driver": "local",
"Labels": {},
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
"Name": "my-vol",
"Options": {},
"Scope": "local"
}
]
關鍵欄位:
Mountpoint:資料卷在宿主機上的實際儲存位置Driver:儲存驅動 (預設 local,也可以用第三方驅動)
8.1.5 掛載資料卷
方式一:--mount:推薦
$ docker run -d \
--name web \
--mount source=my-vol,target=/usr/share/nginx/html \
nginx
引數說明:
| 引數 | 說明 |
|---|---|
source |
資料卷名稱 (不存在會自動建立) |
target |
容器內掛載路徑 |
readonly |
可選,只讀掛載 |
方式二:-v:簡寫
$ docker run -d \
--name web \
-v my-vol:/usr/share/nginx/html \
nginx
格式:-v 資料卷名:容器路徑[:選項]
兩種方式對比
| 特性 | --mount | -v |
|---|---|---|
| 語法 | 鍵值對,更清晰 | 冒號分隔,更簡潔 |
| 資料卷 (Volume) 掛載行為 | 卷不存在會自動建立,與 -v 結果一致 |
卷不存在會自動建立 |
| 繫結掛載 (Bind Mount) 行為 | ⭐宿主機路徑不存在會報錯,不會自動建立 | 宿主機路徑不存在會 自動建立為目錄 |
| 推薦程度 | ✅ 推薦 (更明確安全,避免誤建立) | 常用 (更簡潔) |
提示:官方更推薦使用
--mount。除了語法格式可讀性更好之外,最重要的行為差異發生在 繫結掛載 (Bind Mount) 時:如果掛載的宿主機源路徑尚未存在,-v會擅自將其自動建立為一個空目錄;而--mount則會嚴格檢查並直接報錯。這能有效避免因路徑拼寫錯誤而在宿主機上留下垃圾目錄(以及導致的容器訪問空目錄問題)。而對於本節的 資料卷 (Volume) 掛載而言,兩者在目標指定的卷不存在時皆會自動建立卷,產生的結果是 完全一致 的。
只讀掛載
## --mount 方式
$ docker run -d \
--mount source=my-vol,target=/data,readonly \
nginx
## -v 方式
$ docker run -d \
-v my-vol:/data:ro \
nginx
8.1.6 使用情境示例
情境一:資料庫持久化
## 建立資料卷
$ docker volume create postgres_data
## 啟動 PostgreSQL,資料儲存在資料卷中
$ docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16 # 確保與環境相容的 PostgreSQL 版本
## 即使刪除容器,資料仍然保留
$ docker rm -f postgres
## 重新啟動,資料還在
$ docker run -d \
--name postgres \
-e POSTGRES_PASSWORD=secret \
-v postgres_data:/var/lib/postgresql/data \
postgres:16 # 確保與環境相容的 PostgreSQL 版本
情境二:多容器共享資料
## 建立共享資料卷
$ docker volume create shared-data
## 容器 A 寫入資料
$ docker run -d --name writer \
-v shared-data:/data \
alpine sh -c "while true; do date >> /data/log.txt; sleep 5; done"
## 容器 B 讀取資料
$ docker run --rm \
-v shared-data:/data \
alpine cat /data/log.txt
情境三:配置檔案持久化
## 將 nginx 配置儲存在資料卷中
$ docker run -d \
-v nginx-config:/etc/nginx/conf.d \
-v nginx-logs:/var/log/nginx \
-p 80:80 \
nginx
8.1.7 資料卷管理
刪除資料卷
## 刪除指定資料卷
$ docker volume rm my-vol
## 刪除容器時同時刪除資料卷
$ docker rm -v container_name
清理未使用的資料卷
## 檢視未被任何容器使用的資料卷
$ docker volume ls -f dangling=true
## 刪除所有未使用的資料卷
$ docker volume prune
## 強制刪除(不提示確認)
$ docker volume prune -f
⚠️ 注意:資料卷不會自動垃圾回收。長期執行的系統應定期清理無用資料卷。
8.1.8 資料卷備份與恢復
備份資料卷
## 使用臨時容器掛載資料卷,打包備份
$ docker run --rm \
-v my-vol:/source:ro \
-v $(pwd):/backup \
alpine tar czf /backup/my-vol-backup.tar.gz -C /source .
原理:
- 建立臨時容器
- 掛載要備份的資料捲到
/source - 掛載當前目錄到
/backup - 使用 tar 打包
恢復資料卷
## 建立新資料卷
$ docker volume create my-vol-restored
## 解壓備份到新資料卷
$ docker run --rm \
-v my-vol-restored:/target \
-v $(pwd):/backup:ro \
alpine tar xzf /backup/my-vol-backup.tar.gz -C /target
備份指令碼示例
#!/bin/bash
## backup-volume.sh
VOLUME_NAME=$1
BACKUP_DIR=${2:-/backups}
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
docker run --rm \
-v ${VOLUME_NAME}:/source:ro \
-v ${BACKUP_DIR}:/backup \
alpine tar czf /backup/${VOLUME_NAME}_${TIMESTAMP}.tar.gz -C /source .
echo "Backed up ${VOLUME_NAME} to ${BACKUP_DIR}/${VOLUME_NAME}_${TIMESTAMP}.tar.gz"
8.1.9 資料卷 vs 繫結掛載
Docker 有兩種主要的資料持久化方式:
| 特性 | 資料卷 (Volume) | 繫結掛載 (Bind Mount) |
|---|---|---|
| 管理方式 | Docker 管理 | 使用者管理 |
| 儲存位置 | /var/lib/docker/volumes/ |
任意宿主機路徑 |
| 可移植性 | 更好 | 依賴宿主機路徑 |
| 適用情境 | 生產資料持久化 | 開發時同步程式碼 |
| 備份 | 需要工具 | 直接訪問檔案 |
## 資料卷
$ docker run -v mydata:/app/data nginx
## 繫結掛載
$ docker run -v /host/path:/app/data nginx
詳見繫結掛載章節。
8.1.10 常見問題
Q:如何知道容器使用了哪些資料卷?
$ docker inspect container_name --format '{{json .Mounts}}' | jq
Q:資料卷的資料在哪裡?
## 檢視資料卷詳情
$ docker volume inspect my-vol
## Mountpoint 欄位顯示實際路徑
"Mountpoint": "/var/lib/docker/volumes/my-vol/_data"
⚠️ 注意:不建議直接修改 Mountpoint 中的檔案,應透過容器操作。
Q:如何在不同機器間遷移資料卷?
- 在源機器備份:
docker run --rm -v mydata:/data -v $(pwd):/backup alpine tar czf /backup/data.tar.gz -C /data . - 傳輸 tar.gz 檔案
- 在目標機器恢復
8.2 掛載主機目錄
8.2.1 什麼是繫結掛載
Bind Mount (繫結掛載) 將 Docker daemon 所在主機 上的目錄或檔案直接掛載到容器中。容器可以讀寫這臺主機上的檔案系統。
flowchart LR
subgraph Host ["宿主機"]
Dir1["/home/user/code/"]
end
subgraph Container ["容器"]
Dir2["/usr/share/nginx/html/"]
end
Dir1 <-->|Bind Mount| Dir2
目錄結構(同一份檔案):
/home/user/code/ (或 /usr/share/nginx/html/)
├── index.html
├── style.css
└── app.js
8.2.2 Bind Mount vs Volume
| 特性 | Bind Mount | Volume |
|---|---|---|
| 資料位置 | 宿主機任意路徑 | Docker 管理的目錄 |
| 路徑指定 | 必須是絕對路徑 | 卷名 |
| 可移植性 | 依賴宿主機路徑 | 更好 (Docker 管理) |
| 效能 | 依賴宿主機檔案系統 | 最佳化的儲存驅動 |
| 適用情境 | 開發環境、配置檔案 | 生產資料持久化 |
| 備份 | 直接訪問檔案 | 需要透過 Docker |
選擇建議
| 需求 | 推薦方案 |
|---|---|
| 開發時同步程式碼 | Bind Mount |
| 持久化資料庫資料 | Volume |
| 共享配置檔案 | Bind Mount |
| 容器間共享資料 | Volume |
| 備份方便 | Bind Mount (直接訪問) |
| 生產環境 | Volume |
8.2.3 基本語法
使用 --mount:推薦
$ docker run -d \
--mount type=bind,source=/宿主機路徑,target=/容器路徑 \
nginx
使用 -v:簡寫
$ docker run -d \
-v /宿主機路徑:/容器路徑 \
nginx
兩種語法對比
| 特性 | --mount | -v |
|---|---|---|
| 語法 | 鍵值對,更清晰 | 冒號分隔,更簡潔 |
| 路徑不存在時 | 直接報錯 (Fail Fast) | 靜默自動建立 目錄 |
| 推薦程度 | ✅ 推薦 | 常用 |
⚠️ 陷阱:如果不小心掛載了一個不存在的主機路徑,使用
-v會在 daemon 主機 上靜默建立一個空目錄。對於本地 Docker Desktop 使用者,這個主機通常就是本機;對於遠端 daemon,這個主機就是遠端機器。這也是 Docker 官方更推薦使用--mount的原因:它會直接報錯,避免因路徑拼寫錯誤而掛錯位置。
8.2.4 使用情境
情境一:開發環境程式碼同步
## 將原生程式碼目錄掛載到容器
$ docker run -d \
-p 8080:80 \
--mount type=bind,source=$(pwd)/src,target=/usr/share/nginx/html \
nginx
## 修改本地檔案,容器內立即生效(熱更新)
$ echo "Hello" > src/index.html
## 瀏覽器重新整理即可看到變化
...
情境二:配置檔案掛載
## 掛載自定義 nginx 配置
$ docker run -d \
--mount type=bind,source=/path/to/nginx.conf,target=/etc/nginx/nginx.conf,readonly \
nginx
情境三:日誌收集
## 將容器日誌輸出到宿主機目錄
$ docker run -d \
--mount type=bind,source=/var/log/myapp,target=/app/logs \
myapp
情境四:共享 SSH 金鑰
## 掛載 SSH 金鑰(只讀)
$ docker run --rm -it \
--mount type=bind,source=$HOME/.ssh,target=/root/.ssh,readonly \
alpine ssh user@remote
8.2.5 只讀掛載
防止容器修改宿主機檔案:
## --mount 語法
$ docker run -d \
--mount type=bind,source=/config,target=/app/config,readonly \
myapp
## -v 語法
$ docker run -d \
-v /config:/app/config:ro \
myapp
容器內嘗試寫入會報錯:
$ touch /app/config/new.txt
touch: /app/config/new.txt: Read-only file system
8.2.6 掛載單個檔案
## 掛載 bash 歷史記錄
$ docker run --rm -it \
--mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
ubuntu bash
## 掛載自定義配置檔案
$ docker run -d \
--mount type=bind,source=/path/to/my.cnf,target=/etc/mysql/my.cnf \
mysql
⚠️ 注意:掛載單個檔案時,如果宿主機上的檔案被編輯器替換 (而非原地修改),容器內仍是舊檔案的 inode。建議重啟容器或掛載目錄。
8.2.7 檢視掛載資訊
$ docker inspect mycontainer --format '{{json .Mounts}}' | jq
輸出:
[
{
"Type": "bind",
"Source": "/home/user/code",
"Destination": "/app",
"Mode": "",
"RW": true,
"Propagation": "rprivate"
}
]
| 欄位 | 說明 |
|---|---|
Type |
掛載型別 (bind) |
Source |
宿主機路徑 |
Destination |
容器內路徑 |
RW |
是否可讀寫 |
Propagation |
掛載傳播模式 |
8.2.8 常見問題
Q:路徑不存在報錯
$ docker run --mount type=bind,source=/not/exist,target=/app nginx
docker: Error response from daemon: invalid mount config for type "bind":
bind source path does not exist: /not/exist
解決:確保源路徑存在。若你確實需要自動建立目錄,可改用 -v,但要先確認建立位置就是你想要的主機路徑。
Q:許可權問題
容器內使用者可能無權訪問掛載的檔案:
## 方法1:確保宿主機檔案許可權允許容器使用者訪問
$ chmod -R 755 /path/to/data
## 方法2:以 root 執行容器
$ docker run -u root ...
## 方法3:使用相同的 UID
$ docker run -u $(id -u):$(id -g) ...
Q:macOS/Windows 效能問題
在 Docker Desktop 上,Bind Mount 效能通常不如 Volume,因為資料需要在宿主機檔案系統和 Linux VM 之間同步:
## 使用 :cached 或 :delegated 提高效能(macOS,僅 Docker Desktop 4.5 及更早版本)
$ docker run -v /host/path:/container/path:cached myapp
| 選項 | 說明 |
|---|---|
:cached |
宿主機權威,容器讀取可能延遲 |
:delegated |
容器權威,宿主機讀取可能延遲 |
:consistent |
預設,完全一致 (最慢) |
注意:Docker Desktop 4.6+ 預設使用 VirtioFS 檔案共享引擎,上述
:cached/:delegated選項已被靜默忽略。如需最佳化檔案同步效能,請參考 Docker Desktop 的 Synchronized file shares 功能。
8.2.9 最佳實務
1. 開發環境使用 Bind Mount
## 程式碼熱更新
$ docker run -v $(pwd):/app -p 3000:3000 node npm run dev
2. 生產環境使用 Volume
## 資料持久化
$ docker run -v mysql_data:/var/lib/mysql mysql
3. 配置檔案使用只讀掛載
$ docker run -v /config/nginx.conf:/etc/nginx/nginx.conf:ro nginx
4. 注意路徑安全
## ❌ 危險:掛載根目錄或敏感目錄
$ docker run -v /:/host ...
## ✅ 只掛載必要的目錄
$ docker run -v /app/data:/data ...
8.3 tmpfs 掛載
tmpfs 掛載會把資料放在記憶體中,而不是寫入容器可寫層或資料卷。它只適用於 Linux 語義的容器環境,適合需要快速讀寫但不要求持久化的資料。
8.3.1 適用情境
- 臨時快取
- 會話資料
- 不希望落盤的敏感中間檔案
8.3.2 基本用法
使用 --mount 語法(推薦):
$ docker run --mount type=tmpfs,destination=/run,tmpfs-size=67108864,tmpfs-mode=1770 nginx
也可以使用 --tmpfs 簡寫語法:
$ docker run --tmpfs /run:size=64m nginx
注意:
--tmpfs更適合簡單情境;如果你希望顯式描述掛載點、大小和許可權,--mount type=tmpfs,...的可讀性更好,也更便於後續維護。
8.3.3 注意事項
- 容器停止後,
tmpfs資料會丟失。 tmpfs佔用宿主機記憶體,建議顯式限制大小。- 不適合需要持久化的資料。
tmpfs不適合多個容器共享同一份資料,也不適合當作跨重啟的快取層。- 在記憶體壓力較高時,部分資料可能受系統交換機制影響,因此不要把
tmpfs當作絕對不會落盤的安全邊界。
8.3.4 與 Volume / Bind Mount 對比
| 型別 | 資料位置 | 持久化 | 典型用途 |
|---|---|---|---|
| Volume | Docker 管理目錄 | 是 | 資料庫、長期業務資料 |
| Bind Mount | 宿主機指定目錄 | 是 | 開發聯調、配置檔案共享 |
| tmpfs | 記憶體 | 否 | 高速臨時資料、敏感臨時檔案 |
本章小結
本章介紹了 Docker 的三種資料管理方式:資料卷 (Volume)、繫結掛載 (Bind Mount) 和 tmpfs 掛載。
| 方式 | 特點 | 適用情境 |
|---|---|---|
| 資料卷 (Volume) | Docker 管理,生命週期獨立於容器 | 資料庫、應用資料(推薦生產環境) |
| 繫結掛載 (Bind Mount) | 掛載宿主機目錄,更靈活 | 開發環境、配置檔案、日誌 |
| tmpfs 掛載 | 僅儲存在記憶體中,容器停止即消失 | 臨時敏感資料、快取記憶體 |
| 操作 | 命令 |
|---|---|
| 建立資料卷 | docker volume create name |
| 列出資料卷 | docker volume ls |
| 檢視詳情 | docker volume inspect name |
| 刪除資料卷 | docker volume rm name |
| 清理未用 | docker volume prune |
| 掛載資料卷 | -v name:/path 或 --mount source=name,target=/path |
延伸閱讀
- 資料卷:Docker 管理的持久化儲存
- 繫結掛載:掛載宿主機目錄
- tmpfs 掛載:記憶體中的臨時儲存
- 儲存驅動:Docker 儲存的底層原理
- Compose 資料管理:Compose 中的掛載配置
第九章 網路配置
第九章 網路配置
Docker 容器需要網路來與外部世界通訊、容器之間相互通訊以及與宿主機通訊。Docker 在安裝時會自動配置網路基礎設施,大多數情況下開箱即用。
概述
Docker 啟動時自動建立以下網路元件:
graph TD
subgraph Host [宿主機]
eth0[物理網絡卡 eth0<br>192.168.1.100]
docker0[docker0 網橋<br>172.17.0.1]
subgraph Containers
subgraph ContainerA [容器 A]
eth0_A[eth0<br>172.17.0.2]
end
subgraph ContainerB [容器 B]
eth0_B[eth0<br>172.17.0.3]
end
end
eth0 <--> docker0
docker0 <--> eth0_A
docker0 <--> eth0_B
end
Internet((網際網路)) <--> eth0
本章將詳細介紹 Docker 網路配置的各個方面。
本章內容
9.1 配置 DNS
Docker 1.10.0 以後,內建了一個 DNS 伺服器,使得容器可以直接透過容器名稱通訊。方法很簡單,只要在建立容器時使用 --name 為容器命名即可。
但是使用 Docker DNS 有個前提條件,就是它只能在 自定義網路 中使用。也就是說,如果使用的是預設的 bridge 網路,是無法使用 DNS 的,所以我們就需要自定義網路。
9.1.1 容器的 DNS 機制
Docker 容器的 DNS 配置有兩種情況:
- 預設 Bridge 網路:繼承宿主機的 DNS 配置 (
/etc/resolv.conf)。 - 自定義網路(推薦):使用 Docker 嵌入式 DNS 伺服器 (Embedded DNS),支援透過 容器名 進行服務發現。
9.1.2 嵌入式 DNS
這是 Docker 網路最強大的功能之一。在自定義網路中,容器可以透過 “名字” 找到彼此,而不需要知道對方的 IP (因為 IP 可能會變)。
## 1. 建立自定義網路
$ docker network create mynet
## 2. 啟動容器 web 並加入網路
$ docker run -d --name web --network mynet nginx
## 3. 啟動容器 client 並嘗試 ping web
$ docker run -it --rm --network mynet alpine ping web
PING web (172.18.0.2): 56 data bytes
64 bytes from 172.18.0.2: seq=0 ttl=64 time=0.074 ms
原理:
Docker 守護程序在 127.0.0.11 執行了一個 DNS 伺服器。容器內的 DNS 請求會被轉發到這裡。如果是容器名,解析為容器 IP;如果是外部域名 (如 google.com),轉發給上游 DNS。
9.1.3 配置 DNS 引數
如果你需要手動配置容器的 DNS (例如使用內網 DNS 伺服器),可以在 docker run 中使用以下引數:
1. --dns
指定 DNS 伺服器 IP。
$ docker run -it --dns=114.114.114.114 ubuntu cat /etc/resolv.conf
nameserver 114.114.114.114
2. --dns-search
指定 DNS 搜尋域。例如設定為 example.com,則 ping host 會嘗試解析 host.example.com。
$ docker run --dns-search=example.com myapp
3. --hostname 與 -h
設定容器的主機名。
$ docker run -h myweb nginx
9.1.4 全域性 DNS 配置
如果希望所有容器都使用特定的 DNS 伺服器 (而不是繼承宿主機),可以修改 /etc/docker/daemon.json:
{
"dns": [
"114.114.114.114",
"8.8.8.8"
]
}
修改後需要重啟 Docker 服務:systemctl restart docker。
9.1.5 常見問題
以下是使用容器 DNS 時常見的問題及解決方法:
Q:容器無法解析域名
現象:ping www.baidu.com 失敗,但 ping 8.8.8.8 成功。解決:
- 宿主機的
/etc/resolv.conf可能有問題 (例如使用了本地迴環地址 127.0.0.53,特別是 Ubuntu 系統)。Docker 可能會嘗試修復,但有時會失敗。 - 嘗試手動指定 DNS:
docker run --dns 8.8.8.8 ... - 檢查防火牆是否攔截了 UDP 53 埠。
Q:無法透過容器名通訊
現象:ping db 提示 bad address 'db'。原因:
- 你可能在使用 預設的 bridge 網路。預設 bridge 網路 不支援 透過容器名進行 DNS 解析 (這是一個歷史遺留設計)。
- 解決:使用自定義網路 (
docker network create ...)。
9.2 網路型別
Docker 提供了多種網路驅動來滿足不同的使用情境。安裝 Docker 後,系統會自動建立三個預設網路:
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
abc123... bridge bridge local
def456... host host local
ghi789... none null local
9.2.1 網路型別對比
各網路型別的特點和適用情境如下:
| 網路型別 | 說明 | 適用情境 |
|---|---|---|
| bridge | 預設型別,容器連線到虛擬網橋 | 大多數單機情境 |
| host | 容器直接使用宿主機網路棧 | 需要最高網路效能時 |
| none | 禁用網路 | 完全隔離的容器 |
| overlay | 跨主機網路 | Docker Swarm 叢集 |
| macvlan | 容器擁有獨立 MAC 地址 | 需要直接接入物理網路 |
| ipvlan | 容器共享父介面 MAC,獨享 IP | 同網段大量容器、對 MAC 數量受限的網路 |
9.2.2 Bridge 網路:預設
Bridge 是 Docker 預設使用的網路模式。Docker 啟動時會自動建立 docker0 虛擬網橋,所有未指定網路的容器都會連線到這個網橋上。
核心元件如下:
| 元件 | 說明 |
|---|---|
| docker0 | 虛擬網橋,充當交換機角色 |
| veth pair | 虛擬網絡卡對,一端在容器內,一端連線網橋 |
| 容器 eth0 | 容器內的網絡卡 |
| IP 地址 | 自動從 172.17.0.0/16 網段分配 |
9.2.3 Host 網路
使用 --network host 引數啟動的容器會直接使用宿主機的網路棧,不再擁有獨立的網路名稱空間。容器內的埠就是宿主機的埠,無需埠對映。
$ docker run -d --network host nginx
這種模式下網路效能最高,但容器之間和宿主機之間沒有網路隔離。
9.2.4 None 網路
使用 --network none 引數啟動的容器只有 lo 迴環網絡卡,完全沒有外部網路連線。適用於只需要執行計算任務、不需要網路的容器。
$ docker run -it --network none alpine ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> ...
inet 127.0.0.1/8 scope host lo
9.2.5 資料流向
容器網路中的資料流向可以分為以下幾種情況:
flowchart LR
subgraph Comm1 ["容器間通訊"]
direction LR
C1A["容器 A (172.17.0.2)"] --> D0A["docker0"] --> C1B["容器 B (172.17.0.3)"]
end
subgraph Comm2 ["訪問外網"]
direction LR
C2A["容器 A (172.17.0.2)"] --> D0B["docker0"] --> Eth0A["eth0"] --> InternetA["網際網路"]
end
subgraph Comm3 ["被外部訪問,需埠對映"]
direction LR
Req["外部請求"] --> Eth0B["eth0"] --> D0C["docker0"] --> C3A["容器 A"]
end
9.3 自定義網路
在生產環境中,推薦使用使用者自定義網路代替預設的 bridge 網路。自定義網路提供了更好的隔離性和服務發現能力。
9.3.1 為什麼要用自定義網路
預設 bridge 網路存在以下侷限,而自定義網路可以很好地解決這些問題:
| 問題 | 自定義網路的優勢 |
|---|---|
| 只能用 IP 通訊 | 支援容器名 DNS 解析 |
| 所有容器在同一網路 | 更好的隔離性 |
| 需要 --link (已廢棄) | 原生支援服務發現 |
9.3.2 建立自定義網路
使用 docker network create 命令可以建立自定義網路:
## 建立網路
$ docker network create mynet
## 檢視網路詳情
$ docker network inspect mynet
9.3.3 使用自定義網路
啟動容器時透過 --network 引數指定連線的網路:
## 啟動容器並連線到自定義網路
$ docker run -d --name web --network mynet nginx
$ docker run -d --name db --network mynet postgres
## 在 web 容器中可以直接用容器名訪問 db
$ docker exec web ping db
PING db (172.18.0.3): 56 data bytes
64 bytes from 172.18.0.3: seq=0 ttl=64 time=0.083 ms
9.3.4 容器名 DNS 解析
自定義網路自動提供 DNS 服務。Docker 守護程序在 127.0.0.11 執行了一個嵌入式 DNS 伺服器,容器內的 DNS 請求會被轉發到這裡:
- 如果是容器名,解析為容器 IP
- 如果是外部域名 (如 google.com),轉發給上游 DNS
flowchart LR
subgraph MyNet ["mynet 網路 : web 容器可以用 'db' 作為主機名訪問 db 容器"]
direction LR
Web["web<br/>172.18.0.2"] -- "DNS: 'db' → 172.18.0.3" --> DB["db<br/>172.18.0.3"]
end
9.3.5 常用網路命令
以下是 Docker 網路管理中常用的命令:
## 列出網路
$ docker network ls
## 建立網路
$ docker network create mynet
## 檢視網路詳情
$ docker network inspect mynet
## 連線容器到網路
$ docker network connect mynet mycontainer
## 斷開網路連線
$ docker network disconnect mynet mycontainer
## 刪除網路
$ docker network rm mynet
## 清理未使用的網路
$ docker network prune
🔥 踩坑實錄
一個新手開發者透過
docker compose部署了兩個容器化服務:服務 A 和服務 B。他在服務 A 的程式碼中嘗試用localhost:3000訪問服務 B,結果始終連線超時。這個錯誤非常隱蔽——在本地單機開發時看不出問題,因為他可能在同一個程序中測試。排查時他錯誤地認為是防火牆或網路配置問題。實際原因是:每個容器都有獨立的網路名稱空間,localhost在容器內部只指向容器自己,不是宿主機也不是其他容器。正確的做法是使用 Compose 自動建立的服務名作為主機名:http://service-b:3000。Compose 會自動在網路中註冊服務名的 DNS,這樣容器間通訊才能正確解析。改動僅需一行程式碼,問題隨之消失。
9.4 容器互聯
容器之間的網路通訊是 Docker 網路的核心功能之一。本節介紹容器互聯的幾種方式。
9.4.1 同一網路內的容器
同一自定義網路內的容器可以直接透過容器名通訊,這是推薦的容器互聯方式:
## 建立網路
$ docker network create app-net
## 啟動應用和資料庫
$ docker run -d --name redis --network app-net redis
$ docker run -d --name app --network app-net myapp
## app 容器中可以用 redis:6379 連線 Redis
...
9.4.2 連線到多個網路
一個容器可以同時連線到多個網路,這對於需要跨網路通訊的中介軟體容器特別有用:
## 啟動容器
$ docker run -d --name multi-net-container --network frontend nginx
## 再連線到另一個網路
$ docker network connect backend multi-net-container
## 檢視容器的網路
$ docker inspect multi-net-container --format '{{json .NetworkSettings.Networks}}'
9.4.3 ⚠️ --link 已廢棄
--link 是 Docker 早期用於容器互聯的方式,已經被廢棄,不建議在新專案中使用。請使用自定義網路替代:
## 舊方式(不推薦)
$ docker run --link db:database myapp
## 新方式(推薦)
$ docker network create mynet
$ docker run --network mynet --name db postgres
$ docker run --network mynet --name app myapp
使用自定義網路的優勢在於:
- 原生支援 DNS 解析
- 不需要在容器啟動時顯式宣告依賴
- 更靈活,可以動態 connect/disconnect
9.5 外部訪問容器
容器執行在自己的隔離網路環境中 (通常是 Bridge 模式)。為了讓外部網路訪問容器內的服務,我們需要將容器的埠對映到宿主機的埠。
9.5.1 為什麼要對映埠
容器的網路訪問規則如下:
- 容器之間:可以透過 IP 或容器名 (自定義網路) 互通。
- 宿主機訪問容器:可以透過容器 IP 訪問。
- 外部網路訪問容器:❌ 預設無法直接訪問。
為了讓外部 (如你的瀏覽器、其他區域網機器) 訪問容器內的服務,我們需要將容器的埠 對映 到宿主機的埠。
flowchart TD
User["外部使用者 (Browser)"] --> Host["宿主機 (localhost:8080)"]
Host --> Proxy["Docker Proxy<br/>埠對映 (8080 -> 80)"]
Proxy --> Container["容器 (Class B: 80)"]
9.5.2 埠對映方式
Docker 提供了多種方式來指定埠對映。
1. 指定對映
使用 -p <宿主機埠>:<容器埠> 格式:
## 將宿主機的 8080 埠對映到容器的 80 埠
$ docker run -d -p 8080:80 nginx
此時訪問 http://localhost:8080 即可看到 Nginx 頁面。
多種格式:
| 格式 | 含義 | 示例 |
|---|---|---|
ip:hostPort:containerPort |
繫結指定 IP 的特定埠 | -p 127.0.0.1:8080:80 (僅本機訪問) |
ip::containerPort |
繫結指定 IP 的隨機埠 | -p 127.0.0.1::80 |
hostPort:containerPort |
繫結所有 IP (0.0.0.0) 的特定埠 | -p 8080:80 (預設) |
containerPort |
繫結所有 IP 的隨機埠 | -p 80 |
2. 隨機對映
如果不關心宿主機使用哪個埠,可以使用隨機對映。使用 -P (大寫) 引數,Docker 會把 Dockerfile 中 EXPOSE 指令暴露的所有埠釋出到宿主機的隨機高位埠。具體落在哪個埠,取決於宿主機當前可用的臨時埠範圍。
$ docker run -d -P nginx
檢視對映結果:
$ docker ps
CONTAINER ID PORTS
abc123456 0.0.0.0:49153->80/tcp
此時 Nginx 被對映到了宿主機的一個隨機高位埠,例如 49153。
9.5.3 檢視埠對映
可以使用以下命令檢視容器的埠對映:
docker port
執行 docker port 可以檢視到指定容器的埠對映情況:
$ docker port mycontainer
80/tcp -> 0.0.0.0:8080
80/tcp -> [::]:8080
docker ps
執行 docker ps 可以檢視到所有容器的埠對映列表:
$ docker ps
CONTAINER ID IMAGE PORTS NAMES
abc123456 nginx 0.0.0.0:8080->80/tcp web
9.5.4 最佳實務與安全
在配置埠對映時,需要注意以下安全事項:
1. 限制監聽 IP
預設情況下,-p 8080:80 會監聽 0.0.0.0:8080,這意味著任何人只要能連線你的宿主機 IP,就能訪問該服務。
如果不希望對外暴露 (例如資料庫服務),應繫結到 127.0.0.1:
## 僅允許本機訪問
$ docker run -d -p 127.0.0.1:3306:3306 mysql
2. 避免埠衝突
如果宿主機 8080 已經被佔用了,容器將無法啟動。
解決:
- 更換宿主機埠:
-p 8081:80 - 讓 Docker 自動分配:
-p 80
3. UDP 對映
預設是 TCP 協議。如果要對映 UDP 服務 (如 DNS,Syslog):
$ docker run -d -p 53:53/udp dns-server
9.5.5 實現原理
Docker 使用 docker-proxy 程序 (使用者態) 或 iptables DNAT 規則 (核心態) 來實現埠轉發。
當流量到達宿主機埠時,iptables 規則將其目標地址修改為容器 IP 並轉發:
## 簡化的 iptables 邏輯
iptables -t nat -A DOCKER -p tcp --dport 8080 -j DNAT --to-destination 172.17.0.2:80
這也是為什麼你在容器內部看到的訪問來源 IP 通常是閘道器 IP (如 172.17.0.1),而不是真實的外部 Client IP (除非使用 host 網路模式)。
9.6 網路隔離
Docker 網路提供了天然的隔離能力,不同網路之間的容器預設無法通訊。這是 Docker 網路安全的重要基礎。
9.6.1 網路隔離原理
不同網路之間預設隔離,容器只能與同一網路中的容器直接通訊:
## 建立兩個網路
$ docker network create frontend
$ docker network create backend
## 容器 A 在 frontend
$ docker run -d --name web --network frontend nginx
## 容器 B 在 backend
$ docker run -d --name db --network backend postgres
## web 無法直接訪問 db(不同網路)
$ docker exec web ping db
ping: db: Name or service not known
9.6.2 安全優勢
這種隔離機制帶來以下安全優勢:
| 情境 | 說明 |
|---|---|
| 前後端分離 | 前端容器無法直接訪問資料庫網路 |
| 微服務隔離 | 不同微服務組可以使用不同網路 |
| 多租戶 | 不同租戶的容器在不同網路中完全隔離 |
| 最小許可權 | 容器只能訪問必要的網路資源 |
9.6.3 跨網路通訊
如果確實需要某個容器跨網路通訊,可以將其同時連線到多個網路:
## 建立一箇中介軟體容器,連線到兩個網路
$ docker run -d --name api --network frontend myapi
$ docker network connect backend api
## 現在 api 容器既可以訪問 frontend 中的 web,也可以訪問 backend 中的 db
這種方式讓你可以精確控制哪些容器可以跨網路通訊,遵循最小許可權原則。
9.6.4 典型網路架構
一個典型的多層應用網路架構如下:
graph TD
subgraph FrontendNet ["frontend 網路"]
LB["負載均衡器"]
Web1["Web 伺服器 1"]
Web2["Web 伺服器 2"]
end
subgraph BackendNet ["backend 網路"]
API["API 伺服器"]
DB["資料庫"]
Cache["Redis 快取"]
end
LB --> Web1
LB --> Web2
Web1 -.-> API
Web2 -.-> API
API --> DB
API --> Cache
在這種架構中,API 伺服器同時連線到 frontend 和 backend 網路,充當兩個網路之間的橋樑。負載均衡器和 Web 伺服器無法直接訪問資料庫,增強了安全性。
9.7 容器網路高階特性
本節聚焦 Docker 自身可見的高階網路能力:Overlay 網路、服務發現和 DNS 解析。更偏 Kubernetes 編排網路的概念,如 CNI 和 NetworkPolicy,不在本章展開。
9.7.1 Overlay 網路原理與配置
Overlay 網路在現有網路基礎上建立虛擬網路,允許容器跨宿主機通訊。它是 Swarm 情境下的核心網路驅動之一。
Overlay 網路工作原理
Overlay 網路透過隧道封裝技術(通常是 VXLAN)將容器網路流量封裝在宿主機物理網路的 UDP 資料包中傳輸。Docker overlay 網路預設使用 4789/udp 作為資料通道埠,同時 Swarm 控制面與節點通訊還需要相應開放 2377/tcp、7946/tcp 和 7946/udp。
容器 A (192.168.0.2)
↓
veth 對
↓
br-net (網橋)
↓
Docker 引擎 (VXLAN 封裝)
↓
物理網路 (172.16.0.0/24)
↓
Docker 引擎 (VXLAN 解封裝)
↓
br-net (網橋)
↓
veth 對
↓
容器 B (192.168.0.3,不同宿主機)
建立和使用 Overlay 網路
Overlay 網路依賴 Swarm。若要讓獨立容器加入 overlay 網路,需要把網路建立為 attachable。
# 初始化 Swarm
docker swarm init
# 建立 attachable overlay 網路
docker network create --driver overlay \
--attachable \
--subnet 192.168.0.0/24 \
--opt com.docker.network.driver.mtu=1450 \
my-overlay-net
# 驗證網路建立
docker network ls
docker network inspect my-overlay-net
# 在 Swarm 服務中使用 overlay 網路
docker service create --name web \
--network my-overlay-net \
--replicas 3 \
nginx # 確保與環境相容的 Nginx 版本
# 單機環境下也可以讓普通容器加入 attachable overlay 網路
docker run -d --name container1 --network my-overlay-net busybox sleep 1d
docker run -d --name container2 --network my-overlay-net busybox sleep 1d
# 測試跨容器通訊
docker exec container1 ping -c 1 container2
Overlay 網路效能最佳化
# 調整 MTU(Maximum Transmission Unit)避免分片
# VXLAN 開銷 50 位元組,物理 MTU 1500 時建議設定為 1450
docker network create --driver overlay \
--attachable \
--opt com.docker.network.driver.mtu=1450 \
optimized-overlay
# 啟用 IP 地址管理(IPAM)自定義
docker network create --driver overlay \
--attachable \
--subnet 10.0.9.0/24 \
--aux-address "my-router=10.0.9.2" \
my-custom-overlay
9.7.2 容器 DNS 解析機制
Docker 內建 DNS 伺服器,但 DNS 解析涉及多個層面的配置。
DNS 解析流程
容器應用 (dig www.example.com)
↓
容器內 /etc/resolv.conf (127.0.0.11:53)
↓
Docker 內嵌 DNS 伺服器 (127.0.0.11)
↓
使用者自定義 DNS 或宿主機 /etc/resolv.conf
↓
外部 DNS 伺服器 (8.8.8.8 等)
↓
DNS 響應 → 容器快取 → 應用
配置容器 DNS
在執行時指定 DNS:
# 單個容器
docker run -d \
--dns 8.8.8.8 \
--dns 1.1.1.1 \
--dns-search example.com \
nginx # 確保與環境相容的 Nginx 版本
# DNS 選項
docker run -d \
--dns-option ndots:2 \
--dns-option timeout:1 \
--dns-option attempts:3 \
nginx # 確保與環境相容的 Nginx 版本
# 檢視容器 DNS 配置
docker exec <container_id> cat /etc/resolv.conf
Docker Compose DNS 配置:
services:
web:
image: nginx
dns:
- 8.8.8.8
- 1.1.1.1
dns_search:
- example.com
- local
db:
image: postgres
networks:
- backend
hostname: postgres-db
networks:
backend:
driver: bridge
# 容器內 /etc/resolv.conf 將被自動配置
# search example.com local
# nameserver 8.8.8.8
# nameserver 1.1.1.1
Docker 守護程序級別配置:
{
"dns": ["8.8.8.8", "1.1.1.1"],
"dns-search": ["example.com"]
}
自定義服務發現
使用 Docker 內建 DNS 的服務發現:
# 建立自定義網路
docker network create mynet
# 執行服務
docker run -d --name web --network mynet nginx # 確保與環境相容的 Nginx 版本
docker run -d --name db --network mynet postgres # 確保與環境相容的 PostgreSQL 版本
# 在其他容器中透過服務名訪問
docker run -it --network mynet busybox sh
# ping web # 自動解析到 web 容器 IP
# ping db # 自動解析到 db 容器 IP
Compose 服務名自動發現:
services:
frontend:
image: nginx
depends_on:
- backend
environment:
BACKEND_URL: http://backend:8080
backend:
image: myapp
depends_on:
- database
database:
image: postgres
environment:
POSTGRES_DB: mydb
# frontend 容器可以直接訪問 http://backend:8080
# backend 容器可以直接訪問 postgres://database:5432
9.7.3 本章邊界
Docker 的網路章節重點討論 bridge、host、none、overlay 和 DNS 等能力。CNI、NetworkPolicy、Calico、Cilium 這類概念屬於 Kubernetes 編排網路的範疇,後續在 Kubernetes 章節中再展開會更清晰。
本章小結
本章介紹了 Docker 網路配置的各個方面:
| 概念 | 要點 |
|---|---|
| DNS 配置 | 自定義網路支援嵌入式 DNS,可透過容器名解析 |
| 網路型別 | bridge (預設)、host、none、overlay、macvlan |
| 自定義網路 | 推薦使用,支援容器名 DNS 解析和更好的隔離 |
| 容器互聯 | 同一自定義網路內容器可直接透過容器名通訊 |
| 埠對映 | -p 宿主機埠:容器埠 暴露服務到外部 |
| 網路隔離 | 不同網路預設隔離,增強安全性 |
| --link | 已廢棄,使用自定義網路替代 |
延伸閱讀
- 配置 DNS:自定義 DNS 設定
- 網路型別:Bridge、Host、None 等網路模式
- 自定義網路:建立和管理自定義網路
- 容器互聯:容器間通訊方式
- 埠對映:高階埠配置
- 網路隔離:網路安全與隔離策略
- EXPOSE 指令:在 Dockerfile 中宣告埠
- Compose 網路:Compose 中的網路配置
第十章 Docker Buildx
第十章 Docker Buildx
Docker Buildx 是一個 docker CLI 外掛,其擴充套件了 docker 命令,支援 Moby BuildKit 提供的功能。提供了與 docker build 相同的使用者體驗,並增加了許多新功能。
Buildx 需要 Docker v23.0+(該版本起 BuildKit 成為預設構建引擎)。推薦使用 Docker v28 及以上版本以獲得最完整的 Buildx 功能支援。
本章內容
本章將詳細介紹 Docker Buildx 的使用,包括:
供應鏈安全與儲存後端前瞻:現代軟體供應鏈中,映像檔來源證明(Provenance,在 BuildKit 中預設以
mode=min新增)和軟體物料清單(SBOM,可透過--sbom=true顯式開啟)已經成為極其重要的構建產出。這些 Attestations 資料會作為 manifest 附著在 映像檔索引 (Image Index) 上。 正是基於此訴求,自 Docker Engine 29 起在新安裝情境預設啟用的containerd image store提供對 Image Index 的完美本地支援能力,解決了傳統經典儲存後端(Classic Store)無法有效處理帶 Attestations 映像檔索引的瓶頸。這使得你可以利用docker buildx imagetools inspect等手段,甚至做到無需拉取完整映像檔內容即可在 Registry 或本地高效校驗映像檔的安全後設資料。
10.1 BuildKit
BuildKit 是下一代的映像檔構建元件,在 moby/buildkit 開源。
重要:自 Docker 23 起,BuildKit 已成為 預設穩定構建器,無需手動啟用。Docker Engine 29 進一步將 Containerd 映像檔儲存設為預設,提升與 Kubernetes 的互操作性。
目前,Docker Hub 自動構建已經支援 BuildKit,具體請參考 docker-practice/docker-hub-buildx。
10.1.1 Dockerfile 新增指令詳解
BuildKit 引入了多項新指令,旨在最佳化構建快取和安全性。以下將詳細介紹這些指令的用法。
使用 BuildKit 後,我們可以使用下面幾個新的 Dockerfile 指令來加快映像檔構建。
要使用最新的 Dockerfile 語法特性,建議在 Dockerfile 開頭新增語法指令:
# syntax=docker/dockerfile:1
這將使用最新的穩定版語法解析器,確保你可以使用所有最新特性。
RUN --mount=type=cache
目前,幾乎所有的程式都會使用依賴管理工具,例如 Go 中的 go mod、Node.js 中的 npm 等等,當我們構建一個映像檔時,往往會重複的從網際網路中獲取依賴包,難以快取,大大降低了映像檔的構建效率。
例如一個前端工程需要用到 npm:
FROM node:alpine as builder
WORKDIR /app
COPY package.json /app/
RUN npm i --registry=https://registry.npmmirror.com \
&& rm -rf ~/.npm
COPY src /app/src
RUN npm run build
FROM nginx:alpine
COPY --from=builder /app/dist /app/dist
使用多階段構建,構建的映像檔中只包含了目標資料夾 dist,但仍然存在一些問題,當 package.json 檔案變動時,RUN npm i && rm -rf ~/.npm 這一層會重新執行,變更多次後,生成了大量的中間層映像檔。
為解決這個問題,進一步的我們可以設想一個類似 資料卷 的功能,在映像檔構建時把 node_modules 資料夾掛載上去,在構建完成後,這個 node_modules 資料夾會自動解除安裝,實際的映像檔中並不包含 node_modules 這個資料夾,這樣我們就省去了每次獲取依賴的時間,大大增加了映像檔構建效率,同時也避免了生成了大量的中間層映像檔。
BuildKit 提供了 RUN --mount=type=cache 指令,可以實現上邊的設想。
# syntax=docker/dockerfile:1
FROM node:alpine as builder
WORKDIR /app
COPY package.json /app/
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
--mount=type=cache,target=/root/.npm,id=npm_cache \
npm i --registry=https://registry.npmmirror.com
COPY src /app/src
RUN --mount=type=cache,target=/app/node_modules,id=my_app_npm_module,sharing=locked \
## --mount=type=cache,target=/app/dist,id=my_app_dist,sharing=locked \
npm run build
FROM nginx:alpine
## COPY --from=builder /app/dist /app/dist
## 為了更直觀的說明 from 和 source 指令,這裡使用 RUN 指令
RUN --mount=type=cache,target=/tmp/dist,from=builder,source=/app/dist \
# --mount=type=cache,target=/tmp/dist,from=my_app_dist,sharing=locked \
mkdir -p /app/dist && cp -r /tmp/dist/* /app/dist
第一個 RUN 指令執行後,id 為 my_app_npm_module 的快取資料夾掛載到了 /app/node_modules 資料夾中。多次執行也不會產生多箇中間層映像檔。
第二個 RUN 指令執行時需要用到 node_modules 資料夾,node_modules 已經掛載,命令也可以正確執行。
第三個 RUN 指令將上一階段產生的檔案複製到指定位置,from 指明快取的來源,這裡 builder 表示快取來源於構建的第一階段,source 指明快取來源的資料夾。
上面的 Dockerfile 中 --mount=type=cache,... 中指令作用如下:
| Option | Description |
|---|---|
id |
id 設定一個標誌,以便區分快取。 |
target (必填項) |
快取的掛載目標資料夾。 |
ro,readonly |
只讀,快取資料夾不能被寫入。 |
sharing |
有 shared private locked 值可供選擇。sharing 設定當一個快取被多次使用時的表現,由於 BuildKit 支援並行構建,當多個步驟使用同一快取時 (同一 id) 會發生衝突。shared 表示多個步驟可以同時讀寫,private 表示當多個步驟使用同一快取時,每個步驟使用不同的快取,locked 表示當一個步驟完成釋放快取後,後一個步驟才能繼續使用該快取。 |
from |
快取來源 (構建階段),不填寫時為空資料夾。 |
source |
來源的資料夾路徑。 |
RUN --mount=type=bind
該指令可以將一個映像檔 (或上一構建階段) 的檔案掛載到指定位置。
# syntax=docker/dockerfile:1
RUN --mount=type=bind,from=php:alpine,source=/usr/local/bin/docker-php-entrypoint,target=/docker-php-entrypoint \
cat /docker-php-entrypoint
RUN --mount=type=tmpfs
該指令可以將一個 tmpfs 檔案系統掛載到指定位置。
# syntax=docker/dockerfile:1
RUN --mount=type=tmpfs,target=/temp \
mount | grep /temp
RUN --mount=type=secret
該指令可以將一個檔案 (例如金鑰) 掛載到指定位置。
# syntax=docker/dockerfile:1
RUN --mount=type=secret,id=aws,target=/root/.aws/credentials \
test -s /root/.aws/credentials && echo "credentials mounted"
$ docker build -t test --secret id=aws,src=$HOME/.aws/credentials .
RUN --mount=type=ssh
該指令可以掛載 ssh 金鑰。
# syntax=docker/dockerfile:1
FROM alpine
RUN apk add --no-cache openssh-client
RUN mkdir -p -m 0700 ~/.ssh && ssh-keyscan gitlab.com >> ~/.ssh/known_hosts
RUN --mount=type=ssh ssh [email protected] | tee /hello
$ eval $(ssh-agent)
$ ssh-add ~/.ssh/id_rsa
(Input your passphrase here)
$ docker build -t test --ssh default=$SSH_AUTH_SOCK .
10.1.2 使用 docker compose build 與 BuildKit
Docker Compose 同樣支援 BuildKit,這使得多服務應用的構建更加高效。
自 Docker 23 起,BuildKit 已預設啟用,無需額外配置。如果使用舊版本,可設定 DOCKER_BUILDKIT=1 環境變數啟用。
10.1.3 官方文件
- https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md
10.2 使用 buildx 構建映像檔
10.2.1 使用
Buildx 的使用非常直觀,絕大多數情況下可以替代 docker build 命令。
你可以直接使用 docker buildx build 命令構建映像檔。
$ docker buildx build .
[+] Building 8.4s (23/32)
=> ...
Buildx 使用 BuildKit 引擎進行構建,支援許多新的功能,具體參考 Buildkit 一節。
需要注意的是,預設 docker driver 會把構建結果載入到本地映像檔儲存;使用 docker-container、remote、cloud 等 builder 時,如未指定 --load、--push 或 --output,結果通常只保留在構建快取中。
構建前檢查
Buildx 0.15 起支援構建檢查:常規構建會預設檢查 Dockerfile 與構建引數;如果只想做檢查而不真正構建,可以使用 --check:
$ docker buildx build --check .
這適合作為 CI 的快速門禁:普通構建中的檢查告警預設不會讓構建失敗,但 --check 發現問題會以非零狀態退出;需要把告警提升為錯誤時,可在 Dockerfile 頂部配合 # check=error=true。
使用 bake
docker buildx bake 是一個高階構建命令,支援從 HCL、JSON 或 Compose 檔案中定義構建目標,實現複雜的流水線構建。
## 從 Compose 檔案構建所有服務
$ docker buildx bake
## 僅構建指定目標
$ docker buildx bake web
生成 SBOM
Buildx 支援在構建時直接生成 SBOM (Software Bill of Materials),這對於軟體供應鏈安全至關重要。
$ docker buildx build --sbom=true -t myimage .
該命令會在構建結果中包含 SPDX 或 CycloneDX 格式的 SBOM 資料。
⚠️ 注意與失敗模式: 要使 SBOM (或其它 attestation 後設資料) 成功附著並可見,對底層的儲存格式有前置要求:預設的 classic image store 不支援 manifest list/index 這種存放 attestation 的結構。
如果只簡單執行上述命令,你可能會面臨 “命令成功執行,但本地映像檔中看不到 SBOM” 的體會落差。
正確的解決路徑有兩條: 1. 推送到遠端倉庫:使用
docker buildx build --sbom=true --push -t myimage:tag時,SBOM 會正確儲存到遠端倉庫。遠端 OCI 相容的映像檔倉庫能夠完整儲存這些後設資料。 2. 啟用 containerd image store:在 Docker 守護程序中啟用containerd image store特性(Docker 29+,新安裝情境預設啟用,Docker Desktop 上也更容易直接使用),可以在本地檢視和管理 SBOM 等 attestation 後設資料。
10.2.2 官方文件
10.3 使用 buildx 構建多種系統架構支援的 Docker 映像檔
Docker 映像檔可以支援多種系統架構,這意味著你可以在 x86_64、arm64 等不同架構的機器上執行同一個映像檔。這是透過一個名為 “manifest list” (或稱為 “fat manifest”) 的檔案來實現的。
10.3.1 Manifest List 是什麼?
為了理解多架構映像檔的原理,我們需要先了解 Manifest List 的概念。
Manifest list 是一個包含了多個指向不同架構映像檔的 manifest 的檔案。當你拉取一個支援多架構的映像檔時,Docker 會自動根據你當前的系統架構選擇並拉取對應的映像檔。
例如,官方的 hello-world 映像檔就支援多種架構。你可以使用 docker manifest inspect 命令來檢視它的 manifest list:
$ docker manifest inspect hello-world
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 525,
"digest": "sha256:80852a401a974d9e923719a948cc5335a0a4435be8778b475844a7153a2382e5",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 525,
"digest": "sha256:3adea81344be1724b383d501736c3852939b33b3903d02474373700b25e5d6e3",
"platform": {
"architecture": "arm",
"os": "linux",
"variant": "v5"
}
},
// ... more architectures
]
}
10.3.2 使用 docker buildx 構建多架構映像檔
docker buildx 是構建多架構映像檔的最佳實務工具,它遮蔽了底層的複雜性,提供了一鍵構建多架構映像檔的能力。
在 Docker 19.03+ 版本中,docker buildx 是推薦的用於構建多架構映像檔的工具。它使用 BuildKit 作為後端,可以大大簡化構建過程(Docker 23+ 預設啟用 BuildKit)。
新建 builder 例項
首先,你需要建立一個新的 builder 例項,因為它支援同時為多個平臺構建。
$ docker buildx create --name mybuilder --use
$ docker buildx inspect --bootstrap
構建和推送
使用 docker buildx build 命令並指定 --platform 引數,可以同時構建支援多種架構的映像檔。--push 引數會將構建好的映像檔和 manifest list 推送到 Docker 倉庫。
## Dockerfile
FROM --platform=$TARGETPLATFORM alpine
RUN uname -a > /os.txt
CMD cat /os.txt
$ docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t your-username/multi-arch-image . --push
構建完成後,你就可以在不同架構的機器上拉取並執行 your-username/multi-arch-image 這個映像檔了。
架構相關的構建引數
在 Dockerfile 中,你可以使用一些預定義的構建引數來根據目標平臺定製構建過程:
TARGETPLATFORM:構建映像檔的目標平臺,例如linux/amd64。TARGETOS:目標平臺的作業系統,例如linux。TARGETARCH:目標平臺的架構,例如amd64。TARGETVARIANT:目標平臺的變種,例如v7。BUILDPLATFORM:構建環境的平臺。BUILDOS:構建環境的作業系統。BUILDARCH:構建環境的架構。BUILDVARIANT:構建環境的變種。
例如,你可以這樣編寫 Dockerfile 來複製特定架構的二進位制檔案:
FROM scratch
ARG TARGETOS
ARG TARGETARCH
COPY bin/dist-${TARGETOS}-${TARGETARCH} /dist
ENTRYPOINT ["/dist"]
10.3.3 使用 docker manifest:底層工具
除了 docker buildx,我們也可以直接操作 Manifest List 來手動組合不同架構的映像檔。
docker manifest 是一個更底層的命令,可以用來建立、檢查和推送 manifest list。雖然 docker buildx 在大多數情況下更方便,但瞭解 docker manifest 仍然有助於理解其工作原理。
建立 manifest list
## 首先,為每個架構構建並推送映像檔
$ docker buildx build --platform linux/amd64 -t your-username/my-app:amd64 . --push
$ docker buildx build --platform linux/arm64 -t your-username/my-app:arm64 . --push
## 然後,建立一個 manifest list,將它們組合在一起
$ docker manifest create your-username/my-app:latest \
--amend your-username/my-app:amd64 \
--amend your-username/my-app:arm64
## 最後,推送 manifest list
$ docker manifest push your-username/my-app:latest
檢查 manifest list
你可以使用 docker manifest inspect 來檢視一個 manifest list 的詳細資訊,如上文所示。
本章小結
Docker Buildx 是 Docker 構建系統的重要進化,提供了高效、安全且支援多平臺的映像檔構建能力。
| 概念 | 要點 |
|---|---|
| BuildKit | 下一代構建引擎,Docker 23+ 預設啟用 |
| 快取掛載 | RUN --mount=type=cache 加速依賴安裝 |
| Secret 掛載 | RUN --mount=type=secret 安全傳遞金鑰 |
| buildx build | 替代 docker build,支援更多構建功能 |
| 構建檢查 | --check 可在不執行構建的情況下檢查 Dockerfile 與構建引數 |
| 多架構構建 | --platform 引數一鍵構建多種架構映像檔 |
| Manifest List | 多架構映像檔的索引檔案 |
| SBOM | 透過 --sbom=true 生成軟體物料清單 |
延伸閱讀
- Dockerfile 指令詳解:Dockerfile 編寫基礎
- 多階段構建:最佳化映像檔體積
- Dockerfile 最佳實務:編寫高效 Dockerfile
第十一章 Docker Compose
第十一章 Docker Compose
Docker Compose 是 Docker 官方編排 (Orchestration) 專案之一,負責快速的部署分散式應用。
⚠️ 重要提示:Compose V1 已停止支援
早期基於 Python 編寫的 Compose V1(命令為
docker-compose)已於 2023 年中正式停止支援。現已全面升級為基於 Go 編寫的 Compose V2,作為 Docker CLI 的官方外掛提供(命令為docker compose,中間為空格)。本書強烈推薦且後續章節均以 V2 為核心標準進行講解。
Docker Compose 解決什麼問題?
在學習 Compose 之前,筆者想強調它的真正價值。假設你正在開發一個微服務應用——前端、後端、資料庫三個服務。如果你用 Docker 容器分別執行它們,你會遇到這些問題:
- 啟動順序:需要先啟資料庫,再啟後端,最後啟前端
- 網路連線:三個容器需要能彼此通訊
- 卷掛載:原生程式碼需要對映到容器內
- 環境變數:每個服務的配置需要逐個設定
使用 docker run 逐個啟動的話,需要記住 3 條複雜的命令。而 Docker Compose 的核心價值就是用一個 YAML 檔案來定義整個應用,然後一條命令 docker compose up 啟動所有服務。這是 Compose 被廣泛採用的原因——它極大地簡化了本地開發和測試的複雜性。
誰應該學 Compose? 任何使用 Docker 進行本地開發的人,以及需要快速部署多容器應用的團隊。
本章將介紹 Compose 專案情況以及安裝和使用。
11.1 簡介
Compose 專案是 Docker 官方的開源專案,負責實現對 Docker 容器的快速編排。從功能上看,跟 OpenStack 中的 Heat 十分類似。
其程式碼目前在 docker/compose 倉庫 上開源。
Compose 定位是 “定義和執行多個 Docker 容器的應用 (Defining and running multi-container Docker applications)”,其前身是開源專案 Fig。
透過第一部分中的介紹,我們知道使用一個 Dockerfile 模板檔案,可以讓使用者很方便的定義一個單獨的應用容器。然而,在日常工作中,經常會碰到需要多個容器相互配合來完成某項任務的情況。例如要實現一個 Web 專案,除了 Web 服務容器本身,往往還需要再加上後端的資料庫服務容器,甚至還包括負載均衡容器等。
Compose 恰好滿足了這樣的需求。它允許使用者透過一個單獨的 compose.yaml (歷史預設名也常見為 docker-compose.yml) 模板檔案 (YAML 格式) 來定義一組相關聯的應用容器為一個專案 (project)。
11.1.1 概述
Docker Compose 讓使用者能夠以宣告式方式定義和管理多容器應用。它的核心價值在於:用一個 YAML 檔案取代一連串手動的 docker run 命令,使得複雜應用的啟動、停止和重建變得一鍵可達。
對於開發團隊而言,Compose 解決了三個關鍵問題:環境一致性(“在我機器上能跑”的問題)、服務依賴管理(確保資料庫在應用之前啟動)、以及開發-測試-生產的配置差異管理(透過 compose.override.yaml 實現多環境適配)。
11.1.2 模板檔案規範
Compose 模板檔案採用 YAML 格式,副檔名為 .yml 或 .yaml。
注意:Compose Specification 的頂層
version欄位僅用於向後相容,當前已標記為 obsolete。新檔案建議直接省略該欄位。
Docker Compose 預設使用 compose.yaml,也相容 compose.yml、docker-compose.yaml、docker-compose.yml 等檔名。
Compose 中有兩個重要的概念:
-
服務 (
service):一個應用的容器,實際上可以包括若干執行相同映像檔的容器例項。 -
專案 (
project):由一組關聯的應用容器組成的一個完整業務單元,在 Compose 檔案中定義。
Compose 的預設管理物件是專案,透過子命令對專案中的一組容器進行便捷地生命週期管理。
Compose 專案早期由 Python 編寫,稱為 Docker Compose V1。
現在的 Docker Compose V2 是一個 Go 語言編寫的 Docker CLI 外掛(當前版本號已演進至 v5.x 系列,以避免與舊 Compose 檔案格式版本混淆)。Docker Desktop 預設包含它;在 Linux 上,也可以將它作為獨立的 CLI 外掛安裝後直接透過 docker compose 命令使用。它提供了更快的效能和更好的整合體驗。
只要所操作的平臺支援 Docker API,就可以在其上利用 Compose 來進行編排管理。
11.2 安裝與解除安裝
Compose 是 Docker 官方的開源專案,負責實現對 Docker 容器叢集的快速編排。
當前的 Compose 以 docker compose 子命令的形式提供。Docker Desktop 在 macOS、Windows 和 Linux 上預設包含它;如果你已經在 Linux 上單獨安裝了 Docker Engine 和 Docker CLI,也可以再安裝 Compose CLI 外掛。
11.2.1 Linux
在 Linux 上,你可以透過 Docker 官方釋出頁安裝 Compose CLI 外掛。把二進位制檔案儲存到 $DOCKER_CONFIG/cli-plugins/docker-compose,並賦予執行許可權即可。
$ DOCKER_CONFIG=${DOCKER_CONFIG:-$HOME/.docker}
$ mkdir -p $DOCKER_CONFIG/cli-plugins
$ curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 -o $DOCKER_CONFIG/cli-plugins/docker-compose
$ chmod +x $DOCKER_CONFIG/cli-plugins/docker-compose
11.2.2 測試安裝
$ docker compose version
Docker Compose version v5.x.x
11.2.3 解除安裝
如果是二進位制包方式安裝的,刪除二進位制檔案即可。
$ rm $DOCKER_CONFIG/cli-plugins/docker-compose
11.3 使用
本節將透過一個具體的 Web 應用案例,介紹 Docker Compose 的基本概念和常用操作。
11.3.1 術語
首先介紹幾個術語。
-
服務 (
service):一個應用容器,實際上可以執行多個相同映像檔的例項。 -
專案 (
project):由一組關聯的應用容器組成的一個完整業務單元。
可見,一個專案可以由多個服務 (容器) 關聯而成,Compose 面向專案進行管理。
11.3.2 準備示例
下面建立一個由 web 和 redis 組成的簡單示例專案。
應用程式碼
新建資料夾,在該目錄中編寫 app.py 檔案
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 該頁面已被訪問 {} 次。\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)
Dockerfile
編寫 Dockerfile 檔案,內容為
FROM python:3.12-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]
compose.yaml
編寫 compose.yaml 檔案,這是 Compose 推薦使用的主模板檔案 (也相容 docker-compose.yml 等歷史檔名)。
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"
11.3.3 啟動與驗證
啟動專案
$ docker compose up
此時訪問本地 5000 埠,每次重新整理頁面,計數就會加 1。
按下 Ctrl-C 停止專案。
後臺執行與停止
$ docker compose up -d
$ docker compose stop
檢視日誌
$ docker compose logs -f
進入服務
$ docker compose exec redis sh
/data # redis-cli
127.0.0.1:6379> get hits
"9"
11.3.4 常用命令
構建與重建
$ docker compose build
$ docker compose start
執行一次性命令
$ docker compose run web python app.py
驗證與清理
$ docker compose config
$ docker compose down
💡
docker compose down預設會刪除容器和網路,但 保留資料卷。如需同時刪除資料卷,請使用docker compose down -v。
11.4 命令說明
Docker Compose 提供了豐富的命令來管理專案和容器。本節將詳細介紹這些命令的使用格式和常用選項。
何時用哪個命令:情境化指南
在學習具體命令前,讓我們從使用情境出發,這樣可以幫助你更快地找到需要的命令:
專案啟動與停止:
docker compose up:第一次啟動專案,拉取映像檔、建立容器docker compose start:啟動已停止的容器(專案已存在)docker compose stop:優雅地停止容器(不刪除容器)docker compose down:完全清理,刪除容器和網路(開發時常用)
除錯與檢視:
docker compose ps:檢視專案中的容器狀態docker compose logs:檢視容器日誌(排查問題的第一步)docker compose exec:進入正在執行的容器執行命令
構建與更新:
docker compose build:重新構建映像檔(修改 Dockerfile 後)docker compose pull:更新所有映像檔到最新版本
配置驗證:
docker compose config:驗證 docker-compose.yml 格式是否正確
11.4.1 命令物件與格式
對於 Compose 來說,大部分命令的物件既可以是專案本身,也可以指定為專案中的服務或者容器。如果沒有特別的說明,命令物件將是專案,這意味著專案中所有的服務都會受到命令影響。
執行 docker compose [COMMAND] --help 或者 docker compose help [COMMAND] 可以檢視具體某個命令的使用格式。
docker compose 命令的基本的使用格式是
docker compose [-f=<arg>...] [options] [COMMAND] [ARGS...]
11.4.2 命令選項
-
-f, --file FILE指定使用的 Compose 模板檔案。預設會自動識別compose.yaml(也相容docker-compose.yml等),並且可以多次指定。 -
-p, --project-name NAME指定專案名稱,預設將使用所在目錄名稱作為專案名。 -
--verbose輸出更多除錯資訊。(已棄用:在 Docker Compose V2 中,請改用docker --log-level debug compose ...或設定環境變數COMPOSE_DEBUG=1。) -
-v, --version列印版本並退出。
11.4.3 命令使用說明
build
格式為 docker compose build [options] [SERVICE...]。
構建 (重新構建) 專案中的服務容器。
服務容器一旦構建後,將會帶上一個標記名,例如對於 web 專案中的一個 db 容器,可能是 web_db。
可以隨時在專案目錄下執行 docker compose build 來重新構建服務。
選項包括:
-
--force-rm刪除構建過程中的臨時容器。 -
--no-cache構建映像檔過程中不使用 cache (這將加長構建過程)。 -
--pull始終嘗試透過 pull 來獲取更新版本的映像檔。
config
驗證 Compose 檔案格式是否正確,若正確則顯示配置,若格式錯誤顯示錯誤原因。
down
此命令將會停止 up 命令所啟動的容器,並移除網路
exec
進入指定的容器。
help
獲得一個命令的幫助。
images
列出 Compose 檔案中包含的映像檔。
kill
格式為 docker compose kill [options] [SERVICE...]。
透過傳送 SIGKILL 訊號來強制停止服務容器。
支援透過 -s 引數來指定傳送的訊號,例如透過如下指令傳送 SIGINT 訊號。
$ docker compose kill -s SIGINT
logs
格式為 docker compose logs [options] [SERVICE...]。
檢視服務容器的輸出。預設情況下,docker compose 將對不同的服務輸出使用不同的顏色來區分。可以透過 --no-color 來關閉顏色。
該命令在除錯問題的時候十分有用。
pause
格式為 docker compose pause [SERVICE...]。
暫停一個服務容器。
port
格式為 docker compose port [options] SERVICE PRIVATE_PORT。
列印某個容器埠所對映的公共埠。
選項:
-
--protocol=proto指定埠協議,tcp (預設值) 或者 udp。 -
--index=index如果同一服務存在多個容器,指定命令物件容器的序號 (預設為 1)。
ps
格式為 docker compose ps [options] [SERVICE...]。
列出專案中目前的所有容器。
選項:
-q只列印容器的 ID 資訊。
pull
格式為 docker compose pull [options] [SERVICE...]。
拉取服務依賴的映像檔。
選項:
--ignore-pull-failures忽略拉取映像檔過程中的錯誤。
push
推送服務依賴的映像檔到 Docker 映像檔倉庫。
restart
格式為 docker compose restart [options] [SERVICE...]。
重啟專案中的服務。
選項:
-t, --timeout TIMEOUT指定重啟前停止容器的超時 (預設為 10 秒)。
rm
格式為 docker compose rm [options] [SERVICE...]。
刪除所有 (停止狀態的) 服務容器。推薦先執行 docker compose stop 命令來停止容器。
選項:
-
-f, --force強制直接刪除,包括非停止狀態的容器。一般儘量不要使用該選項。 -
-v刪除容器所掛載的資料卷。
run
格式為 docker compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]。
在指定服務上執行一個命令。
例如:
$ docker compose run ubuntu ping docker.com
將會啟動一個 ubuntu 服務容器,並執行 ping docker.com 命令。
預設情況下,如果存在關聯,則所有關聯的服務將會自動被啟動,除非這些服務已經在執行中。
該命令類似啟動容器後執行指定的命令,相關卷、連結等等都將會按照配置自動建立。
兩個不同點:
-
給定命令將會覆蓋原有的自動執行命令;
-
不會自動建立埠,以避免衝突。
如果不希望自動啟動關聯的容器,可以使用 --no-deps 選項,例如
$ docker compose run --no-deps web python manage.py shell
將不會啟動 web 容器所關聯的其它容器。
選項:
-
-d後臺執行容器。 -
--name NAME為容器指定一個名字。 -
--entrypoint CMD覆蓋預設的容器啟動指令。 -
-e KEY=VAL設定環境變數值,可多次使用選項來設定多個環境變數。 -
-u, --user=""指定執行容器的使用者名稱或者 uid。 -
--no-deps不自動啟動關聯的服務容器。 -
--rm執行命令後自動刪除容器,d模式下將忽略。 -
-p, --publish=[]對映容器埠到本地主機。 -
--service-ports配置服務埠並對映到本地主機。 -
-T不分配偽 tty,意味著依賴 tty 的指令將無法執行。
scale
在當前 Compose CLI 中,更穩妥的擴縮容寫法是透過 docker compose up --scale 完成。
例如:
$ docker compose up -d --scale web=3 --scale db=2
將啟動 3 個容器執行 web 服務,2 個容器執行 db 服務。
說明:有些舊環境或實驗性入口中仍可能出現
docker compose scale,但它不應再被視為當前 Compose 的通用穩定預設命令。
一般的,當指定數目多於該服務當前實際執行容器,將新建立並啟動容器;反之,將停止容器。
常用搭配選項:
-d後臺啟動。--scale SERVICE=NUM指定服務例項數量,可重複使用。
start
格式為 docker compose start [SERVICE...]。
啟動已經存在的服務容器。
stop
格式為 docker compose stop [options] [SERVICE...]。
停止已經處於執行狀態的容器,但不刪除它。透過 docker compose start 可以再次啟動這些容器。
選項:
-t, --timeout TIMEOUT停止容器時候的超時 (預設為 10 秒)。
top
檢視各個服務容器內執行的程序。
unpause
格式為 docker compose unpause [SERVICE...]。
恢復處於暫停狀態中的服務。
up
格式為 docker compose up [options] [SERVICE...]。
該命令十分強大,它將嘗試自動完成包括構建映像檔,(重新) 建立服務,啟動服務,並關聯服務相關容器的一系列操作。
連結的服務都將會被自動啟動,除非已經處於執行狀態。
可以說,大部分時候都可以直接透過該命令來啟動一個專案。
預設情況,docker compose up 啟動的容器都在前臺,控制檯將會同時列印所有容器的輸出資訊,可以很方便進行除錯。
當透過 Ctrl-C 停止命令時,所有容器將會停止。
如果使用 docker compose up -d,將會在後臺啟動並執行所有的容器。一般推薦生產環境下使用該選項。
預設情況,如果服務容器已經存在,docker compose up 將會嘗試停止容器,然後重新建立 (保持使用 volumes-from 掛載的卷),以保證新啟動的服務匹配 Compose 檔案的最新內容。如果使用者不希望容器被停止並重新建立,可以使用 docker compose up --no-recreate。這樣將只會啟動處於停止狀態的容器,而忽略已經執行的服務。如果使用者只想重新部署某個服務,可以使用 docker compose up --no-deps -d <SERVICE_NAME> 來重新建立服務並後臺停止舊服務,啟動新服務,並不會影響到其所依賴的服務。
選項:
-
-d在後臺執行服務容器。 -
--no-color不使用顏色來區分不同的服務的控制檯輸出。 -
--no-deps不啟動服務所連結的容器。 -
--force-recreate強制重新建立容器,不能與--no-recreate同時使用。 -
--no-recreate如果容器已經存在了,則不重新建立,不能與--force-recreate同時使用。 -
--no-build不自動構建缺失的服務映像檔。 -
-t, --timeout TIMEOUT停止容器時候的超時 (預設為 10 秒)。
version
格式為 docker compose version。
列印版本資訊。
watch
格式為 docker compose watch [options] [SERVICE...]。
啟用開發模式,自動監視原始碼並在檔案發生變化時重新整理服務。這需要專案中有 compose.yaml (或 docker-compose.yml),且定義了 x-develop 或 develop 配置段。
例如:
services:
web:
build: .
develop:
watch:
- action: sync
path: ./web
target: /src/web
ignore:
- node_modules/
- action: rebuild
path: package.json
選項:
-
--no-up不自動啟動服務。 -
--quiet靜默模式。
11.5 Compose 模板檔案
模板檔案是使用 Compose 的核心,涉及到的指令關鍵字也比較多。但大家不用擔心,這裡面大部分指令跟 docker run 相關引數的含義都是類似的。
預設的模板檔名稱為 compose.yaml (也相容 docker-compose.yml 等歷史檔名),格式為 YAML。
services:
webapp:
image: examples/web
ports:
- "80:80"
volumes:
- "/data"
注意每個服務都必須透過 image 指令指定映像檔或 build 指令 (需要 Dockerfile) 等來自動構建生成映像檔。
如果使用 build 指令,在 Dockerfile 中設定的選項 (例如:CMD、EXPOSE、VOLUME、ENV 等) 將會自動被獲取,無需在 Compose 檔案中重複設定。
下面分別介紹各個指令的用法。
11.5.1 build
指定 Dockerfile 所在資料夾的路徑 (可以是絕對路徑,或者相對 Compose 檔案的路徑)。Compose 將會利用它自動構建這個映像檔,然後使用這個映像檔。
services:
webapp:
build: ./dir
你也可以使用 context 指令指定 Dockerfile 所在資料夾的路徑。
使用 dockerfile 指令指定 Dockerfile 檔名。
使用 arg 指令指定構建映像檔時的變數。
services:
webapp:
build:
context: ./dir
dockerfile: Dockerfile-alternate
args:
buildno: 1
使用 cache_from 指定構建映像檔的快取
build:
context: .
cache_from:
- alpine:latest
- corp/web_app:3.14
11.5.2 cap_add, cap_drop
指定容器的核心能力 (capacity) 分配。
例如,讓容器擁有所有能力可以指定為:
cap_add:
- ALL
去掉 NET_ADMIN 能力可以指定為:
cap_drop:
- NET_ADMIN
11.5.3 command
覆蓋容器啟動後預設執行的命令。
command: echo "hello world"
11.5.4 configs
configs 來自 Compose Specification。它在 Swarm 中是原生物件;在本地 docker compose 模式下通常以檔案掛載的形式實現,具體能力取決於 Compose 版本與執行平臺。
11.5.5 cgroup_parent
指定父 cgroup 組,意味著將繼承該組的資源限制。
例如,建立了一個 cgroup 組名稱為 cgroups_1。
cgroup_parent: cgroups_1
11.5.6 container_name
指定容器名稱。預設將會使用 專案名稱_服務名稱_序號 這樣的格式。
container_name: docker-web-container
注意:指定容器名稱後,該服務將無法進行擴充套件 (scale),因為 Docker 不允許多個容器具有相同的名稱。
11.5.7 deploy
deploy 用於描述副本數、更新策略、資源限制等部署引數。該欄位在 Swarm 中支援最完整;在本地 docker compose up 情境下通常只有部分欄位生效。
11.5.8 devices
指定裝置對映關係。
devices:
- "/dev/ttyUSB1:/dev/ttyUSB0"
11.5.9 depends_on
解決容器的依賴、啟動先後的問題。以下例子中會先啟動 redis db 再啟動 web
services:
web:
build: .
depends_on:
- db
- redis
redis:
image: redis
db:
image: postgres
注意:上述簡寫形式中,
web服務不會等待redisdb“完全啟動” 之後才啟動。
如果需要等待依賴服務就緒,可以使用 condition 欄位配合 healthcheck:
services:
web:
build: .
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
redis:
image: redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
condition 支援三個值:
service_started:容器啟動即滿足(預設)。service_healthy:容器的healthcheck狀態為 healthy 時滿足。service_completed_successfully:容器成功退出(退出碼為 0)時滿足,適用於初始化任務等一次性容器。
11.5.10 dns
自定義 DNS 伺服器。可以是一個值,也可以是一個列表。
dns: 8.8.8.8
dns:
- 8.8.8.8
- 114.114.114.114
11.5.11 dns_search
配置 DNS 搜尋域。可以是一個值,也可以是一個列表。
dns_search: example.com
dns_search:
- domain1.example.com
- domain2.example.com
11.5.12 tmpfs
掛載一個 tmpfs 檔案系統到容器。
tmpfs: /run
tmpfs:
- /run
- /tmp
11.5.13 env_file
從檔案中獲取環境變數,可以為單獨的檔案路徑或列表。
如果透過 docker compose -f FILE 方式來指定 Compose 模板檔案,則 env_file 中變數的路徑會基於模板檔案路徑。
如果有變數名稱與 environment 指令衝突,則按照慣例,以後者為準。
env_file: .env
env_file:
- ./common.env
- ./apps/web.env
- /opt/secrets.env
環境變數檔案中每一行必須符合格式,支援 # 開頭的註釋行。
## common.env: Set development environment
PROG_ENV=development
11.5.14 environment
設定環境變數。你可以使用陣列或字典兩種格式。
只給定名稱的變數會自動獲取執行 Compose 主機上對應變數的值,可以用來防止洩露不必要的資料。
environment:
RACK_ENV: development
SESSION_SECRET:
environment:
- RACK_ENV=development
- SESSION_SECRET
如果變數名稱或者值中用到 true|false,yes|no 等表達布林含義的詞彙,最好放到引號裡,避免 YAML 自動解析某些內容為對應的布林語義。這些特定詞彙,包括
y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF
11.5.15 expose
暴露埠,但不對映到宿主機,只被連線的服務訪問。
僅可以指定內部埠為引數
expose:
- "3000"
- "8000"
11.5.16 external_links
注意:不建議使用該指令。
連結到 Compose 檔案外部的容器,甚至並非 Compose 管理的外部容器。
external_links:
- redis_1
- project_db_1:mysql
- project_db_1:postgresql
11.5.17 extra_hosts
類似 Docker 中的 --add-host 引數,指定額外的 host 名稱對映資訊。
extra_hosts:
- "googledns:8.8.8.8"
- "dockerhub:52.1.157.61"
會在啟動後的服務容器中 /etc/hosts 檔案中新增如下兩條條目。
8.8.8.8 googledns
52.1.157.61 dockerhub
11.5.18 healthcheck
透過命令檢查容器是否健康執行。
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost"]
interval: 1m30s
timeout: 10s
retries: 3
11.5.19 image
指定為映像檔名稱或映像檔 ID。如果映像檔在本地不存在,Compose 將會嘗試拉取這個映像檔。
image: ubuntu
image: orchardup/postgresql
image: a4bc65fd
11.5.20 labels
為容器新增 Docker 後設資料 (metadata) 資訊。例如可以為容器新增輔助說明資訊。
labels:
com.startupteam.description: "webapp for a startup team"
com.startupteam.department: "devops department"
com.startupteam.release: "rc3 for v1.0"
11.5.21 links
注意:不推薦使用該指令。容器之間應透過 Docker 網路 (networks) 進行互聯。
11.5.22 logging
配置日誌選項。
logging:
driver: syslog
options:
syslog-address: "tcp://192.168.0.42:123"
目前支援三種日誌驅動型別。
driver: "json-file"
driver: "syslog"
driver: "none"
options 配置日誌驅動的相關引數。
options:
max-size: "200k"
max-file: "10"
11.5.23 network_mode
設定網路模式。使用和 docker run 的 --network 引數一樣的值。
network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
11.5.24 networks
配置容器連線的網路。
services:
some-service:
networks:
- some-network
- other-network
networks:
some-network:
other-network:
11.5.25 pid
跟主機系統共享程序名稱空間。開啟該選項的容器之間,以及容器和宿主機系統之間可以透過程序 ID 來相互訪問和操作。
pid: "host"
11.5.26 ports
暴露埠資訊。
使用宿主埠:容器埠 (HOST:CONTAINER) 格式,或者僅僅指定容器的埠 (宿主將會隨機選擇埠) 都可以。
ports:
- "3000"
- "8000:8000"
- "49100:22"
- "127.0.0.1:8001:8001"
注意:當使用 HOST:CONTAINER 格式來對映埠時,如果你使用的容器埠小於 60 並且沒放到引號裡,可能會得到錯誤結果,因為 YAML 會自動解析 xx:yy 這種數字格式為 60 進位制。為避免出現這種問題,建議數字串都採用引號包括起來的字串格式。
11.5.27 secrets
儲存敏感資料,例如 mysql 服務密碼。
services:
mysql:
image: mysql
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
secrets:
- db_root_password
- my_other_secret
secrets:
db_root_password:
file: ./db_root_password.txt
my_other_secret:
external: true
11.5.28 security_opt
指定容器模板標籤 (label) 機制的預設屬性 (使用者、角色、型別、級別等)。例如配置標籤的使用者名稱和角色名。
security_opt:
- label:user:USER
- label:role:ROLE
11.5.29 stop_signal
設定另一個訊號來停止容器。在預設情況下使用的是 SIGTERM 停止容器。
stop_signal: SIGUSR1
11.5.30 sysctls
配置容器核心引數。
sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0
sysctls:
- net.core.somaxconn=1024
- net.ipv4.tcp_syncookies=0
11.5.31 ulimits
指定容器的 ulimits 限制值。
例如,指定最大程序數為 65535,指定檔案控制代碼數為 20000 (軟限制,應用可以隨時修改,不能超過硬限制) 和 40000 (系統硬限制,只能 root 使用者提高)。
ulimits:
nproc: 65535
nofile:
soft: 20000
hard: 40000
11.5.32 volumes
資料卷所掛載路徑設定。可以設定為宿主機路徑 (HOST:CONTAINER) 或者資料卷名稱 (VOLUME:CONTAINER),並且可以設定訪問模式 (HOST:CONTAINER:ro)。
該指令中路徑支援相對路徑。
volumes:
- /var/lib/mysql
- cache/:/tmp/cache
- ~/configs:/etc/configs/:ro
如果路徑為資料卷名稱,必須在檔案中配置資料卷。
services:
my_src:
image: mysql:8.4
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
11.5.33 其它指令
此外,還有包括 domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir 等指令,基本跟 docker run 中對應引數的功能一致。
指定服務容器啟動後執行的入口檔案。
entrypoint: /code/entrypoint.sh
指定容器中執行應用的使用者名稱。
user: nginx
指定容器中工作目錄。
working_dir: /code
指定容器中搜尋域名、主機名、mac 地址等。
domainname: your_website.com
hostname: test
mac_address: 08-00-27-00-0C-0A
允許容器中執行一些特權命令。
privileged: true
指定容器退出後的重啟策略。該命令對保持服務始終執行十分有效,在生產環境中推薦配置為 always 或者 unless-stopped。
| 策略 | 說明 |
|---|---|
no |
預設值,不自動重啟 |
always |
無論退出碼如何,始終重啟;Docker 守護程序啟動時也會重啟 |
on-failure[:max-retries] |
僅在非零退出碼時重啟,可指定最大重試次數 |
unless-stopped |
類似 always,但手動停止的容器在守護程序重啟後不會自動啟動 |
restart: always
以只讀模式掛載容器的 root 檔案系統,意味著不能對容器內容進行修改。
read_only: true
開啟標準輸入,可以接受外部輸入。
stdin_open: true
模擬一個偽終端。
tty: true
11.5.34 profiles
profiles 用於按情境選擇性啟動服務。未指定 profiles 的服務預設始終啟動;標記了 profiles 的服務僅在啟用對應 profile 時才啟動。
services:
web:
image: nginx
debug:
image: busybox
profiles:
- debug
test:
image: node
profiles:
- test
啟動時透過 --profile 啟用:
$ docker compose --profile debug up # 啟動 web + debug
$ docker compose up # 僅啟動 web
11.5.35 讀取變數
Compose 模板檔案支援動態讀取主機的系統環境變數和當前目錄下的 .env 檔案中的變數。
例如,下面的 Compose 檔案將從執行它的環境中讀取變數 ${MONGO_VERSION} 的值,並寫入執行的指令中。
services:
db:
image: "mongo:${MONGO_VERSION}"
如果執行 MONGO_VERSION=3.2 docker compose up 則會啟動一個 mongo:3.2 映像檔的容器;如果執行 MONGO_VERSION=2.8 docker compose up 則會啟動一個 mongo:2.8 映像檔的容器。
若當前目錄存在 .env 檔案,執行 docker compose 命令時將從該檔案中讀取變數。
在當前目錄新建 .env 檔案並寫入以下內容。
## 支援 # 號註釋
MONGO_VERSION=3.6
執行 docker compose up 則會啟動一個 mongo:3.6 映像檔的容器。
11.6 實戰 Django
本小節內容適合
Python開發人員閱讀。版本說明:本示例使用以下映像檔版本: - Python:3.12-slim(可替換為其他 3.x 版本) - PostgreSQL:16(可替換為其他 16.x、15.x 等版本) - Django:>=5.0,<6.0(可根據專案需求調整)
本節將使用 Docker Compose 配置並執行一個 Django + PostgreSQL 應用。筆者不僅會介紹具體步驟,還會解釋每個配置項的作用,以及開發環境和生產環境的差異。
11.6.1 架構概覽
在開始之前,先看整體架構 (如圖 11-1 所示):
flowchart TD
subgraph Network ["Docker Compose 網路"]
direction LR
subgraph Web ["web 服務"]
direction TB
Django["Django<br/>應用"]
Port8000[":8000"]
Django ~~~ Port8000
end
subgraph DB ["db 服務"]
direction TB
Postgres["PostgreSQL<br/>資料庫"]
end
Django -- ":5432" --> Postgres
end
Browser["localhost:8000<br/>(瀏覽器訪問)"]
Port8000 --> Browser
圖 11-1:Django + PostgreSQL 的 Compose 架構
關鍵點:
web服務執行 Django 應用,對外暴露 8000 埠db服務執行 PostgreSQL 資料庫,只在內部網路可訪問- 兩個服務透過 Docker Compose 自動建立的網路相互通訊
web服務可以透過服務名db訪問資料庫 (Docker 內建 DNS)
11.6.2 準備工作
建立一個專案目錄並進入:
$ mkdir django-docker && cd django-docker
我們需要建立三個檔案:Dockerfile、requirements.txt 和 compose.yaml。
11.6.3 步驟 1:建立 Dockerfile
FROM python:3.12-slim
## 防止 Python 緩衝 stdout/stderr,讓日誌實時輸出
ENV PYTHONUNBUFFERED=1
## 設定工作目錄
WORKDIR /code
## 先複製依賴檔案,利用 Docker 快取加速構建
COPY requirements.txt /code/
## 安裝依賴
RUN pip install --no-cache-dir -r requirements.txt
## 複製專案程式碼
COPY . /code/
逐行解釋:
| 指令 | 作用 | 為什麼這樣寫 |
|---|---|---|
FROM python:3.12-slim |
基礎映像檔 | slim 版本比完整版小很多,但包含執行 Python 所需的一切 |
ENV PYTHONUNBUFFERED=1 |
關閉輸出緩衝 | 讓 print() 和日誌立即顯示,便於除錯 |
WORKDIR /code |
設定工作目錄 | 後續命令都在此目錄執行 |
COPY requirements.txt 在前 |
分層複製 | 只有 requirements.txt 變化時才重新安裝依賴,加速構建 |
--no-cache-dir |
不快取 pip 下載 | 減小映像檔體積 |
💡 筆者建議:總是將變化頻率低的檔案先複製,變化頻率高的後複製。這樣可以最大化利用 Docker 的構建快取。
11.6.4 步驟 2:建立 requirements.txt
Django>=5.0,<6.0
psycopg[binary]>=3.1,<4.0
gunicorn>=21.0,<22.0
依賴說明:
| 包名 | 作用 |
|---|---|
Django |
Web 框架 |
psycopg[binary] |
PostgreSQL 資料庫驅動 (推薦使用 psycopg 3) |
gunicorn |
生產環境 WSGI 伺服器 (可選,開發時可不用) |
11.6.5 步驟 3:建立 compose.yaml
配置如下:
services:
db:
image: postgres:16
environment:
POSTGRES_DB: django_db
POSTGRES_USER: django_user
POSTGRES_PASSWORD: django_password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
interval: 5s
timeout: 5s
retries: 5
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://django_user:django_password@db:5432/django_db
volumes:
postgres_data:
配置詳解:
db 服務
db 服務配置如下:
db:
image: postgres:16 # 使用官方 PostgreSQL 16 映像檔
environment:
POSTGRES_DB: django_db # 建立的資料庫名
POSTGRES_USER: django_user # 資料庫使用者
POSTGRES_PASSWORD: django_password # 資料庫密碼
volumes:
- postgres_data:/var/lib/postgresql/data # 持久化資料
healthcheck: # 健康檢查,確保資料庫就緒
test: ["CMD-SHELL", "pg_isready -U django_user -d django_db"]
interval: 5s
⚠️ 筆者提醒:
volumes配置很重要!沒有它,每次容器重啟資料都會丟失。筆者見過不少新手因為忘記這一步,導致開發資料全部丟失。
web 服務
web 服務配置如下:
web:
build: . # 從當前目錄的 Dockerfile 構建
command: python manage.py runserver # 啟動 Django 開發伺服器
volumes:
- .:/code # 掛載程式碼目錄,支援熱更新
ports:
- "8000:8000" # 對映埠
depends_on:
db:
condition: service_healthy # 等待資料庫健康後再啟動
關鍵配置說明:
| 配置項 | 作用 | 筆者建議 |
|---|---|---|
volumes: .:/code |
程式碼掛載 | 開發時必備,修改程式碼無需重新構建映像檔 |
depends_on + healthcheck |
啟動順序 | 確保資料庫就緒後 Django 才啟動,避免連線錯誤 |
environment |
環境變數 | 推薦用環境變數管理配置,避免硬編碼 |
11.6.6 步驟 4:建立 Django 專案
執行以下命令建立新的 Django 專案:
$ docker compose run --rm web django-admin startproject mysite .
命令解釋:
docker compose run:執行一次性命令--rm:命令執行後刪除臨時容器web:在 web 服務環境中執行django-admin startproject mysite .:在當前目錄建立 Django 專案
生成的目錄結構:
django-docker/
├── compose.yaml
├── Dockerfile
├── requirements.txt
├── manage.py
└── mysite/
├── __init__.py
├── settings.py
├── urls.py
├── asgi.py
└── wsgi.py
💡 Linux 使用者注意:如果遇到許可權問題,執行
sudo chown -R $USER:$USER .
11.6.7 步驟 5:配置資料庫連線
修改 mysite/settings.py,配置資料庫連線:
import os
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': os.environ.get('POSTGRES_DB', 'django_db'),
'USER': os.environ.get('POSTGRES_USER', 'django_user'),
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', 'django_password'),
'HOST': 'db', # Docker Compose 服務名
'PORT': 5432,
}
}
## 允許的主機(開發環境)
ALLOWED_HOSTS = ['*']
為什麼 HOST 是 db 而不是 localhost?
在 Docker Compose 中,各服務透過服務名相互訪問。Docker 內建的 DNS 會將 db 解析為 db 服務容器的 IP 地址。這是 Docker Compose 的核心功能之一。
11.6.8 步驟 6:啟動應用
$ docker compose up
你會看到:
- 首先構建 web 映像檔 (第一次執行)
- 啟動 db 服務,等待健康檢查透過
- 啟動 web 服務
db-1 | PostgreSQL init process complete; ready for start up.
db-1 | LOG: database system is ready to accept connections
web-1 | Watching for file changes with StatReloader
web-1 | Starting development server at http://0.0.0.0:8000/
開啟瀏覽器訪問 http://localhost:8000,可以看到 Django 歡迎頁面!
11.6.9 常用開發命令
在另一個終端視窗執行:
## 執行資料庫遷移
$ docker compose exec web python manage.py migrate
## 建立超級使用者
$ docker compose exec web python manage.py createsuperuser
## 進入 Django shell
$ docker compose exec web python manage.py shell
## 進入 PostgreSQL 命令列
$ docker compose exec db psql -U django_user -d django_db
💡 筆者建議使用
exec而不是run。exec在已執行的容器中執行命令,run會建立新容器。
11.6.10 常見問題排查
Q1:資料庫連線失敗
錯誤資訊:django.db.utils.OperationalError: could not connect to server 可能原因與解決方案:
| 原因 | 解決方案 |
|---|---|
| 資料庫還沒啟動完成 | 使用 depends_on + healthcheck |
| HOST 配置錯誤 | 確保使用服務名 db 而不是 localhost |
| 網路未建立 | 執行 docker compose down 後重新 up |
## 除錯:檢查資料庫是否正常執行
$ docker compose ps
$ docker compose logs db
Q2:程式碼修改沒有生效
可能原因:
- 開發伺服器沒有自動過載:確保使用
runserver而不是gunicorn - Volume 掛載問題:檢查
compose.yaml中的 volumes 配置 - 快取問題:嘗試
docker compose restart web
Q3:許可權問題
## 如果容器內建立的檔案 root 使用者所有
$ sudo chown -R $USER:$USER .
11.6.11 開發 vs 生產:關鍵差異
筆者特別提醒,本節的配置是 開發環境 配置。生產環境需要以下調整:
| 配置項 | 開發環境 | 生產環境 |
|---|---|---|
| Web 伺服器 | runserver |
gunicorn + Nginx |
| DEBUG | True |
False |
| 密碼管理 | 明文寫在配置 | 使用 Docker Secrets 或環境變數 |
| Volume | 掛載程式碼目錄 | 程式碼直接 COPY 進映像檔 |
| ALLOWED_HOSTS | ['*'] |
具體域名 |
生產環境 Compose 檔案示例:
## compose.prod.yaml
services:
web:
build: .
command: gunicorn mysite.wsgi:application --bind 0.0.0.0:8000
# 不掛載程式碼,使用映像檔內的程式碼
environment:
DEBUG: 'False'
ALLOWED_HOSTS: 'example.com,www.example.com'
# ...
11.6.12 延伸閱讀
- Compose 模板檔案詳解:深入理解 Compose 檔案的所有配置項
- 使用 WordPress:另一個 Compose 實戰案例
- Dockerfile 最佳實務:構建更小、更安全的映像檔
- 資料管理:Volume 和資料持久化詳解
11.7 實戰 Rails
本小節內容適合 Ruby 開發人員閱讀。
版本說明:本示例使用以下映像檔版本: - Ruby:3.2(可替換為其他 3.x 版本) - PostgreSQL:16(可替換為其他 16.x、15.x 等版本) - Rails:~> 7.1(可根據專案需求調整)
本節使用 Docker Compose 配置並執行一個 Rails + PostgreSQL 應用。
11.7.1 架構概覽
如圖 11-2 所示,Rails 與 PostgreSQL 在同一 Compose 網路中協同工作。
flowchart TD
subgraph Network ["Docker Compose 網路"]
direction LR
subgraph Web ["web 服務"]
direction TB
Rails["Rails<br/>應用"]
Port3000[":3000"]
Rails ~~~ Port3000
end
subgraph DB ["db 服務"]
direction TB
Postgres["PostgreSQL<br/>資料庫"]
end
Rails -- ":5432" --> Postgres
end
Browser["localhost:3000"]
Port3000 --> Browser
圖 11-2:Rails + PostgreSQL 的 Compose 架構
11.7.2 準備工作
建立專案目錄:
$ mkdir rails-docker && cd rails-docker
需要建立三個檔案:Dockerfile、Gemfile 和 compose.yaml。
11.7.3 步驟 1:建立 Dockerfile
FROM ruby:3.2
## 安裝系統依賴
RUN apt-get update -qq && \
apt-get install -y build-essential libpq-dev nodejs && \
rm -rf /var/lib/apt/lists/*
## 設定工作目錄
WORKDIR /myapp
## 先複製 Gemfile,利用快取加速構建
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
## 複製應用程式碼
COPY . /myapp
配置說明:
| 指令 | 作用 |
|---|---|
build-essential |
編譯原生擴充套件所需 |
libpq-dev |
PostgreSQL 用戶端庫 |
nodejs |
Rails Asset Pipeline 需要 |
| 先複製 Gemfile | 只有依賴變化時才重新 bundle install |
11.7.4 步驟 2:建立 Gemfile
建立一個初始的 Gemfile,稍後會被 rails new 覆蓋:
source 'https://rubygems.org'
gem 'rails', '~> 7.1'
建立空的 Gemfile.lock:
$ touch Gemfile.lock
11.7.5 步驟 3:建立 compose.yaml
配置如下:
services:
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
web:
build: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/myapp
ports:
- "3000:3000"
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://postgres:password@db:5432/myapp_development
volumes:
postgres_data:
配置詳解:
| 配置項 | 說明 |
|---|---|
rm -f tmp/pids/server.pid |
清理上次異常退出留下的 PID 檔案 |
volumes: .:/myapp |
掛載程式碼目錄,支援熱更新 |
depends_on + condition |
等待資料庫健康檢查透過後再啟動 |
DATABASE_URL |
Rails 12-factor 風格的資料庫配置 |
11.7.6 步驟 4:生成 Rails 專案
使用 docker compose run 生成專案骨架:
$ docker compose run --rm web rails new . --force --database=postgresql --skip-bundle
命令解釋:
--rm:執行後刪除臨時容器--force:覆蓋已存在的檔案--database=postgresql:配置使用 PostgreSQL--skip-bundle:暫不安裝依賴 (稍後統一安裝)
生成的目錄結構:
$ ls
Dockerfile Gemfile Rakefile config lib tmp
Gemfile.lock README.md app config.ru log vendor
compose.yaml bin db public
⚠️ Linux 使用者:如遇許可權問題,執行
sudo chown -R $USER:$USER .
11.7.7 步驟 5:重新構建映像檔
由於生成了新的 Gemfile,需要重新構建映像檔以安裝完整依賴:
$ docker compose build
11.7.8 步驟 6:配置資料庫連線
修改 config/database.yml:
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
url: <%= ENV['DATABASE_URL'] %>
development:
<<: *default
test:
<<: *default
database: myapp_test
production:
<<: *default
💡 使用
DATABASE_URL環境變數配置資料庫,符合 12-factor 應用原則,便於在不同環境間切換。
11.7.9 步驟 7:啟動應用
$ docker compose up
輸出示例:
db-1 | PostgreSQL init process complete; ready for start up.
db-1 | LOG: database system is ready to accept connections
web-1 | => Booting Puma
web-1 | => Rails 7.1.0 application starting in development
web-1 | => Run `bin/rails server --help` for more startup options
web-1 | Puma starting in single mode...
web-1 | * Listening on http://0.0.0.0:3000
11.7.10 步驟 8:建立資料庫
在另一個終端執行:
$ docker compose exec web rails db:create
Created database 'myapp_development'
Created database 'myapp_test'
訪問 http://localhost:3000 檢視 Rails 歡迎頁面。
11.7.11 常用開發命令
## 資料庫遷移
$ docker compose exec web rails db:migrate
## Rails 控制檯
$ docker compose exec web rails console
## 執行測試
$ docker compose exec web rails test
## 生成腳手架
$ docker compose exec web rails generate scaffold Post title:string body:text
## 進入容器 Shell
$ docker compose exec web bash
11.7.12 常見問題
Q:資料庫連線失敗
檢查 DATABASE_URL 環境變數格式是否正確,確保 db 服務已啟動:
$ docker compose ps
$ docker compose logs db
Q:server.pid 檔案導致啟動失敗
錯誤資訊:A server is already running
已在 command 中新增 rm -f tmp/pids/server.pid 處理。如仍有問題:
$ docker compose exec web rm -f tmp/pids/server.pid
Q:Gem 安裝失敗
可能需要更新 bundler 或清理快取:
$ docker compose run --rm web bundle update
11.7.13 開發 vs 生產
| 配置項 | 開發環境 | 生產環境 |
|---|---|---|
| Rails 伺服器 | Puma (開發模式) | Puma + Nginx |
| 程式碼掛載 | 使用 volumes | 程式碼打包進映像檔 |
| 靜態資源 | 動態編譯 | 預編譯 (rails assets:precompile) |
| 資料庫密碼 | 明文配置 | 使用 Secrets 管理 |
11.7.14 延伸閱讀
- 使用 Django:Python Web 框架實戰
- Compose 模板檔案:配置詳解
- 資料管理:資料持久化
11.8 實戰 WordPress
版本說明:本示例使用以下映像檔版本: - MySQL:8.0(可替換為其他 8.x 版本,或使用 MariaDB 替代) - WordPress:latest(建議在生產環境指定具體版本,如 6.x)
WordPress 是全球最流行的內容管理系統 (CMS)。使用 Docker Compose 可以在幾分鐘內搭建一個包含資料庫、Web 服務和持久化儲存的生產級 WordPress 環境。
11.8.1 專案結構
wordpress/
├── compose.yaml
├── .env # 環境變數(敏感資訊)
└── nginx/ # 可選:反向代理配置
└── nginx.conf
11.8.2 編寫 compose.yaml
這是一個可執行的單機最小配置,不是完整生產安全基線。正式部署前應固定映像檔版本、放在反向代理/TLS 後面、限制公開埠、配置備份與監控,並按 WordPress 與外掛生命週期做升級測試。
services:
# 資料庫服務
db:
image: mysql:8.4
container_name: wordpress_db
restart: always
command:
# 啟用原生密碼認證(MySQL 8.4 預設禁用,舊版 WP 相容性需要)
- --mysql-native-password=ON
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- db_data:/var/lib/mysql
networks:
- wp_net
# WordPress 服務
wordpress:
# 示例保留 latest 以便讀者快速體驗;生產環境請固定到經過測試的明確版本標籤
image: wordpress:latest
container_name: wordpress_app
restart: always
ports:
# 本機除錯入口;生產環境請透過反向代理釋出 HTTPS
- "127.0.0.1:8000:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
# 增加上傳檔案大小限制
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
depends_on:
- db
networks:
- wp_net
volumes:
db_data: # 資料庫持久化
wp_data: # WordPress 檔案(外掛/主題/上傳)持久化
networks:
wp_net:
11.8.3 配置檔案詳解
1. 環境變數檔案 .env
為了安全,不要在 compose.yaml 中直接寫密碼。建立 .env 檔案:
DB_ROOT_PASSWORD=somestrongrootpassword
DB_PASSWORD=somestronguserpassword
Compose 會自動讀取此同級目錄下的檔案。
2. 資料持久化
我們定義了兩個命名卷:
db_data:確保 MySQL 容器重建後資料不丟失wp_data:儲存 WordPress 的核心檔案、外掛、主題和上傳的媒體檔案
3. PHP 配置最佳化
預設的 WordPress 映像檔上傳檔案限制較小 (通常 2MB)。建立 uploads.ini:
file_uploads = On
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 600
11.8.4 啟動與執行
- 啟動服務:
$ docker compose up -d
-
訪問安裝介面: 開啟瀏覽器訪問
http://localhost:8000 -
檢視日誌:
$ docker compose logs -f
11.8.5 生產環境最佳實務
1. 資料庫備份
不要只依賴 Volume。建議定期備份資料庫:
## 匯出 SQL
$ docker exec wordpress_db mysqldump -u wordpress -pwordpress wordpress > backup.sql
或者新增一個自動備份容器:
backup:
image: tiredofit/db-backup
volumes:
- ./backups:/backup
environment:
- DB_TYPE=mysql
- DB_HOST=db
- DB_NAME=wordpress
- DB_USER=wordpress
- DB_PASS=${DB_PASSWORD}
- DB_DUMP_FREQ=1440 # 每天備份一次
depends_on:
- db
networks:
- wp_net
2. 使用 Nginx 反向代理
在生產環境中,不要直接暴露 WordPress 埠,而是透過 Nginx 進行反向代理並配置 SSL。
3. 使用 Redis 快取
WordPress 支援 Redis 快取以提高效能。
redis:
image: redis:alpine
restart: always
networks:
- wp_net
在 WordPress 容器環境變數中新增:
WORDPRESS_REDIS_HOST: redis
並安裝 Redis Object Cache 外掛。
11.8.6 常見問題
Q:資料庫連線錯誤
現象:訪問頁面顯示 “Error establishing a database connection”。排查:
- 檢查
docker compose logs wordpress - 確認
.env中的密碼與 YAML 檔案引用一致 - 確認
WORDPRESS_DB_HOST也是db(服務名) - MySQL 8.4 可能需要幾秒鐘啟動,WordPress 會自動重試,稍等片刻即可。
Q:無法上傳大檔案
解決:確保掛載了 uploads.ini 配置,並且重啟了容器:
$ docker compose restart wordpress
11.8.7 延伸閱讀
- Compose 模板檔案:深入瞭解配置項
- 資料卷:理解資料持久化
- Docker Hub WordPress:官方映像檔文件
11.9 實戰 LNMP
什麼是 LNMP
LNMP 是一個經典的 Web 應用棧,由以下四個開源軟體組合而成:
- L:Linux(作業系統)
- N:Nginx(Web 伺服器)
- M:MySQL(資料庫伺服器)
- P:PHP(指令碼語言)
這個組合被廣泛用於構建高效能的 Web 應用。
使用 Docker Compose 部署 LNMP
本專案的維護者 khs1994 的開源專案 khs1994-docker/lnmp 使用 Docker Compose 搭建了一套完整的 LNMP 環境。
參考專案
該專案中包含的服務:
- Nginx:Web 伺服器,用於處理 HTTP 請求
- MySQL/MariaDB:關係型資料庫服務
- PHP-FPM:PHP 處理器,與 Nginx 透過 Fast CGI 協議通訊
- Redis:可選的記憶體快取服務(用於會話或快取)
學習資源
各位開發者可以參考該專案在以下情境中執行 LNMP:
- Docker 容器化部署
- Kubernetes 叢集編排
- 開發環境快速搭建
- 生產環境配置參考
專案地址:khs1994-docker/lnmp
透過該專案,你可以學習到如何使用 Docker Compose 定義多個相互關聯的服務,以及如何在容器化環境中管理應用的生命週期。
本章小結
Docker Compose 是管理多容器應用的利器,透過 YAML 檔案宣告式地定義服務、網路和資料卷。
| 概念 | 要點 |
|---|---|
| 核心概念 | 服務 (service) 和專案 (project) |
| 配置檔案 | compose.yaml (推薦) 或 docker-compose.yml |
| 版本 | Compose V2 為 Go 編寫的 CLI 外掛,透過 docker compose 使用 |
| 啟動 | docker compose up -d 啟動所有服務 |
| 停止 | docker compose down 停止並移除容器 |
| 檢視狀態 | docker compose ps 檢視服務狀態 |
| 檢視日誌 | docker compose logs 檢視服務日誌 |
| 模板檔案 | 支援 services、networks、volumes 等頂級配置 |
延伸閱讀
- Compose 模板檔案:詳細模板語法參考
- Compose 命令說明:完整命令列表
- 網路配置:Docker 網路基礎
- 資料管理:資料卷管理
第十二章 底層實現
第十二章 底層實現
Docker 底層的核心技術包括 Linux 上的名稱空間 (Namespaces)、控制組 (Control groups)、Union 檔案系統 (Union file systems) 和容器格式 (Container format)。
我們知道,傳統的虛擬機器透過在宿主主機中執行 hypervisor 來模擬一整套完整的硬體環境提供給虛擬機器的作業系統。虛擬機器系統看到的環境是可限制的,也是彼此隔離的。 這種直接的做法實現了對資源最完整的封裝,但很多時候往往意味著系統資源的浪費。 例如,以宿主機和虛擬機器系統都為 Linux 系統為例,虛擬機器中執行的應用其實可以利用宿主機系統中的執行環境。
我們知道,在作業系統中,包括核心、檔案系統、網路、PID、UID、IPC、記憶體、硬碟、CPU 等等,所有的資源都是應用程序直接共享的。 要想實現虛擬化,除了要實現對記憶體、CPU、網路 IO、硬碟 IO、儲存空間等的限制外,還要實現檔案系統、網路、PID、UID、IPC 等等的相互隔離。 前者相對容易實現一些,後者則需要宿主機系統的深入支援。
隨著 Linux 系統對於名稱空間功能的完善實現,程式設計師已經可以實現上面的所有需求,讓某些程序在彼此隔離的名稱空間中執行。大家雖然都共用一個核心和某些執行時環境 (例如一些系統命令和系統庫),但是彼此卻看不到,都以為系統中只有自己的存在。這種機制就是容器 (Container),利用名稱空間來做許可權的隔離控制,利用 cgroups 來做資源分配。
本章內容
12.1 基本架構
Docker 的架構設計簡潔而高效,主要由用戶端和伺服器端兩部分組成。
12.1.1 核心架構圖
Docker 採用了 C/S (用戶端/伺服器端) 架構。Client 向 Daemon 傳送請求,Daemon 負責構建、執行和分發容器。
graph LR
C1["用戶端"] -->|docker run| D["dockerd<br/>守護程序"]
C1 -->|docker pull| D
D -->|管理| C2["Containers<br/>容器"]
D -->|管理| C3["Images<br/>映像檔"]
12.1.2 元件詳解
Docker 的內部架構如同洋蔥一樣分層,每一層專注解決特定問題:
1. Docker CLI:用戶端
使用者與 Docker 互動的主要方式。它將使用者命令 (如 docker run) 轉換為 API 請求傳送給 dockerd。
2. Dockerd:守護程序
Docker 的大腦。
- 監聽 API 請求
- 管理 Docker 物件 (映像檔、容器、網路、卷)
- 編排下層元件完成工作
3. Containerd:高階執行時
行業標準的容器執行時 (CNCF 畢業專案)。
- 管理容器的完整生命週期 (啟動、停止)
- 映像檔拉取與儲存
- 不包含 複雜的與容器無關的功能 (如構建、API)
- Kubernetes 也可以直接使用 containerd (跳過 Docker)
4. Runc:低階執行時
用於建立和執行容器的 CLI 工具。
- 直接與核心互動 (Namespaces,Cgroups)
- 遵循 OCI (Open Container Initiative) 規範
- 主要職責:根據配置啟動一個容器,然後退出 (將控制權交給容器程序)
5. Shim
每個容器都有一個 shim 程序。
- 解耦:允許 dockerd 重啟而不影響容器執行
- 保持 IO:維持容器的標準輸入輸出
- 狀態彙報:向 containerd 彙報容器退出狀態
12.1.3 容器啟動流程
當執行 docker run -d nginx 時,內部發生了什麼?
flowchart TD
U["使用者"]
K["docker run -d nginx"]
D["Dockerd"]
C["Containerd"]
B["OCI Bundle"]
S["Containerd-shim"]
R["Runc"]
P["容器程序<br/>nginx"]
E["退出"]
U -->|1. REST API| D
D -->|2. gRPC| C
C -->|3. 準備映像檔和 Bundle| B
C -->|4. 啟動 Shim| S
S -->|5. 執行| R
R -->|6. 建立 Namespaces 和 Cgroups| P
R -->|7. 程序退出| E
S -->|8. 監控 IO 和退出| P
- CLI 傳送請求給 Dockerd
- Dockerd 解析請求,呼叫 Containerd
- Containerd 準備映像檔,轉換為 OCI Bundle
- Containerd 建立 Shim 程序
- Shim 呼叫 Runc
- Runc 與系統核心互動,建立 Namespaces 和 Cgroups
- Runc 啟動 nginx 程序後退出
- Shim 接管容器 IO 和生命週期監控
12.1.4 Docker Engine v29.x 變化
從 Docker Engine v29.x 開始,架構進一步簡化和標準化:
- Containerd 映像檔儲存 (Image Store):在 v29.x 的新安裝情境中預設啟用。Docker 直接使用 Containerd 的映像檔管理能力,不再維護自己的一套 graphdriver。
- 優勢:多平臺映像檔支援更好、映像檔拉取更快 (lazy pulling)、與 K8s 共享映像檔。
- 實驗性 nftables 支援:隨著主流 Linux 發行版逐步棄用 iptables,Docker v29.x 引入了實驗性 nftables 後端。啟用方式為
dockerd --firewall-backend=nftables,可直接建立 nftables 規則而無需依賴 iptables-nft 轉換層。生產環境請謹慎使用。
12.1.5 Docker Desktop 架構
在 macOS 和 Windows 上,因為核心差異,架構稍微複雜:
flowchart TD
subgraph HostOS ["MacOS / Windows"]
CLI["Docker CLI"]
subgraph LinuxVM ["Linux VM (虛擬機器)"]
Engine["Dockerd <--> Containerd <--> Runc"]
end
CLI -- "(Socket 對映)" --> Engine
end
- 使用輕量級虛擬機器 (Apple Virtualization / WSL 2) 執行 Linux 核心
- 檔案掛載 (Bind Mount) 需要跨越 VM 邊界 (這也是檔案 I/O 慢的原因)
- 網路埠需要從宿主機轉發到 VM
12.1.6 總結
| 元件 | 角色 | 關鍵職責 |
|---|---|---|
| CLI | 指揮官 | 傳送指令,展示結果 |
| Dockerd | 大管家 | API 介面,整體排程 |
| Containerd | 經理 | 容器生命週期,映像檔管理 |
| Shim | 監工 | 保持 IO,允許無守護程序重啟 |
| Runc | 工人 | 真正幹活 (建立容器),幹完就走 |
12.1.7 延伸閱讀
12.2 名稱空間
名稱空間是 Linux 核心一個強大的特性。每個容器都有自己單獨的名稱空間,執行在其中的應用都像是在獨立的作業系統中執行一樣。名稱空間保證了容器之間彼此互不影響。
12.2.1 什麼是 Namespace
Namespace 是 Linux 核心提供的資源隔離機制,它讓容器內的程序彷彿執行在獨立的作業系統中。Namespace 是容器技術的核心基礎之一。它回答了一個關鍵問題:如何讓一個程序 “以為” 自己獨佔整個系統?
flowchart LR
subgraph Host ["宿主機視角"]
direction TB
H1["PID 1: systemd"]
H2["PID 2: sshd"]
H3["PID 3: dockerd"]
H4["PID 1234: nginx"]
H5["PID 1235: nginx worker"]
end
subgraph Container ["容器內視角"]
direction TB
C1["PID 1: nginx<br/>← 容器認為自己是 PID 1"]
C2["PID 2: nginx worker"]
end
H4 -. "(實際是宿主機的 1234)" .- C1
12.2.2 Namespace 的型別
Linux 核心(5.6+)共提供 8 種 Namespace。Docker 容器預設使用其中 7 種(不含 Time):
| Namespace | 隔離內容 | 容器中的效果 |
|---|---|---|
| PID | 程序 ID | 容器內 PID 從 1 開始,看不到其他容器和宿主機程序 |
| NET | 網路棧 | 獨立的網絡卡、IP 地址、埠、路由表 |
| MNT | 掛載點 | 獨立的檔案系統檢視,自己的根目錄 |
| UTS | 主機名 | 獨立的主機名和域名 |
| IPC | 程序間通訊 | 獨立的訊號量、訊息佇列、共享記憶體 |
| USER | 使用者/組 ID | 容器內的 root 可以對映為宿主機的普通使用者 |
| Cgroup | Cgroup 根目錄 | 隔離 cgroup 層級檢視 (Linux 4.6+) |
| Time | 系統時鐘 | 隔離 CLOCK_MONOTONIC 和 CLOCK_BOOTTIME (Linux 5.6+) |
12.2.3 PID Namespace
PID Namespace 負責程序 ID 的隔離,使得容器內的程序彼此不可見。
PID 的作用
隔離程序 ID,讓每個容器有自己的程序編號空間。
PID 隔離效果
## 宿主機上檢視程序
$ ps aux | grep nginx
root 12345 0.0 0.1 nginx: master process
root 12346 0.0 0.1 nginx: worker process
## 容器內檢視程序
$ docker exec mycontainer ps aux
PID USER COMMAND
1 root nginx: master process ← 在容器內是 PID 1
2 root nginx: worker process
PID 關鍵點
- 容器內的 PID 1 程序特殊重要——它是容器的主程序,退出則容器停止
- 容器內無法看到宿主機或其他容器的程序
- 宿主機可以看到所有容器內的程序 (但 PID 不同)
12.2.4 NET Namespace
NET Namespace 負責網路棧的隔離,包括網絡卡、路由表和 iptables 規則等。
NET 的作用
隔離網路棧,每個容器擁有獨立的網路環境。
NET 隔離效果
flowchart LR
subgraph Host ["宿主機"]
direction TB
H1["eth0: 192.168.1.10<br/>埠 80 可用"]
H2["docker0: 172.17.0.1"]
end
subgraph Container ["容器"]
direction TB
C1["eth0: 172.17.0.2<br/>埠 80 可用"]
C2["(veth pair 連線)"]
end
H2 <--> C2
NET 關鍵點
- 每個容器有獨立的網絡卡、IP、路由表、iptables 規則
- 多個容器可以監聽相同埠 (如都監聽 80)
- Docker 使用 veth pair 連線容器網路和宿主機網橋
12.2.5 MNT Namespace
MNT Namespace 負責檔案系統掛載點的隔離,確保容器看到獨立的檔案系統檢視。
MNT 的作用
隔離檔案系統掛載點,每個容器有自己的根目錄。
MNT 隔離效果
宿主機檔案系統: 容器內看到的:
/ / ← 容器的根目錄
├── bin/ ├── bin/
├── home/ ├── home/
├── var/ ├── var/
│ └── lib/ │ └── lib/
│ └── docker/ │
│ └── overlay2/ │
│ └── merged/ ────┼─── 這個目錄成為容器的 /
└── ... └── ...
與 chroot 的區別
| 特性 | chroot | MNT Namespace |
|---|---|---|
| 安全性 | 可以逃逸 | 更安全 |
| 掛載隔離 | 無 | 完全隔離 |
| /proc/mounts | 共享 | 獨立 |
12.2.6 UTS Namespace
UTS Namespace 主要用於隔離主機名和域名。
UTS 的作用
隔離主機名和域名,讓每個容器可以有自己的主機名。
UTS 隔離效果
## 宿主機
$ hostname
my-server
## 容器內
$ docker run --hostname mycontainer ubuntu hostname
mycontainer
UTS = “UNIX Time-sharing System”,是歷史遺留的名稱。
12.2.7 IPC Namespace
IPC Namespace 用於隔離程序間通訊資源,如 System V IPC 和 POSIX 訊息佇列。
IPC 的作用
隔離 System V IPC 和 POSIX 訊息佇列。
隔離的資源
- 訊號量 (semaphores)
- 訊息佇列 (message queues)
- 共享記憶體 (shared memory)
IPC 關鍵點
- 同一容器內的程序可以透過 IPC 通訊
- 不同容器的程序無法透過 IPC 通訊 (除非顯式共享)
12.2.8 USER Namespace
USER Namespace 允許將容器內的使用者 ID 對映到宿主機的不同使用者 ID。
USER 的作用
隔離使用者和組 ID,實現許可權隔離。
USER 隔離效果
flowchart LR
subgraph Container ["容器內"]
direction TB
C1["UID 0 (root)"]
C2["UID 1 (daemon)"]
end
subgraph Host ["宿主機"]
direction TB
H1["UID 100000<br/>← 非特權使用者"]
H2["UID 100001"]
end
C1 -- 對映 --> H1
C2 -- 對映 --> H2
安全意義
容器內的 root 使用者可以對映為宿主機上的普通使用者,即使容器被突破,攻擊者在宿主機上也只有普通許可權。
💡 筆者建議:生產環境建議啟用 User Namespace,增強安全性。
12.2.9 動手實驗:體驗 Namespace
使用 unshare 命令可以在不使用 Docker 的情況下體驗 Namespace:
實驗 1:UTS Namespace
## 建立新的 UTS namespace 並啟動 shell
$ sudo unshare --uts /bin/bash
## 修改主機名(隻影響這個 namespace)
$ hostname container-test
$ hostname
container-test
## 退出後檢視宿主機主機名(未改變)
$ exit
$ hostname
my-server
實驗 2:PID Namespace
## 建立新的 PID 和 MNT namespace
$ sudo unshare --pid --mount --fork /bin/bash
## 掛載新的 /proc
$ mount -t proc proc /proc
## 檢視程序(只能看到當前 shell)
$ ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 8960 4516 pts/0 S 10:00 0:00 /bin/bash
root 8 0.0 0.0 10072 3200 pts/0 R+ 10:00 0:00 ps aux
實驗 3:NET Namespace
## 建立新的網路 namespace
$ sudo unshare --net /bin/bash
## 檢視網路介面(只有 lo)
$ ip addr
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
12.2.10 Namespace 的侷限性
Namespace 提供了隔離但不是安全邊界:
| 方面 | 說明 |
|---|---|
| 共享核心 | 所有容器共享宿主機核心,核心漏洞可能影響所有容器 |
| 部分資源未隔離 | /proc、/sys 部分內容仍可見;Time Namespace (Linux 5.6+) 雖已可用,但 Docker 預設不啟用 |
| 非虛擬化 | 比虛擬機器隔離性弱 |
需要更強隔離時,可考慮 gVisor、Kata Containers 等安全容器方案。
12.3 控制組
控制組 (Cgroups) 是 Linux 核心提供的另一種關鍵機制,主要用於資源的限制和審計。
12.3.1 什麼是控制組
控制組 (Control Groups,簡稱 cgroups) 是 Linux 核心的一個特性,用於 限制、記錄和隔離 程序組的資源使用 (CPU、記憶體、磁碟 I/O、網路等)。
核心作用:讓多個容器公平共享宿主機資源,防止單個容器耗盡系統資源。
flowchart LR
subgraph NoLimit ["無 cgroups 限制"]
direction TB
subgraph HostRes1 ["宿主機資源"]
A["容器 A<br/>佔用所有<br/>記憶體和 CPU"]
B["容器 B、C 飢餓"]
end
end
subgraph Limit ["有 cgroups 限制"]
direction TB
subgraph HostRes2 ["宿主機資源"]
direction LR
C_A["A<br/>1GB<br/>2核"]
C_B["B<br/>1GB<br/>1核"]
C_C["C<br/>1GB<br/>1核"]
end
end
12.3.2 cgroups 的歷史
| 時間 | 事件 |
|---|---|
| 2006 | Google 工程師提出 “process containers” 概念 |
| 2007 | 為避免與 Linux 容器概念混淆,更名為 “control groups” (cgroups) |
| 2008 | Linux 2.6.24(2008年1月)正式合併 cgroups v1 |
| 2016 | Linux 4.5 引入 cgroups v2 |
| 現在 | Docker 在宿主機支援 cgroups v2 時會自動使用 v2,否則回退到 v1 |
12.3.3 cgroups 可以限制的資源
| 資源型別 | 子系統 | 說明 |
|---|---|---|
| CPU | cpu, cpuset |
CPU 使用時間和核心分配 |
| 記憶體 | memory |
記憶體使用上限和 swap |
| 塊裝置 I/O | blkio |
磁碟讀寫速度限制 |
| 網路 | net_cls, net_prio |
網路頻寬優先順序 |
| 程序數 | pids |
限制程序/執行緒數量 |
12.3.4 Docker 中的資源限制
Docker 提供了豐富的引數來配置容器的資源限制,主要包括記憶體、CPU、磁碟 I/O 等。
記憶體限制
## 限制容器最多使用 512MB 記憶體
$ docker run -m 512m myapp
## 限制記憶體 + swap
$ docker run -m 512m --memory-swap 1g myapp
## 軟限制(超過時警告,不會 OOM Kill)
$ docker run --memory-reservation 256m myapp
| 引數 | 說明 |
|---|---|
-m / --memory |
硬限制 (超過會 OOM Kill) |
--memory-swap |
記憶體 + swap 總限制 |
--memory-reservation |
軟限制 (記憶體競爭時生效) |
--oom-kill-disable |
禁用 OOM Killer (謹慎使用) |
CPU 限制
## 限制使用 1.5 個 CPU 核心
$ docker run --cpus=1.5 myapp
## 限制使用 CPU 0 和 1
$ docker run --cpuset-cpus="0,1" myapp
## 設定 CPU 使用權重(相對值,預設 1024)
$ docker run --cpu-shares=512 myapp
| 引數 | 說明 |
|---|---|
--cpus |
限制 CPU 核心數 (如 1.5) |
--cpuset-cpus |
繫結到特定 CPU 核心 |
--cpu-shares |
CPU 時間片權重 (相對值) |
--cpu-period / --cpu-quota |
精細控制 CPU 配額 |
磁碟 I/O 限制
## 限制裝置寫入速度為 10MB/s
$ docker run --device-write-bps /dev/sda:10mb myapp
## 限制裝置讀取速度
$ docker run --device-read-bps /dev/sda:10mb myapp
## 限制 IOPS
$ docker run --device-write-iops /dev/sda:100 myapp
程序數限制
## 限制最多 100 個程序
$ docker run --pids-limit=100 myapp
12.3.5 檢視容器資源使用
## 實時監控所有容器的資源使用
$ docker stats
CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
abc123 web 0.50% 45.5MiB / 512MiB 8.89% 1.2kB / 0B 0B / 0B
def456 db 2.30% 256MiB / 1GiB 25.00% 5.6kB / 3.2kB 4.1MB / 2.3MB
## 檢視特定容器
$ docker stats mycontainer
## 檢視容器的 cgroup 配置
$ docker inspect mycontainer --format '{{json .HostConfig}}' | jq
12.3.6 資源限制的效果
記憶體超限
## 啟動限制 100MB 記憶體的容器
$ docker run -m 100m stress --vm 1 --vm-bytes 200M
## 容器會被 OOM Killer 殺死
$ docker ps -a
CONTAINER ID STATUS NAMES
abc123 Exited (137) 5 seconds ago hopeful_darwin
## 137 = 128 + 9,表示被 SIGKILL(9) 殺死
...
CPU 限制驗證
## 不限制 CPU
$ docker run --rm stress --cpu 4
## 佔滿所有 CPU
## 限制為 1 個核心
$ docker run --rm --cpus=1 stress --cpu 4
## 只能使用約 100% CPU(1 個核心)
...
12.3.7 cgroups v1 vs v2
| 特性 | cgroups v1 | cgroups v2 |
|---|---|---|
| 層級結構 | 多層級 (每個資源單獨) | 統一層級 |
| 管理複雜度 | 複雜 | 簡化 |
| 資源分配 | 基於層級 | 基於子樹 |
| PSI (壓力監控) | ❌ | ✅ |
| rootless 容器 | 部分支援 | 完整支援 |
Docker 對 cgroups v2 的支援
Docker 20.10+ 開始支援 cgroups v2(如果系統支援),提供更好的效能和資源隔離。如果需要明確控制或回退到 v1,可以透過 Docker 守護程序配置檔案 /etc/docker/daemon.json 修改 cgroup-driver 引數:
{
"cgroup-driver": "systemd"
}
常見的 cgroup-driver 值包括 systemd (推薦) 和 cgroupfs。重啟 Docker 守護程序後生效。
檢查系統使用的版本
## 檢視 cgroup 版本
$ mount | grep cgroup
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)
## 如果顯示 cgroup2 表示 v2
## 或者
$ cat /proc/filesystems | grep cgroup
nodev cgroup
nodev cgroup2
12.3.8 在 Compose 中設定限制
在 Compose 中設定限制配置如下:
services:
web:
image: nginx
deploy:
resources:
limits:
cpus: '0.5'
memory: 512M
reservations:
cpus: '0.25'
memory: 256M
12.3.9 最佳實務
在使用 Cgroups 限制資源時,遵循一些最佳實務可以避免潛在的問題。
1. 始終設定記憶體限制
## 防止 OOM 影響宿主機
$ docker run -m 1g myapp
2. 為關鍵應用設定 CPU 保證
$ docker run --cpus=2 --cpu-shares=2048 critical-app
3. 監控資源使用
## 配合 Prometheus + cAdvisor 監控
$ docker run -d --name cadvisor \
-v /:/rootfs:ro \
-v /var/run:/var/run:ro \
-v /sys:/sys:ro \
-v /var/lib/docker:/var/lib/docker:ro \
ghcr.io/google/cadvisor
12.4 聯合檔案系統
聯合檔案系統 (UnionFS) 是 Docker 映像檔分層儲存的基礎,它允許將多個目錄掛載為同一個虛擬檔案系統。
12.4.1 什麼是聯合檔案系統
聯合檔案系統 (UnionFS) 是一種 分層、輕量級 的檔案系統,它將多個目錄 “聯合” 掛載到同一個虛擬目錄,形成一個統一的檔案系統檢視。
核心思想:將多個只讀層疊加,最上層可寫,形成完整的檔案系統。
flowchart TD
ContainerFS["容器看到的檔案系統<br/>/bin /etc /lib /usr /var /app /data"]
UnionFS["UnionFS 聯合掛載"]
ContainerLayer["容器層 (讀寫) : /app/data/log.txt (新寫入)"]
ImageLayer3["映像檔層3 (只讀) : /app/app.py"]
ImageLayer2["映像檔層2 (只讀) : /usr/local/bin/python"]
ImageLayer1["映像檔層1 (只讀) : /bin /etc /lib (基礎系統)"]
ContainerFS --> UnionFS
UnionFS --> ContainerLayer --> ImageLayer3 --> ImageLayer2 --> ImageLayer1
12.4.2 為什麼 Docker 使用聯合檔案系統
Docker 選擇聯合檔案系統作為其儲存驅動,主要基於以下幾個核心優勢。
1. 映像檔分層複用
flowchart TD
Nginx["nginx:alpine"] --> Alpine["alpine:3.19 (共享基礎層)"]
MyApp["myapp:latest"] --> Alpine
多個映像檔共享相同的底層,節省磁碟空間。
2. 快速構建
每個 Dockerfile 指令建立一層,只有變化的層需要重建:
FROM node:22 # 層1:基礎映像檔
COPY package.json ./ # 層2:依賴定義
RUN npm install # 層3:安裝依賴
COPY . . # 層4:應用程式碼
程式碼變化時,只需重建層 4,層 1-3 使用快取。
3. 容器啟動快
容器啟動時不需要複製映像檔,只需:
- 在映像檔層上建立一個薄的可寫層
- 聯合掛載所有層
12.4.3 Copy-on-Write:寫時複製
當容器修改只讀層中的檔案時:
flowchart LR
subgraph Before ["修改前"]
direction TB
B_C["容器層 (空)"]
B_I["映像檔層<br/>/etc/nginx.conf"]
end
subgraph After ["修改後"]
direction TB
A_C["容器層<br/>/etc/nginx.conf ← 複製到容器層後修改"]
A_I["映像檔層<br/>/etc/nginx.conf (原檔案仍在,但被遮蔽)"]
end
B_C --- B_I
A_C --- A_I
流程:
- 從只讀層讀取檔案
- 複製到容器的可寫層
- 在可寫層中修改
- 後續讀取使用可寫層的版本
12.4.4 Docker 支援的儲存驅動
Docker 的儲存驅動經歷了從早期各式各樣的機制(如 aufs, devicemapper),到被廣泛使用的現代經典 graph driver (overlay2),再到當下(Engine v29.x 及以後)在新安裝情境中預設啟用的 containerd 映像檔儲存引擎(containerd image store) 的演進。
| 儲存後端 / 驅動 | 核心特性說明 | 推薦程度 |
|---|---|---|
| containerd image store | (v29.x 新一代預設後端,新裝預設) 基於 containerd 的 snapshotters,原生支援 OCI image index、多架構映像檔與 Attestations 構建溯源後設資料儲存。 | ✅強烈推薦 (現代預設) |
| overlay2 | (經典 Graph Driver) 傳統架構下的現代 Linux 預設驅動,效能優秀,但在處理複雜溯源後設資料(索引)時受限。 | ✅推薦 (主要後備) |
| aufs | 早期預設,相容性好 | 遺留系統 |
| btrfs/zfs | 使用原生穩定檔案系統快照能力 | 特定情境 |
| devicemapper | 塊裝置級儲存 | 遺留系統 (已被逐步棄用) |
| vfs | 不使用 CoW,每層完整複製 | 僅測試 |
Classic Graph Drivers 與 Snapshotters 的核心差異
傳統模型(如 overlay2)將映像檔拉取解包的過程由 Docker 的 graph drivers 處理。而新的 containerd image store 則將這一職責徹底下放給了 containerd 自身的 snapshotters(底層在 Linux 發行版通常依然利用作業系統的 overlayfs)。這種架構改變帶來了:
- 本地免拉取檢視多平臺映像檔 index manifest 與 attestations (SBOM、Provenance)。
- 避免了以前繞過 CRI 獲取本地映像檔的問題,帶來更好的原生 Kubernetes 生態相容性。
檢視當前儲存驅動與後端
## 檢視預設儲存驅動
$ docker info | grep "Storage Driver"
Storage Driver: overlay2
## 在 Engine v29.x 中,可以透過如下輸出驗證是否開啟了 containerd 映像檔後端:
$ docker info | grep "containerd image store"
containerd image store: true
12.4.5 overlay2 工作原理
overlay2 是目前最推薦的儲存驅動:
flowchart TD
Merged["merged (合併檢視)<br/>容器看到的完整檔案系統"]
OverlayFS["OverlayFS"]
Upper["upper<br/>(容器層)<br/>讀寫"]
Lower2["lower2<br/>(映像檔層)<br/>只讀"]
Lower1["lower1<br/>(基礎層)<br/>只讀"]
Merged --> OverlayFS
OverlayFS --> Upper
OverlayFS --> Lower2
OverlayFS --> Lower1
- lowerdir:只讀的映像檔層 (可以有多個)
- upperdir:可寫的容器層
- workdir:OverlayFS 的工作目錄
- merged:聯合掛載後的檢視
檔案操作行為
| 操作 | 行為 |
|---|---|
| 讀取 | 從上到下查詢第一個匹配的檔案 |
| 建立 | 在 upper 層建立 |
| 修改 | 如果在 lower 層,先複製到 upper 層再修改 |
| 刪除 | 在 upper 層建立 whiteout 檔案標記刪除 |
12.4.6 檢視映像檔層
## 檢視映像檔的層資訊
$ docker history nginx:alpine
IMAGE CREATED CREATED BY SIZE
a6eb2a334a9f 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
...
## 檢視層的儲存位置
$ docker inspect nginx:alpine --format '{{json .GraphDriver.Data}}' | jq
{
"LowerDir": "/var/lib/docker/overlay2/.../diff:/var/lib/docker/overlay2/.../diff",
"MergedDir": "/var/lib/docker/overlay2/.../merged",
"UpperDir": "/var/lib/docker/overlay2/.../diff",
"WorkDir": "/var/lib/docker/overlay2/.../work"
}
12.4.7 最佳實務
為了構建高效、輕量的映像檔,我們在使用聯合檔案系統時應注意以下幾點。
1. 減少映像檔層數
## ❌ 每條命令建立一層
RUN apt-get update
RUN apt-get install -y nginx
RUN rm -rf /var/lib/apt/lists/*
## ✅ 合併為一層
RUN apt-get update && \
apt-get install -y nginx && \
rm -rf /var/lib/apt/lists/*
2. 避免在容器中寫入大量資料
容器層的寫入效能低於直接寫入。大量資料應使用:
- 資料卷 (Volume)
- 繫結掛載 (Bind Mount)
3. 使用 .dockerignore
排除不需要的檔案可以:
- 減小構建上下文
- 避免建立不必要的層
12.5 容器格式
Docker 容器格式的演進
最初,Docker 採用了 LXC 中的容器格式。從 0.7 版本以後開始去除 LXC 的依賴,轉而使用自行開發的 libcontainer。從 1.11 開始,則進一步演進為使用 runC 和 containerd。
關鍵元件說明
LXC
Docker 早期版本(0.1-0.7)直接使用 LXC 作為容器執行時,利用 Linux Namespaces 和 Cgroups 實現容器隔離。
libcontainer
- Docker 自行開發的容器庫
- 提供了容器的通用介面
- 不依賴於特定的 Linux 容器實現
- 更靈活和可控
runC
- OCI(Open Container Initiative)標準實現
- 輕量級的容器執行時
- 獨立的二進位制檔案,可單獨使用
- 基於 libcontainer 發展而來
containerd
- Docker 開源的容器執行時
- 提供了容器的完整生命週期管理
- 支援 runC 和其他 OCI 相容的執行時
- 在 Kubernetes 等編排系統中廣泛使用
容器規範標準
Docker 積極參與 Open Container Initiative (OCI) 的制定,推動了以下規範的發展:
- Image Spec:容器映像檔格式規範
- Runtime Spec:容器執行時介面規範
- Distribution Spec:容器映像檔分發規範
架構演變的優勢
從 LXC → libcontainer → runC/containerd 的演變提供了以下優勢:
- 減少外部依賴
- 提高執行效率
- 遵循行業標準(OCI)
- 增強可移植性和互操作性
- 支援多種容器執行時選擇
12.6 網路
Docker 的網路實現其實就是利用了 Linux 上的網路名稱空間和虛擬網路裝置 (特別是 veth pair)。建議先熟悉瞭解這兩部分的基本概念再閱讀本章。
12.6.1 基本原理
首先,要實現網路通訊,機器需要至少一個網路介面 (物理介面或虛擬介面) 來收發資料包;此外,如果不同子網之間要進行通訊,需要路由機制。
Docker 中的網路介面預設都是虛擬的介面。虛擬介面的優勢之一是轉發效率較高。 Linux 透過在核心中進行資料複製來實現虛擬介面之間的資料轉發,傳送介面的傳送快取中的資料包被直接複製到接收介面的接收快取中。對於本地系統和容器內系統看來就像是一個正常的乙太網卡,只是它不需要真正同外部網路裝置通訊,速度要快很多。
Docker 容器網路就利用了這項技術。它在本地主機和容器內分別建立一個虛擬介面,並讓它們彼此連通 (這樣的一對介面叫做 veth pair)。
12.6.2 建立網路引數
Docker 建立一個容器的時候,會執行如下操作:
- 建立一對虛擬介面,分別放到本地主機和新容器中;
- 本地主機一端橋接到預設的 docker0 或指定網橋上,並具有一個唯一的名字,如 veth65f9;
- 容器一端放到新容器中,並修改名字作為 eth0,這個介面只在容器的名稱空間可見;
- 從網橋可用地址段中獲取一個空閒地址分配給容器的 eth0,並配置預設路由到橋接網絡卡 veth65f9。
完成這些之後,容器就可以使用 eth0 虛擬網絡卡來連線其他容器和其他網路。
可以在 docker run 的時候透過 --network 引數來指定容器的網路配置,有 4 個可選值:
--network=bridge這個是預設值,連線到預設的網橋。--network=host告訴 Docker 不要將容器網路放到隔離的名稱空間中,即不要容器化容器內的網路。此時容器使用本地主機的網路,它擁有完全的本地主機介面訪問許可權。容器程序可以跟主機其它 root 程序一樣可以開啟低範圍的埠,可以訪問本地網路服務比如 D-bus,還可以讓容器做一些影響整個主機系統的事情,比如重啟主機。因此使用這個選項的時候要非常小心。如果進一步的使用--privileged=true,容器會被允許直接配置主機的網路堆疊。--network=container:NAME_or_ID讓 Docker 將新建容器的程序放到一個已存在容器的網路棧中,新容器程序有自己的檔案系統、程序列表和資源限制,但會和已存在的容器共享 IP 地址和埠等網路資源,兩者程序可以直接透過lo環回介面通訊。--network=none讓 Docker 將新容器放到隔離的網路棧中,但是不進行網路配置。之後,使用者可以自己進行配置。
12.6.3 網路配置細節
使用者使用 --net=none 後,可以自行配置網路,讓容器達到跟平常一樣具有訪問網路的許可權。透過這個過程,可以瞭解 Docker 配置網路的細節。
首先,啟動一個 /bin/bash 容器,指定 --net=none 引數。
$ docker run -i -t --rm --net=none base /bin/bash
root@63f36fc01b5f:/#
在本地主機查詢容器的程序 id,併為它建立網路名稱空間。
$ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f
2778
$ pid=2778
$ sudo mkdir -p /var/run/netns
$ sudo ln -s /proc/$pid/ns/net /var/run/netns/$pid
檢查橋接網絡卡的 IP 和子網掩碼資訊。
$ ip addr show docker0
21: docker0: ...
inet 172.17.0.1/16 scope global docker0
...
建立一對 “veth pair” 介面 A 和 B,繫結 A 到網橋 docker0,並啟用它
$ sudo ip link add A type veth peer name B
$ sudo brctl addif docker0 A
$ sudo ip link set A up
將 B 放到容器的網路名稱空間,命名為 eth0,啟動它並配置一個可用 IP (橋接網段) 和預設閘道器。
$ sudo ip link set B netns $pid
$ sudo ip netns exec $pid ip link set dev B name eth0
$ sudo ip netns exec $pid ip link set eth0 up
$ sudo ip netns exec $pid ip addr add 172.17.0.99/16 dev eth0
$ sudo ip netns exec $pid ip route add default via 172.17.0.1
以上,就是 Docker 配置網路的具體過程。
當容器結束後,Docker 會清空容器,容器內的 eth0 會隨網路名稱空間一起被清除,A 介面也被自動從 docker0 解除安裝。
此外,使用者可以使用 ip netns exec 命令來在指定網路名稱空間中進行配置,從而配置容器內的網路。
本章小結
本章深入介紹了 Docker 的底層實現,包括名稱空間、控制組和聯合檔案系統三大核心技術。
| 技術 | 作用 | 要點 |
|---|---|---|
| Namespace | 資源隔離 | PID、NET、MNT、UTS、IPC、USER 六種名稱空間 |
| Cgroups | 資源限制 | 限制 CPU、記憶體、磁碟 I/O、程序數 |
| Union FS | 分層儲存 | overlay2 為推薦驅動,支援 Copy-on-Write |
| Namespace | 隔離內容 | 一句話說明 |
|---|---|---|
| PID | 程序 ID | 容器有自己的程序樹 |
| NET | 網路 | 容器有自己的 IP 和埠 |
| MNT | 檔案系統 | 容器有自己的根目錄 |
| UTS | 主機名 | 容器有自己的 hostname |
| IPC | 程序間通訊 | 容器間 IPC 隔離 |
| USER | 使用者 ID | 容器 root ≠ 宿主機 root |
| 資源 | 限制引數 | 示例 |
|---|---|---|
| 記憶體 | -m |
-m 512m |
| CPU 核心數 | --cpus |
--cpus=1.5 |
| CPU 繫結 | --cpuset-cpus |
--cpuset-cpus="0,1" |
| 磁碟 I/O | --device-write-bps |
--device-write-bps /dev/sda:10mb |
| 程序數 | --pids-limit |
--pids-limit=100 |
延伸閱讀
- 名稱空間:資源隔離機制詳解
- 控制組 (Cgroups):資源限制機制
- 聯合檔案系統:分層儲存的實現
- 安全:容器安全實踐
- 映像檔:理解映像檔分層
- 容器:容器儲存層
- 構建映像檔:Dockerfile 層的建立
第十三章 容器編排基礎
第十三章 容器編排基礎
Kubernetes 是 Google 發起的開源容器編排系統,它支援多種雲平臺與私有資料中心。
Kubernetes 負責對容器工作負載進行排程與編排,其目的是讓使用者透過叢集宣告式地管理應用,而無需手動干預每個容器的生命週期細節。
Kubernetes 的最小排程單位是 Pod。一個 Pod 由一組緊密協作的容器構成,它們共享網路名稱空間、IP 以及部分儲存資源,也可以根據需要對 Pod 進行埠對映。
如果你已經熟悉 Docker,可以用以下對照來理解 Kubernetes 的核心概念:Docker 中的“容器”對應 Kubernetes 的 Pod(一個或多個容器的組合);docker-compose.yml 的角色類似於 Kubernetes 的 Deployment + Service 宣告;docker run 的埠對映和網路配置,在 Kubernetes 中由 Service 和 Ingress 接管。掌握這些對映關係,有助於從單機 Docker 平滑過渡到叢集編排。
本章將分為 5 節介紹 Kubernetes:
13.1 簡介
如圖 13-1 所示,Kubernetes 使用舵手圖示作為專案標識。

圖 13-1:Kubernetes 專案標識
13.1.1 什麼是 Kubernetes
Kubernetes (常簡稱為 K8s) 是 Google 開源的容器編排引擎。如果說 Docker 解決了 “如何打包和運送集裝箱” 的問題,那麼 Kubernetes 解決的就是 “如何管理海量集裝箱的排程、執行和維護” 的問題。
它不僅僅是一個編排系統,更是一個 雲原生應用作業系統。
名字由來:Kubernetes 在希臘語中意為 “舵手” 或 “飛行員”。K8s 是因為 k 和 s 之間有 8 個字母。
13.1.2 為什麼需要 Kubernetes
當我們在單機執行幾個容器時,Docker Compose 就足夠了。但在生產環境中,我們需要面對:
- 多主機排程:容器應該執行在哪臺機器上?
- 自動恢復:容器崩潰了怎麼辦?節點掛了怎麼辦?
- 服務發現:容器 IP 變了,其他服務怎麼找到它?
- 負載均衡:流量大了,如何分發給多個副本?
- 滾動更新:如何不中斷服務升級應用?
Kubernetes 完美解決了這些問題。
13.1.3 核心概念
Kubernetes 的核心概念包括 Pod、Node、Deployment、Service 和 Namespace 等,這些是學習和使用 Kubernetes 的基礎。
Pod:豆莢
Kubernetes 的最小排程單位。一個 Pod 可以包含一個或多個緊密協作的容器 (共享網路和儲存)。就像豌豆莢裡的豌豆一樣。
Node:節點
執行 Pod 的物理機或虛擬機器。
Deployment:部署
定義應用的期望狀態 (如:需要 3 個副本,映像檔版本為 v1)。K8s 會持續確保當前狀態符合期望狀態。
Service:服務
定義一組 Pod 的訪問策略。提供穩定的 Cluster IP 和 DNS 名稱,負責負載均衡。
Namespace:名稱空間
用於多租戶資源隔離。
13.1.4 Docker 使用者如何過渡
如果你已經熟悉 Docker,學習 K8s 會很容易:
| Docker 概念 | Kubernetes 概念 | 說明 |
|---|---|---|
| Container | Pod | K8s 增加了一層 Pod 包裝 |
| Volume | PersistentVolume | K8s 的儲存更加抽象和強大 |
| Network | Service/Ingress | K8s 的網路模型更扁平 |
| Compose | Deployment + Service | 宣告式配置的理念是一致的 |
13.1.5 架構
Kubernetes 也是 C/S 架構,由 Control Plane (控制平面) 和 Worker Node (工作節點) 組成:
- Control Plane:負責決策 (API Server,Scheduler,Controller Manager,etcd)
- Worker Node:負責幹活 (Kubelet,Kube-proxy,Container Runtime)
13.1.6 學習建議
Kubernetes 的學習曲線較陡峭。建議的學習路徑:
- 理解基本概念:Pod,Deployment,Service
- 動手實踐:使用 Minikube 或 Kind 在本地搭建叢集
- 部署應用:編寫 YAML 部署一個無狀態應用
- 深入原理:網路模型、儲存機制、排程演算法
13.1.7 延伸閱讀
- Minikube 安裝:本地體驗 K8s
- Kubernetes 官網:官方文件
13.2 基本概念
如圖 13-2 所示,Kubernetes 由控制平面與工作節點構成。

圖 13-2:Kubernetes 基本概念示意圖
- 節點 (
Node):一個節點是一個執行 Kubernetes 中的主機。 - 容器組 (
Pod):一個 Pod 對應於由若干容器組成的一個容器組,同個組內的容器共享一個儲存卷 (volume)。 - 容器組生命週期 (
pod-states):包含所有容器狀態集合,包括容器組狀態型別,容器組生命週期,事件,重啟策略,以及 replication controllers。 - Replication Controllers:主要負責指定數量的 pod 在同一時間一起執行。
- 服務 (
services):一個 Kubernetes 服務是容器組邏輯的高階抽象,同時也對外提供訪問容器組的策略。 - 卷 (
volumes):一個卷就是一個目錄,容器對其有訪問許可權。 - 標籤 (
labels):標籤是用來連線一組物件的,比如容器組。標籤可以被用來組織和選擇子物件。 - 介面許可權 (
accessing_the_api):埠,IP 地址和代理的防火牆規則。 - web 介面 (
ux):使用者可以透過 web 介面操作 Kubernetes。 - 命令列操作 (
cli):kubectl命令。
13.2.1 節點
在 Kubernetes 中,節點是實際工作的點,節點可以是虛擬機器或者物理機器,依賴於一個叢集環境。每個節點都有一些必要的服務以執行容器組,並且它們都可以透過主節點來管理。必要服務包括 Docker,kubelet 和代理服務。
容器狀態
容器狀態用來描述節點的當前狀態。現在,其中包含三個資訊:
主機 IP
主機 IP 需要雲平臺來查詢,Kubernetes 把它作為狀態的一部分來儲存。如果 Kubernetes 沒有執行在雲平臺上,節點 ID 就是必需的。IP 地址可以變化,並且可以包含多種型別的 IP 地址,如公共 IP,私有 IP,動態 IP,ipv6 等等。
節點狀態
注:Pending / Running / Succeeded / Failed / Unknown 是 Pod 的生命週期階段(pod.status.phase),不是節點狀態。節點本身沒有 Terminated 狀態。
節點的狀態透過一組條件(Conditions)來描述。主要條件包括 Ready(kubelet 健康且可以接收 Pod)、MemoryPressure(記憶體不足)、DiskPressure(磁碟不足)和 PIDPressure(程序數過多)等。其中 Ready 條件最為關鍵:值為 True 表示節點健康可排程,False 表示節點異常,Unknown 表示節點控制器超過一定時間未收到心跳。被標記為 Unknown 較長時間的節點,其上的 Pod 會被驅逐重新排程到其他節點。
節點管理
節點並非 Kubernetes 建立,而是由雲平臺建立,或者就是物理機器、虛擬機器。在 Kubernetes 中,節點僅僅是一條記錄,節點建立之後,Kubernetes 會檢查其是否可用。可以透過 kubectl 檢視節點資訊:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
control-plane Ready control-plane 10d v1.36.0
worker-1 Ready <none> 10d v1.36.0
worker-2 Ready <none> 10d v1.36.0
每個節點的詳細資訊以如下結構儲存:
apiVersion: v1
kind: Node
metadata:
name: worker-1
labels:
kubernetes.io/os: linux
status:
capacity:
cpu: "4"
memory: 8Gi
conditions:
- type: Ready
status: "True"
節點控制器
在 Kubernetes 控制平面中,節點控制器 (Node Controller) 負責管理節點的生命週期,主要包含:
- 叢集範圍內節點狀態同步
- 單節點生命週期管理
節點控制器會持續監控節點的健康狀態。當節點變為不可達時,控制器會等待一個超時期限,然後將該節點上的 Pod 標記為失敗,並觸發重新排程。可以使用 kubectl 來管理節點,例如標記節點為不可排程或排空節點上的工作負載:
## 標記節點為不可排程
$ kubectl cordon worker-1
## 排空節點上的 Pod
$ kubectl drain worker-1 --ignore-daemonsets
13.2.2 容器組
在 Kubernetes 中,使用的最小排程單位是容器組 (Pod),它是建立、排程、管理的最小單位。一個 Pod 包含一個或多個緊密協作的容器,它們共享網路名稱空間和儲存卷。
Pod 通常不會被直接建立,而是透過 Deployment 等控制器來管理。當節點發生故障時,控制器會在其他可用節點上重新建立 Pod。
容器組設計的初衷
容器組 (Pod) 的設計主要是為了解決應用間的緊密協作和資源共享問題。
資源共享和通訊
容器組主要是為了資料共享和它們之間的通訊。
在一個容器組中,容器都使用相同的網路地址和埠,可以透過本地網路來相互通訊。每個容器組都有獨立的 IP,可用透過網路來和其他物理主機或者容器通訊。
容器組有一組儲存卷 (掛載點),主要是為了讓容器在重啟之後可以不丟失資料。
容器組管理
容器組是一個應用管理和部署的高層次抽象,同時也是一組容器的介面。容器組是部署、水平放縮的最小單位。
容器組的使用
容器組可以透過組合來構建複雜的應用,典型的使用模式包含:
- 內容管理,檔案和資料載入以及本地快取管理等。
- 日誌和檢查點備份,壓縮,快照等。
- 監聽資料變化,跟蹤日誌,日誌和監控代理,訊息釋出等。
- 代理,網橋
- 控制器,管理,配置以及更新
為什麼不在一個容器裡執行多個程式
- 透明化:為了使容器組中的容器保持一致的基礎設施和服務,比如程序管理和資源監控。
- 解耦依賴:每個容器都可能獨立地重新構建和釋出。
- 方便使用:使用者不必執行獨立的程式管理,也不用擔心每個應用程式的退出狀態。
- 高效:考慮到基礎設施有更多的職責,容器必須要輕量化。
容器組的生命狀態
包括若干狀態值:Pending、Running、Succeeded、Failed。
| 狀態 | 說明 |
|---|---|
| Pending | Pod 已被叢集接受,但有一個或多個容器還沒有執行起來(可能在拉取映像檔)。 |
| Running | Pod 已被排程到節點,並且所有容器都已啟動。至少有一個容器處於執行狀態。 |
| Succeeded | Pod 中的所有容器都正常退出,且不會被重啟。 |
| Failed | Pod 中的所有容器都已終止,且至少有一個容器以失敗狀態退出。 |
容器組生命週期與重啟策略
Pod 的重啟策略 (restartPolicy) 決定了容器退出後的行為:
| 重啟策略 | 容器正常退出 | 容器異常退出 |
|---|---|---|
| Always (預設) | 重啟容器 | 重啟容器 |
| OnFailure | 不重啟 | 重啟容器 |
| Never | 不重啟 | 不重啟 |
當節點故障或不可達時,節點控制器會將該節點上所有 Pod 的狀態標記為 Failed。如果這些 Pod 由 Deployment 等控制器管理,控制器會自動在其他節點上重新建立。
13.2.3 Deployment 與 ReplicaSet
Deployment 是管理無狀態應用的推薦方式,它透過 ReplicaSet 來確保指定數量的 Pod 副本始終在執行。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.28
ports:
- containerPort: 80
Deployment 的核心能力包括:
- 副本管理:確保始終有指定數量的 Pod 在執行
- 滾動更新:逐步替換舊版本 Pod,實現零停機部署
- 回滾:如果新版本出現問題,可以快速回滾到之前的版本
早期 Kubernetes 使用 Replication Controller (RC) 來管理副本,現已被 ReplicaSet/Deployment 取代。
13.2.4 StatefulSet
Deployment 適合無狀態應用,而 StatefulSet 用於管理有狀態應用(如資料庫、訊息佇列)。與 Deployment 不同,StatefulSet 為每個 Pod 提供穩定的網路標識和持久化儲存。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql
replicas: 3
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:8.4
volumeMounts:
- name: data
mountPath: /var/lib/mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
StatefulSet 的核心特性:
- 穩定的網路標識:Pod 名稱按順序編號(mysql-0, mysql-1, mysql-2),配合 Headless Service 提供可預測的 DNS 名稱
- 有序部署與刪除:Pod 按序號順序建立,逆序刪除
- 持久化儲存:透過
volumeClaimTemplates為每個 Pod 自動建立獨立的 PVC
13.2.5 DaemonSet
DaemonSet 確保在叢集的每個節點(或指定節點)上執行一個 Pod 副本。典型用途包括日誌收集、監控代理和網路外掛。
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd
spec:
selector:
matchLabels:
app: fluentd
template:
metadata:
labels:
app: fluentd
spec:
containers:
- name: fluentd
image: fluent/fluentd:v1.17
volumeMounts:
- name: varlog
mountPath: /var/log
volumes:
- name: varlog
hostPath:
path: /var/log
當新節點加入叢集時,DaemonSet 會自動在該節點上建立 Pod;當節點被移除時,對應的 Pod 也會被回收。
13.2.6 Job 與 CronJob
Job 用於執行一次性任務,確保指定數量的 Pod 成功完成後自動退出。CronJob 則按照 cron 表示式週期性地建立 Job。
apiVersion: batch/v1
kind: Job
metadata:
name: data-migration
spec:
completions: 1
template:
spec:
containers:
- name: migrate
image: myapp/migrate:latest
command: ["python", "migrate.py"]
restartPolicy: Never
backoffLimit: 3
apiVersion: batch/v1
kind: CronJob
metadata:
name: daily-backup
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: backup
image: myapp/backup:latest
restartPolicy: OnFailure
Job 的 backoffLimit 控制失敗重試次數,completions 指定需要成功完成的 Pod 數量。CronJob 適用於定時備份、報表生成等情境。
13.2.7 服務
服務 (Service) 定義了一組 Pod 的邏輯集合和訪問策略。由於 Pod 的 IP 地址是動態分配的,Service 提供了一個穩定的訪問入口。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
type: ClusterIP
常見的 Service 型別:
| 型別 | 說明 |
|---|---|
| ClusterIP | 預設型別,僅叢集內部可訪問 |
| NodePort | 在每個節點上開放固定埠,叢集外部可透過 節點IP:埠 訪問 |
| LoadBalancer | 透過雲平臺的負載均衡器暴露服務 |
13.2.8 卷
卷 (Volume) 為 Pod 中的容器提供持久化儲存。Kubernetes 支援多種卷型別:
| 卷型別 | 說明 |
|---|---|
| emptyDir | 臨時儲存,Pod 刪除後資料丟失 |
| hostPath | 掛載節點上的檔案或目錄 |
| PersistentVolumeClaim | 使用持久卷宣告,與底層儲存解耦 |
| configMap / secret | 將配置或敏感資料掛載為檔案 |
生產環境中,推薦使用 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 來管理儲存,實現儲存資源與使用者的解耦。
13.2.9 標籤
標籤 (Label) 是附加到 Kubernetes 物件上的鍵值對,用於組織和選擇物件子集。標籤是 Kubernetes 中實現松耦合的關鍵機制。
## 為 Pod 新增標籤
$ kubectl label pod my-pod env=production
## 透過標籤選擇器查詢
$ kubectl get pods -l env=production
Service、Deployment 等資源都透過標籤選擇器 (selector) 來關聯目標 Pod。
13.2.10 API 訪問控制
Kubernetes API 的訪問透過三個階段進行控制:
- 認證 (Authentication):驗證請求者的身份(如證書、Token、OIDC)
- 授權 (Authorization):判斷請求者是否有許可權執行操作(通常使用 RBAC)
- 准入控制 (Admission Control):在請求被持久化之前對其進行校驗或修改
13.2.11 Dashboard
Kubernetes Dashboard 是一個基於 Web 的使用者介面,用於部署容器化應用、監控叢集資源和排查問題。Dashboard 的部署方法詳見部署 Dashboard 章節。
13.2.12 命令列工具 kubectl
kubectl 是 Kubernetes 的命令列工具,用於與叢集進行互動。常用命令如下:
## 檢視叢集中的資源
$ kubectl get pods,deployments,services,nodes
## 建立資源
$ kubectl apply -f deployment.yaml
## 檢視 Pod 日誌
$ kubectl logs my-pod
## 進入 Pod 執行命令
$ kubectl exec -it my-pod -- /bin/sh
## 檢視資源詳情
$ kubectl describe pod my-pod
更多 kubectl 操作詳見kubectl 命令列章節。
13.3 架構設計
任何優秀的專案都離不開優秀的架構設計。本小節將介紹 Kubernetes 在架構方面的設計考慮。
13.3.1 基本考慮
如果讓我們自己從頭設計一套容器管理平臺,有如下幾個方面是很容易想到的:
- 分散式架構,保證擴充套件性;
- 邏輯集中式的控制平面 + 物理分散式的執行平面;
- 一套資源排程系統,管理哪個容器該分配到哪個節點上;
- 一套對容器內服務進行抽象和 HA 的系統。
13.3.2 執行原理
如圖 13-3 所示,該圖完整展示了 Kubernetes 的執行原理。

圖 13-3:Kubernetes 執行原理圖
可見,Kubernetes 首先是一套分散式系統,由多個節點組成,節點分為兩類:一類是屬於管理平面的主節點/控制節點 (Master Node);一類是屬於執行平面的工作節點 (Worker Node)。
顯然,複雜的工作肯定都交給控制節點去做了,工作節點負責提供穩定的操作介面和能力抽象即可。
從這張圖上,我們沒有能發現 Kubernetes 中對於控制平面的分散式實現,但是由於資料後端自身就是一套分散式的資料庫 Etcd,因此可以很容易擴充套件到分散式實現。
13.3.3 控制平面
控制平面 (Control Plane) 是 Kubernetes 叢集的大腦,負責做出全域性決策 (如排程) 以及檢測和響應叢集事件。
主節點服務
主節點上需要提供如下的管理服務:
apiserver是整個系統的對外介面,提供一套 RESTful 的 Kubernetes API,供用戶端和其它元件呼叫;scheduler負責對資源進行排程,分配某個 pod 到某個節點上。是 pluggable 的,意味著很容易選擇其它實現方式;controller-manager負責管理控制器,包括 endpoint-controller (重新整理服務和 pod 的關聯資訊) 和 replication-controller (維護某個 pod 的複製為配置的數值)。
Etcd
這裡 Etcd 即作為資料後端,又作為訊息中介軟體。
透過 Etcd 來儲存所有的主節點上的狀態資訊,很容易實現主節點的分散式擴充套件。
元件可以自動的去偵測 Etcd 中的數值變化來獲得通知,並且獲得更新後的資料來執行相應的操作。
13.3.4 工作節點
- kubelet 是工作節點執行操作的 agent,負責具體的容器生命週期管理,根據從資料庫中獲取的資訊來管理容器,並上報 pod 執行狀態等;
- kube-proxy 是一個簡單的網路訪問代理,同時也是一個 Load Balancer。它負責將訪問到某個服務的請求具體分配給工作節點上的 Pod (同一類標籤)。

圖 13-4:kube-proxy 請求轉發示意圖
13.4 高階特性
掌握了 Kubernetes 的核心概念 (Pod,Service,Deployment) 後,我們需要了解更多高階特性以構建生產級應用。
13.4.1 Helm - 包管理工具
Helm 被稱為 Kubernetes 的包管理器 (類似於 Linux 的 apt/yum)。它將一組 Kubernetes 資源定義檔案打包為一個 Chart。
- 安裝應用:
helm install my-release bitnami/mysql - 版本管理:輕鬆回滾應用的釋出版本。
- 模板化:支援複雜的應用部署邏輯配置。
13.4.2 Gateway API 與 Ingress
Service 雖然提供了負載均衡,但通常是 4 層 (TCP/UDP)。叢集需要 7 層 (HTTP/HTTPS) 路由能力來充當閘道器。
Gateway API(推薦)
重要:Kubernetes 社群推薦使用 Gateway API 作為新一代流量管理標準。原
kubernetes/ingress-nginx專案已於 2026 年 3 月退役停止維護,不再接收安全更新。
Gateway API 基於 CRD 實現,提供了比 Ingress 更強大和標準化的流量管理能力:
- GatewayClass:定義閘道器實現(類似 IngressClass)。
- Gateway:定義監聽埠和協議(由基礎設施團隊管理)。
- HTTPRoute:定義 HTTP 路由規則(由應用團隊管理)。
- 職責分離:基礎設施、叢集運維和應用開發者各管各的資源。
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: my-route
spec:
parentRefs:
- name: my-gateway
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /api
backendRefs:
- name: api-svc
port: 80
常見的 Gateway API 實現有 Envoy Gateway、Istio、Cilium、Traefik、Kong 等。
Ingress(傳統方案)
Ingress 資源仍可正常使用,但建議新專案直接採用 Gateway API。已有 Ingress 配置可按需逐步遷移。
- 域名路由:基於 Host 將請求轉發不同服務。
- 路徑路由:基於 Path 將請求轉發。
- SSL/TLS:集中管理證書。
13.4.3 Persistent Volume 與 StorageClass
容器內的檔案是臨時的。對於有狀態應用 (如資料庫),需要持久化儲存。
- PVC (Persistent Volume Claim):使用者申請儲存的宣告。
- PV (Persistent Volume):實際的儲存資源 (NFS,AWS EBS,Ceph 等)。
- StorageClass:定義儲存類,支援動態建立 PV。
- VolumeAttributesClass:Kubernetes 1.34 起 GA,用於在 CSI 驅動支援
ModifyVolume時動態調整卷屬性,例如效能等級或服務質量引數。
13.4.4 Horizontal Pod Autoscaling
HPA 根據 CPU 利用率或其他指標 (如記憶體、自定義指標) 自動擴縮 Deployment 或 ReplicaSet 中的 Pod 數量。
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-apache
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-apache
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
13.4.5 ConfigMap 與 Secret
- ConfigMap:儲存非機密的配置資料 (配置檔案、環境變數)。
- Secret:儲存機密資料 (密碼、Token、證書)。Secret 值預設只是 Base64 編碼並存入 etcd;生產環境應顯式啟用 etcd 靜態加密或 KMS。
透過將配置與映像檔分離,保證了容器的可移植性。
13.4.6 Pod Security Standards
注意:PodSecurityPolicy (PSP) 已在 Kubernetes 1.25 中完全移除。
Kubernetes 使用 Pod Security Standards 定義三個安全級別,透過內建的 Pod Security Admission 控制器在名稱空間級別執行:
- Privileged:不受限制,適用於系統級和基礎設施工作負載。
- Baseline:防止已知的許可權提升,適用於大多數工作負載。
- Restricted:嚴格限制,遵循 Pod 安全加固最佳實務。
apiVersion: v1
kind: Namespace
metadata:
name: my-app
labels:
pod-security.kubernetes.io/enforce: baseline
pod-security.kubernetes.io/warn: restricted
13.4.7 Sidecar Containers
Kubernetes 1.33 起 Sidecar Containers 進入 GA。它們透過 initContainers 中帶 restartPolicy: Always 的容器表達,既保留 init container 的啟動順序,又會在主容器生命週期內持續執行。對於舊叢集或不需要啟動順序控制的情境,仍可使用普通多容器 Pod。
13.5 實戰練習
本章將透過一個具體的案例:部署一個 Nginx 網站,併為其配置 Service 和 Ingress,來串聯前面學到的知識。
13.5.1 目標
- 部署一個 Nginx Deployment。
- 建立一個 Service 暴露 Nginx。
- (可選) 透過 Ingress 訪問服務。
13.5.2 步驟 1:建立 Deployment
建立一個名為 nginx-deployment.yaml 的檔案:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.28
ports:
- containerPort: 80
應用配置:
kubectl apply -f nginx-deployment.yaml
13.5.3 步驟 2:建立 Service
建立一個名為 nginx-service.yaml 的檔案:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
type: NodePort # 使用 NodePort 方便本地測試
應用配置:
kubectl apply -f nginx-service.yaml
檢視分配的埠:
kubectl get svc nginx-service
如果輸出埠是 80:30080/TCP,你可以透過 http://<NodeIP>:30080 訪問 Nginx。
13.5.4 步驟 3:模擬滾動更新
修改 nginx-deployment.yaml,將映像檔版本改為 nginx:1.28-alpine。
kubectl apply -f nginx-deployment.yaml
觀察更新過程:
kubectl rollout status deployment/nginx-deployment
13.5.5 步驟 4:清理資源
練習結束後,記得清理資源:
kubectl delete -f nginx-service.yaml
kubectl delete -f nginx-deployment.yaml
本章小結
Kubernetes 是當前最主流的容器編排平臺,其宣告式管理模型和豐富的 API 為大規模容器化應用提供了堅實的基礎。
| 概念 | 要點 |
|---|---|
| Pod | 最小排程單位,包含一組共享網路和儲存的容器 |
| Deployment | 管理無狀態應用的 Pod 副本集,支援滾動更新和回滾 |
| StatefulSet | 管理有狀態應用,提供穩定的網路標識和持久化儲存 |
| DaemonSet | 確保每個節點執行一個 Pod 副本,適用於日誌、監控等情境 |
| Job/CronJob | 執行一次性或定時任務,確保任務成功完成 |
| Service | 為 Pod 提供穩定的網路訪問入口和負載均衡 |
| Namespace | 資源隔離和多租戶支援 |
| ConfigMap/Secret | 配置與敏感資訊的管理 |
| Master 節點 | 執行 API Server、Scheduler、Controller Manager |
| Worker 節點 | 執行 kubelet、kube-proxy 和容器執行時 |
延伸閱讀
- 部署 Kubernetes:搭建 Kubernetes 叢集
- Etcd:Kubernetes 使用的分散式儲存
- 底層實現:容器技術原理
第十四章 部署 Kubernetes
第十四章 部署 Kubernetes
目前,Kubernetes 支援在多種環境下使用,包括本地主機 (Ubuntu、Debian、CentOS、Fedora 等)、雲服務 (騰訊雲、阿里雲、百度雲等)。
你可以使用以下幾種方式部署 Kubernetes,接下來的小節會對各種方式進行詳細介紹。
- 使用 kubeadm 部署 (CRI 使用 containerd)
- Kubernetes 也支援 CRI-O 等符合 CRI 的執行時;本文以 containerd 為主線。
- 使用 kubeadm 部署 (使用 Docker)
- 在 Docker Desktop 使用
- Kind - Kubernetes IN Docker
- K3s - 輕量級 Kubernetes
- 一步步部署 Kubernetes 叢集
- 部署 Dashboard
- Kubernetes 命令列 kubectl
除了上述方式,企業生產環境中還有兩個常見的部署工具值得關注:
- KubeKey:KubeSphere 社群開源的叢集部署工具(CNCF 認證),支援一條命令從裸機部署到高可用叢集,內建對 containerd 和多 Linux 發行版的適配,適合需要快速搭建私有化 Kubernetes 的團隊。
- RKE2:SUSE Rancher 出品的安全加固型 Kubernetes 發行版,預設啟用 CIS 基準合規、SELinux 支援和 etcd 自動快照,適合對安全審計有嚴格要求的企業情境。
14.1 使用 kubeadm 部署 Kubernetes
kubeadm 提供了 kubeadm init 以及 kubeadm join 這兩個命令,作為快速建立 Kubernetes 叢集的常用工具。
版本說明:Kubernetes 版本更新較快 (約每 4 個月一個新版本),本文件基於 Kubernetes 1.36 編寫。更完整的安裝和相容性說明請以 Kubernetes 官方 kubeadm 文件 和 containerd 官方文件 為準。
14.1.1 安裝 containerd
參考安裝 Docker 一節新增 apt/yum 源,之後執行如下命令。
# debian 系
$ sudo apt install containerd.io
# rhel 系
$ sudo yum install containerd.io
14.1.2 配置 containerd
先生成預設配置檔案,然後只調整與 kubeadm 相關的關鍵項:
$ sudo mkdir -p /etc/containerd
$ containerd config default | sudo tee /etc/containerd/config.toml > /dev/null
開啟 /etc/containerd/config.toml,確認 runc 選項使用 systemd cgroup 驅動:
# containerd 2.x(當前預設版本)
[plugins."io.containerd.cri.v1.runtime".containerd.runtimes.runc.options]
SystemdCgroup = true
# containerd 1.x(舊版本)使用以下路徑,2.x 亦相容
# [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
# SystemdCgroup = true
提示:
containerd config default生成的預設配置會自動使用當前版本的正確路徑。如果你使用的是 containerd 2.x,配置中的外掛路徑將以io.containerd.cri.v1.runtime開頭;如果仍在使用 1.x,則為io.containerd.grpc.v1.cri。
預設配置裡的其它內容通常可以保持不變。修改完成後重啟 containerd:
$ sudo systemctl restart containerd
14.1.3 安裝 kubelet、kubeadm、kubectl、cri-tools、kubernetes-cni
需要在每臺機器上安裝以下的軟體包:
Ubuntu/Debian
$ K8S_MINOR="v1.36"
$ sudo apt-get update
$ sudo apt-get install -y ca-certificates curl gpg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL "https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ sudo chmod a+r /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl cri-tools kubernetes-cni
$ sudo apt-mark hold kubelet kubeadm kubectl
CentOS/Fedora
$ K8S_MINOR="v1.36"
$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/repodata/repomd.xml.key
EOF
$ sudo yum install -y kubelet kubeadm kubectl cri-tools kubernetes-cni
14.1.4 修改核心的執行引數
載入核心模組
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
$ sudo modprobe overlay
$ sudo modprobe br_netfilter
cgroup v2 要求:必須
Kubernetes v1.36 預設要求節點使用 cgroup v2。kubelet 在 cgroup v1 節點上預設會拒絕啟動,但管理員可以在 kubelet 配置中設定 failCgroupV1: false 來相容 cgroup v1(僅建議用於遺留系統過渡期)。驗證節點是否支援 cgroup v2:
$ mount | grep cgroup2
如果輸出包含 cgroup2,則系統已支援 cgroup v2。對於仍在使用 cgroup v1 的系統(如較舊的 RHEL 8),建議升級核心或更新系統配置以啟用 cgroup v2。
禁用 swap:必須
kubelet 預設要求禁用 swap,否則可能導致初始化失敗或節點無法加入叢集。
$ sudo swapoff -a
# 如需永久禁用,可在 /etc/fstab 中註釋 swap 對應行
$ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
# 應用配置
$ sysctl --system
14.1.5 配置 kubelet
為了讓 kubelet 正確執行,我們需要對其進行一些必要的配置。
修改 kubelet.service(可選:IPVS 模式)
注意:kube-proxy 的 IPVS 模式已在 Kubernetes 1.35 中被標記為棄用;Kubernetes 1.36 文件仍將其列為已棄用模式,並推薦遷移到 nftables。新部署應使用預設的 iptables 模式或 nftables 模式(Kubernetes 1.33+ 穩定)。以下 IPVS 配置僅供舊環境參考。
/etc/systemd/system/kubelet.service.d/10-proxy-ipvs.conf 寫入以下內容
# 啟用 ipvs 相關核心模組(已棄用,建議遷移至 nftables)
[Service]
ExecStartPre=-/sbin/modprobe ip_vs
ExecStartPre=-/sbin/modprobe ip_vs_rr
ExecStartPre=-/sbin/modprobe ip_vs_wrr
ExecStartPre=-/sbin/modprobe ip_vs_sh
執行以下命令應用配置。
$ sudo systemctl daemon-reload
14.1.6 部署
安裝配置完成後,我們將分別在 Master 節點和 Worker 節點上進行部署操作。
master
$ sudo systemctl enable containerd
$ sudo systemctl start containerd
$ sudo kubeadm init \
--image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \
--pod-network-cidr 10.244.0.0/16 \
--cri-socket unix:///run/containerd/containerd.sock \
--v 5
--pod-network-cidr 10.244.0.0/16引數與後續 CNI 外掛有關,這裡以flannel為例,若後續部署其他型別的網路外掛請更改此引數。
若
kubeadm預檢失敗,應按提示修復缺失依賴、核心引數、swap 或執行時配置。實驗環境確需忽略預檢時,只忽略明確理解且可接受的單項檢查,不建議使用--ignore-preflight-errors=all。
執行成功會輸出
...
[addons] Applied essential addon: CoreDNS
I1116 12:35:13.270407 86677 request.go:538] Throttling request took 181.409184ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/serviceaccounts
I1116 12:35:13.470292 86677 request.go:538] Throttling request took 186.088112ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/configmaps
[addons] Applied essential addon: kube-proxy
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe
node 工作節點
在 另一主機 重複 部署 小節以前的步驟,安裝配置好 kubelet。根據提示,加入到叢集。
$ systemctl enable containerd
$ systemctl start containerd
$ kubeadm join 192.168.199.100:6443 \
--token cz81zt.orsy9gm9v649e5lf \
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe \
--cri-socket unix:///run/containerd/containerd.sock
14.1.7 檢視服務
所有服務啟動後,透過 crictl 檢視本地實際執行的容器。這些服務大概分為三類:主節點服務、工作節點服務和其它服務。
CONTAINER_RUNTIME_ENDPOINT=unix:///run/containerd/containerd.sock crictl ps -a
主節點服務
-
apiserver是整個系統的對外介面,提供 RESTful 方式供用戶端和其它元件呼叫; -
scheduler負責對資源進行排程,分配某個 pod 到某個節點上; -
controller-manager負責管理控制器,包括 endpoint-controller (重新整理服務和 pod 的關聯資訊) 和 replication-controller (維護某個 pod 的複製為配置的數值)。
工作節點服務
proxy為 pod 上的服務提供訪問的代理。
其它服務
- Etcd 是所有狀態的儲存資料庫;
14.1.8 使用
將 /etc/kubernetes/admin.conf 複製到 ~/.kube/config
執行 $ kubectl get all -A 檢視啟動的服務。
由於未部署 CNI 外掛,CoreDNS 未正常啟動。如何使用 Kubernetes,請參考後續章節。
14.1.9 部署 CNI
這裡以 flannel 為例進行介紹。
flannel
檢查 podCIDR 設定
$ kubectl get node -o yaml | grep CIDR
# 輸出
podCIDR: 10.244.0.0/16
podCIDRs:
# 注意:v0.28.2 為編寫本文件時的最新版本,請根據需要替換為當前版本
# 參見 https://github.com/flannel-io/flannel/releases
$ kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.28.2/Documentation/kube-flannel.yml
14.1.10 master 節點預設不能執行 pod
如果用 kubeadm 部署一個單節點叢集,預設情況下無法使用,請執行以下命令解除限制
$ kubectl taint nodes --all node-role.kubernetes.io/control-plane-
# 較舊版本使用 master taint
# $ kubectl taint nodes --all node-role.kubernetes.io/master-
# 恢復預設值
# $ kubectl taint nodes NODE_NAME node-role.kubernetes.io/control-plane=true:NoSchedule
...
14.2 使用 kubeadm 部署 Kubernetes:使用 Docker
kubeadm 提供了 kubeadm init 以及 kubeadm join 這兩個命令,作為快速建立 Kubernetes 叢集的最佳實務。
版本說明:本文件基於 Kubernetes v1.36 編寫。Kubernetes 版本更新較快(約每 4 個月一個新版本),本文件中的第三方工具版本(如 cri-dockerd、flannel)僅為示例,請根據實際需求更新至當前版本。更完整的安裝和相容性說明請以 Kubernetes 官方文件 為準。
⚠️ 強烈提示:Docker 與 Kubernetes 環境的時代分界
自 Kubernetes v1.24 起,內建的
dockershim元件已被正式移除。這意味著 Kubernetes 不再將 Docker Engine 作為預設內建的容器執行時。雖然 Docker 仍然是你本地構建、管理映像檔的絕佳工具,但它已不再是 kubelet 的預設執行時選項。因此,強烈推薦 讀者直接參考同目錄下的《使用 kubeadm 部署 Kubernetes (CRI 使用 containerd)》作為主要的部署路線。
本文件保留,主要用於歷史環境維護或特殊需求情境:如果你必須在較新的 Kubernetes 叢集中繼續使用 Docker Engine 作為底層執行時,你必須理解 CRI 層機制,額外部署並配置第三方相容層
cri-dockerd,同時在部署時手動補充--cri-socket等引數約束。
14.2.1 安裝 Docker
參考安裝 Docker 一節安裝 Docker。
14.2.2 安裝並配置 cri-dockerd
由於 Kubernetes v1.24 移除了內建的 dockershim,需要額外安裝 cri-dockerd 作為 Docker 與 kubelet 之間的 CRI(Container Runtime Interface)適配層。
Ubuntu/Debian
# 安裝 cri-dockerd
# 注意:v0.3.24 為編寫本文件時的最新版本,請根據需要替換為當前版本
# 參見 https://github.com/Mirantis/cri-dockerd/releases
$ cd /tmp
$ wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.24/cri-dockerd-0.3.24.amd64.tgz
$ tar xzvf cri-dockerd-0.3.24.amd64.tgz
$ sudo mv cri-dockerd/cri-dockerd /usr/local/bin/
# 下載並安裝 systemd service 檔案
$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service
$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket
$ sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' cri-docker.service
$ sudo mv cri-docker.service cri-docker.socket /etc/systemd/system/
# 啟動 cri-dockerd
$ sudo systemctl daemon-reload
$ sudo systemctl enable cri-docker
$ sudo systemctl start cri-docker
# 驗證安裝
$ sudo /usr/local/bin/cri-dockerd --version
CentOS/Fedora
# 安裝 cri-dockerd
# 注意:v0.3.24 為編寫本文件時的最新版本,請根據需要替換為當前版本
# 參見 https://github.com/Mirantis/cri-dockerd/releases
$ cd /tmp
$ wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.3.24/cri-dockerd-0.3.24.amd64.tgz
$ tar xzvf cri-dockerd-0.3.24.amd64.tgz
$ sudo mv cri-dockerd/cri-dockerd /usr/local/bin/
# 下載並安裝 systemd service 檔案
$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.service
$ wget https://raw.githubusercontent.com/Mirantis/cri-dockerd/master/packaging/systemd/cri-docker.socket
$ sed -i -e 's,/usr/bin/cri-dockerd,/usr/local/bin/cri-dockerd,' cri-docker.service
$ sudo mv cri-docker.service cri-docker.socket /etc/systemd/system/
# 啟動 cri-dockerd
$ sudo systemctl daemon-reload
$ sudo systemctl enable cri-docker
$ sudo systemctl start cri-docker
14.2.3 安裝 kubelet、kubeadm、kubectl
需要在每臺機器上安裝以下的軟體包:
Ubuntu/Debian
$ K8S_MINOR="v1.36"
$ sudo apt-get update
$ sudo apt-get install -y ca-certificates curl gpg
$ sudo install -m 0755 -d /etc/apt/keyrings
$ curl -fsSL "https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/Release.key" | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ sudo chmod a+r /etc/apt/keyrings/kubernetes-apt-keyring.gpg
$ echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/deb/ /" | sudo tee /etc/apt/sources.list.d/kubernetes.list > /dev/null
$ sudo apt-get update
$ sudo apt-get install -y kubelet kubeadm kubectl
$ sudo apt-mark hold kubelet kubeadm kubectl
CentOS/Fedora
$ K8S_MINOR="v1.36"
$ cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/${K8S_MINOR}/rpm/repodata/repomd.xml.key
EOF
$ sudo yum install -y kubelet kubeadm kubectl
14.2.4 修改核心的執行引數
cgroup v2 要求:必須
Kubernetes v1.36 預設要求節點使用 cgroup v2。kubelet 在 cgroup v1 節點上預設會拒絕啟動,但管理員可以在 kubelet 配置中設定 failCgroupV1: false 來相容 cgroup v1(僅建議用於遺留系統過渡期)。驗證節點是否支援 cgroup v2:
$ mount | grep cgroup2
如果輸出包含 cgroup2,則系統已支援 cgroup v2。對於仍在使用 cgroup v1 的系統(如較舊的 RHEL 8),建議升級核心或更新系統配置以啟用 cgroup v2。
載入核心模組
$ cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
$ sudo modprobe overlay
$ sudo modprobe br_netfilter
swap 設定:現代叢集仍建議優先關閉
在現代 Kubernetes 中,Linux 節點已經支援更細粒度的 swap 行為控制。對於多數 kubeadm 教學環境,優先關閉 swap 仍然是最穩妥的預設做法,因為這樣最接近常見生產基線,也能避免不同發行版和 kubelet 配置差異帶來的排障成本。
如果你明確要在啟用 swap 的前提下執行節點,則應額外核對 kubelet 的 NoSwap / LimitedSwap 配置與當前 Kubernetes 版本文件,而不要把“開著 swap 也能跑”直接當作通用預設路徑。
$ sudo swapoff -a
# 如需永久禁用,可在 /etc/fstab 中註釋 swap 對應行
$ cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF
# 應用配置
$ sysctl --system
14.2.5 配置 kubelet
為了讓 kubelet 正確執行,我們需要對其進行一些必要的配置。
修改 kubelet.service(可選:IPVS 模式)
注意:kube-proxy 的 IPVS 模式已在 Kubernetes 1.35 中被標記為棄用;Kubernetes 1.36 文件仍將其列為已棄用模式,並推薦遷移到 nftables。新部署應使用預設的 iptables 模式或 nftables 模式(Kubernetes 1.33+ 穩定)。以下 IPVS 配置僅供舊環境參考。
/etc/systemd/system/kubelet.service.d/10-proxy-ipvs.conf 寫入以下內容
# 啟用 ipvs 相關核心模組(已棄用,建議遷移至 nftables)
[Service]
ExecStartPre=-/sbin/modprobe ip_vs
ExecStartPre=-/sbin/modprobe ip_vs_rr
ExecStartPre=-/sbin/modprobe ip_vs_wrr
ExecStartPre=-/sbin/modprobe ip_vs_sh
執行以下命令應用配置。
$ sudo systemctl daemon-reload
14.2.6 部署
安裝配置完成後,我們將分別在 Master 節點和 Worker 節點上進行部署操作。
master
$ sudo kubeadm init --image-repository registry.cn-hangzhou.aliyuncs.com/google_containers \
--pod-network-cidr 10.244.0.0/16 \
--cri-socket unix:///var/run/cri-dockerd.sock \
--v 5
--cri-socket unix:///var/run/cri-dockerd.sock引數指定使用 cri-dockerd 作為容器執行時介面。--pod-network-cidr 10.244.0.0/16引數與後續 CNI 外掛有關,這裡以flannel為例,若後續部署其他型別的網路外掛請更改此引數。
若
kubeadm預檢失敗,應按提示修復缺失依賴、核心引數、swap 或執行時配置。實驗環境確需忽略預檢時,只忽略明確理解且可接受的單項檢查,不建議使用--ignore-preflight-errors=all。
執行成功會輸出
...
[addons] Applied essential addon: CoreDNS
I1116 12:35:13.270407 86677 request.go:538] Throttling request took 181.409184ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/serviceaccounts
I1116 12:35:13.470292 86677 request.go:538] Throttling request took 186.088112ms, request: POST:https://192.168.199.100:6443/api/v1/namespaces/kube-system/configmaps
[addons] Applied essential addon: kube-proxy
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
https://kubernetes.io/docs/concepts/cluster-administration/addons/
Then you can join any number of worker nodes by running the following on each as root:
kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe
node 工作節點
在 另一主機 重複 部署 小節以前的步驟,安裝配置好 kubelet。根據提示,加入到叢集。
$ kubeadm join 192.168.199.100:6443 --token cz81zt.orsy9gm9v649e5lf \
--discovery-token-ca-cert-hash sha256:5edb316fd0d8ea2792cba15cdf1c899a366f147aa03cba52d4e5c5884ad836fe \
--cri-socket unix:///var/run/cri-dockerd.sock
14.2.7 檢視服務
所有服務啟動後,檢視本地實際執行的 Docker 容器。這些服務大概分為三類:主節點服務、工作節點服務和其它服務。
主節點服務
-
apiserver是整個系統的對外介面,提供 RESTful 方式供用戶端和其它元件呼叫; -
scheduler負責對資源進行排程,分配某個 pod 到某個節點上; -
controller-manager負責管理控制器,包括 endpoint-controller (重新整理服務和 pod 的關聯資訊) 和 replication-controller (維護某個 pod 的複製為配置的數值)。
工作節點服務
proxy為 pod 上的服務提供訪問的代理。
其它服務
- Etcd 是所有狀態的儲存資料庫;
14.2.8 使用
將 /etc/kubernetes/admin.conf 複製到 ~/.kube/config
執行 $ kubectl get all -A 檢視啟動的服務。
由於未部署 CNI 外掛,CoreDNS 未正常啟動。如何使用 Kubernetes,請參考後續章節。
14.2.9 部署 CNI
這裡以 flannel 為例進行介紹。
flannel
檢查 podCIDR 設定
$ kubectl get node -o yaml | grep CIDR
# 輸出
podCIDR: 10.244.0.0/16
podCIDRs:
# 注意:v0.28.2 為編寫本文件時的最新版本,請根據需要替換為當前版本
# 參見 https://github.com/flannel-io/flannel/releases
$ kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/v0.28.2/Documentation/kube-flannel.yml
14.2.10 master 節點預設不能執行 pod
如果用 kubeadm 部署一個單節點叢集,預設情況下無法使用,請執行以下命令解除限制
$ kubectl taint nodes --all node-role.kubernetes.io/control-plane-
# 較舊版本使用 master taint
# $ kubectl taint nodes --all node-role.kubernetes.io/master-
# 恢復預設值
# $ kubectl taint nodes NODE_NAME node-role.kubernetes.io/control-plane=true:NoSchedule
...
14.3 在 Docker Desktop 使用
使用 Docker Desktop 可以很方便的啟用 Kubernetes。
14.3.1 啟用 Kubernetes
在 Docker Desktop 設定頁面,點選 Kubernetes,選擇 Enable Kubernetes,稍等片刻,看到左下方 Kubernetes 變為 running,Kubernetes 啟動成功。

注意:Kubernetes 的映像檔儲存在
registry.k8s.io,如果國內網路無法直接訪問,可以在 Docker Desktop 配置中的Docker Engine處配置映像檔加速器,或者利用國內雲服務商的映像檔倉庫手動拉取映像檔並 retag。
14.3.2 測試
$ kubectl version
如果正常輸出資訊,則證明 Kubernetes 成功啟動。
14.4 Kind - Kubernetes IN Docker
Kind (Kubernetes in Docker) 是一個使用 Docker 容器作為節點執行本地 Kubernetes 叢集的工具。主要用於測試 Kubernetes 本身,也非常適合本地開發和 CI 環境。
版本說明:本文件基於 Kind v0.31.0 編寫。Kind 會根據下載時的預設行為自動拉取對應的 Kubernetes 版本。建議定期更新 Kind 到最新版本以獲得最新的 Kubernetes 支援。詳見 Kind Releases。
14.4.1 為什麼選擇 Kind
Kind 相比其他本地叢集方案 (如 Minikube) 有以下顯著優勢:
- 輕量便捷:只要有 Docker 環境即可,無需額外虛擬機器。
- 多叢集支援:可以輕鬆在本地啟動多個叢集。
- 多版本支援:支援指定 Kubernetes 版本進行測試。
- HA 支援:支援模擬高可用叢集 (多 Control Plane)。
14.4.2 安裝 Kind
Kind 是一個二進位制檔案,並在 PATH 中即可使用。以下是不同系統的安裝方法。
macOS
brew install kind
Linux / Windows
可以下載二進位制檔案:
# Linux AMD64
# 注意:v0.31.0 為編寫本文件時的最新版本,請根據需要替換為當前版本
# 參見 https://github.com/kubernetes-sigs/kind/releases
curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.31.0/kind-linux-amd64
chmod +x ./kind
sudo mv ./kind /usr/local/bin/kind
14.4.3 建立叢集
最簡單的建立方式:
kind create cluster
指定叢集名稱:
kind create cluster --name my-cluster
14.4.4 與叢集互動
Kind 會自動將 kubeconfig 合併到 ~/.kube/config。
kubectl cluster-info --context kind-kind
kubectl get nodes
14.4.5 高階用法:配置叢集
建立一個 kind-config.yaml 來定製叢集,例如對映埠到宿主機:
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 8080
protocol: TCP
- role: worker
- role: worker
應用配置:
kind create cluster --config kind-config.yaml
14.4.6 刪除叢集
kind delete cluster
14.5 K3s - 輕量級 Kubernetes
K3s 是一個輕量級的 Kubernetes 發行版,由 Rancher Labs 開發。它專為邊緣計算、物聯網、CI、ARM 等資源受限的環境設計。K3s 被打包為單個二進位制檔案,只有不到 100MB,但透過了 CNCF 的一致性測試。
版本說明:K3s 版本與 Kubernetes 版本對應。安裝指令碼會自動拉取最新穩定版本。如需指定特定版本,可在安裝時設定
INSTALL_K3S_VERSION環境變數。詳見 K3s Releases。
14.5.1 核心特性
- 輕量級:移除過時的、非必須的 Kubernetes 功能 (如傳統的雲提供商外掛),使用 SQLite 作為預設資料儲存 (也支援 Etcd/MySQL/Postgres)。
- 單一二進位制:所有元件 (API Server,Controller Manager,Scheduler,Kubelet,Kube-proxy) 打包在一個程序中執行。
- 開箱即用:內建 Helm Controller、Traefik Ingress controller、ServiceLB、Local-Path-Provisioner。
- 安全:預設啟用安全配置,基於 TLS 通訊。
14.5.2 安裝
K3s 的安裝非常簡單,官方提供了便捷的安裝指令碼。
指令碼安裝
K3s 提供了極為便捷的安裝指令碼。該命令會從網路下載指令碼並直接交給 sh 執行,生產環境建議先下載審查指令碼內容,並按官方文件固定版本或安裝引數:
curl -sfL https://get.k3s.io | sh -
安裝完成後,K3s 會自動啟動並配置好 systemd 服務。
檢視狀態
sudo k3s kubectl get nodes
輸出類似:
NAME STATUS ROLES AGE VERSION
k3s-master Ready control-plane,master 1m v1.36.0+k3s1
版本說明:輸出中的
v1.36.0+k3s1為編寫本文件時的版本示例。實際輸出版本號取決於你所安裝的 K3s 版本。更多資訊請參見 K3s Releases。
14.5.3 快速使用
K3s 內建了 kubectl 命令 (透過 k3s kubectl 呼叫),為了方便,通常會建立別名或配置 KUBECONFIG。
## 讀取 K3s 的配置檔案
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
## 現在可以直接使用 kubectl
kubectl get pods -A
14.5.4 清理解除安裝
/usr/local/bin/k3s-uninstall.sh
14.6 一步步部署 Kubernetes 叢集
14.6.1 概述
部署 Kubernetes 叢集涉及多個元件的安裝和配置,包括 Master 節點和 Worker 節點。本章介紹如何使用 systemd 管理這些服務的生命週期。
14.6.2 Kubernetes 主要元件
Master 節點元件
- kube-apiserver:API 伺服器,Kubernetes 叢集的中心
- kube-controller-manager:控制器管理器
- kube-scheduler:排程器,負責 Pod 排程
- etcd:分散式鍵值儲存,儲存叢集資料
Worker 節點元件
- kubelet:節點代理,管理容器生命週期
- kube-proxy:網路代理,處理服務網路
- Container Runtime:容器執行時(Docker、containerd 等)
14.6.3 使用 systemd 管理 Kubernetes 服務
服務單元檔案
為了讓 systemd 管理 Kubernetes 服務,需要建立相應的 .service 檔案,例如:
/etc/systemd/system/kubelet.service
/etc/systemd/system/kube-proxy.service
/etc/systemd/system/kube-apiserver.service
常用命令
# 啟動服務
sudo systemctl start kubelet
# 停止服務
sudo systemctl stop kubelet
# 重啟服務
sudo systemctl restart kubelet
# 檢視服務狀態
sudo systemctl status kubelet
# 設定開機自啟
sudo systemctl enable kubelet
如果希望檢視更完整的 systemd 部署案例,可以參考 opsnull/follow-me-install-kubernetes-cluster 這類社群專案,再結合本章前文的 kubeadm 與元件配置說明理解整體流程。
14.6.4 推薦學習路徑
- 理解 Kubernetes 架構和各元件的作用
- 準備所需的系統環境(Linux 主機、網路配置等)
- 按步驟安裝各個 Kubernetes 元件
- 配置 systemd 服務單元檔案
- 驗證叢集健康狀態
14.7 部署 Dashboard
Kubernetes Dashboard 是基於網頁的 Kubernetes 使用者介面。
注意:原
kubernetes/dashboard專案已於 2026 年 1 月 21 日歸檔停止維護。推薦使用 Headlamp 等替代方案。以下內容僅供歷史參考,基於歸檔前的最新 Helm 安裝方式。

14.7.1 部署
Dashboard 7.0+ 版本僅支援透過 Helm 安裝:
$ helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/
$ helm upgrade --install kubernetes-dashboard kubernetes-dashboard/kubernetes-dashboard \
--create-namespace --namespace kubernetes-dashboard
14.7.2 訪問
透過埠轉發訪問 Dashboard:
$ kubectl -n kubernetes-dashboard port-forward svc/kubernetes-dashboard-kong-proxy 8443:443
然後在瀏覽器開啟 https://localhost:8443 即可訪問。
14.7.3 登入
為歷史 Dashboard 建立只讀服務賬戶並獲取短期登入令牌。不要為 Dashboard 建立 cluster-admin 繫結;如果確實需要臨時管理員許可權,應走單獨的 break-glass 審批和審計流程。
$ kubectl create sa dashboard-readonly -n kubernetes-dashboard
$ kubectl create clusterrole dashboard-readonly \
--verb=get,list,watch \
--resource=pods,deployments,services,configmaps,namespaces,nodes
$ kubectl create clusterrolebinding dashboard-readonly \
--clusterrole=dashboard-readonly \
--serviceaccount=kubernetes-dashboard:dashboard-readonly
$ kubectl create token dashboard-readonly -n kubernetes-dashboard --duration=1h
將輸出的令牌貼上到登入頁面,即可登入。
14.8 Kubernetes 命令列 kubectl
kubectl 是 Kubernetes 自帶的用戶端,可以用它來直接操作 Kubernetes。
kubectl 的基本用法格式為:
kubectl [command] [resource] [name] [flags]
其中 command 是操作命令(如 get、apply、delete),resource 是資源型別(如 pod、deployment、service),name 是資源名稱(可選),flags 是各種選項引數。
14.8.1 get
顯示一個或多個資源。最常用的命令之一,用於查詢叢集中的資源狀態。
# 檢視所有 Pod
$ kubectl get pods
# 檢視指定名稱空間中的 Deployment
$ kubectl get deployment -n kube-system
# 檢視所有資源
$ kubectl get all
# 使用更詳細的輸出格式
$ kubectl get pods -o wide
# 輸出為 YAML 格式
$ kubectl get pod my-pod -o yaml
# 根據標籤選擇資源
$ kubectl get pods -l app=nginx
14.8.2 describe
顯示資源的詳細資訊,包括事件、狀態、配置等。用於除錯和檢視資源的完整資訊。
# 檢視特定 Pod 的詳情
$ kubectl describe pod my-pod
# 檢視 Node 的詳情
$ kubectl describe node node-1
# 檢視 Deployment 的詳情
$ kubectl describe deployment nginx-deployment
14.8.3 apply
從檔案或標準輸入應用配置。這是宣告式資源管理的標準方式,可用於建立或更新資源。
# 應用 YAML 檔案
$ kubectl apply -f deployment.yaml
# 應用目錄中的所有 YAML 檔案
$ kubectl apply -f ./manifests/
# 從標準輸入應用配置
$ cat deployment.yaml | kubectl apply -f -
14.8.4 create
從檔案或標準輸入建立資源。與 apply 不同,create 僅用於建立新資源,如果資源已存在會報錯。
# 建立資源
$ kubectl create -f pod.yaml
# 從標準輸入建立
$ kubectl create -f - < deployment.yaml
14.8.5 delete
刪除一個或多個資源。支援按名稱、標籤、資源型別等多種方式刪除。
# 按名稱刪除
$ kubectl delete pod my-pod
# 刪除整個 Deployment(同時刪除其管理的 Pod)
$ kubectl delete deployment nginx-deployment
# 按標籤刪除
$ kubectl delete pods -l app=nginx
# 從檔案刪除
$ kubectl delete -f deployment.yaml
14.8.6 logs
檢視 Pod 中容器的日誌。用於除錯應用和檢視容器輸出。
# 檢視 Pod 的日誌
$ kubectl logs my-pod
# 檢視 Pod 中特定容器的日誌
$ kubectl logs my-pod -c container-name
# 實時跟蹤日誌(類似 tail -f)
$ kubectl logs -f my-pod
# 檢視最近 100 行日誌
$ kubectl logs my-pod --tail=100
# 檢視 Pod 啟動前的日誌(適用於已崩潰的容器)
$ kubectl logs my-pod --previous
14.8.7 exec
在執行中的容器內部執行命令。用於除錯、排查問題或執行應用內的操作。
# 進入容器的互動式 shell
$ kubectl exec -it my-pod -- /bin/sh
# 在容器中執行命令
$ kubectl exec my-pod -- ls -la /app
# 在 Pod 中的特定容器執行命令
$ kubectl exec -it my-pod -c container-name -- /bin/bash
14.8.8 port-forward
將本地埠轉發到 Pod 的埠。用於本地訪問叢集內的服務,無需暴露 Service。
# 將本地 8080 埠轉發到 Pod 的 80 埠
$ kubectl port-forward pod/my-pod 8080:80
# 使用隨機本地埠
$ kubectl port-forward pod/my-pod :80
# 轉發到 Service
$ kubectl port-forward svc/my-service 8080:80
14.8.9 rollout
對 Deployment、DaemonSet、StatefulSet 等資源執行滾動更新、暫停、繼續或回滾操作。
# 檢視滾動更新狀態
$ kubectl rollout status deployment/nginx-deployment
# 暫停滾動更新
$ kubectl rollout pause deployment/nginx-deployment
# 繼續滾動更新
$ kubectl rollout resume deployment/nginx-deployment
# 檢視更新歷史
$ kubectl rollout history deployment/nginx-deployment
# 回滾到前一個版本
$ kubectl rollout undo deployment/nginx-deployment
# 回滾到特定版本
$ kubectl rollout undo deployment/nginx-deployment --to-revision=2
14.8.10 label
向資源新增、修改或刪除標籤。標籤用於組織和選擇資源。
# 為 Pod 新增標籤
$ kubectl label pod my-pod env=production
# 修改已有標籤
$ kubectl label pod my-pod env=staging --overwrite
# 刪除標籤
$ kubectl label pod my-pod env-
# 為多個資源新增標籤
$ kubectl label pods -l app=nginx version=v1
14.8.11 annotate
向資源新增或修改註解。註解用於儲存任意後設資料,不用於資源選擇。
# 新增註解
$ kubectl annotate pod my-pod description="Production pod"
# 修改註解
$ kubectl annotate pod my-pod description="Staging pod" --overwrite
# 刪除註解
$ kubectl annotate pod my-pod description-
14.8.12 edit
直接編輯 Kubernetes 資源。編輯器由 EDITOR 環境變數指定。
# 編輯 Pod
$ kubectl edit pod my-pod
# 編輯 Deployment
$ kubectl edit deployment nginx-deployment
14.8.13 config
管理 kubectl 的配置檔案(通常位於 ~/.kube/config)。
# 檢視當前配置
$ kubectl config view
# 切換上下文
$ kubectl config use-context my-cluster
# 檢視所有上下文
$ kubectl config get-contexts
# 設定預設名稱空間
$ kubectl config set-context --current --namespace=my-namespace
14.8.14 cluster-info
顯示叢集的連線資訊和元件狀態。
# 顯示叢集資訊
$ kubectl cluster-info
# 顯示完整的叢集狀態(注:componentstatuses 自 1.19 起已棄用,建議使用下方替代命令)
$ kubectl get componentstatuses
# 推薦替代:
$ kubectl get --raw='/readyz?verbose'
14.8.15 version
顯示 kubectl 用戶端和 Kubernetes API server 的版本資訊。
# 顯示版本
$ kubectl version
# 僅顯示用戶端版本
$ kubectl version --client
# 以 JSON 格式輸出
$ kubectl version --output=json
14.8.16 api-versions
列出 API server 支援的所有 API 版本,格式為 “組/版本”。
$ kubectl api-versions
14.8.17 explain
解釋資源的欄位含義。用於瞭解 YAML 配置檔案中各欄位的作用。
# 檢視 Pod 資源的欄位說明
$ kubectl explain pod
# 檢視 Pod 下 spec 欄位的說明
$ kubectl explain pod.spec
# 檢視具體欄位的詳細說明
$ kubectl explain pod.spec.containers
14.8.18 auth
檢查使用者許可權,用於驗證 RBAC 配置。
# 檢查當前使用者是否有許可權執行操作
$ kubectl auth can-i create pods
# 檢查特定使用者的許可權
$ kubectl auth can-i create pods --as=other-user
14.8.19 help
顯示任何命令的幫助資訊。
# 顯示 kubectl 的一般幫助
$ kubectl help
# 顯示特定命令的幫助
$ kubectl get --help
# 顯示資源的 API 文件
$ kubectl explain deployment
第十五章 Etcd 專案
第十五章 Etcd 專案
etcd 是 CoreOS 團隊發起的一個管理配置資訊和服務發現 (Service Discovery) 的專案,在這一章裡面,我們將基於 etcd 3.5 系列版本介紹該專案的目標,安裝和使用,以及實現的技術。
版本說明: 本章示例基於 etcd 3.5 系列版本編寫。etcd 官方維護最新兩個次版本(當前為 3.5 和 3.6)。請訪問 etcd 官方釋出頁 獲取最新版本資訊。
本章內容
15.1 簡介
版本說明: 本章內容基於 etcd 3.5 系列版本編寫。官方維護最新兩個次版本(當前為 3.5 和 3.6)。請訪問 etcd 官方釋出頁 獲取最新版本資訊。
如圖 15-1 所示,etcd 專案使用該標識。

圖 15-1:etcd 專案標識
etcd 是 CoreOS 團隊於 2013 年 6 月發起的開源專案,它的目標是構建一個高可用的分散式鍵值 (key-value) 資料庫,基於 Go 語言實現。我們知道,在分散式系統中,各種服務的配置資訊的管理分享,服務的發現是一個很基本同時也是很重要的問題。CoreOS 專案就希望基於 etcd 來解決這一問題。
etcd 目前在 github.com/etcd-io/etcd 進行維護。
受到 Apache ZooKeeper 專案和 doozer 專案的啟發,etcd 在設計的時候重點考慮了下面四個要素:
- 簡單:具有定義良好、面向使用者的
API(gRPC) - 安全:支援
HTTPS方式的訪問 - 快速:支援併發
10 k/s的寫操作 - 可靠:支援分散式結構,基於
Raft的一致性演算法
Apache ZooKeeper 是一套知名的分散式系統中進行同步和一致性管理的工具。
doozer 是一個一致性分散式資料庫。
Raft 是一套透過選舉主節點來實現分散式系統一致性的演算法,相比於大名鼎鼎的 Paxos 演算法,它的過程更容易被人理解,由 Stanford 大學的 Diego Ongaro 和 John Ousterhout 提出。更多細節可以參考 raftconsensus.github.io。
一般情況下,使用者使用 etcd 可以在多個節點上啟動多個例項,並新增它們為一個叢集。同一個叢集中的 etcd 例項將會保持彼此資訊的一致性。
15.2 安裝
本節將介紹 etcd 的幾種常見安裝方式,包括二進位制安裝、Docker 映像檔執行以及在 macOS 上的安裝。
etcd 基於 Go 語言實現,因此,使用者可以從專案主頁下載原始碼自行編譯,也可以下載編譯好的二進位制檔案,甚至直接使用製作好的 Docker 映像檔案來體驗。
注意:etcd 官方僅維護最新兩個次版本(當前為 3.5 和 3.6)。etcd 3.4 已於 2026 年 5 月結束支援(EOL),仍在使用 3.4 的使用者應儘快升級。本章示例基於 etcd
3.5.x版本編寫。etcd 3.6.x 可用於新部署。請訪問 etcd 官方釋出頁 獲取最新版本。
15.2.1 二進位制檔案方式下載
編譯好的二進位制檔案都在 github.com/etcd-io/etcd/releases 頁面,使用者可以選擇需要的版本,或透過下載工具下載。
例如,使用 curl 工具下載壓縮包,並解壓。
# 下載 etcd v3.5.29 版本(請訪問 https://github.com/etcd-io/etcd/releases 獲取最新版本)
$ curl -L https://github.com/etcd-io/etcd/releases/download/v3.5.29/etcd-v3.5.29-linux-amd64.tar.gz -o etcd-v3.5.29-linux-amd64.tar.gz
## 國內使用者可選擇就近的網路加速方式(以可用映像檔站為準)
$ tar xzvf etcd-v3.5.29-linux-amd64.tar.gz
$ cd etcd-v3.5.29-linux-amd64
解壓後,可以看到檔案包括
$ ls
Documentation README-etcdctl.md README.md READMEv2-etcdctl.md etcd etcdctl
其中 etcd 是服務主檔案,etcdctl 是提供給使用者的命令用戶端,其他檔案是支援文件。
下面將 etcd etcdctl 檔案放到系統可執行目錄 (例如 /usr/local/bin/)。
$ sudo cp etcd* /usr/local/bin/
預設 2379 埠處理用戶端的請求,2380 埠用於叢集各成員間的通訊。啟動 etcd 顯示類似如下的資訊:
$ etcd
...
2017-12-03 11:18:34.411579 I | embed: listening for peers on http://localhost:2380
2017-12-03 11:18:34.411938 I | embed: listening for client requests on localhost:2379
此時,可以使用 etcdctl 命令進行測試,設定和獲取鍵值 testkey: "hello world",檢查 etcd 服務是否啟動成功:
$ ETCDCTL_API=3 etcdctl member list
8e9e05c52164694d, started, default, http://localhost:2380, http://localhost:2379
$ ETCDCTL_API=3 etcdctl put testkey "hello world"
OK
$ ETCDCTL_API=3 etcdctl get testkey
testkey
hello world
說明 etcd 服務已經成功啟動了。
15.2.2 Docker 映像檔方式執行
映像檔名稱為 quay.io/coreos/etcd,可以透過下面的命令啟動 etcd 服務監聽到 2379 和 2380 埠。
版本說明: 示例中使用
v3.5.29標籤。請訪問 etcd 官方釋出頁 獲取最新可用版本標籤。
$ docker run \
-p 2379:2379 \
-p 2380:2380 \
--mount type=bind,source=/tmp/etcd-data.tmp,destination=/etcd-data \
--name etcd-gcr-v3.5.29 \
quay.io/coreos/etcd:v3.5.29 \
/usr/local/bin/etcd \
--name s1 \
--data-dir /etcd-data \
--listen-client-urls http://0.0.0.0:2379 \
--advertise-client-urls http://<HOST_IP>:2379 \
--listen-peer-urls http://0.0.0.0:2380 \
--initial-advertise-peer-urls http://<HOST_IP>:2380 \
--initial-cluster s1=http://<HOST_IP>:2380 \
--initial-cluster-token tkn \
--initial-cluster-state new \
--log-level info \
--logger zap \
--log-outputs stderr
其中 listen-* 可以繫結 0.0.0.0 監聽所有網絡卡,但 advertise-* 應填寫其他節點或用戶端實際可訪問的主機地址,不能直接寫成 0.0.0.0。
開啟新的終端按照上一步的方法測試 etcd 是否成功啟動。
15.2.3 macOS 中執行
$ brew install etcd
$ etcd
$ ETCDCTL_API=3 etcdctl member list
15.3 叢集
版本說明: 本節示例使用 etcd v3.5.29。請訪問 etcd 官方釋出頁 獲取最新版本資訊。
下面我們使用 Docker Compose 模擬啟動一個 3 節點的 etcd 叢集。
編輯 compose.yaml (或 docker-compose.yml) 檔案
services:
node1:
image: quay.io/coreos/etcd:v3.5.29
volumes:
- node1-data:/etcd-data
expose:
- 2379
- 2380
networks:
cluster_net:
ipv4_address: 172.16.238.100
environment:
- ETCDCTL_API=3
command:
- /usr/local/bin/etcd
- --data-dir=/etcd-data
- --name
- node1
- --initial-advertise-peer-urls
- http://172.16.238.100:2380
- --listen-peer-urls
- http://0.0.0.0:2380
- --advertise-client-urls
- http://172.16.238.100:2379
- --listen-client-urls
- http://0.0.0.0:2379
- --initial-cluster
- node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380
- --initial-cluster-state
- new
- --initial-cluster-token
- docker-etcd
node2:
image: quay.io/coreos/etcd:v3.5.29
volumes:
- node2-data:/etcd-data
networks:
cluster_net:
ipv4_address: 172.16.238.101
environment:
- ETCDCTL_API=3
expose:
- 2379
- 2380
command:
- /usr/local/bin/etcd
- --data-dir=/etcd-data
- --name
- node2
- --initial-advertise-peer-urls
- http://172.16.238.101:2380
- --listen-peer-urls
- http://0.0.0.0:2380
- --advertise-client-urls
- http://172.16.238.101:2379
- --listen-client-urls
- http://0.0.0.0:2379
- --initial-cluster
- node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380
- --initial-cluster-state
- new
- --initial-cluster-token
- docker-etcd
node3:
image: quay.io/coreos/etcd:v3.5.29
volumes:
- node3-data:/etcd-data
networks:
cluster_net:
ipv4_address: 172.16.238.102
environment:
- ETCDCTL_API=3
expose:
- 2379
- 2380
command:
- /usr/local/bin/etcd
- --data-dir=/etcd-data
- --name
- node3
- --initial-advertise-peer-urls
- http://172.16.238.102:2380
- --listen-peer-urls
- http://0.0.0.0:2380
- --advertise-client-urls
- http://172.16.238.102:2379
- --listen-client-urls
- http://0.0.0.0:2379
- --initial-cluster
- node1=http://172.16.238.100:2380,node2=http://172.16.238.101:2380,node3=http://172.16.238.102:2380
- --initial-cluster-state
- new
- --initial-cluster-token
- docker-etcd
volumes:
node1-data:
node2-data:
node3-data:
networks:
cluster_net:
driver: bridge
ipam:
driver: default
config:
-
subnet: 172.16.238.0/24
使用 docker compose up 啟動叢集之後使用 docker exec 命令登入到任一節點測試 etcd 叢集。
/ # etcdctl member list
daf3fd52e3583ff, started, node3, http://172.16.238.102:2380, http://172.16.238.102:2379
422a74f03b622fef, started, node1, http://172.16.238.100:2380, http://172.16.238.100:2379
ed635d2a2dbef43d, started, node2, http://172.16.238.101:2380, http://172.16.238.101:2379
15.4 使用 etcdctl
版本說明: 本節示例基於 etcd 3.5 系列版本。請訪問 etcd 官方釋出頁 獲取最新版本資訊。
etcdctl 是一個命令列用戶端,它能提供一些簡潔的命令,供使用者直接跟 etcd 服務打交道,而無需基於 HTTP API 方式。這在某些情況下將很方便,例如使用者對服務進行測試或者手動修改資料庫內容。我們也推薦在剛接觸 etcd 時透過 etcdctl 命令來熟悉相關的操作,這些操作跟 HTTP API 實際上是對應的。
etcd 專案二進位制發行包中已經包含了 etcdctl 工具,沒有的話,可以從 github.com/etcd-io/etcd/releases 下載。
etcdctl 支援如下的命令,大體上分為資料庫操作和非資料庫操作兩類,後面將分別進行解釋。
NAME:
etcdctl - A simple command line client for etcd3.
USAGE:
etcdctl
VERSION:
3.5.29
API VERSION:
3.5
COMMANDS:
get Gets the key or a range of keys
put Puts the given key into the store
del Removes the specified key or range of keys [key, range_end)
txn Txn processes all the requests in one transaction
compaction Compacts the event history in etcd
alarm disarm Disarms all alarms
alarm list Lists all alarms
defrag Defragments the storage of the etcd members with given endpoints
endpoint health Checks the healthiness of endpoints specified in `--endpoints` flag
endpoint status Prints out the status of endpoints specified in `--endpoints` flag
watch Watches events stream on keys or prefixes
version Prints the version of etcdctl
lease grant Creates leases
lease revoke Revokes leases
lease timetolive Get lease information
lease keep-alive Keeps leases alive (renew)
member add Adds a member into the cluster
member remove Removes a member from the cluster
member update Updates a member in the cluster
member list Lists all members in the cluster
snapshot save Stores an etcd node backend snapshot to a given file
snapshot restore Restores an etcd member snapshot to an etcd directory
snapshot status Gets backend snapshot status of a given file
make-mirror Makes a mirror at the destination etcd cluster
migrate Migrates keys in a v2 store to a mvcc store
lock Acquires a named lock
elect Observes and participates in leader election
auth enable Enables authentication
auth disable Disables authentication
user add Adds a new user
user delete Deletes a user
user get Gets detailed information of a user
user list Lists all users
user passwd Changes password of user
user grant-role Grants a role to a user
user revoke-role Revokes a role from a user
role add Adds a new role
role delete Deletes a role
role get Gets detailed information of a role
role list Lists all roles
role grant-permission Grants a key to a role
role revoke-permission Revokes a key from a role
check perf Check the performance of the etcd cluster
help Help about any command
OPTIONS:
--cacert="" verify certificates of TLS-enabled secure servers using this CA bundle
--cert="" identify secure client using this TLS certificate file
--command-timeout=5s timeout for short running command (excluding dial timeout)
--debug[=false] enable client-side debug logging
--dial-timeout=2s dial timeout for client connections
--endpoints=[127.0.0.1:2379] gRPC endpoints
--hex[=false] print byte strings as hex encoded strings
--insecure-skip-tls-verify[=false] skip server certificate verification
--insecure-transport[=true] disable transport security for client connections
--key="" identify secure client using this TLS key file
--user="" username[:password] for authentication (prompt if password is not supplied)
-w, --write-out="simple" set the output format (fields, json, protobuf, simple, table)
15.4.1 資料庫操作
資料庫操作圍繞對鍵值和目錄的 CRUD (符合 REST 風格的一套操作:Create) 完整生命週期的管理。
etcd 在鍵的組織上採用了層次化的空間結構 (類似於檔案系統中目錄的概念),使用者指定的鍵可以為單獨的名字,如 testkey,此時實際上放在根目錄 / 下面,也可以為指定目錄結構,如 cluster1/node2/testkey,則將建立相應的目錄結構。
注:CRUD 即 Create,Read,Update,Delete,是符合 REST 風格的一套 API 操作。
put
$ etcdctl put /testdir/testkey "Hello world"
OK
get
獲取指定鍵的值。例如
$ etcdctl put testkey hello
OK
$ etcdctl get testkey
testkey
hello
支援的選項為
--sort-by 指定排序欄位(CREATE / KEY / MODIFY / VALUE / VERSION)
--order 指定排序順序(ASCEND / DESCEND)
--consistency 指定一致性級別(l 線性一致,s 序列)
del
刪除某個鍵值。例如
$ etcdctl del testkey
1
15.4.2 非資料庫操作
watch
監測一個鍵值的變化,一旦鍵值發生更新,就會輸出最新的值。
例如,使用者更新 testkey 鍵值為 Hello world。
$ etcdctl watch testkey
PUT
testkey
2
member
透過 list、add、update、remove 命令列出、新增、更新、刪除 etcd 例項到 etcd 叢集中。
例如本地啟動一個 etcd 服務例項後,可以用如下命令進行檢視。
$ etcdctl member list
422a74f03b622fef, started, node1, http://172.16.238.100:2380, http://172.16.238.100:2379
本章小結
etcd 是 Kubernetes 的核心儲存元件,為分散式系統提供可靠的鍵值儲存和服務發現能力。
| 概念 | 要點 |
|---|---|
| 定位 | 分散式鍵值儲存系統,用於配置管理和服務發現 |
| 協議 | 基於 Raft 一致性演算法,保證資料強一致 |
| API | 提供 gRPC 和 HTTP API |
| 叢集 | 建議使用奇數節點 (3 或 5 個) 部署 |
| etcdctl | 命令列管理工具,支援 put/get/del/watch 等操作 |
| 安全 | 支援 TLS 加密通訊和 RBAC 訪問控制 |
延伸閱讀
- 容器編排基礎:Kubernetes 如何使用 etcd
- 部署 Kubernetes:在叢集中部署 etcd
第十六章 容器與雲端計算
第十六章 容器與雲端計算
版本說明:雲平臺的 Kubernetes 版本和容器服務功能更新迅速。本章示例使用通用的 API 版本(如
apps/v1),建議查閱各雲廠商官方文件獲取最新的服務版本和配置指南。
Docker 目前已經得到了眾多公有云平臺的支援,併成為除虛擬機器之外的核心雲業務。
除了 AWS、Google、Azure 等,國內的各大公有云廠商,基本上都同時支援了虛擬機器服務和基於 Kubernetes 的容器雲業務。有的還推出了其他服務,例如容器映像檔服務讓使用者在雲上享有安全高效的映像檔託管、分發等服務。
本章內容
16.1 簡介
版本說明:各雲平臺的容器服務版本更新頻繁。本章示例適用於常見的 Kubernetes API 版本。建議訪問各雲廠商官方文件(如 AWS EKS、Azure AKS、Google GKE、阿里雲 ACK、騰訊雲 TKE)獲取最新資訊。
隨著容器技術的普及,目前主流的雲端計算服務商都提供了成熟的容器服務。與容器相關的雲端計算服務主要分為以下幾種型別:
16.1.1 容器編排託管服務
這是目前最主流的形式。雲廠商託管 Kubernetes 的控制平面 (Master 節點),使用者只需管理工作節點 (Worker Node)。
- 優勢:降低了 Kubernetes 叢集的維護成本,高可用性由廠商保證。
- 典型服務:AWS EKS,Azure AKS,Google GKE,阿里雲 ACK,騰訊雲 TKE。
16.1.2 容器例項服務
這一類服務通常被稱為 CaaS (Container as a Service)。使用者無需管理底層伺服器 (EC2/CVM),只需提供映像檔和配置即可執行容器。
- 優勢:極致的彈性,按秒計費,零運維。
- 典型服務:AWS Fargate,Azure Container Instances,Google Cloud Run,阿里雲 ECI。
16.1.3 映像檔倉庫服務
提供安全、可靠的私有 Docker 映像檔儲存服務,通常與雲廠商的 CI/CD 流水線深度整合。
- 典型服務:AWS ECR,Azure ACR,Google GCR/GAR,阿里雲 ACR。
本章將介紹如何在幾個主流雲平臺上使用 Docker 和 Kubernetes 服務。
16.2 騰訊雲
騰訊雲容器服務 TKE 是騰訊雲提供的 Kubernetes 託管服務,適合把容器化應用部署到雲上。官方文件見 騰訊雲容器服務。

圖 16-1:騰訊雲標識
下面的示例只保留建立叢集、部署應用、管理映像檔和配置加速器這幾類最常見操作。

圖 16-2:騰訊雲容器服務示意圖
騰訊雲容器服務:TKE 簡介
騰訊雲容器服務 (TKE, Tencent Kubernetes Engine) 是一款容器編排平臺,基於原生 Kubernetes 提供,支援自動擴充套件、負載均衡、多可用區高可用等企業級功能。TKE 幫助開發者快速部署和管理容器化應用,消除叢集運維的複雜度。
基本使用步驟
1. 建立叢集
登入騰訊雲控制檯,進入容器服務模組:
- 選擇 “建立叢集”,配置叢集名稱、地域和網路
- 選擇節點配置(雲伺服器規格和數量)
- 設定 Kubernetes 版本和安全組
- 完成建立後獲得叢集 kubeconfig 檔案
# 下載 kubeconfig 檔案後,配置本地環境
export KUBECONFIG=/path/to/kubeconfig.yaml
kubectl cluster-info
2. 部署容器應用
建立 Deployment 部署應用:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
應用配置檔案:
kubectl apply -f deployment.yaml
kubectl get pods
kubectl get svc
3. 管理映像檔
使用騰訊雲容器映像檔服務 (TCR) 儲存和分發私有映像檔:
# 登入騰訊雲映像檔倉庫
docker login ccr.ccs.tencentyun.com -u <username>
# 標記本地映像檔
docker tag my-app:latest ccr.ccs.tencentyun.com/namespace/my-app:latest
# 推送映像檔到騰訊雲
docker push ccr.ccs.tencentyun.com/namespace/my-app:latest
騰訊雲 Docker 映像檔加速器配置
如果你的賬號開通了映像檔加速器,可以把控制檯給出的地址寫入 Docker 配置。
Linux 系統配置
編輯 /etc/docker/daemon.json 檔案(如果不存在則建立):
# 建立或編輯配置檔案
sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
新增以下內容:
{
"registry-mirrors": [
"https://mirror.ccs.tencentyun.com"
],
"insecure-registries": []
}
重啟 Docker 服務:
sudo systemctl daemon-reload
sudo systemctl restart docker
驗證配置:
docker info | grep -A 5 "Registry Mirrors"
Windows/Mac 配置
對於 Docker Desktop,在設定介面中開啟 Docker Engine,把上述 registry-mirrors 欄位寫入 JSON 後重啟即可。
騰訊雲容器映像檔服務:TCR
TCR 提供私有映像檔倉庫、訪問控制和映像檔分發能力。一個最小示例如下:
# 登入到騰訊雲 TCR
docker login ccr.ccs.tencentyun.com --username <username>
# 構建並推送映像檔
docker build -t my-app:v1.0 .
docker tag my-app:v1.0 ccr.ccs.tencentyun.com/my-namespace/my-app:v1.0
docker push ccr.ccs.tencentyun.com/my-namespace/my-app:v1.0
TKE 叢集中使用 TCR 映像檔
配置映像檔拉取憑證後,在 Deployment 中直接引用 TCR 映像檔:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
imagePullSecrets:
- name: tcr-secret
containers:
- name: my-app
image: ccr.ccs.tencentyun.com/my-namespace/my-app:v1.0
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
16.3 阿里雲
如圖 16-3:所示,阿里雲是國內主流雲服務平臺之一。

圖 16-3:阿里雲標識
阿里雲創立於 2009 年,是中國較早的雲端計算平臺。阿里雲致力於提供安全、可靠的計算和資料處理能力。
阿里雲的客戶群體中,活躍著微博、虎牙、魅族、優酷等一大批明星網際網路公司。在天貓雙 11 全球狂歡節等極富挑戰的應用情境中,阿里雲保持著良好的執行紀錄。
阿里雲容器服務 Kubernetes 版 ACK 提供了高效能、可伸縮的容器應用管理服務,支援在一組雲伺服器上透過 Docker 容器來進行應用生命週期管理。容器服務極大簡化了使用者對容器管理叢集的搭建工作,無縫整合了阿里雲虛擬化、儲存、網路和安全能力。容器服務提供了多種應用釋出方式和流水線般的持續交付能力,原生支援微服務架構,助力使用者無縫上雲和跨雲管理。
圖 16-4:阿里雲容器服務示意圖(請訪問 阿里雲容器服務 ACK 官方文件 檢視最新介面)
阿里雲容器服務 ACK 簡介
阿里雲容器服務 Kubernetes 版 (ACK, Container Service for Kubernetes) 是一款託管式 Kubernetes 服務,基於開源 Kubernetes 構建,提供企業級的容器編排和管理能力。ACK 整合了阿里雲端儲存、網路和安全能力,支援多種應用部署模式和持續交付流程。
基本使用步驟
1. 建立叢集
登入阿里雲控制檯,進入容器服務 > Kubernetes 叢集:
- 點選 “建立叢集”,選擇叢集配置
- 配置叢集名稱、地域、可用區和節點型別
- 選擇節點規格和數量(支援彈性伸縮)
- 配置網路引數和安全設定
- 完成建立,下載 kubeconfig 檔案
# 配置本地 kubectl
export KUBECONFIG=/path/to/kubeconfig.yaml
kubectl get nodes
2. 部署容器應用
透過 Deployment 部署應用示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: registry.cn-hangzhou.aliyuncs.com/myapp/web:v1
ports:
- containerPort: 8080
resources:
limits:
memory: "512Mi"
cpu: "500m"
部署應用:
kubectl apply -f deployment.yaml
kubectl get pods -o wide
kubectl logs <pod-name>
3. 暴露服務
建立 Service 暴露應用:
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 8080
selector:
app: web
應用:
kubectl apply -f service.yaml
kubectl get svc web-service
阿里雲 Docker 映像檔加速器配置
為了加快從阿里雲映像檔源拉取官方映像檔的速度,可以配置映像檔加速器。阿里云為容器服務 ACK 使用者提供了免費的映像檔加速服務。
獲取加速器地址
登入阿里雲容器映像檔服務控制檯,在 “映像檔工具” > “映像檔加速器” 中可獲取個人的加速器地址(類似於 https://xxxxxx.mirror.aliyuncs.com)。
Linux 系統配置
編輯或建立 /etc/docker/daemon.json 檔案:
sudo mkdir -p /etc/docker
sudo nano /etc/docker/daemon.json
新增或修改以下內容(替換為你的加速器地址):
{
"registry-mirrors": [
"https://xxxxxx.mirror.aliyuncs.com"
]
}
重新載入並重啟 Docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
驗證配置生效:
docker info | grep -A 5 "Registry Mirrors"
Windows/Mac 配置
在 Docker Desktop 的 Settings 中:
- 進入 “Docker Engine” 標籤
- 編輯 JSON 配置,新增
registry-mirrors欄位 - 點選 “Apply & Restart”
測試加速效果
# 從加速器拉取映像檔(速度應該明顯提升)
docker pull nginx:latest
time docker pull alpine:latest
阿里雲容器映像檔服務:ACR
阿里雲容器映像檔服務 (ACR, Container Registry) 是企業級的容器映像檔儲存和分發平臺:
- 私有映像檔倉庫:支援多個名稱空間,細粒度許可權控制
- 映像檔構建:雲端編譯和構建,支援自動化 CI/CD
- 映像檔掃描:自動檢測映像檔中的漏洞和惡意程式碼
- 跨地域複製:支援映像檔在多個地域的同步和加速
- 整合 ACK:與 ACK 無縫整合,自動身份認證
- 映像檔版本管理:標籤管理、映像檔過期清理、保留策略
完整推送/拉取示例
# 登入阿里雲映像檔倉庫(使用 Docker 登入)
# 使用阿里雲賬戶 ID 和 RAM 訪問金鑰或密碼
docker login registry.cn-hangzhou.aliyuncs.com \
--username=<阿里雲賬戶ID>
# 拉取阿里雲公開映像檔
docker pull registry.cn-hangzhou.aliyuncs.com/library/nginx:latest
# 構建本地映像檔
docker build -t my-app:v1.0 .
# 標記映像檔為阿里雲倉庫地址
docker tag my-app:v1.0 \
registry.cn-hangzhou.aliyuncs.com/myapp/my-app:v1.0
# 推送映像檔到阿里雲 ACR
docker push registry.cn-hangzhou.aliyuncs.com/myapp/my-app:v1.0
# 在 Dockerfile 中使用 ACR 映像檔
FROM registry.cn-hangzhou.aliyuncs.com/myapp/my-app:v1.0
COPY . /app
RUN echo "已成功使用阿里雲映像檔"
ACK 叢集中使用 ACR 映像檔
在 ACK 叢集中,需要先配置映像檔拉取憑證(Secret),然後在 Deployment 中引用:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
# 如果是私有映像檔,需配置映像檔拉取憑證
imagePullSecrets:
- name: acr-secret
containers:
- name: web
image: registry.cn-hangzhou.aliyuncs.com/myapp/web:v2.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8080
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
affinity:
# 配置 Pod 反親和性,分散到不同節點
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
建立映像檔拉取憑證
在 ACK 叢集中建立 Secret,用於拉取私有映像檔:
# 建立映像檔拉取 Secret
kubectl create secret docker-registry acr-secret \
--docker-server=registry.cn-hangzhou.aliyuncs.com \
--docker-username=<阿里雲賬戶ID> \
--docker-password=<RAM訪問金鑰或密碼> \
--docker-email=<郵箱地址>
# 檢視建立的 Secret
kubectl get secret acr-secret
kubectl describe secret acr-secret
ACR 優勢
- 在 ACK 叢集中與映像檔倉庫無縫整合,簡化身份認證
- 支援 Helm Chart 儲存和版本管理,方便應用交付
- 提供完整的圖形化映像檔倉庫管理介面
- 完整的審計日誌和操作追蹤功能
- 支援映像檔自動掃描和漏洞報告
16.4 亞馬遜雲
如圖 16-5:所示,AWS 是全球主流雲服務平臺之一。

圖 16-5:AWS 標識
AWS,即 Amazon Web Services,是亞馬遜 (Amazon) 公司的 IaaS 和 PaaS 平臺服務。AWS 提供了一整套基礎設施和應用程式服務,使使用者幾乎能夠在雲中執行一切應用程式:從企業應用程式和大資料專案,到社交遊戲和移動應用程式。AWS 面向使用者提供包括彈性計算、儲存、資料庫、應用程式在內的一整套雲端計算服務,能夠幫助企業降低 IT 投入成本和維護成本。
在容器領域,AWS 目前主流能力可以按情境分為四類:
Amazon EKS:託管 Kubernetes 控制平面,適合標準雲原生工作負載。Amazon ECS:AWS 原生容器編排服務,適合深度整合 AWS 生態 (IAM、ALB、CloudWatch) 情境。AWS Fargate:無伺服器容器執行時,可與 EKS/ECS 結合使用,減少節點運維。Amazon ECR:映像檔倉庫服務,提供私有映像檔管理、掃描與訪問控制。
實踐建議:
- 團隊已具備 Kubernetes 經驗,優先選擇 EKS;
- 追求更低運維複雜度且業務主要執行在 AWS,可優先 ECS + Fargate;
- 無論編排方案如何,都建議使用 ECR 統一管理映像檔生命週期。

圖 16-6:AWS 容器服務示意圖
16.5 多雲部署策略
企業在選擇容器雲平臺時,通常會在 AWS EKS,Azure AKS,Google GKE 以及國內的阿里雲 ACK,騰訊雲 TKE 之間進行權衡。
16.5.1 三大公有云 Kubernetes 服務對比
| 特性 | Google GKE | AWS EKS | Azure AKS |
|---|---|---|---|
| 版本更新 | 最快,通常是 K8s 新特性的首發地 | 相對保守,注重穩定性 | 跟隨社群,更新速度適中 |
| 控制平面管理 | 全託管,自動升級,$0.10/h(有 Free Tier 抵扣) | 託管,$0.10/h | 全託管;Free 層適合開發/測試,Standard/Premium 層預設包含 Uptime SLA |
| 節點管理 | GKE Autopilot 模式完全託管節點 | Managed Node Groups 簡化管理 | Virtual Machine Scale Sets |
| 網路模型 | VPC-native, 效能優秀 | AWS VPC CNI, Pod 直接獲取 VPC IP | Azure CNI (消耗 IP 多) 或 Kubenet |
| 整合度 | 與 GCP 資料分析、AI 服務整合緊密 | 與 AWS IAM, ALB, CloudWatch 整合深度高 | 與 Active Directory, Azure DevOps 整合好 |
16.5.2 多雲部署策略
隨著企業業務的擴充套件,單一雲平臺可能無法滿足所有需求,多雲部署成為趨勢。
1. 跨雲災備:Active-Passive
主要業務執行在一個雲 (如 AWS),資料實時複製到另一個雲 (如阿里雲)。當主雲發生故障時,流量切換到備雲。
- 優點:架構相對簡單,資料一致性好控制。
- 缺點:資源閒置浪費,切換可能有 RTO。
2. 多活部署:Active-Active
業務同時在多個雲上執行,透過全域性流量管理 (DNS/GSLB) 分發流量。
- 優點:高可用,就近接入提升使用者體驗。
- 缺點:資料同步複雜,跨雲網路延遲問題。
3. 混合雲
核心資料和敏感業務保留在私有云 (IDC),彈性業務或前端業務部署在公有云。
- 工具:Google Anthos,AWS Outposts,Azure Arc 都是為了解決混合雲統一管理而生。
16.5.3 建議
- 技術選型:儘量使用標準的 Kubernetes API,避免過度依賴特定雲廠商的 CRD 或專有服務,以保持應用的可移植性。
- IaC 管理:使用 Terraform 或 Pulumi 等工具統一管理多雲基礎設施。
第十七章 容器其它生態
第十七章 容器其它生態
版本說明(核驗日期:2026-05-16):本章介紹的工具和執行時(Podman、Buildah、Skopeo、containerd、Kata Containers、gVisor、WasmEdge 等)都保持活躍的開發。建議: - 查閱各專案官方文件獲取最新版本 - 在生產環境使用前驗證版本相容性 - 關注官方釋出說明了解重大變更
本章將介紹 Docker 和 Kubernetes 之外的容器生態技術。
同時,Docker 自身的生態也在向雲構建、AI 本地推理和企業級桌面安全擴充套件。當前需要額外關注:
- Docker Model Runner:在 Docker Desktop / Docker Engine 中管理、執行和服務本地 AI 模型,支援 OpenAI 與 Ollama 相容 API,並可將 GGUF、Safetensors 等模型檔案作為 OCI Artifact 管理。
- Docker Build Cloud:透過遠端 BuildKit 和共享構建快取加速本地與 CI 構建,適合多平臺映像檔和團隊共享快取情境。
- Docker Offload:把容器構建和執行解除安裝到雲端,適合 VDI、受限本機或不支援巢狀虛擬化的開發環境。
- Hardened Docker Desktop / Enhanced Container Isolation (ECI):透過更強的名稱空間隔離、敏感掛載保護和系統呼叫限制降低桌面容器逃逸風險。
本章內容
- Fedora CoreOS 簡介
-
專為容器化工作負載設計的作業系統。
-
CoreOS 的安裝方式與基本配置。
-
相容 Docker CLI 的下一代無守護程序容器引擎。
-
無需守護程序的 OCI 容器映像檔構建工具。
-
遠端檢查和管理容器映像檔的利器。
-
作為現代容器生態基石的核心容器執行時。
-
透過提供更強隔離性來保證安全的技術方案(如 Kata Containers、gVisor)。
- 一種極具潛力的輕量級跨平臺二進位制指令格式。
17.1 Fedora CoreOS 簡介
版本說明:Fedora CoreOS 定期釋出更新。建議訪問 官方下載頁面 獲取最新版本和相容性資訊。
Fedora CoreOS 是一個自動更新的,最小的,整體的,以容器為中心的作業系統,不僅適用於叢集,而且可獨立執行,並針對執行 Kubernetes 進行了最佳化。它旨在結合 CoreOS Container Linux 和 Fedora Atomic Host 的優點,將 Container Linux 中的 Ignition 與 rpm-ostree 和 Project Atomic 中的 SELinux 強化等技術相整合。其目標是提供最佳的容器主機,以安全,大規模地執行容器化的工作負載。
17.1.1 FCOS 特性
一個最小化作業系統
FCOS 被設計成一個基於容器的最小化的現代作業系統。它比現有的 Linux 安裝平均節省 40% 的 RAM (大約 114M) 並允許從 PXE 或 iPXE 非常快速的啟動。
系統初始化
Ignition 是一種配置實用程式,可讀取配置檔案 (JSON 格式) 並根據該配置配置 FCOS 系統。可配置的元件包括儲存,檔案系統,systemd 和使用者。
Ignition 在系統首次啟動期間 (在 initramfs 中) 僅執行一次。由於 Ignition 在啟動過程中的早期執行,因此它可以在使用者空間開始啟動之前重新對磁碟分割槽,格式化檔案系統,建立使用者並寫入檔案。當 systemd 啟動時,systemd 服務已被寫入磁碟,從而加快了啟動時間。
自動更新
FCOS 使用 rpm-ostree 系統進行事務性升級。無需像 yum 升級那樣升級單個軟體包,而是 rpm-ostree 將 OS 升級作為一個原子單元進行。新的 OS 部署在升級期間進行,並在下次重新引導時生效。如果升級出現問題,則一次回滾和重新啟動會使系統返回到先前的狀態。確保了系統升級對群集容量的影響降到最小。
容器工具
對於諸如構建,複製和其他管理容器的任務,FCOS 用一組容器工具代替了 Docker CLI。podman CLI 工具支援許多容器執行時功能,例如執行,啟動,停止,列出和刪除容器和映像檔。skopeo CLI 工具可以複製,認證和簽名映像檔。您還可以使用 crictl CLI 工具來處理 CRI-O 容器引擎中的容器和映像檔。
17.2 Fedora CoreOS 安裝
17.2.1 下載 ISO
在下載頁面 Bare Metal & Virtualized 標籤頁下載 ISO。
17.2.2 編寫 Butane 配置
注意:Fedora CoreOS 配置工具已從
fcct(Fedora CoreOS Config Transpiler) 更名為 Butane。新版本使用.bu副檔名和更新的 spec 版本。
## example.bu
variant: fcos
version: 1.6.0
passwd:
users:
- name: core
ssh_authorized_keys:
- ssh-rsa AAAA...
將 ssh-rsa AAAA... 替換為自己的 SSH 公鑰 (位於 ~/.ssh/id_rsa.pub)。
17.2.3 轉換 Butane 配置為 Ignition
$ docker run -i --rm quay.io/coreos/butane:release --pretty --strict < example.bu > example.ign
17.2.4 掛載 ISO 啟動虛擬機器並安裝
虛擬機器需要分配 3GB 以上記憶體,否則會無法啟動。
在虛擬機器終端執行以下命令安裝:
$ sudo coreos-installer install /dev/sda --ignition-file example.ign
安裝之後重新啟動即可使用。
17.2.5 使用
$ ssh core@虛擬機器IP
$ podman --version
17.3 Podman - 下一代 Linux 容器工具
版本說明:Podman 保持活躍的開發和釋出週期。建議訪問 Podman 官方文件 和 GitHub Releases 獲取最新版本。
Podman 是一個無守護程序、與 Docker 命令高度相容的下一代 Linux 容器工具。它由 Red Hat 開發,旨在提供一個更安全的容器執行環境。
17.3.1 Podman vs Docker
Podman 和 Docker 在設計理念上存在顯著差異,主要體現在架構和許可權模型上。
| 特性 | Docker | Podman |
|---|---|---|
| 架構 | C/S 架構,依賴守護程序 (dockerd) |
無守護程序 (Daemonless) |
| 許可權 | 預設需要 root 許可權 (雖有 Rootless 模式) | 預設支援 Rootless (非 root 使用者執行) |
| 生態 | 完整的生態系統 (Compose, Swarm) | 專注單機容器,配合 Kubernetes 使用 |
| 映像檔構建 | docker build |
podman build 或 buildah |
17.3.2 安裝
Podman 支援多種作業系統,安裝過程也相對簡單。
CentOS / RHEL
$ sudo yum -y install podman
macOS
macOS 上需要安裝 Podman Desktop 或透過 Homebrew 安裝:
$ brew install podman
$ podman machine init
$ podman machine start
17.3.3 基本使用
podman 的命令列幾乎與 docker 完全相容,大多數情況下,你只需將 docker 替換為 podman 即可。
執行容器
## $ docker run -d -p 80:80 nginx:alpine
$ podman run -d -p 80:80 nginx:alpine
列出容器
$ podman ps
構建映像檔
$ podman build -t myimage .
17.3.4 Pods 的概念
與 Docker 不同,Podman 支援“Pod”的概念 (類似於 Kubernetes 的 Pod),允許你在同一個網路名稱空間中執行多個容器。
## 建立一個 Pod
$ podman pod create --name mypod -p 8080:80
## 在 Pod 中執行容器
$ podman run -d --pod mypod --name webbing nginx
17.3.5 遷移到 Podman
如果你習慣使用 docker 命令,可以簡單地設定別名:
$ alias docker=podman
Systemd 整合
Podman 可以生成 systemd 單元檔案,讓容器像普通系統服務一樣管理。
## 建立容器
$ podman run -d --name myweb -p 8080:80 nginx
## 生成 systemd 檔案
$ podman generate systemd --name myweb --files --new
## 啟用並啟動服務
$ systemctl --user enable --now container-myweb.service
Podman Compose
雖然 Podman 相容 Docker Compose,但在某些情境下你可能需要明確使用 podman-compose。
$ pip3 install podman-compose
$ podman-compose up -d
17.4 Buildah - 容器映像檔構建工具
版本說明:Buildah 與 Podman 和 Skopeo 共同維護。建議查閱 Buildah 官方文件 和 GitHub Releases 瞭解最新版本。
本節介紹 Buildah,包括其基礎概念、應用情境以及基本指令。
17.4.1 Buildah 簡介
Buildah 是一個用於構建 OCI(Open Container Initiative)相容格式容器映像檔的開源命令列工具。與 Docker 需要一直執行的守護程序(daemon)不同,Buildah 的設計初衷是無需守護程序(daemonless)即可工作,並且也不強制要求 root 許可權(rootless)。這使得在持續整合/持續部署(CI/CD)環境中構建映像檔時能夠更加輕量且安全。
Buildah 由 Red Hat 主導開發,通常和 Podman、Skopeo 一起使用,被認為是構建、執行和管理容器的一套現代化工具鏈。在很多需要增強安全性和無需依賴守護程序的情境中,Buildah 是 docker build 命令的最佳替代方案。
17.4.2 核心特性
- 無守護程序(Daemonless):Buildah 直接透過系統呼叫拉取、構建和推送映像檔,減少了單點故障的風險和資源開銷。
- 構建效率高:可以掛載映像檔的根檔案系統到本地,並直接利用宿主機的工具對其進行操作,非常靈活。
- 相容性:不僅支援處理傳統的 Dockerfile,還能完全相容 OCI(Open Container Initiative)標準和 Docker 格式。
- 與 Podman 整合:Podman 自身構建映像檔的命令
podman build底層實際上也是依賴 Buildah 庫來實現的。
17.4.3 安裝 Buildah
在許多主流的 Linux 發行版中都可以透過包管理器直接安裝 Buildah。
以 Fedora/CentOS/RHEL 為例:
$ sudo dnf install -y buildah
以 Ubuntu/Debian 為例(需引入官方源後):
$ sudo apt-get update
$ sudo apt-get -y install buildah
17.4.4 基礎用法示例
1. 從現有的 Dockerfile 構建映像檔
Buildah 最常見的用法就是像 Docker 一樣根據 Dockerfile 來構建映像檔,可以直接使用 buildah bud(或者 buildah build-using-dockerfile)命令:
$ buildah bud -t my-app:latest .
可以看到在這點上,它與 docker build 的體驗完全一致。
2. 互動式從空映像檔開始構建
除了使用 Dockerfile,Buildah 最強大的功能來自於它的互動式和指令碼化構建機制。我們可以從一個極簡的映像檔(或基礎映像檔)開始構建:
# 獲取一個基礎映像檔
$ container=$(buildah from alpine:latest)
# 獲取掛載點,並檢視其路徑
$ mnt=$(buildah mount $container)
$ echo $mnt
/var/lib/containers/storage/overlay/xxx/merged
# 利用宿主機直接建立檔案,而不需要在容器內部執行命令
$ echo "Hello Buildah" > $mnt/hello.txt
# 新增一些配置和命令
$ buildah config --cmd "cat /hello.txt" $container
# 將容器提交為映像檔
$ buildah commit $container my-hello-image:latest
# 構建完成後可以解除安裝並清理容器上下文
$ buildah unmount $container
$ buildah rm $container
這種模式在自動化流水線中極為有用,因為我們可以將上述過程編寫成標準的 bash 指令碼,無需為了構建映像檔而撰寫只在其獨立語法中執行的 Dockerfile 指令。
3. 檢視和推送映像檔
透過 buildah images 可以檢視當前環境中的映像檔。推送映像檔到外部 Registry 也十分安全方便:
# 檢視本地構建的映像檔
$ buildah images
# 推送映像檔到 Docker Hub(注意需要先登入)
$ buildah push my-hello-image:latest docker://docker.io/username/my-hello-image:latest
結合其無需特權和靈活指令碼的優點,Buildah 正變得越來越受到構建和分發 OCI 映像檔的使用者喜愛。
17.5 Skopeo - 容器映像檔管理工具
版本說明:Skopeo 屬於 containers 專案生態。建議訪問 Skopeo 官方倉庫 和 GitHub Releases 獲取最新版本資訊。
本節介紹 Skopeo,包括其基礎概念、應用情境以及基本指令。
17.5.1 Skopeo 簡介
Skopeo 是一個由 Red Hat 贊助開源的命令列工具,它可以在不需要執行容器守護程序(如 Docker Daemon)的前提下,對容器映像檔進行極其高效的操作和管理,包括:檢查、複製、刪除和簽名等操作。
Skopeo 最大的特點是其可以在“不將映像檔拉取到本地”的情況下,直接在遠端 Registry(映像檔倉庫)之間完成檢查和搬運,從而大幅度節省頻寬和磁碟空間。這也是它在容器運維和分發領域非常受歡迎的原因。
17.5.2 核心特性
- 遠端巡檢:透過
skopeo inspect可以檢視遠端倉庫中映像檔的後設資料(例如包含哪些層、環境變數資訊、入口命令等),而完全無需拉取該映像檔。 - 映像檔複製與同步:支援各種格式之間的相互傳輸,如在不同的容器倉庫之間、或者從倉庫拉取到本地的目錄、或者儲存為 OCI 佈局結構等。
- 映像檔刪除:可以遠端刪除倉庫中的映像檔(需要擁有許可權)。
- 簽名驗證:支援在分發映像檔前進行數字簽名以保障安全性。
17.5.3 安裝 Skopeo
類似於 Buildah,Skopeo 也直接包含在大部分主流的 Linux 源中。
在 Fedora/CentOS/RHEL 等發行版中:
$ sudo dnf install -y skopeo
在 Ubuntu/Debian 中:
$ sudo apt-get update
$ sudo apt-get -y install skopeo
如果是 macOS 環境,可以透過 Homebrew 安裝:
$ brew install skopeo
17.5.4 基礎用法示例
1. 遠端檢查映像檔
有時候我們只想知道遠端映像檔的詳細資訊,並不想拉取它。下面是檢查 Docker Hub 上的 Alpine 映像檔的例子:
$ skopeo inspect docker://docker.io/library/alpine:latest
這個命令會返回一段 JSON 格式的資料,其中包含了諸如映像檔摘要(Digest)、建立時間、架構(Architecture)、標籤(Tags)等豐富資訊。在自動化的工具和系統審查環境中,這是一個不可或缺的利器。
2. 同步與複製映像檔
skopeo copy 是使用最為廣泛的命令。它可以在不同的倉庫、不同的格式之間無縫搬運映像檔。
例如,從一個公共倉庫直接搬運映像檔到一個私有企業倉庫(無需將其先 docker pull 到本地,再 docker push):
$ skopeo copy docker://docker.io/library/alpine:latest docker://registry.example.com/library/alpine:latest
又或者,你可以將遠端映像檔拉取到本地,只為了檢查其拆解後的格式,比如將映像檔解壓到本地某個目錄下以 OCI 規範存放:
$ skopeo copy docker://docker.io/library/alpine:latest oci:alpine-oci
如果我們要將本地的某個目錄下的打包好的映像檔再次推向 Registry 或轉換為其它儲存型別也是完全支援的,諸如:
docker://遠端 Registrydocker-archive:/docker-daemon:Docker 對應的歸檔檔案或本地守護程序oci:/oci-archive:OCI 相關檔案格式dir:本地純目錄
3. 校驗機制與安全
如果你不信任公開倉庫上的映像檔,或是需要透過特定的 TLS 證書和鑑權,Skopeo 的功能也是能很好的支援的,比如它可以直接傳遞諸如 --src-creds 或 --dest-tls-verify=false 等引數,這在進行網路隔離的複雜映像檔搬運操作中常常會用到。
對於複雜的容器環境或那些純粹用於映像檔資產管理的節點來說,Skopeo 提供了一個直接而強大的資料“搬運工”。
17.6 containerd - 核心容器執行時
版本說明:containerd 和 nerdctl 保持活躍的釋出週期。建議查閱 containerd 官方文件 和 nerdctl GitHub Releases 獲取最新版本資訊。
本節介紹 containerd,它是現代容器技術棧中最為核心的基礎元件之一。瞭解 containerd 有助於更深入地理解 Docker 和 Kubernetes 的底層執行機制。
17.6.1 containerd 簡介
containerd 是一個行業標準的容器執行時,它最初是由 Docker 引擎中剝離出來的一個核心元件,後來 Docker 將其捐贈給了雲原生計算基金會(CNCF),目前已經是一個 CNCF 畢業(Graduated)專案。
它的主要職責是管理單個宿主機上完整的容器生命週期,包括:
- 映像檔的傳輸和儲存
- 容器執行和管理
- 儲存和網路介面的管理
簡單來說,當你在使用 Docker 或者 Kubernetes 時,真正去底層呼叫作業系統介面(如 Linux 的 Namespace 和 Cgroups)來啟動和管理容器程序的,往往是 containerd(及它所呼叫的 runc 元件)。
17.6.2 與 Docker 和 Kubernetes 的關係
版本說明:本章節涉及 containerd、Docker 和 Kubernetes 的多個版本。建議查閱官方文件瞭解最新的相容性資訊。
理解 containerd,首先要理清它與使用者日常操作的 Docker 以及 Kubernetes 的關係。
Docker 的架構
在早期,Docker 引擎是一個包含了所有功能的單體架構。隨著技術的發展和標準化要求,Docker 將底層關於容器執行時的部分解耦出來,形成了 containerd 和 runc。
當你執行一個 docker run 命令時,呼叫鏈路大致如下:
- Docker Client 傳送請求給 Docker Daemon(
dockerd)。 dockerd將請求轉發給containerd。containerd準備好映像檔和容器的必要環境,然後呼叫runc。runc負責按照 OCI(Open Container Initiative)標準,拉起並執行真正的容器程序。
因此,Docker 現在實際上是構建在 containerd 之上的一個包含更多開發者友好特性(如構建映像檔、Compose 管理等)的增強平臺。
Kubernetes 與 CRI
Kubernetes 作為一個容器編排系統,為了遮蔽底層不同容器執行時的實現差異,引入了 CRI(Container Runtime Interface)標準。
- 早期版本中,Kubernetes 預設使用 docker 作為執行時,透過一個名為
dockershim的橋接元件對接 Docker,Docker 再對接 containerd。 - 隨著 containerd 原生支援了 CRI 外掛,Kubernetes 開始直接與 containerd 通訊,去掉了
dockershim和dockerd的中間層。這就是為什麼從 Kubernetes 1.24+ 開始“棄用 Docker”引發了廣泛關注,實際上 Kubernetes 只是棄用dockershim,底層可以使用 containerd、CRI-O 等符合 CRI 的執行時。參見 Kubernetes 官方文件。 - containerd 2.0+ 移除了已棄用的 CRI v1alpha2 介面,僅保留 CRI v1(Kubernetes 1.26+ 僅支援 CRI v1)。如果叢集中仍有依賴 CRI v1alpha2 的元件,升級 containerd 2.x 前需先完成遷移。containerd 2.3+ 是 2.x 系列首個 LTS 版本,支援從 1.7 LTS 直接升級,生產環境推薦使用。詳見 containerd 釋出說明。
17.6.3 為什麼直接使用 containerd?
對普通應用開發者來說,Docker 依然是本地開發和測試的首選。但對於構建雲平臺、自動化流水線或深度管理 Kubernetes 叢集的系統工程師來說,直接使用 containerd 可以帶來:
- 更高的效能與更少的開銷:去掉了 Docker Daemon 等附加元件的資源佔用,鏈路更短。
- 更強的穩定性:作為專注於執行時的底層元件,它的核心功能極為穩定且更新受控。
- 直接符合 Kubernetes CRI 標準:在生產級 Kubernetes 叢集中作為標準配置。
17.6.4 基礎用法與工具介紹
不同於 docker 命令列工具,containerd 提供了不同的用戶端來滿足不同的使用情境:
- ctr:containerd 自帶的除錯用用戶端。它功能比較基礎,主要用於開發者在開發 containerd 時進行快速除錯,一般不作為終端使用者的日常管理工具。
- crictl:Kubernetes 提供的 CRI 命令列工具。它用於排查 Kubernetes 節點上的容器和沙箱(Pod)問題。
- nerdctl:這是一個由 containerd 專案維護者開發的,完全相容 Docker CLI 體驗的 containerd 命令列用戶端。對於習慣了
docker run/ps/build命令的使用者來說,nerdctl可以作為直接操作 containerd 的理想替代品,並且它還支援直接構建映像檔(依賴 BuildKit)。
nerdctl 使用示例
安裝完 containerd 和 nerdctl 後,你可以體驗到幾乎與 Docker 完全一致的命令列:
# 啟動一個 nginx 容器
$ nerdctl run -d -p 8080:80 --name my-nginx nginx:alpine
# 檢視執行中的容器
$ nerdctl ps
# 檢視本地映像檔
$ nerdctl images
對於那些希望在生產伺服器上剝離 Docker 龐大體積,但又想要保留類似 Docker 方便的命令列體驗的使用者,containerd + nerdctl 是一個極佳的組合。
17.7 安全容器執行時
版本說明:Kata Containers、gVisor 和 Firecracker 均為活躍開發的專案。建議查閱各專案官方文件獲取最新版本和相容性資訊:Kata Containers、gVisor、Firecracker。
本節介紹容器技術生態中的安全執行時機制,主要探討在隔離性和安全性上比標準 Linux 容器更進一步的方案,重點介紹 Kata Containers 和 gVisor。
17.7.1 為什麼需要安全容器?
標準的 Linux 容器(如 Docker、Podman 或基礎的 containerd/runc 所提供的)依賴於 Linux 核心的 Namespace(名稱空間) 和 Cgroups(控制組) 來實現程序級別的隔離與資源限制。這種輕量級的虛擬化方式共享同一個宿主機的核心。
儘管這種方式在效能和啟動速度上擁有巨大優勢,但也帶來了一個顯著的缺點:隔離性(Isolation)不足。如果某個容器內的惡意程序利用了宿主機核心的漏洞完成了越獄(Privilege Escalation),它將對整個宿主機以及其上執行的所有其他容器造成毀滅性威脅。
如果在公有云環境(多租戶情境)或執行不可信的第三方程式碼時,共享核心顯然是不夠安全的。為了解決這一問題,社群推出了“安全容器(Secure Containers/Sandboxed Containers)”的概念。安全容器的核心理念是:提供類似虛擬機器的強隔離性,同時保持類似容器的輕量、快速啟動和標準化管理。
17.7.2 什麼是 Kata Containers?
Kata Containers 是一個開源專案,由 OpenStack Foundation(現更名為 OpenInfra Foundation)託管。它將早期的兩個專案——Intel Clear Containers 和 Hyper runV 結合而成。
Kata Containers 的核心思路是:使用輕量級的虛擬機器(Lightweight VM)來執行每一個容器或者 Pod。
工作原理
當使用 Kata Containers 時,它不是在宿主機上啟動一個普通的獨立程序,而是呼叫一個精簡高度最佳化的虛擬機器管理程式(如 QEMU、Firecracker 或 Cloud Hypervisor)啟動一個小型的虛擬機器。容器內的應用程序執行在這個虛擬機器擁有獨立特製核心的沙箱中。
- 高度隔離:因為擁有自己的獨立核心,即使容器內的應用利用核心漏洞溢位,它也只能破壞虛擬機器虛擬出來的核心,根本無法觸及宿主機真正的核心。
- 相容性:Kata Containers 完全實現了 OCI(Open Container Initiative)規範和 CRI(Container Runtime Interface)標準。這意味著它可以作為
containerd或Docker的底層執行時無縫替換預設的runc。 - 與 Kubernetes 整合:在 Kubernetes 中,你可以為一個特定的 Pod 指定
runtimeClassName: kata,讓高敏感的任務自動執行在虛擬機器級別的隔離環境中。
17.7.3 什麼是 gVisor?
gVisor 是由 Google 開發並開源的一種不同流派的沙箱容器執行時方案。
它採取了與 Kata Containers 完全不同的技術路線,它不是啟動完整的虛擬機器,而是提供了一個 應用態核心(User-space Kernel)。
工作原理
gVisor 的核心元件是一個名為 Sentry 的使用者空間程序。Sentry 扮演了一個“核心代理”的角色。
- 當容器內的應用想要進行系統呼叫(System Call,比如讀寫檔案、網路通訊)時,這些呼叫會被 Sentry 攔截並進行一層虛擬化處理,然後再由 Sentry 把經過安全過濾和轉換的請求轉發給宿主機核心。
- 因為 Sentry 在使用者態實現了一套 Linux 系統呼叫介面,它極大減少了應用直接接觸底層作業系統核心的表面積。這樣就有效防禦了利用底層核心漏洞突破隔離的攻擊方式。
- gVisor 同樣相容 OCI 規範,其核心執行時元件稱為
runsc,可以作為底層執行時與 Docker 或 Kubernetes 進行整合。
17.7.4 總結與對比
| 特性 | 標準 Linux 容器 (runc) | Kata Containers | gVisor (runsc) |
|---|---|---|---|
| 隔離技術 | Namespace & Cgroups | 輕量級虛擬機器 (獨立核心) | 使用者態核心 (系統呼叫攔截) |
| 安全性 | 較低(共享宿主機核心) | 極高(硬體級虛擬化隔離) | 高(減少核心攻擊面) |
| 效能開銷 | 極小(原生程序) | 中等(因輕量化而優於傳統 VM) | 中等到較高(取決於系統呼叫頻率開銷) |
| 啟動速度 | 極快(毫秒/秒級) | 快(秒級之內) | 快(接近原生容器) |
| 相容性 | 完美(所有系統呼叫均支援原始實現) | 極好(擁有完整核心) | 好(但在極少數複雜的未被攔截支援的系統呼叫中可能報錯) |
如今,諸如 AWS 這樣的雲廠商也推出了針對無伺服器容器功能(Serverless Containers)的高度最佳化的輕量級虛擬機器管理器 Firecracker,可以看作是安全容器生態中與 Kata 類似方案的底層基石。
對於普通的微服務開發來說可能不需要考慮使用安全容器,但在提供多租戶平臺即服務(PaaS)、執行無狀態邊緣計算函式(FaaS)等對安全隔離要求極高的情境中,以 Kata Containers 和 gVisor 為代表的安全容器技術展現出了巨大的價值。
17.8 WebAssembly 與容器
版本說明:WebAssembly 和相關的 Wasm 執行時(如 WasmEdge、Spin)處於快速演進階段。建議查閱 WebAssembly 官方文件、WasmEdge 和 Spin 官方文件獲取最新資訊。
本節介紹 WebAssembly (簡寫為 Wasm) 以及它為何成為現代容器生態中備受矚目的前瞻技術路線。
17.8.1 什麼是 WebAssembly?
WebAssembly (Wasm) 最初是由 W3C 主導的一項為了解決網頁中 JavaScript 效能瓶頸而發明的技術標準。它是一種小體積的、載入極快的、提供安全沙盒的高效二進位制格式的指令集架構。透過將 C/C++、Rust、Go 等高階語言編譯成 .wasm 格式,這些程式可以直接在所有現代的瀏覽器中以接近原生程式碼的速度安全地執行。
然而,一項原本用於前端領域的技術,為何如今卻與容器雲端計算生態產生了強烈的化學反應?
因為開源社群很快意識到,Wasm 所具備的核心特性完美契合了雲原生後端的訴求。人們制定了諸如 WASI (WebAssembly System Interface) 這樣的標準,將其能力從瀏覽器擴充套件到了伺服器端作業系統上。
17.8.2 Wasm 與容器特性的完美契合
將 Wasm 應用於伺服器端時,它展現出了一些可能比傳統 Linux 容器更為優異的特性:
-
極速的冷啟動效能: 傳統 Linux 容器雖然比虛擬機器輕量很多,但它啟動依然需要建立 Namespace 和 Cgroups 以及一整套檔案系統,通常需要近百毫秒到幾秒。而 Wasm 模組不需要這樣龐雜的環境初始化,能夠在幾毫秒之內完成從載入到執行,這對於無伺服器函式(Serverless Functions)而言是巨大的提升。
-
跨平臺性 (Write Once, Run Anywhere): 我們知道 Docker 等容器通常是繫結架構的。如果是 x86_64 平臺上打出的映像檔,通常無法直接在基於 ARM 的系統(如蘋果 M 系列晶片甚至樹莓派)上直接執行原生程式碼,除非使用 QEMU 進行低效轉譯或者專門構建多架構(Multi-arch)映像檔和 manifests。 而 Wasm 二進位制本身是平臺無關的平臺中間語言程式碼形式!你編譯出來的一份
.wasm可執行模組,不用做任何修改,就可以在 x86 的 Linux 伺服器、ARM 的邊緣裝置、甚至是 Windows 和 macOS 上直接透過 Wasm 執行時來驅動和執行。真正做到了“編寫一次,到處執行”。 -
天然的安全沙箱機制: Wasm 設計之初就是在不被信任的瀏覽器沙盒環境中執行未知程式碼的,因此執行環境非常安全,採用了極好的能力導向安全模型。應用只能訪問它被明確授予許可權的檔案或能力。其預設安全隔離性比起依靠 Namespaces 機制的共享核心的 Linux 容器更加堅固。
-
極小的包體積: Linux 容器需要打包一整套依賴甚至是簡化的 OS 根目錄結構。而一個編譯好的功能完善的 Wasm 模組體積常常不到幾兆甚至僅僅幾十 Kb,極大地加快了儲存及網路利用效率。
17.8.3 當 Docker 遇上 WebAssembly
在現代的容器生態系統中,Wasm 並不被看作是要被取代傳統的 Docker 或者 Kubernetes 的技術,而是成為了一種 與 Linux 容器互補並且共生 的全新工作負載型別。
目前,這透過 OCI (Open Container Initiative) 和 CRI 標準實現了整合上的統一:
- 將 Wasm 打包為 OCI 映像檔:雖然內容並非傳統的 Linux RootFS,但是透過標準化的打包工具同樣可以將應用程式及其
.wasm構建結果轉化為一個可以被推送至 Docker Hub 或其他 registry 的標準化映像檔規範。 - 透過容器執行時直接執行:Docker 已經與如 WasmEdge 和 Spin 等高效能的企業級 Wasm 執行時進行了官方整合合作。
如今在 Docker Desktop 或者整合了 containerd 的環境中,我們可以十分簡易地以類似普通映像檔的形式去拉取並執行一個基於 Wasm 編譯的後端服務(透過指定相應的 --platform 或者是特別的 --runtime=io.containerd.wasmedge.v1 設定),將其如同對待一個標準應用程序一樣讓 Docker 為其接管日誌、配置相關的網路埠對映,甚至透過 Docker Compose 將一個普通的資料庫容器例項與一個 Wasm 微服務例項協同起來混布。
17.8.4 總結
隨著技術底座如 WASI 規範不斷的成熟完善(例如提供完備的套接字網路支援以及系統資源訪問支援),我們有理由相信不僅是邊緣計算與無伺服器呼叫,會有越來越多對於速度和安全性有極高指標要求的雲原生後端微服務開始採用這一顛覆傳統邊界的輕量級“微型智慧體”架構。在可見的將來,Wasm 勢必成為雲原生與 Docker 生態的重要拼圖。
本章小結
Docker 並非容器生態的唯一選擇,瞭解其他工具有助於根據情境做出合適的技術選型。
| 專案 | 定位 | 特點 |
|---|---|---|
| Fedora CoreOS | 容器化作業系統 | 自動更新、不可變基礎設施、專為執行容器設計 |
| Podman | 容器管理引擎 | 無守護程序、相容 Docker CLI、支援 Rootless 模式、支援原生 Pod |
| Buildah | 映像檔構建工具 | Daemonless 工作模式、靈活的指令碼化構建能力 |
| Skopeo | 映像檔倉庫管理 | 無需拉取即可檢查遠端映像檔、跨倉庫/格式無縫遷移映像檔 |
| containerd | 核心底層執行時 | 穩定高效、符合 CRI 規範、是 Docker 的基石之一 |
| 安全容器 | 強隔離沙箱執行 | 利用輕量級虛擬機器 (Kata) 或使用者態核心 (gVisor) 防止越獄,極其安全 |
| Wasm | 新型工作負載 | 體積極小、冷啟動超快且具備跨平臺及高度特徵化沙盒能力的後端架構新方向 |
Podman vs Docker
兩者的主要區別:
| 對比項 | Docker | Podman |
|---|---|---|
| 守護程序 | 需要 dockerd | 無需守護程序 |
| 許可權 | 預設需要 root | 原生支援 Rootless |
| CLI 相容 | - | 與 Docker 命令相容 |
| Pod 支援 | 不支援 | 原生支援 Pod 概念 |
| Compose | docker compose | podman-compose 或相容模式 |
延伸閱讀
第十八章 安全
第十八章 安全
容器安全是生產環境部署的核心考量。本章介紹 Docker 的安全機制和最佳實務。
容器安全的本質
核心問題:容器共享宿主機核心,隔離性弱於虛擬機器。如何在便利性和安全性之間取得平衡?
flowchart LR
subgraph VM ["虛擬機器安全模型:<br/>完全隔離(效能損耗)"]
direction TB
Guest["Guest OS"]
Hyper["Hypervisor<br/><-- 隔離邊界"]
Host["Host OS"]
Guest --> Hyper --> Host
end
subgraph Container ["容器安全模型:<br/>程序隔離(輕量但需加固)"]
direction TB
Proc["容器程序<br/>(共享核心)"]
Mech["Namespace <-- 隔離邊界<br/>Cgroups<br/>Capabilities"]
Proc --> Mech
end
本章內容
本章涵蓋 Docker 安全的多個層面,從核心隔離機制到執行時防護和供應鏈安全。
- 核心名稱空間
-
名稱空間的安全意義、User Namespace 與提權防護。
-
透過 Cgroups 限制容器資源使用,防止資源耗盡攻擊。
-
Docker 守護程序的安全配置與網路訪問控制。
-
Linux Capabilities 的細粒度許可權控制。
-
映像檔安全(漏洞掃描、簽名驗證)、執行時安全(非 root 執行、只讀檔案系統、Seccomp、AppArmor)、Dockerfile 安全實踐、軟體供應鏈安全(SBOM、SLSA)。
- 容器映像檔的安全掃描、漏洞檢測與簽名驗證。
安全掃描清單
部署前檢查:
| 檢查項 | 命令/方法 |
|---|---|
| 漏洞掃描 | docker scout cves 或 trivy |
| 非 root 執行 | 檢查 Dockerfile 中的 USER |
| 資源限制 | 檢查 -m, --cpus 引數 |
| 只讀檔案系統 | 檢查 --read-only |
| 無特權模式 | 確認沒有 --privileged |
| 最小能力 | 檢查 --cap-drop=all |
| 網路隔離 | 檢查網路配置 |
| 敏感資訊 | 確認無硬編碼密碼 |
18.1 核心名稱空間
名稱空間 (Namespace) 是 Linux 容器隔離的基礎,它確保了容器內的程序無法直接干擾主機或其他容器。雖然在本書第 12 章中我們已經從底層實現的角度介紹了 Namespace,但在本節中,我們將重點探討其 安全意義 及相關配置。
18.1.1 隔離的安全本質
Docker 守護程序在啟動容器時,會在後臺為容器建立一套獨立的名稱空間。名稱空間提供了最基礎也是最直接的隔離:
- PID Namespace:防止容器內的程序檢視或終止宿主機或其他容器的程序。惡意攻擊者即使在容器內獲得了 root 許可權,也無法透過
kill命令影響宿主機上的關鍵服務。 - NET Namespace:每個容器都有自己獨立的網路棧。如果沒有顯式地進行埠對映或將容器連線到同一網路,容器之間無法網路互通,從而限制了橫向移動的能力。
- MNT Namespace:為容器提供獨立的檔案系統檢視。這可以防止容器不經意或惡意地修改宿主機的重要系統檔案(如
/etc/passwd)。
18.1.2 名稱空間不是絕對安全的護城河
儘管名稱空間提供了很好的隔離性,但我們必須認識到:所有的容器依然共享同一個宿主機的 Linux 核心。
這意味著,一旦宿主機的核心存在提權漏洞(如著名的 Dirty COW 漏洞),攻擊者有可能透過突破 Namespace 的限制,直接在核心層面執行惡意程式碼,從而實現“容器逃逸”。
[!WARNING] 為了緩解核心漏洞帶來的威脅,生產環境務必保持宿主機 Linux 核心的及時修補與更新,或者藉助諸如 gVisor、Kata Containers 等提供了獨立核心的安全容器技術。同時,需要及時修補容器執行時(如 runC)的漏洞。2025 年 11 月披露的一系列 runC 容器逃逸漏洞(CVE-2025-31133、CVE-2025-52565、CVE-2025-52881)就表明,即使核心保持更新,執行時層的缺陷仍然可能導致容器隔離被突破。
透過名稱空間,Docker 也能限制程序從外部環境獲取資訊。 例如,由於程序環境被隔離,程序在內部其實是無法感知到外部宿主機的存在的。它既不能獲取其他容器的程序列表,也無法透過網路與其他系統進行互動(除非經過配置)。
18.1.3 使用者名稱空間與提權防護
在所有的 Namespace 中,User Namespace 對安全的影響尤為關鍵。
在預設情況下,容器內的 root 使用者(UID=0)就是宿主機上的 root 使用者。如果攻擊者設法突破了容器的其他隔離機制獲取了宿主機的訪問許可權,他將擁有宿主機的最高系統許可權。
透過啟用 User Namespace Remapping (使用者名稱空間對映),我們可以將容器內的 root 使用者對映到宿主機上的一個無特權普通使用者。
如何配置 User Namespace
要在 Docker 伺服器端啟用這一特性,需要修改 Docker 的配置檔案 /etc/docker/daemon.json。
- 設定對映策略
編輯配置檔案,新增 userns-remap 配置項:
{
"userns-remap": "default"
}
使用 default 值時,Docker 會自動在宿主機上建立一個名為 dockremap 的使用者和使用者組。
- 驗證子 UID 和子 GID 分配
Docker 會透過 /etc/subuid 和 /etc/subgid 檔案為 dockremap 分配一個高位的 UID 範圍:
$ cat /etc/subuid
dockremap:165536:65536
這意味著:容器內的 UID 0(root 使用者)在宿主機上實際被對映成了 UID 165536。如果是容器內的 UID 1,對應宿主機的 165537,以此類推。
- 重啟 Docker 守護程序
$ sudo systemctl restart docker
驗證對映效果
我們可以執行一個簡單的容器並執行 sleep 命令,同時在宿主機上觀察程序的所有者:
## 在容器內以 root 身份執行
$ docker run -d --name userns_test alpine sleep 3600
## 在宿主機上檢視該 sleep 程序
$ ps aux | grep sleep
165536 12345 0.0 0.0 1568 4 ? Ss 14:20 0:00 sleep 3600
你會發現,儘管在容器內該程序是由 root 啟動的,但在宿主機上,它的屬主是 165536(一個完全沒有特權的使用者)。
[!TIP] 啟用 User Namespace 會對容器共享宿主機資料卷(Bind Mount)產生許可權影響。你需要確保對映後的高位 UID 對宿主機上的掛載目錄具有合適的讀寫許可權。
18.1.4 總結
核心名稱空間從 Linux 2.6.15 版本 (2006 年) 被引入,十餘年間,這些機制的可靠性在諸多大型生產系統中被實踐驗證。透過合理利用名稱空間(尤其是 User Namespace),可以極大地收窄攻擊面,顯著提升容器部署的安全性。
18.2 控制組
控制組 (Cgroups) 是 Linux 容器機制的另外一個關鍵元件。如果說名稱空間 (Namespace) 決定了容器能 看到 什麼,那麼控制組就決定了容器能 使用 多少資源。
在安全領域中,資源的不可用性本身就是一種安全威脅。控制組負責實現資源的審計和限制,這對於抵禦資源耗盡型攻擊(如拒絕服務攻擊 DoS)至關重要。
18.2.1 為什麼資源限制關乎安全?
預設情況下,Docker 容器對系統資源的使用是沒有限制的:一個容器理論上可以使用宿主機所有的 CPU 計算能力、吃光所有的記憶體、耗盡所有的系統 PID。
想象一下以下情境:
- 一個惡意使用者向你暴露在公網的應用發起海量併發請求。
- 應用程式邏輯中存在記憶體洩漏漏洞。
- 駭客在入侵容器後,在裡面執行了挖礦木馬程式。
如果沒有 Cgroups 的限制,某個容器內的異常行為(或惡意攻擊)將會榨乾宿主機的資源,導致宿主機上其他健康的容器甚至 Docker 守護程序自身因為 OOM(Out Of Memory)崩潰或 CPU 飢餓而停止響應。
18.2.2 核心資源限制實戰
為了確保多租戶平臺(如公有或私有的 PaaS 平臺)的穩定性,或者在生產環境防止服務級聯故障,我們要養成在啟動容器時 顯式宣告資源上限 的習慣。
1. 記憶體限制
限制記憶體可以防止應用程式因記憶體洩漏或惡意載荷導致宿主機 OOM。
關鍵引數:
-m, --memory="":硬限制,容器可使用的最大記憶體量。--memory-swap="":限制容器可使用的記憶體與 Swap 總量。
實戰示例:
限制容器最多隻能使用 512MB 記憶體,並且禁用 Swap(將 memory 和 memory-swap 設定成一樣的值即可):
$ docker run -d \
--name web_app \
--memory="512m" \
--memory-swap="512m" \
nginx:alpine
如果該容器內的應用嘗試分配超過 512MB 的記憶體,該程序將會被核心的 OOM Killer 殺掉,但絕不會波及到宿主機的其他部分。
2. CPU 限制
限制 CPU 可以防止個別計算密集型的容器壟斷 CPU 時間片,保證系統的排程公平性。
關鍵引數:
--cpus=<value>:指定容器可以使用的 CPU 核心數量(可以是小數)。-c, --cpu-shares=0:軟限制,設定容器使用 CPU 的相對權重(預設是 1024)。
實戰示例:
限制容器最多使用 1.5 個 CPU 核心的算力:
$ docker run -d \
--name worker_app \
--cpus="1.5" \
busybox \
md5sum /dev/urandom
即使上面的命令是一個死迴圈的雜湊計算程序,容器也永遠無法吃滿雙核 CPU 系統的全部算力。
3. 程序數限制
程序炸彈(Fork Bomb)是一種典型的拒絕服務攻擊方式,它透過不斷 fork() 新程序來耗盡系統的程序表條目,導致系統無法建立任何新任務。
關鍵引數:
--pids-limit=<number>:限制容器內允許建立的最大程序數。
實戰示例:
一個常規的 Web 服務程序數通常在幾十到上百之間。我們可以設定一個合理的上限來防範 Fork 炸彈:
$ docker run -d \
--name app_service \
--pids-limit=100 \
python:alpine python app.py
當容器內的程序總數達到 100 時,任何嘗試派生新程序的操作都會失敗並返回 Resource temporarily unavailable,從而挫敗相關的攻擊行為。
18.2.3 最佳實務建議
在生產環境中,不僅要在單機使用 Docker 命令時設定這些引數,更應當在叢集編排工具中將資源配額制度化。
例如,在 Kubernetes 中,強烈建議為每個 Pod 設定 requests 和 limits:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
透過 Cgroups 的資源邊界控制,你可以從根本上切斷一條導致整個系統雪崩的脆弱鏈路。這也進一步使得 Docker 以及容器技術成為了現代高可用服務的基礎設施首選。
18.3 伺服器端防護
Docker 守護程序(dockerd)是容器生命週期的核心驅動力。預設情況下,Docker 服務的執行需要極高的系統特權(root 許可權),因此其安全性關係到整臺宿主機的生死存亡。
如果 Docker 守護程序的訪問控制沒有做好,惡意攻擊者可以透過 Docker API 輕易地啟動一個特權容器,並將宿主機的根目錄(/)掛載到容器中,從而完全接管伺服器。
為了加強對伺服器端的保護,我們需要從訪問控制、通訊加密和許可權最小化三個維度進行加固。
18.3.1 限制 API 訪問
Docker 用戶端(docker 命令)透過 REST API 與守護程序進行通訊。
在早期版本中,Docker 有時會繫結在 127.0.0.1 的 TCP 套接字上,但這容易遭遇跨站指令碼(跨協議)攻擊。現在的發行版預設使用 Unix Domain Socket(/var/run/docker.sock)並依賴檔案系統的許可權控制。
原則 1:決不可將無認證的 TCP 埠暴露在公網
這是最常見的 Docker 被入侵抓去挖礦的原因!絕不能在沒有任何安全控制的情況下強行開啟 -H tcp://0.0.0.0:2375。
如果業務確實需要遠端訪問 Docker 守護程序,必須啟用 TLS 認證機制,讓用戶端和伺服器端互相進行證書校驗。
開啟 TLS 認證雙向加密
利用安全機制,確保只有經過授權的主機網路,並在強證書保護下進行通訊:
- 首先使用
openssl或基於本地 CA 工具生成一套用戶端與伺服器的證書。 - 配置 Docker 守護程序(通常是
daemon.json或dockerd啟動引數),指定證書路徑:
dockerd \
--tlsverify \
--tlscacert=ca.pem \
--tlscert=server-cert.pem \
--tlskey=server-key.pem \
-H=0.0.0.0:2376
- 用戶端想要連線時,也必須出示用戶端證書。
[!TIP] 配置 TLS 生成證書的完整步驟可以查閱 Docker 官方 TLS 文件。在現代編排系統(如 Kubernetes)中,通常會有自動化方案管理這些憑據。
18.3.2 保護本地 Socket 訪問
哪怕不開啟網路埠,本地的 /var/run/docker.sock 也需要謹慎對待。
任何被授予該 Socket 讀寫許可權的使用者(通常被加入 docker 使用者組),等同於擁有了對宿主機的零成本提權途徑,即“無需密碼的免密 sudo 許可權”。
[!CAUTION] 永遠不要將不可信的普通使用者加入到
docker使用者組中。同樣,在容器編排時儘量避免將宿主機的/var/run/docker.sock直接對映給普通容器使用,這種模式被稱為 Docker-in-Docker (DinD) 或 Docker-out-of-Docker (DooD),存在極高的越權風險。
18.3.3 Rootless 模式:非特權執行
為了從根本上解決“擁有 Docker socket 就是 root”的問題,Docker 自 19.03 版本(2019 年)起提供了 Rootless 模式。
Rootless 模式允許在完全侷限於非 root 使用者的環境中執行 Docker 守護程序(dockerd)和容器。該模式利用了現代 Linux 核心的 User Namespace 技術和非特權網路名稱空間實現。
配置執行 Rootless Docker
要在非 root 環境中執行 Docker,需要先滿足宿主機條件:安裝 newuidmap / newgidmap,並在 /etc/subuid 與 /etc/subgid 中為該使用者分配足夠的 subordinate UID/GID。若系統級 Docker daemon 仍在執行,命令列也仍可能連到 rootful socket,因此應明確切換 Docker context 或 DOCKER_HOST。
- 安裝必要的依賴(通常是
uidmap工具包以便系統支援newuidmap和newgidmap):
$ sudo apt-get install uidmap
- 切換到一個沒有任何
sudo許可權的普通使用者(假設使用者名稱為testuser):
$ su - testuser
- 下載並檢查 Docker 官方提供的 Rootless 安裝指令碼,確認來源和內容後再執行:
$ curl -fsSL https://get.docker.com/rootless -o install-rootless-docker.sh
$ less install-rootless-docker.sh
$ sh install-rootless-docker.sh
- 配置環境變數指向新建立的私有 socket:
$ export DOCKER_HOST=unix:///run/user/1000/docker.sock
$ docker version
安裝並暴露相應的配置後,該使用者的環境將能獨立啟動屬於他自己的 Docker Daemon。即使由於某些未知 0-Day 漏洞使得攻擊者突破了容器,他們也只會受限於 testuser 這個非特權使用者所在的有限系統環境內。
[!NOTE] Rootless 模式不是無條件替代 rootful Docker。埠繫結、網路、cgroup、儲存驅動和系統服務自啟動能力都受發行版、核心與 systemd 使用者服務配置影響;生產環境應先驗證具體工作負載,並用
loginctl enable-linger <user>等方式顯式配置開機自啟動。
18.3.4 授權外掛(Authorization Plugin)與訪問策略
在企業環境中,對 Docker 守護程序的訪問控制往往不僅限於檔案系統許可權,還需要更細粒度的授權策略。Authorization Plugin 機制允許在 API 層級對請求進行攔截和審批。
常見的授權外掛包括:
- OPA/Conftest:開放策略引擎,支援宣告式策略定義。
- Prisma Cloud(Twistlock):商業容器安全平臺。
- 自定義指令碼:根據請求內容(映像檔、命令、使用者等)做出允許/拒絕決定。
配置 Authorization Plugin
在 daemon.json 中指定授權外掛:
{
"authorization-plugins": ["myauthorizer"]
}
此後,Docker 守護程序會在執行任何 API 請求前,將請求轉發給授權外掛進行審批。
[!CAUTION] 重要的安全事項:在配置 Authorization Plugin 時,務必確保外掛本身的可靠性和及時更新。歷史上已發現多個 AuthZ 驗證繞過漏洞,可能導致攻擊者繞過授權檢查並獲得宿主機訪問許可權。建議: - 定期審計授權外掛的日誌,檢查是否有可疑的請求被錯誤允許。 - 使用來自可信來源的授權外掛,並保持其版本最新。 - 將授權檢查結果與其他安全措施(如 TLS 認證、Rootless 模式)結合使用,構建縱深防禦。
18.3.5 結語
保障 Docker 伺服器端的安全主要是做減法:關閉不必要的網路監聽點,嚴管 Socket 訪問許可權。在基礎系統、網路和儲存約束都驗證透過後,Rootless 模式是一項值得優先評估的安全加固選擇。
18.4 核心能力機制
傳統 Linux 的許可權模型非常粗放:程序分為“特權程序”(以 root 使用者 UID 0 執行)和“非特權程序”(其他 UID 執行)。這帶來了一個致命問題——只要一個後臺服務需要一個微小的特權(例如繫結低於 1024 的埠),就必須被賦予所有的 root 許可權。一旦該服務被攻陷,系統便會全面淪陷。
為了解決這一問題,Linux 引入了 能力機制(Capabilities)。它將傳統的全能 root 許可權劃分為幾十個細粒度的操作能力。
18.4.1 容器內建的 Capability 白名單
在預設情況下,即便一個容器是在以 root 使用者執行,Docker 也只為其核心授予了所有可用能力中的 一小部分“白名單”能力。
常見的 Linux Capabilities 包含:
CAP_CHOWN: 修改檔案所有者。CAP_NET_BIND_SERVICE: 繫結特權埠(即 1024 以下的埠)。CAP_NET_ADMIN: 網路管理的最高許可權(例如調整路由配置,設定防火牆規則等)。CAP_SYS_ADMIN: 被譽為“Linux 核心的特權網管”,允許各種高危操作(掛載磁碟、訪問敏感裝置等)。
為了在 “最小特權原則” 的指導下加強安全,Docker 預設 移除了 大量可能導致容器大範圍破壞宿主機的能力,例如:
- 完全禁止了任何透過
CAP_SYS_ADMIN進行的核心掛載或裝置操作。 - 禁止修改核心模組。
- 禁止直接訪問硬體套接字。
這種“非完整”的 root 使用者能保證大部分應用在擁有其所需許可權的同時,把惡意行為對系統的影響降到最低。
18.4.2 實戰:新增與剝奪能力
當啟動一個 Docker 容器時,我們可以利用 --cap-add(增加特權)和 --cap-drop(剝奪特權)兩個引數精細地控制程序環境。
實戰情境一:構建極限安全的 Web 靶機
假設你正在提供一個公共的 Web 容器。你不希望裡面的任何惡意指令碼修改程序許可權或者建立裝置節點,你可以透過命令先移除 所有 預設能力,然後再按需授權該守護程序一個僅僅能綁埠的能力。
$ docker run -d \
--name max_secure_web \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
nginx:alpine
這裡的 --cap-drop ALL 是實現“特權最小化”的最強殺手鐧。此時,即便某駭客利用 0-Day 手段拿到了 Web 服務的容器 root Shell,當他試圖改變任何不屬於他自己的程序配置或者所有權時,系統都會報錯拒絕訪問。
實戰情境二:需要捕獲網路資料包的網路實驗
假設容器內的主程式是一個網路嗅探器(如 tshark 或 tcpdump),這顯然不在 Docker 提供的預設白名單之內,因為該程式試圖直接操縱底層網絡卡流量,會觸發 Permission Denied。
此時,我們需要給它適當補發缺失的部分核心管理能力:
$ docker run -it --rm \
--name network_sniffer \
--cap-add NET_ADMIN \
--cap-add NET_RAW \
tshark-image /bin/bash
我們只授予了所需的網路管理控制(NET_ADMIN)和偵聽底層套接字的許可權(NET_RAW),而免去了賦予整個容器終極殺器 --privileged 引數。
[!WARNING] 大量開發人員遇到了“許可權遭到拒絕”的錯誤時,往往習慣性圖省事新增
--privileged這個核選項。但這將把 宿主機上一切特權和所有訪問裝置完全投射給容器內的根使用者,其危險性等價於根本沒有做隔離!請務必查明程序出錯的實際原因,精準施加必要的隔離CAP_*能力。
18.4.3 總結
利用能力機制(Capabilities)是進行精細化系統級訪問控制的關鍵一環。遵循“白名單剝奪一切不必要權利(--cap-drop ALL)”的極端配置並不過分,這將使得即便程式本身漏洞百出,攻擊面也被死死壓縮在一個幾乎毫無後續伸展潛力的受限維度中。
18.5 其它安全特性
除了上述的名稱空間、控制組以及能力機制,Linux 核心與雲原生生態還提供了大量安全增強功能,它們共同築成了一道防禦縱深的“馬奇諾防線”。本節主要介紹強制訪問控制、系統呼叫攔截以及自動化的容器漏洞掃描技術。
18.5.1 系統呼叫過濾
Seccomp(Secure Computing mode)是 Linux 核心的一個安全機制,用於限制程序能夠發起的系統呼叫數量。
一個普通的 Linux 核心提供了 300 多個系統呼叫,而一個正常執行的容器化應用(例如 Nginx 服務)通常只會用到幾十個呼叫,這就給攻擊者留下了大量的閒置入口點來進行核心層的緩衝區溢位攻擊。
Docker 預設啟用了 Seccomp,並使用預置的 預設配置檔案 作為 allowlist:預設拒絕未顯式允許的系統呼叫,並額外允許常見應用所需的呼叫。Docker 官方文件將其描述為預設禁用約 44 個系統呼叫(核心與 Docker 版本不同會有差異),例如與核心模組、系統重啟或特權名稱空間操作相關的呼叫。
如果你對應用的系統呼叫特徵瞭如指掌,你可以為容器定製專屬規則。
實戰:禁用 chmod 系統呼叫過濾
首先,編寫一個 no-chmod.json 的策略檔案:
{
"defaultAction": "SCMP_ACT_ALLOW",
"syscalls": [
{
"name": "chmod",
"action": "SCMP_ACT_ERRNO"
}
]
}
在啟動時告訴 Docker 載入這套過濾配置:
$ docker run --rm -it \
--security-opt seccomp=no-chmod.json \
alpine sh
/ # chmod 777 /etc/passwd
chmod: /etc/passwd: Operation not permitted
應用只要被劫持進行越界嘗試,其作業系統層命令便會立刻吃癟。
18.5.2 強制訪問控制:AppArmor / SELinux
傳統的 Linux 模型遵循 DAC(自主訪問控制),這意味著如果一個檔案被賦予了全員讀寫許可權(777),普通隔離下任何人便都能修改。但 MAC(強制訪問控制) 技術,諸如 AppArmor (常用於 Ubuntu/Debian) 或 SELinux (常用於 CentOS/RHEL),可以制定比“檔案所有權”更宏觀且優先的策略控制模組。
在開啟了上述機制的機器上:
- AppArmor: 在啟用 AppArmor 的系統上,Docker 預設為容器載入
docker-defaultprofile;如果需要自定義策略,先用apparmor_parser載入 profile,再透過--security-opt apparmor=<profile>指定。 - SELinux: 在啟用 SELinux 整合的系統上,容器與掛載目錄需要正確的 SELinux label。繫結掛載時常用
:z表示多個容器共享,:Z表示該掛載只給單個容器使用;不要對/home、/usr等系統目錄隨意使用:Z,否則可能破壞宿主機標籤。
如果想為某些受信任應用施加特定的外部強化檔案策略,可以透過如下方法指派規則表:
$ docker run --rm -it \
--security-opt apparmor=custom-nginx-profile \
nginx
$ docker run --rm -it \
-v "$PWD/html":/usr/share/nginx/html:Z \
nginx
18.5.3 容器映像檔漏洞靜態掃描
現代防護的防禦已經不僅僅在執行階段,而向“左”延伸至了構建與分發時期控制。很多安全隱患並不是使用者程式碼中的直接邏輯異常,而是打包環境或者引入庫的基礎 APT 安裝層面潛伏了開源界眾所周知的歷史漏洞。
使用 Trivy 識別風險
Trivy 是由 Aqua Security 發行的一款針對容器技術的快速映像檔漏洞掃描利器。在分發應用前透過它的掃描可以規避絕大多數風險。
## 如果使用本地命令列掃描容器映像檔
$ trivy image alpine:3.20
2024-03-01T10:05:07.124Z INFO Number of language-specific files: 1
2024-03-01T10:05:07.124Z INFO Detecting vulnerabilities...
alpine:3.20 (alpine 3.20.0)
===========================
Total: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 1, CRITICAL: 0)
+---------+------------------+----------+-------------------+---------------+---------------------------------------+
| LIBRARY | VULNERABILITY ID | SEVERITY | INSTALLED VERSION | FIXED VERSION | TITLE |
+---------+------------------+----------+-------------------+---------------+---------------------------------------+
| busybox | CVE-2022-28391 | HIGH | 1.30.1-r3 | 1.30.1-r4 | busybox: out-of-bounds read in... |
...
只要確保所有上傳給私有或公共倉庫分發服務的產物先被引入至 CI/CD 流水線,如果出現 HIGH 及 CRITICAL 嚴重的報錯記錄強行阻攔部署,這本身便是構建環節極其有力的自動化安全大門保障。除 Trivy 外,最新的 Docker 版本也已內建支援官方掃描利刃 Docker Scout。
18.5.4 容器核心層基石結語
到這裡,Docker 為保障宿主和容器界限安全的幾個護城河:從 資源剝離限制(Cgroups) 到 程序/網路/身份矇蔽(Namespace)、到 特權能力回收(Capabilities) 再到 核心強制策略攔截管制(Seccomp/AppArmor) 已悉數交代完畢。雖然絕沒有“100% 免疫網路穿刺的防線”,只要開發者牢記 許可權最小化原則 ,容器的堡壘就可以做到令攻擊者望洋興嘆。
18.6 容器映像檔安全掃描與供應鏈安全
在 DevOps 流程中,容器映像檔安全已經成為不容忽視的關鍵環節。從開發、構建、儲存到部署,映像檔的整個生命週期都需要安全防護。本節深入討論映像檔漏洞掃描、軟體物料清單(SBOM)、映像檔簽名驗證等供應鏈安全實踐。
18.6.1 容器映像檔漏洞掃描工具對比
Trivy - 輕量級通用掃描器
Trivy 是由 Aqua Security 開發的開源漏洞掃描器,以其輕量級、快速、準確而聞名,已成為業界標準。
優點:
- 零依賴,單個二進位制檔案
- 掃描速度快(秒級)
- 支援映像檔、檔案系統、Git 倉庫多種掃描源
- 資料庫每日自動更新
- 支援多種輸出格式(JSON、表格、SBOM 等)
安裝與基本使用:
# 安裝 Trivy
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh -o install-trivy.sh
less install-trivy.sh # 先審閱指令碼內容
sudo sh install-trivy.sh -b /usr/local/bin
# 掃描本地映像檔
trivy image nginx:latest
# 生成 JSON 格式報告
trivy image -f json -o report.json nginx:latest
# 掃描檔案系統
trivy fs /path/to/project
# 掃描 Git 倉庫
trivy repo https://github.com/aquasecurity/trivy
在 CI/CD 中整合:
# 設定嚴重程度過濾
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
myregistry.com/myapp:v1.0.0
Grype - 支援多種軟體包的掃描器
Grype 由 Anchore 開發,支援更廣泛的軟體包管理器和語言。
優點:
- 支援 Java、Python、Go、Ruby、JavaScript 等多種語言的依賴檢測
- 與 Syft(SBOM 生成器)配合效果好
- 可自定義漏洞資料庫源
- 支援離線掃描模式
安裝與使用:
# 安裝 Grype
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh -o install-grype.sh
less install-grype.sh
sudo sh install-grype.sh -b /usr/local/bin
# 掃描映像檔
grype docker:nginx:latest
# 與 Syft 配合生成 SBOM
syft docker:nginx:latest -o json > sbom.json
grype sbom:sbom.json
# 掃描特定目錄
grype dir:/path/to/app
Snyk - 完整的安全平臺
Snyk 提供了商業級的安全掃描服務,特別適合企業環境。
特點:
- 支援開源漏洞、許可證掃描和修復建議
- 與多個 Git 平臺深度整合(GitHub、GitLab、Bitbucket)
- 提供修復建議和自動化修復 PR
- 支援 Kubernetes 部署後安全監控
基本使用:
# 安裝 Snyk CLI
npm install -g snyk
# 認證
snyk auth
# 掃描映像檔
snyk container test docker-archive://image.tar
# 監控倉庫
snyk monitor --docker
此外,Docker 官方提供的 Docker Scout(docker scout CLI)可以直接在 Docker Desktop 或命令列中對映像檔進行漏洞掃描和依賴分析,適合日常開發流程中快速檢查:
# 掃描本地映像檔
$ docker scout cves myapp:latest
# 檢視映像檔的依賴和漏洞摘要
$ docker scout quickview myapp:latest
工具對比表:
| 特性 | Docker Scout | Trivy | Grype | Snyk |
|---|---|---|---|---|
| Docker 整合 | ✓(原生) | 需安裝 | 需安裝 | 需安裝 |
| 零依賴 | ✗ | ✓ | ✗ | ✗ |
| 離線模式 | ✗ | ✓ | ✓ | ✗ |
| 許可證掃描 | ✓ | ✓ | ✓ | ✓ |
| 自動修復建議 | ✓ | ✗ | ✗ | ✓ |
| 開源免費 | 部分 | ✓ | ✓ | 部分 |
| IDE 整合 | ✓ | ✓ | ✓ | ✓ |
18.6.2 SBOM(軟體物料清單)生成與管理
SBOM(Software Bill of Materials)是一份詳細列表,記錄了軟體中使用的所有元件、依賴庫及其版本資訊。SBOM 在供應鏈安全中至關重要,特別是在發現新的安全漏洞時,能快速定位受影響的應用。
Syft - SBOM 生成工具
Syft 是 Anchore 推出的專業 SBOM 生成工具。
安裝:
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh -o install-syft.sh
less install-syft.sh
sudo sh install-syft.sh -b /usr/local/bin
生成 SBOM:
# 從映像檔生成 SBOM(多種格式)
syft docker:nginx:latest -o json > sbom.json
syft docker:nginx:latest -o spdx > sbom.spdx
syft docker:nginx:latest -o cyclonedx > sbom.xml
# 從本地檔案系統生成
syft dir:/path/to/app -o json > sbom.json
# 從 OCI 映像檔案生成
syft oci-archive:image.tar -o json > sbom.json
CycloneDX 與 SPDX 格式
兩種主流的 SBOM 格式:
CycloneDX 格式示例:
<?xml version="1.0" encoding="UTF-8"?>
<bom xmlns="http://cyclonedx.org/schema/bom/1.4" version="1">
<components>
<component type="library">
<name>openssl</name>
<version>1.1.1k</version>
<purl>pkg:deb/debian/[email protected]+deb11u5</purl>
</component>
<component type="library">
<name>curl</name>
<version>7.74.0-1.3+deb11u1</version>
<purl>pkg:deb/debian/[email protected]+deb11u1</purl>
</component>
</components>
</bom>
SPDX 格式示例:
{
"SPDXID": "SPDXRef-DOCUMENT",
"spdxVersion": "SPDX-2.2",
"creationInfo": {
"created": "2024-03-01T12:00:00Z",
"creators": ["Tool: syft"]
},
"packages": [
{
"SPDXID": "SPDXRef-Package-openssl",
"name": "openssl",
"versionInfo": "1.1.1k",
"downloadLocation": "NOASSERTION"
}
]
}
SBOM 的應用情境
漏洞關聯:
當新的 CVE 被發現時,可快速查詢受影響的應用:
# 使用 Grype 針對 SBOM 進行漏洞掃描
grype sbom:sbom.json --add-cpes-if-none
合規性報告:
將 SBOM 儲存為構建產物,用於審計和合規性檢查。
依賴升級決策:
透過分析 SBOM 中的依賴版本,制定安全升級計劃。
18.6.3 映像檔簽名與驗證
映像檔簽名確保映像檔的來源可信且未被篡改。兩種主流方案是 Cosign 和 Notary。
Cosign - 現代簽名解決方案
Cosign 是 Sigstore 專案的核心工具,支援無金鑰簽名,適合現代 CI/CD 流程。
安裝:
wget https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
chmod +x cosign-linux-amd64
sudo mv cosign-linux-amd64 /usr/local/bin/cosign
生成金鑰對(傳統方式):
cosign generate-key-pair
# 生成 cosign.key 和 cosign.pub
簽名映像檔:
# 先推送映像檔,再使用不可變 digest 簽名
IMAGE_DIGEST="myregistry.com/myapp@sha256:<digest>"
cosign sign --key cosign.key "$IMAGE_DIGEST"
# 系統會提示輸入私鑰密碼
驗證簽名:
# 使用公鑰驗證
cosign verify --key cosign.pub "$IMAGE_DIGEST"
# 輸出結果示例
# Verification successful!
# {
# "critical": {
# "identity": {...},
# "image": {...},
# "type": "cosign container image signature"
# },
# "optional": {...}
# }
Keyless 簽名(推薦用於 CI/CD):
# 在 GitHub Actions 等 CI 中無需儲存金鑰
cosign sign --yes "$IMAGE_DIGEST"
# 驗證時自動使用 OIDC 令牌驗證身份
cosign verify "$IMAGE_DIGEST" \
--certificate-identity https://github.com/myorg/myrepo/.github/workflows/build.yml@refs/heads/main \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Docker Content Trust 與 Notary
注意:DCT 退役時間線
Docker 已宣佈退役 Content Trust,Docker Official Images 的部分 DCT 簽名證書自 2025 年 8 月起已陸續過期;完整退役節奏應以 Docker 官方公告和你所使用的 registry 服務商文件為準。不要把雲廠商特定日期直接當作 Docker 全域性時間線。
新專案應優先使用上文介紹的 Cosign (Sigstore) 或 registry 原生簽名/證明能力;現有 DCT 使用者應先盤點依賴、驗證替代方案,再製定遷移計劃。
Docker Content Trust 使用 Notary 實現映像檔簽名,是 Docker 官方的傳統簽名解決方案。
歷史用法示例(不建議新專案採用):
# 在環境中啟用 DCT
export DOCKER_CONTENT_TRUST=1
# 此後所有 docker push/pull 都需要簽名
docker push myregistry.com/myapp:v1.0.0
# 如果映像檔未簽名,操作會被拒絕
# 禁用 DCT(僅用於特定操作)
docker push --disable-content-trust myregistry.com/myapp:v1.0.0
簽名金鑰管理:
# 首次推送時會提示建立 Delegation Key
# 金鑰儲存在 ~/.docker/trust/private/root_keys/ 和 ~/.docker/trust/private/tuf_keys/
# 檢視 DCT/Notary 信任資料;RepoDigests 只是內容摘要,不等於簽名驗證
docker trust inspect --pretty myregistry.com/myapp:v1.0.0
18.6.4 供應鏈安全最佳實務
1. 基礎映像檔安全
# ❌ 不推薦:使用 latest 標籤
FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl
# ✓ 推薦:固定基礎映像檔版本和摘要
FROM ubuntu:22.04@sha256:a6d2b38300ce017add71440577d5b0a90460d0e6e0e14...(完整 64 位雜湊)
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
2. 構建時掃描
在 CI/CD 構建階段整合安全掃描,避免在 Dockerfile 裡從分支 URL 下載並執行遠端安裝指令碼:
docker run --rm \
-v "$PWD:/work" \
-w /work \
aquasec/trivy:latest \
fs . --exit-code 1 --severity HIGH,CRITICAL
3. 執行時映像檔掃描策略
# 映像檔構建完成後立即掃描
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
--timeout 30m \
$IMAGE_NAME:$IMAGE_TAG
# 定期掃描已部署的映像檔
trivy image --scanners vuln,misconfig registry:5000/myapp:latest
4. 映像檔倉庫安全配置
Harbor(私有映像檔倉庫)的安全掃描:
# harbor.yml 配置示例
trivy:
enabled: true
# 啟用映像檔掃描
image_source: "Official"
# 預設掃描配置
scan_on_push: true # 推送時自動掃描
scan_all: true # 掃描倉庫中的所有映像檔
5. 政策執行
在 Kubernetes 環境中使用 Admission Webhook 強制映像檔簽名和掃描:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: image-security-policy
webhooks:
- name: image-security.example.com
clientConfig:
service:
name: image-security-webhook
namespace: security
path: "/validate"
rules:
- operations: ["CREATE", "UPDATE"]
apiGroups: [""]
apiVersions: ["v1"]
resources: ["pods"]
admissionReviewVersions: ["v1"]
sideEffects: None
18.6.5 CI/CD 中整合安全掃描
GitHub Actions 工作流示例
name: Build and Scan Image
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-scan:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
security-events: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Install Cosign
if: github.event_name == 'push'
uses: sigstore/cosign-installer@v3
- name: Build Docker image for scan
uses: docker/build-push-action@v7
with:
context: .
push: false
load: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Run Trivy vulnerability scan
# 安全提醒:2026 年 3 月 19 日 Trivy GitHub Actions 遭受供應鏈攻擊,
# 76 個版本標籤被劫持。務必使用不可變的 commit SHA 引用,而非可變標籤。
# 使用前請到 https://github.com/aquasecurity/trivy-action/releases 核實 SHA 對應正確版本。
uses: aquasecurity/trivy-action@57a97c7e7821a5776cebc9bb87c984fa69cba8f1 # v0.35.0
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'HIGH,CRITICAL'
- name: Upload Trivy results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'trivy-results.sarif'
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
format: cyclonedx-json
output-file: sbom-cyclonedx.json
- name: Upload SBOM
uses: actions/upload-artifact@v4
with:
name: sbom
path: sbom-cyclonedx.json
- name: Login to Registry and Push
if: github.event_name == 'push'
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Push image
if: github.event_name == 'push'
id: build-push
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Sign image with Cosign
if: github.event_name == 'push'
run: |
cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build-push.outputs.digest }}
GitLab CI 工作流示例
stages:
- build
- scan
- push
- sign
variables:
REGISTRY: registry.gitlab.com
IMAGE_NAME: $REGISTRY/$CI_PROJECT_PATH
build:
stage: build
image: docker:<version>-cli@sha256:<docker-cli-digest>
services:
- name: docker:<version>-dind@sha256:<docker-dind-digest>
script:
- docker build -t $IMAGE_NAME:$CI_COMMIT_SHA .
- docker save $IMAGE_NAME:$CI_COMMIT_SHA > image.tar
scan:trivy:
stage: scan
image: aquasec/trivy:<version>@sha256:<trivy-digest>
script:
- trivy image --severity HIGH,CRITICAL --exit-code 1 docker-archive://image.tar
allow_failure: false
scan:grype:
stage: scan
image: anchore/grype:<version>@sha256:<grype-digest>
script:
- grype docker-archive://image.tar
generate:sbom:
stage: scan
image: anchore/syft:<version>@sha256:<syft-digest>
script:
- syft docker-archive://image.tar -o cyclonedx > sbom.xml
artifacts:
reports:
sbom: sbom.xml
push:
stage: push
image: docker:<version>-cli@sha256:<docker-cli-digest>
services:
- name: docker:<version>-dind@sha256:<docker-dind-digest>
script:
- docker load < image.tar
- echo "$REGISTRY_PASSWORD" | docker login --username "$REGISTRY_USER" --password-stdin "$REGISTRY"
- docker push $IMAGE_NAME:$CI_COMMIT_SHA
- DIGEST=$(docker buildx imagetools inspect "$IMAGE_NAME:$CI_COMMIT_SHA" --format '{{.Manifest.Digest}}')
- echo "$IMAGE_NAME@$DIGEST" > image-digest.txt
artifacts:
paths:
- image-digest.txt
only:
- main
sign:
stage: sign
image: gcr.io/projectsigstore/cosign:<version>@sha256:<cosign-digest>
script:
- cosign sign --key $COSIGN_KEY "$(cat image-digest.txt)"
only:
- main
18.6.6 常見問題與最佳實務
Q: 掃描報告中有過時的 CVE,如何處理?
A: 某些 CVE 可能已經被修復但資料庫未更新,可以:
- 手動驗證安全補丁是否已應用
- 使用工具的忽略列表功能(如 Trivy 的
.trivyignore) - 定期更新掃描工具和漏洞資料庫
Q: 如何平衡映像檔大小和安全性?
A:
- 使用多階段構建減少最終映像檔大小
- 使用精簡基礎映像檔(Alpine、Distroless)
- 定期更新依賴而不是一味求小
- 優先安全性,體積次之
Q: 如何管理和輪換籤名金鑰?
A:
- 在金鑰管理系統(如 HashiCorp Vault)中儲存金鑰
- 定期輪換金鑰(建議每 90 天)
- 使用 Keyless 簽名消除金鑰管理複雜性
- 保留金鑰輪換的審計日誌
本章小結
Docker 的安全性依賴於多層隔離機制的協同工作,同時需要使用者遵循最佳實務。本章涵蓋的核心安全維度包括:
| 維度 | 關鍵措施 |
|---|---|
| 核心隔離 | Namespace 隔離程序/網路/檔案系統,Cgroups 限制資源使用 |
| 許可權控制 | 非 root 執行、--cap-drop ALL 最小能力集、--read-only 只讀根檔案系統 |
| 映像檔安全 | 使用可信基礎映像檔、定期掃描漏洞(Trivy / Snyk)、啟用 Docker Content Trust 簽名驗證 |
| 執行時防護 | Seccomp 系統呼叫過濾、AppArmor / SELinux 強制訪問控制 |
| 網路隔離 | 自定義 bridge 網路隔離容器通訊、限制容器對宿主機網路的訪問 |
總體來看,Docker 容器還是十分安全的,特別是在容器內不使用 root 許可權來執行程序的話。
另外,使用者可以使用現有工具,比如 Apparmor,Seccomp,SELinux,GRSEC 來增強安全性;甚至自己在核心中實現更復雜的安全機制。
第十九章 容器監控與日誌
第十九章 容器監控與日誌
在生產環境中,容器化應用部署完成後,實時掌握容器的執行狀態以及應用日誌非常重要。本章將以 Docker/Compose 的情境為主,介紹容器監控與日誌管理的落地思路與最小實踐閉環。
對於 Kubernetes 情境,可觀測性鏈路與元件選擇通常會有所不同 (例如使用 Prometheus Operator、日誌採集 DaemonSet 等)。本章會在關鍵點給出遷移提示,但不會展開為完整的 Kubernetes 教程。
我們將重點探討以下內容:
- 容器監控:以 Prometheus 為主,講解如何採集和展示容器效能指標。
- 日誌管理:以 ELK (Elasticsearch, Logstash, Kibana) 套件為例,介紹集中式日誌收集平臺。
為了讓讀者能夠在生產環境中真正用起來,本章會補齊以下“最小閉環”:
- 關鍵指標與日誌的驗證方法
- 常見故障排查路徑
- 最小告警閉環 (Prometheus -> Alertmanager -> 接收端)
- 日誌容量治理的最小實踐
本章內容
- Prometheus 監控
-
容器監控基礎、指標採集與告警配置。
-
集中式日誌收集、儲存與檢索。
- 容器和應用效能最佳化實踐。
19.1 Prometheus
Prometheus 和 Grafana 是目前最流行的開源監控組合,前者負責資料採集與儲存,後者負責資料視覺化。
Prometheus 是一個開源的系統監控和報警工具包。它受 Google Borgmon 的啟發,由 SoundCloud 在 2012 年建立。
19.1.1 架構簡介
Prometheus 的主要元件包括:
- Prometheus Server:核心元件,負責收集和儲存時間序列資料。
- Exporters:負責向 Prometheus 暴露監控資料 (如 Node Exporter,cAdvisor)。
- Alertmanager:處理報警傳送。
- Pushgateway:用於支援短生命週期的 Job 推送資料。
19.1.2 快速部署
我們可以使用 Docker Compose 快速部署一套 Prometheus + Grafana 監控環境。
本節示例使用了:
node-exporter:採集宿主機指標 (CPU、記憶體、磁碟、網路等)。cAdvisor:採集容器指標 (容器 CPU/記憶體/網路 IO、檔案系統等)。
在生產環境中,建議將 Prometheus 的資料目錄做持久化,並顯式配置資料保留週期。
1. 準備配置檔案
建立 prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
rule_files:
- /etc/prometheus/rules.yml
2. 編寫 Docker Compose 檔案
建立 compose.yaml (或 docker-compose.yml)。下面示例使用的是相對較新的穩定版本。映像檔版本提示:本示例中 Prometheus、Grafana、node-exporter、cAdvisor 的版本號僅為參考。生產環境部署前,請查閱以下官方資源確認最新推薦版本:
- Prometheus 官方文件
- Grafana 官方釋出
- node-exporter 釋出頁
- cAdvisor 釋出頁
services:
prometheus:
image: prom/prometheus:v3.11.2
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./rules.yml:/etc/prometheus/rules.yml
- prometheus_data:/prometheus
ports:
- "9090:9090"
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=15d
networks:
- monitoring
grafana:
image: grafana/grafana:13.0.1
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
networks:
- monitoring
depends_on:
- prometheus
node-exporter:
image: prom/node-exporter:v1.11.1
ports:
- "9100:9100"
networks:
- monitoring
cadvisor:
image: ghcr.io/google/cadvisor:v0.56.2
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
networks:
- monitoring
networks:
monitoring:
volumes:
prometheus_data:
3. 啟動服務
$ docker compose up -d
啟動後,訪問以下地址:
- Prometheus:
http://localhost:9090 - Grafana:
http://localhost:3000(預設賬號密碼:admin/admin,首次登入後務必立即修改密碼)
19.1.3 配置 Grafana 面板
- 在 Grafana 中新增 Prometheus 資料來源,URL 填寫
http://prometheus:9090。 - 匯入現成的 Dashboard 模板,例如 Node Exporter Full (ID:1860) 和 Docker Container (ID:193)。
這樣,你就擁有了一個直觀的容器監控大屏。
19.1.4 生產要點與告警閉環
完成部署後,建議補齊以下生產要點。
指標採集的“最小閉環”
- 在 Prometheus 頁面開啟 Status -> Targets,確認
prometheus、node-exporter、cadvisor的State均為UP。 - 在 Graph 中嘗試查詢:
uprate(container_cpu_usage_seconds_total[5m])
- 在 Grafana Dashboard 中重點關注:
- 宿主機 CPU/Load/記憶體/磁碟
- 容器 CPU/記憶體使用率、容器重啟次數
如果你發現“面板為空”,通常不是 Grafana 的問題,而是 Prometheus 沒抓到資料或查詢標籤與 Dashboard 不匹配。
常見問題排查
- Target down:檢查容器網路是否互通,埠是否暴露到同一網路,以及 exporter 是否在容器內正常監聽。
- cAdvisor 無資料或報錯:確認掛載了 Docker 目錄與宿主機的
/sys、/var/run等路徑,並確保宿主機上 Docker 執行正常。 - 指標缺失:確認你的 Docker/核心版本與 cAdvisor 相容;對於 containerd 等執行時,採集方式會不同。
關鍵指標速查:節點/容器
在生產環境排障時,建議優先關注下面幾類指標,並在 Grafana 面板中建立對應的常用檢視。
- 節點 CPU 使用率:
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) - 節點記憶體使用率:
(1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)) * 100 - 節點磁碟空間使用率:
(1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"})) * 100 - 容器 CPU:
sum by (namespace, pod, container) (rate(container_cpu_usage_seconds_total[5m])) - 容器記憶體:
sum by (namespace, pod, container) (container_memory_working_set_bytes)
說明:不同採集路徑的 label 命名不同。Docker Compose 中獨立部署的 cAdvisor 常見容器標籤是 name;Kubernetes kubelet 的 /metrics/cadvisor 中,container_cpu_usage_seconds_total 等穩定指標使用 container、pod、namespace。如果查詢為空,先直接查詢 container_cpu_usage_seconds_total 樣本並在 Prometheus 圖形介面檢視實際 label,不要假設存在 container_name。
Targets down 排錯清單
當 Status -> Targets 出現 DOWN 時,建議按以下順序排查:
- 網路連通性:Prometheus 容器是否能解析並訪問目標 (同一 Docker network、DNS、埠)。
- 埠/路徑:確認 exporter 監聽埠與 Prometheus 配置一致;必要時在 Prometheus 容器內
curl http://node-exporter:9100/metrics。 - 許可權/掛載:cAdvisor 需要訪問宿主機
/sys、/var/lib/docker等掛載路徑,缺失會導致指標不全或報錯。 - 時間問題:宿主機與容器時間偏差過大可能導致“資料看起來斷檔”,需要檢查 NTP/時區配置。
- 目標本身異常:確認 exporter 容器是否在重啟,檢視
docker logs。
Alertmanager 告警建議
生產環境建議引入 Alertmanager 做告警聚合與路由,並在 Prometheus 中配置 alerting 與 rule_files。
為了保持“最小告警閉環”,建議至少覆蓋兩類告警:
- 採集鏈路告警:例如
up == 0,用於發現 exporter 或網路故障。 - 資源風險告警:例如節點磁碟空間不足,用於提前發現容量風險。
1. 準備告警規則檔案
建立 rules.yml:
groups:
- name: docker_practice
rules:
- alert: PrometheusTargetDown
expr: up == 0
for: 2m
labels:
severity: warning
annotations:
summary: "Prometheus 抓取目標不可達"
description: "Job={{ $labels.job }}, Instance={{ $labels.instance }}"
- alert: HostDiskSpaceLow
expr: |
(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}) < 0.10
for: 10m
labels:
severity: critical
annotations:
summary: "磁碟可用空間不足"
description: "Instance={{ $labels.instance }}, Mountpoint={{ $labels.mountpoint }}"
說明:這裡的規則是“可用空間低於 10%”的門檻值告警,並非“未來 24 小時寫滿”的預測。生產環境建議針對特定檔案系統與掛載點做更精確的過濾。
2. 配置 Prometheus 載入規則並接入 Alertmanager
修改 prometheus.yml,增加:
rule_files:
- /etc/prometheus/rules.yml
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
並在 Compose 中掛載規則檔案。
3. 部署 Alertmanager
建立 alertmanager.yml:
route:
receiver: default
receivers:
- name: default
webhook_configs:
- url: http://example.com/webhook
再在 compose.yaml 增加服務:
alertmanager:
image: prom/alertmanager:v0.32.0
volumes:
- ./alertmanager.yml:/etc/alertmanager/alertmanager.yml
ports:
- "9093:9093"
networks:
- monitoring
生產環境中,建議將告警傳送到可追蹤的渠道 (如 IM 機器人、事件平臺、工單系統),並在告警中附帶 Dashboard 連結與排障入口,避免告警成為噪聲。
建議的檔案清單
為了避免示例難以復現,建議在同一目錄下準備以下檔案:
compose.yaml:Prometheus、Grafana、exporters、Alertmanager 的部署檔案prometheus.yml:Prometheus 抓取配置與告警配置rules.yml:告警規則alertmanager.yml:告警路由與接收器配置
19.2 ELK 套件
ELK (Elasticsearch,Logstash,Kibana) 是目前業界最流行的開源日誌解決方案。而在容器領域,由於 Fluentd 更加輕量級且對容器支援更好,EFK (Elasticsearch,Fluentd,Kibana) 組合也變得非常流行。
19.2.1 方案架構
我們將採用以下架構:
- Docker Container:容器將日誌輸出到標準輸出 (stdout/stderr)。
- Fluentd:作為 Docker 的 Logging Driver 或執行為守護容器,收集容器日誌。
- Elasticsearch:儲存從 Fluentd 接收到的日誌資料。
- Kibana:從 Elasticsearch 讀取資料並進行視覺化展示。
19.2.2 部署流程
我們將使用 Docker Compose 來一鍵部署整個日誌堆疊。
1. 編寫 Compose 檔案
- 編寫
compose.yaml(或docker-compose.yml) 配置如下。版本提示:本示例使用 Elasticsearch 9.x、Kibana 9.x、Fluentd 等元件的特定版本號,僅作為參考。生產環境部署前,請查閱 Elastic 官方文件 和 Fluentd 官方文件 確認最新版本與配置相容性。
services:
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:9.4.0
container_name: elasticsearch
environment:
- "discovery.type=single-node"
- "xpack.security.enabled=false"
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- es_data:/usr/share/elasticsearch/data
networks:
- logging
kibana:
image: docker.elastic.co/kibana/kibana:9.4.0
container_name: kibana
environment:
- ELASTICSEARCH_HOSTS=http://elasticsearch:9200
ports:
- "5601:5601"
links:
- elasticsearch
networks:
- logging
fluentd:
# elasticsearch8 外掛透過 REST API 與 ES 9.x 相容
image: fluent/fluentd-kubernetes-daemonset:v1.17-debian-elasticsearch8-1
container_name: fluentd
environment:
- "FLUENT_ELASTICSEARCH_HOST=elasticsearch"
- "FLUENT_ELASTICSEARCH_PORT=9200"
- "FLUENT_ELASTICSEARCH_SCHEME=http"
- "FLUENT_UID=0"
ports:
- "24224:24224"
- "24224:24224/udp"
links:
- elasticsearch
volumes:
- ./fluentd/conf:/fluentd/etc
networks:
- logging
volumes:
es_data:
networks:
logging:
2. 配置 Fluentd
建立 fluentd/conf/fluent.conf:
<source>
@type forward
port 24224
bind 0.0.0.0
</source>
<match *.**>
@type copy
<store>
@type elasticsearch
host elasticsearch
port 9200
logstash_format true
logstash_prefix docker
logstash_dateformat %Y%m%d
include_tag_key true
tag_key @log_name
flush_interval 1s
</store>
<store>
@type stdout
</store>
</match>
3. 配置應用容器使用 fluentd 驅動
啟動一個測試容器,指定日誌驅動為 fluentd:
docker run -d \
--log-driver=fluentd \
--log-opt fluentd-address=localhost:24224 \
--log-opt tag=nginx-test \
--name nginx-test \
nginx
注意:確保 fluentd 容器已經啟動並監聽在 localhost:24224。在生產環境中,如果你是在不同機器上,需要將 localhost 替換為執行 fluentd 的主機 IP。
4. 在 Kibana 中檢視日誌
- 訪問
http://localhost:5601。 - 進入 Management->Kibana->Index Patterns。
- 建立新的 Index Pattern,輸入
docker-*(我們在 fluent.conf 中配置的字首)。 - 選擇
@timestamp作為時間欄位。 - 去 Discover 頁面,你就能看到 Nginx 容器的日誌了。
Kibana 建索引模式常見坑
首次接入 EFK/ELK 時,“Elasticsearch 有資料但 Kibana 看不到”很常見,通常是 Kibana 配置或時間視窗問題:
- Index Pattern 不匹配:確認 Kibana 的 Index Pattern 與實際索引字首一致。可以先用
_cat/indices檢視真實索引名。 - 時間欄位選擇錯誤:若索引裡包含
@timestamp,一般選擇它;如果選擇了錯誤的欄位,會導致 Discover 無法按時間篩選。 - 時間視窗/時區:Discover 右上角的時間範圍預設可能是最近 15 分鐘,且時區可能影響顯示。建議先把範圍擴大到最近 24 小時再驗證。
- 資料解析失敗:若日誌是非結構化文字,仍可入庫但欄位不可用;生產環境建議輸出 JSON 並在採集端解析。
5. 驗證日誌是否寫入 Elasticsearch:生產排錯必備
當你在 Kibana 看不到日誌時,建議先跳過 UI,從儲存端直接驗證“日誌是否入庫”。
- 檢視索引是否建立:
bash
curl -s http://localhost:9200/_cat/indices?v
如果 Fluentd 使用了 logstash_format true 且 logstash_prefix docker,通常會看到形如 docker-YYYY.MM.DD 的索引。
- 檢視最近一段時間的日誌文件:
bash
curl -s -H 'Content-Type: application/json' \
http://localhost:9200/docker-*/_search \
-d '{"size":1,"sort":[{"@timestamp":"desc"}]}'
如果 Elasticsearch 中已經有文件,但 Kibana 仍然為空,常見原因是:
- Index Pattern 沒匹配到索引 (例如寫成了
docker-*但實際索引字首不同)。 - 時間欄位沒選對或時區不一致,導致 Discover 時間視窗內看不到資料。
19.2.3 總結
透過 Docker 的日誌驅動機制,結合 ELK/EFK 強大的收集和分析能力,我們可以輕鬆構建一個能夠處理海量日誌的監控平臺,這對於排查生產問題至關重要。
19.2.4 生產要點
在生產環境中,日誌系統往往比監控系統更容易因為“容量與寫入壓力”出問題,建議特別關注:
- 容量規劃:日誌增長速度與磁碟佔用直接相關。建議設定日誌保留週期與索引生命週期策略 (ILM),避免 Elasticsearch 因磁碟水位觸發只讀或不可用。
- 資源配置:Elasticsearch 對 JVM Heap 較敏感。除示例中的
ES_JAVA_OPTS外,生產環境需要結合節點記憶體、分片規模、查詢壓力做評估。 - 鏈路可靠性:採集端到儲存端要考慮網路抖動、背壓與重試策略;當 Elasticsearch 寫入變慢時,採集端的緩衝與落盤策略決定了是否會丟日誌。
- 日誌格式:推薦應用輸出結構化日誌 (JSON) 幷包含關鍵欄位 (如
trace_id、request_id、service、env),以便快速過濾與關聯分析。
索引與保留策略的落地建議
無論是 EFK 還是 ELK,生產上都需要回答兩個問題:
- 日誌保留多久?
- 保留期內的日誌如何保證可查詢、不過度佔用儲存?
建議按環境與業務重要性對日誌分層,並制定不同的保留週期,例如:
- 生產環境:7~30 天
- 測試環境:1~7 天
實現方式通常有兩類:
- 按天滾動索引:如
docker-YYYY.MM.DD,再定期刪除過期索引。 - 使用 ILM:定義 Hot/Warm/Cold/刪除階段,按時間與容量自動滾動與回收。
對於中小規模叢集,先把“按天滾動 + 過期刪除”做紮實,往往就能解決 80% 的容量問題;當日志量上來、查詢壓力變大後,再逐步引入 ILM、分層儲存與更精細的分片規劃。
最小可用的“過期索引清理”示例
如果你採用按天滾動索引 (例如 docker-YYYY.MM.DD),可以透過 Elasticsearch API 定期清理過期索引。
下面示例僅用於演示思路:獲取所有 docker- 字首索引並刪除指定索引。生產環境建議基於日期計算、灰度驗證與許可權控制後再執行自動化清理。
- 列出索引:
bash
curl -s http://localhost:9200/_cat/indices/docker-*?v
- 刪除某個過期索引 (示例):
bash
curl -X DELETE http://localhost:9200/docker-2026.02.01
如果你希望更自動化的治理能力,可以進一步使用 ILM 為索引配置滾動與刪除策略。
19.3 容器效能最佳化與故障診斷
容器的輕量級特性不代表效能問題會自動消失。在實際運維中,效能瓶頸可能來自 CPU 限制、記憶體溢位、磁碟 I/O、網路擁塞等多個層面。本節深入討論容器效能監控、診斷方法和最佳化策略。
19.3.1 容器效能監控指標
核心效能指標體系
容器效能監控涉及以下關鍵指標:
CPU 相關指標:
cpu.usage_usec:容器 CPU 使用時間(微秒)cpu.stat.nr_throttled:CPU 限流發生次數cpu.stat.throttled_usec:CPU 限流總時間cpu_percent:CPU 使用百分比cpu_quota:CPU 配額設定(微秒)
記憶體相關指標:
memory.usage_bytes:當前記憶體使用量memory.max_usage_bytes:記憶體使用峰值memory.limit_in_bytes:記憶體限制memory.fail_cnt:OOM(Out of Memory)失敗次數memory.stat.cache:頁面快取佔用memory.stat.rss:實際記憶體佔用(RSS)memory.stat.swap:SWAP 使用量
網路相關指標:
rx_bytes:接收位元組數tx_bytes:傳送位元組數rx_packets:接收包數tx_packets:傳送包數rx_errors:接收錯誤數tx_errors:傳送錯誤數rx_dropped:接收丟包數tx_dropped:傳送丟包數
I/O 相關指標:
io_service_bytes:I/O 操作位元組數io_service_time:I/O 操作耗時io_queued:I/O 佇列長度fs_limit_bytes:檔案系統限制fs_usage_bytes:檔案系統使用量
19.3.2 使用 docker stats 實時監控
docker stats 是最基礎但強大的監控工具,提供實時的容器資源使用情況。
基本使用:
# 實時監控所有執行中的容器
docker stats
# 輸出示例:
# CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O
# abc123def456 nginx 0.45% 24.3 MiB / 256 MiB 9.49% 1.2kB / 3.4kB 0 B / 0 B
# def789ghi012 redis 0.23% 12.5 MiB / 512 MiB 2.44% 2.1kB / 1.5kB 0 B / 0 B
# 只監控特定容器
docker stats nginx redis
# 一次性輸出不進入互動模式
docker stats --no-stream
# 指定重新整理間隔(單位:秒,預設 1 秒)
docker stats --no-stream --interval 2
# 格式化輸出(使用 Go 模板)
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}" --no-stream
# 匯出為 JSON 格式用於日誌記錄
docker stats --format json --no-stream > stats.json
在指令碼中使用:
#!/bin/bash
# 持續監控並記錄到檔案
while true; do
timestamp=$(date '+%Y-%m-%d %H:%M:%S')
docker stats --no-stream --format "{{.Container}},{{.CPUPerc}},{{.MemUsage}}" | \
awk -v ts="$timestamp" '{print ts","$0}' >> container_stats.log
sleep 10
done
效能指標解讀:
# CPU % 超過 80%:需要增加 CPU 限制或最佳化應用
# MEM % 接近 100%:容器即將 OOM,需要增加記憶體或排查記憶體洩漏
# 如果 NET I/O 中 dropped 為非零:網路擁塞或丟包
19.3.3 cAdvisor 容器監控系統
cAdvisor 是 Google 開發的容器監控工具,提供比 docker stats 更詳細的效能資料。
⚠️ 安全權衡提示:下面的示例為簡化部署使用了
privileged: true,與 第 18 章 中“最小許可權 /cap_drop=all”的原則相沖突。生產環境建議改為按需授予能力(如cap_add: [SYS_ADMIN]加device_cgroup_rules與精確的devices、volumes掛載),並將 cAdvisor 部署在獨立的監控網路中。如何選擇請參考 18.4 節 關於核心能力(capabilities)的細化授權。
Docker Compose 部署 cAdvisor:
services:
cadvisor:
image: ghcr.io/google/cadvisor:v0.56.2
container_name: cadvisor
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
privileged: true
devices:
- /dev/kmsg
networks:
- monitoring
networks:
monitoring:
driver: bridge
啟動後訪問 http://localhost:8080 檢視:
- 容器效能統計
- 系統資源使用情況
- 歷史效能資料
從 cAdvisor 提取指標:
# 獲取所有容器的 JSON 格式效能資料
curl http://localhost:8080/api/v1.3/machine | jq .
# 獲取特定容器資訊
curl http://localhost:8080/api/v1.3/docker | jq '.docker | keys' | head -5
# 獲取容器統計資訊
curl http://localhost:8080/api/v1.3/docker/abc123/ | jq '.stats[-1]'
與 Prometheus 整合:
# prometheus.yml 配置
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'cadvisor'
static_configs:
- targets: ['localhost:8080']
metrics_path: '/metrics'
19.3.4 Prometheus 容器監控配置
使用 Prometheus 和 node-exporter 進行長期的容器效能監控。
完整監控棧部署:
[!TIP] 以下示例中的映像檔標籤(如
prom/prometheus:v3.11.2、prom/node-exporter:v1.11.1、ghcr.io/google/cadvisor:v0.56.2、grafana/grafana:13.0.1)僅為參考。在生產環境部署前,請訪問各專案的官方釋出頁或文件獲取最新版本號。
services:
prometheus:
image: prom/prometheus:v3.11.2
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
- '--storage.tsdb.retention.time=30d'
networks:
- monitoring
node-exporter:
image: prom/node-exporter:v1.11.1
container_name: node-exporter
ports:
- "9100:9100"
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
networks:
- monitoring
cadvisor:
image: ghcr.io/google/cadvisor:v0.56.2
container_name: cadvisor
ports:
- "8080:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker/:/var/lib/docker:ro
privileged: true
networks:
- monitoring
grafana:
image: grafana/grafana:13.0.1
container_name: grafana
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_INSTALL_PLUGINS=grafana-piechart-panel
volumes:
- grafana_data:/var/lib/grafana
networks:
- monitoring
volumes:
prometheus_data:
grafana_data:
networks:
monitoring:
driver: bridge
Prometheus 配置檔案(prometheus.yml):
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node-exporter'
static_configs:
- targets: ['node-exporter:9100']
- job_name: 'cadvisor'
static_configs:
- targets: ['cadvisor:8080']
- job_name: 'docker'
static_configs:
- targets: ['localhost:9323']
常用的 Prometheus 查詢(PromQL):
# 容器 CPU 使用百分比
rate(container_cpu_usage_seconds_total[5m]) * 100
# 容器記憶體使用百分比
(container_memory_usage_bytes / container_spec_memory_limit_bytes) * 100
# 容器網路入站流量(MB/s)
rate(container_network_receive_bytes_total[5m]) / 1024 / 1024
# 容器網路出站流量(MB/s)
rate(container_network_transmit_bytes_total[5m]) / 1024 / 1024
# 容器磁碟讀取速率(MB/s)
rate(container_fs_io_current[5m]) / 1024 / 1024
# CPU 限流情況
rate(container_cpu_cfs_throttled_seconds_total[5m])
# 記憶體快取佔比
container_memory_cache_bytes / container_memory_usage_bytes
# 按映像檔統計容器數
count(container_memory_usage_bytes) by (image)
19.3.5 容器 OOM 排查與記憶體限制調優
OOM 問題診斷
# 檢查容器是否因 OOM 被殺死
docker inspect <container_id> | grep OOMKilled
# 檢視容器退出碼:137 表示被 OOM 殺死
docker ps -a --format "{{.ID}}\t{{.Status}}" | grep "137"
# 檢視容器日誌中的 OOM 資訊
docker logs <container_id> 2>&1 | grep -i "out of memory\|oom"
# 從宿主機日誌檢視 OOM 事件
dmesg | grep -i "oom\|kill"
journalctl -u docker -n 100 | grep -i "oom"
記憶體洩漏檢測
使用專項工具分析應用記憶體使用:
Python 應用記憶體洩漏檢測:
# Dockerfile
FROM python:3.14-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt memory_profiler tracemalloc
COPY app.py .
CMD ["python", "-m", "memory_profiler", "app.py"]
# app.py - 記憶體洩漏示例
from memory_profiler import profile
import tracemalloc
@profile
def memory_leak():
# 不斷建立未釋放的列表
data = []
while True:
data.append([0] * 1000000)
print(f"List size: {len(data)}")
# 使用 tracemalloc 跟蹤記憶體分配
tracemalloc.start()
# 執行可能洩漏的程式碼
# ...
current, peak = tracemalloc.get_traced_memory()
print(f"Current: {current / 1024 / 1024:.2f} MB")
print(f"Peak: {peak / 1024 / 1024:.2f} MB")
Java 應用記憶體分析:
# 在容器中啟用 JVM 遠端除錯
docker run -e JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC" \
-p 5005:5005 \
myapp:latest
# 使用 jstat 檢查垃圾回收情況
jstat -gc <pid> 1000 # 每秒取樣一次
# 輸出示例:
# S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU
# 6144 6144 0 6144 39424 12288 149504 84320 50552 47689 6464 5989
記憶體限制最佳實務
# 為容器設定記憶體限制
docker run -m 512m --memory-swap 1g myapp:latest
# 引數說明:
# -m / --memory:記憶體限制(這裡是 512MB)
# --memory-swap:記憶體+SWAP 總額(這裡是 1GB,意味著 SWAP 為 512MB)
# 如果不設定 --memory-swap,則等於 --memory 值
# Docker Compose 配置
services:
app:
image: myapp:latest
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
記憶體超額提交(Memory Overcommit):
# 在 Docker Compose 中區分限制和預留
# limits:絕不能超過的最大值
# reservations:Compose 排期時的參考值
services:
web:
memory: 512M # 限制
memswap_limit: 1G # SWAP 限制
db:
memory: 2G
memory_reservation: 1G # 預留 1GB,允許突發到 2GB
19.3.6 映像檔體積最佳化與多階段構建
映像檔體積分析工具
使用 dive 分析映像檔層:
# 安裝 dive
wget https://github.com/wagoodman/dive/releases/download/v0.13.1/dive_0.13.1_linux_amd64.deb
sudo apt install ./dive_0.13.1_linux_amd64.deb
# 分析映像檔
dive myapp:latest
# 輸出詳細的分層資訊,顯示每一層的大小和內容
使用 Dockerfile 分析工具:
# 安裝 hadolint
curl https://github.com/hadolint/hadolint/releases/download/v2.14.0/hadolint-Linux-x86_64 -L -o hadolint
chmod +x hadolint
# 檢查 Dockerfile 最佳實務
./hadolint Dockerfile
多階段構建最佳實務
Go 應用的最小化映像檔構建:
# Stage 1: 構建階段
FROM golang:1.26-alpine AS builder
WORKDIR /build
# 安裝依賴
RUN apk add --no-cache git ca-certificates tzdata
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# 構建靜態二進位制(支援 scratch 基礎映像檔)
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-a -installsuffix cgo \
-ldflags="-w -s" \
-o app .
# Stage 2: 執行階段
FROM scratch
# 從 builder 複製必要的檔案
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
COPY --from=builder /build/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]
# 最終映像檔大小通常 < 15MB(相比 golang:1.26-alpine 的 ~1GB)
Node.js 應用的多階段構建:
# Stage 1: 依賴安裝
FROM node:24-alpine AS dependencies
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production && \
npm cache clean --force
# Stage 2: 構建階段
FROM node:24-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 3: 執行階段
FROM node:24-alpine
WORKDIR /app
# 從依賴階段複製 node_modules
COPY --from=dependencies /app/node_modules ./node_modules
# 從構建階段複製構建產物
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/package*.json ./
# 刪除開發依賴和不必要的檔案
RUN rm -rf src tests *.config.js
USER node
EXPOSE 3000
CMD ["node", "dist/index.js"]
# 映像檔大小對比:
# 不最佳化:~500MB
# 多階段構建後:~120MB(減少 76%)
Python 應用的多階段構建:
# Stage 1: 構建階段
FROM python:3.14-slim AS builder
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# Stage 2: 執行階段
FROM python:3.14-slim
WORKDIR /app
# 從 builder 複製虛擬環境
COPY --from=builder /root/.local /root/.local
# 設定 PATH
ENV PATH=/root/.local/bin:$PATH \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
COPY . .
USER nobody
EXPOSE 5000
CMD ["python", "app.py"]
映像檔體積最佳化檢查清單
# 檢查清單
□ 使用精簡基礎映像檔(Alpine、Distroless)
□ 清理包管理器快取(apt-get clean、rm -rf /var/cache/*)
□ 在同一 RUN 指令中安裝和清理依賴
□ 使用 .dockerignore 排除不必要的檔案
□ 多階段構建避免構建依賴汙染最終映像檔
□ 去除除錯符號:-ldflags="-w -s"(Go)、strip 命令(C/C++)
□ 壓縮靜態資源和應用檔案
□ 使用 BuildKit 快取最佳化加速構建
# 最佳化示例:
FROM ubuntu:24.04
# ❌ 不推薦
RUN apt-get update
RUN apt-get install -y curl wget git
RUN apt-get clean
# ✓ 推薦
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
wget \
git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
19.3.7 常見效能問題及解決方案
問題 1: 容器頻繁被 OOM 殺死
症狀:容器程序被無故殺死,exit code 137 解決方案:
# 增加記憶體限制
docker update -m 1g <container_id>
# 排查記憶體洩漏
docker exec <container_id> ps aux | grep -E "VSZ|RSS"
# 使用 docker stats 實時監控
docker stats <container_id>
# 啟用記憶體交換(作為最後手段)
docker run -m 512m --memory-swap 1g myapp:latest
問題 2: CPU 被限流(CPU Throttling)
症狀:應用效能突然下降,但 CPU 使用率不高 診斷:
# 檢視 CPU 限流統計
docker exec <container_id> cat /sys/fs/cgroup/cpu/cpu.stat
# 如果 throttled_time > 0,說明發生了 CPU 限流
# 解決方案:增加 CPU 限制
docker update --cpus 2 <container_id>
問題 3: 網路丟包或延遲高
診斷:
# 進入容器檢查網路狀態
docker exec <container_id> ip -s link show
# 檢查路由和 DNS
docker exec <container_id> cat /etc/resolv.conf
# 測試網路延遲
docker exec <container_id> ping 8.8.8.8
# 檢查容器網路驅動
docker inspect <container_id> | grep -A 10 NetworkSettings
# 解決方案:更換網路驅動或調整 MTU
# host 網路可降低網路棧開銷,但會放棄容器網路隔離;僅在明確接受安全邊界變化時使用
docker run --net=host myapp:latest
本章小結
本章從兩個維度介紹了容器可觀測性:
- 指標監控:以 Prometheus + Grafana 為主,完成指標採集、儲存與視覺化。
- 日誌管理:以 EFK/ELK 為例,完成容器日誌的集中採集、檢索與分析。
生產環境中,建議將“可觀測性”當成一個完整閉環:採集 -> 儲存 -> 展示 -> 告警 -> 排錯 -> 容量治理。
擴充套件閱讀:Docker 日誌驅動
Docker 提供了多種日誌驅動 (Log Driver),用於將容器標準輸出的日誌轉發到不同後端。
常見的日誌驅動包括:
json-file:預設驅動,將日誌以 JSON 格式寫入本地檔案。syslog:將日誌轉發到 syslog 伺服器。journald:將日誌寫入 systemd journal。fluentd:將日誌轉發到 fluentd 收集器。gelf:支援 GELF 協議的日誌後端 (如 Graylog)。awslogs:傳送到 Amazon CloudWatch Logs。
生產建議:無論採用哪種驅動,都要明確日誌的保留週期、容量上限與傳輸可靠性,避免“日誌把磁碟寫滿”或“鏈路抖動導致丟日誌”。
19.4 日誌平臺選型對比與注意事項
日誌平臺通常由“採集/處理/儲存/查詢展示”幾部分組成。常見選型包括:
- EFK/ELK:Elasticsearch + Fluentd/Logstash + Kibana,適合全文檢索與結構化查詢。
- Loki + Grafana:更偏“日誌像指標一樣儲存”的思路,部署與成本可能更友好,但查詢能力與使用習慣不同。
選型時建議關注:
- 寫入壓力與背壓:當儲存端變慢時,採集端是否會緩衝、落盤、重試,是否會影響業務。
- 容量治理:是否具備按天/按大小滾動、保留策略、生命週期管理 (ILM) 等能力。
- 安全與合規:鑑權、TLS、審計、敏感欄位脫敏。
- 可運維性:升級策略、備份恢復、告警指標是否齊全。
19.5 上線前檢查清單
你可以用下面的清單快速檢查“是否具備最小生產可用性”:
- Prometheus 資料目錄已持久化,並設定了合理的保留週期。
- Prometheus Targets 全部為
UP,並且關鍵查詢 (CPU/記憶體/容器指標) 有資料。 - Grafana 已匯入面板並能定位到具體例項/容器;預設賬號密碼已修改。
- 至少有一條關鍵告警已打通 Alertmanager 的接收鏈路,並驗證告警能被正確傳送與抑制。
- Elasticsearch 資料目錄已持久化,並有明確的日誌保留週期與容量上限策略。
- Kibana 能查詢到最新日誌;當 UI 異常時能用 Elasticsearch API 驗證入庫。
- 可觀測性元件未直接暴露到公網,訪問已加鑑權或置於內網。
第二十章 實戰案例 - 作業系統
第二十章 實戰案例 - 作業系統
章節概述
本章將介紹 Docker 在不同作業系統映像檔情境下的實戰案例。當你構建容器化應用時,選擇合適的基礎映像檔至關重要。不同的作業系統映像檔在大小、功能和效能方面各有特點,適用於不同的使用情境。本章透過具體的案例,詳細講解如何在 Docker 中使用主流作業系統映像檔,包括輕量級映像檔 (Busybox、Alpine) 和完整功能映像檔 (Debian、Ubuntu、CentOS 等)。
版本說明
本章示例中使用的作業系統映像檔版本遵循以下原則:
- Alpine、Debian、Ubuntu、CentOS 等作業系統映像檔採用大版本或次版本標籤(如
alpine:3.21、ubuntu:26.04),避免使用latest標籤確保構建的可再現性 - OS 大版本保留,以便獲得最新的安全補丁和修復
- 在生產環境中,建議根據實際需求選擇合適的版本,並定期更新以獲得安全修復
為什麼選擇合適的作業系統映像檔很重要
在容器化應用開發中,選擇合適的基礎作業系統映像檔直接影響容器的大小、啟動速度、安全性和執行效能。不同的映像檔提供了不同的功能集和資源佔用:
- 輕量級映像檔 (Busybox、Alpine) - 映像檔大小僅幾 MB,啟動快速,適合微服務、IoT 裝置和對資源敏感的環境。Busybox 是最小的選擇,整合了常見的 Unix 工具;Alpine 則提供了完整的包管理器,方便安裝額外工具。
- 通用映像檔 (Debian、Ubuntu) - 提供完整的 Linux 功能和豐富的軟體生態,映像檔大小通常在 100-300 MB 之間。適合需要靈活安裝各種依賴和工具的應用情境。
- 企業級映像檔 (CentOS、Fedora) - 基於 Red Hat 生態,廣泛應用於企業環境和複雜系統應用。提供了 yum 包管理器和強大的系統管理工具。
選擇映像檔的關鍵原則是 “小而夠用”——選擇滿足應用需求的最小映像檔。這樣可以減少安全漏洞表面積、加快映像檔拉取和推送速度、降低儲存成本,同時也使容器更便於分發和部署。
常用作業系統映像檔對比
| 映像檔 | 大小 | 包管理器 | 適用情境 | 優勢 |
|---|---|---|---|---|
| Busybox | ~1 MB | 無 | 最小化工具集、initrd | 極致輕量,啟動秒級 |
| Alpine | ~5 MB | apk | 微服務、靜態應用 | 體積小,有包管理器 |
| Debian | ~100 MB | apt-get | 通用應用、開發環境 | 軟體包豐富,穩定性強 |
| Ubuntu | ~80 MB | apt-get | 類似 Debian,現代化系統 | 更新頻繁,使用者多 |
| CentOS | ~200 MB | yum | 企業應用、相容性需求 | 企業級支援,穩定性高 |
| Fedora | ~200 MB | dnf | 新特性需求、開發環境 | 最新技術棧,創新性強 |
學習目標
透過學習本章內容,你將能夠:
- 理解不同作業系統映像檔的特點、大小和適用情境
- 掌握在 Docker 中使用各類作業系統映像檔的方法和最佳實務
- 學習如何根據實際需求選擇合適的基礎映像檔,實現映像檔最佳化
- 瞭解如何在不同作業系統容器中安裝、配置和管理應用程式
- 掌握多階段構建等高階技巧,最小化最終映像檔大小
- 學會使用 Docker Compose 編排多個作業系統容器環境
章節內容導航
- Busybox — 超輕量級工具集映像檔,適合嵌入式和最小化容器
- Alpine — 輕量級 Linux 映像檔,廣泛用於生產環境微服務
- Debian Ubuntu — 功能完整的通用 Linux 映像檔,生態豐富
- CentOS Fedora — 企業級 Linux 映像檔,適合複雜系統應用
- 本章小結
20.1 Busybox
20.1.1 簡介
下圖直觀地展示了本節內容:

BusyBox 是一個整合了一百多個最常用 Linux 命令和工具 (如 cat、echo、grep、mount、telnet 等) 的精簡工具箱,它只需要幾 MB 的大小,很方便進行各種快速驗證,被譽為 “Linux 系統的瑞士軍刀”。
BusyBox 可執行於多款 POSIX 環境的作業系統中,如 Linux (包括 Android)、Hurd、FreeBSD 等。
20.1.2 獲取官方映像檔
可以使用 docker pull 指令下載 busybox:latest 映像檔:
$ docker pull busybox:latest
latest: Pulling from library/busybox
5c4213be9af9: Pull complete
Digest: sha256:c6b45a95f932202dbb27c31333c4789f45184a744060f6e569cc9d2bf1b9ad6f
Status: Downloaded newer image for busybox:latest
docker.io/library/busybox:latest
下載後,可以看到 busybox 映像檔只有 2.433 MB:
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
busybox latest e72ac664f4f0 6 weeks ago 2.433 MB
20.1.3 執行 busybox
啟動一個 busybox 容器,並在容器中執行 grep 命令。
$ docker run -it busybox
/ # grep
BusyBox v1.22.1 (2014-05-22 23:22:11 UTC) multi-call binary.
Usage: grep [-HhnlLoqvsriwFE] [-m N] [-A/B/C N] PATTERN/-e PATTERN.../-f FILE [FILE]...
Search for PATTERN in FILEs (or stdin)
-H Add 'filename:' prefix
-h Do not add 'filename:' prefix
-n Add 'line_no:' prefix
-l Show only names of files that match
-L Show only names of files that don't match
-c Show only count of matching lines
-o Show only the matching part of line
-q Quiet. Return 0 if PATTERN is found, 1 otherwise
-v Select non-matching lines
-s Suppress open and read errors
-r Recurse
-i Ignore case
-w Match whole words only
-x Match whole lines only
-F PATTERN is a literal (not regexp)
-E PATTERN is an extended regexp
-m N Match up to N times per file
-A N Print N lines of trailing context
-B N Print N lines of leading context
-C N Same as '-A N -B N'
-e PTRN Pattern to match
-f FILE Read pattern from file
檢視容器內的掛載資訊。
/ # mount
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/BOTCI5RF24AMC4A2UWF4N6ZWFP:/var/lib/docker/overlay2/l/TWVP5T5DMKJGXZOROR7CAPWGFP,upperdir=/var/lib/docker/overlay2/801ef0bf6cce35288dbb8fe00a4f9cc47760444693bfdf339ed0bdcf926e12a3/diff,workdir=/var/lib/docker/overlay2/801ef0bf6cce35288dbb8fe00a4f9cc47760444693bfdf339ed0bdcf926e12a3/work)
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
tmpfs on /dev type tmpfs (rw,nosuid,size=65536k,mode=755)
devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
sysfs on /sys type sysfs (ro,nosuid,nodev,noexec,relatime)
tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,relatime,mode=755)
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)
mqueue on /dev/mqueue type mqueue (rw,nosuid,nodev,noexec,relatime)
shm on /dev/shm type tmpfs (rw,nosuid,nodev,noexec,relatime,size=65536k)
/dev/vda1 on /etc/resolv.conf type ext3 (rw,noatime,data=ordered)
/dev/vda1 on /etc/hostname type ext3 (rw,noatime,data=ordered)
/dev/vda1 on /etc/hosts type ext3 (rw,noatime,data=ordered)
devpts on /dev/console type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666)
proc on /proc/bus type proc (ro,relatime)
proc on /proc/fs type proc (ro,relatime)
proc on /proc/irq type proc (ro,relatime)
proc on /proc/sys type proc (ro,relatime)
proc on /proc/sysrq-trigger type proc (ro,relatime)
tmpfs on /proc/acpi type tmpfs (ro,relatime)
tmpfs on /proc/kcore type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /proc/keys type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /proc/timer_list type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /proc/sched_debug type tmpfs (rw,nosuid,size=65536k,mode=755)
tmpfs on /sys/firmware type tmpfs (ro,relatime)
busybox 映像檔雖然小巧,但包括了大量常見的 Linux 命令,讀者可以用它快速熟悉 Linux 命令。
20.1.4 相關資源
Busybox官網:https://busybox.net/Busybox官方倉庫:https://git.busybox.net/busybox/Busybox官方映像檔:https://hub.docker.com/_/busybox/Busybox官方倉庫:https://github.com/docker-library/busybox
20.2 Alpine
20.2.1 簡介
下圖直觀地展示了本節內容:

Alpine 作業系統是一個面向安全的輕型 Linux 發行版。它不同於通常 Linux 發行版,Alpine 採用了 musl libc 和 busybox 以減小系統的體積和執行時資源消耗,但功能上比 busybox 又完善的多,因此得到開源社群越來越多的青睞。在保持瘦身的同時,Alpine 還提供了自己的包管理工具 apk,可以透過 Alpine Packages 網站上查詢包資訊,也可以直接透過 apk 命令直接查詢和安裝各種軟體。
Alpine 由非商業組織維護的,支援廣泛情境的 Linux 發行版,它特別為資深/重度 Linux 使用者而最佳化,關注安全,效能和資源效能。Alpine 映像檔可以適用於更多常用情境,並且是一個優秀的可以適用於生產的基礎系統/環境。
Alpine Docker 映像檔也繼承了 Alpine Linux 發行版的這些優勢。相比於其他 Docker 映像檔,它的容量非常小,壓縮後僅約 5 MB(對比 Ubuntu 系列映像檔約 28 MB),且擁有非常友好的包管理機制。官方映像檔來自 docker-alpine 專案。
目前 Docker 官方已開始推薦使用 Alpine 替代之前的 Ubuntu 做為基礎映像檔環境。這樣會帶來多個好處。包括映像檔下載速度加快,映像檔安全性提高,主機之間的切換更方便,佔用更少磁碟空間等。
下表是官方映像檔的大小比較(壓縮後大小,實際資料以 Docker Hub 為準):
REPOSITORY TAG COMPRESSED SIZE
alpine latest ~5 MB
debian latest ~30 MB
ubuntu latest ~28 MB
提示:映像檔大小會隨版本更新而變化,精確值請查閱 Docker Hub 各映像檔的 Tags 頁面。Alpine 的優勢不僅在於體積小,還在於更少的預裝包帶來更小的攻擊面。
20.2.2 獲取並使用官方映像檔
由於映像檔很小,下載時間往往很短,讀者可以直接使用 docker run 指令直接執行一個 Alpine 容器,並指定執行的 Linux 指令,例如:
$ docker run alpine echo '123'
123
20.2.3 遷移至 Alpine 基礎映像檔
目前,大部分 Docker 官方映像檔都已經支援 Alpine 作為基礎映像檔,可以很容易進行遷移。
例如:
ubuntu/debian->alpinepython:3->python:3-alpineruby:2.6->ruby:2.6-alpine
另外,如果使用 Alpine 映像檔替換 Ubuntu 基礎映像檔,安裝軟體包時需要用 apk 包管理器替換 apt 工具,如
$ apk add --no-cache <package>
Alpine 中軟體安裝包的名字可能會與其他發行版有所不同,可以在 https://pkgs.alpinelinux.org/packages 網站搜尋並確定安裝包名稱。如果需要的安裝包不在主索引內,但是在測試或社群索引中。那麼可以按照以下方法使用這些安裝包。
$ echo "http://dl-cdn.alpinelinux.org/alpine/edge/testing" >> /etc/apk/repositories
$ apk --update add --no-cache <package>
由於在國內訪問 apk 倉庫較緩慢,建議在使用 apk 之前先替換倉庫地址為國內映像檔。
RUN sed -i "s/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g" /etc/apk/repositories \
&& apk add --no-cache <package>
20.2.4 相關資源
Alpine官網:https://www.alpinelinux.org/Alpine官方倉庫:https://github.com/alpinelinuxAlpine官方映像檔:https://hub.docker.com/_/alpine/Alpine官方映像檔倉庫:https://github.com/alpinelinux/docker-alpine
20.3 Debian Ubuntu
Debian 和 Ubuntu 都是目前較為流行的 Debian 系 的伺服器作業系統,十分適合研發情境。Docker Hub 上提供了官方映像檔,國內各大容器雲服務也基本都提供了相應的支援。
20.3.1 Debian 系統簡介
下圖直觀地展示了本節內容:

Debian 是由 GPL 和其他自由軟體許可協議授權的自由軟體組成的作業系統,由 Debian 計劃 (Debian Project) 組織維護。Debian 計劃 是一個獨立的、分散的組織,由 3000 人志願者組成,接受世界多個非盈利組織的資金支援,Software in the Public Interest 提供支援並持有商標作為保護機構。Debian 以其堅守 Unix 和自由軟體的精神,以及其給予使用者的眾多選擇而聞名。現時 Debian 包括了超過 25,000 個軟體包並支援 12 個計算機系統結構。
Debian 作為一個大的系統組織框架,其下有多種不同作業系統核心的分支計劃,主要為採用 Linux 核心的 Debian GNU/Linux 系統,其他還有采用 GNU Hurd 核心的 Debian GNU/Hurd 系統、採用 FreeBSD 核心的 Debian GNU/kFreeBSD 系統,以及採用 NetBSD 核心的 Debian GNU/NetBSD 系統。甚至還有利用 Debian 的系統架構和工具,採用 OpenSolaris 核心構建而成的 Nexenta OS 系統。在這些 Debian 系統中,以採用 Linux 核心的 Debian GNU/Linux 最為著名。
眾多的 Linux 發行版,例如 Ubuntu、Knoppix 和 Linspire 及 Xandros 等,都基於 Debian GNU/Linux。
使用 Debian 官方映像檔
Debian 是一個常用的基礎映像檔。
官方提供了大家熟知的 debian 映像檔以及面向科研領域的 neurodebian 映像檔。可以使用 docker run 直接執行 Debian 映像檔。
$ docker run -it debian bash
root@668e178d8d69:/# cat /etc/issue
Debian GNU/Linux 13
Debian 映像檔很適合作為基礎映像檔,構建自定義映像檔。
20.3.2 Ubuntu 系統簡介
下圖直觀地展示了本節內容:

Ubuntu 是一個以桌面應用為主的 GNU/Linux 作業系統,其名稱來自非洲南部祖魯語或豪薩語的 “ubuntu” 一詞 (官方譯名 “友幫拓”,另有 “吾幫託”、“烏班圖”、“有奔頭” 或 “烏斑兔” 等譯名)。Ubuntu 意思是 “人性” 以及 “我的存在是因為大家的存在”,是非洲傳統的一種價值觀,類似華人社會的 “仁愛” 思想。Ubuntu 基於 Debian 發行版和 GNOME/Unity 桌面環境,與 Debian 的不同在於它每 6 個月會釋出一個新版本,每 2 年推出一個長期支援 (Long Term Support,LTS) 版本;LTS 版本通常提供 5 年標準安全維護,Ubuntu Pro/ESM 可進一步延長安全覆蓋。
使用 Ubuntu 官方映像檔
Ubuntu 是目前最流行的 Linux 發行版之一。
下面以 ubuntu:26.04 為例,演示如何使用該映像檔安裝一些常用軟體。
首先使用 -ti 引數啟動容器,登入 bash,檢視 ubuntu 的發行版本號。
$ docker run -ti ubuntu:26.04 /bin/bash
root@7d93de07bf76:/# cat /etc/os-release
PRETTY_NAME="Ubuntu 26.04 LTS"
NAME="Ubuntu"
VERSION_ID="26.04"
VERSION="26.04 LTS (Resolute Raccoon)"
VERSION_CODENAME=resolute
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
當試圖直接使用 apt-get 安裝一個軟體的時候,會提示 E: Unable to locate package。
root@7d93de07bf76:/# apt-get install curl
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
E: Unable to locate package curl
這並非系統不支援 apt-get 命令。Docker 映像檔在製作時為了精簡清除了 apt 倉庫資訊,因此需要先執行 apt-get update 命令來更新倉庫資訊。更新資訊後即可成功透過 apt-get 命令來安裝軟體。
root@7d93de07bf76:/# apt-get update
Get:1 http://archive.ubuntu.com/ubuntu resolute InRelease [256 kB]
Get:2 http://security.ubuntu.com/ubuntu resolute-security InRelease [126 kB]
...
Fetched 25.8 MB in 8s (3215 kB/s)
Reading package lists... Done
首先,安裝 curl 工具。
root@7d93de07bf76:/# apt-get install curl
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
ca-certificates krb5-locales libasn1-8-heimdal libcurl4 libgssapi-krb5-2 libgssapi3-heimdal libhcrypto4-heimdal libheimbase1-heimdal libheimntlm0-heimdal libhx509-5-heimdal
libk5crypto3 libkeyutils1 libkrb5-26-heimdal libkrb5-3 libkrb5support0 libldap-2.4-2 libldap-common libnghttp2-14 libpsl5 libroken18-heimdal librtmp1 libsasl2-2 libsasl2-modules libsasl2-modules-db libsqlite3-0 libssl1.1 libwind0-heimdal openssl publicsuffix
...
root@7d93de07bf76:/# curl
curl: try 'curl --help' or 'curl --manual' for more information
接下來,再安裝 apache 服務。
root@7d93de07bf76:/# apt-get install -y apache2
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following additional packages will be installed:
apache2-bin apache2-data apache2-utils file libapr1 libaprutil1 libaprutil1-dbd-sqlite3 libaprutil1-ldap libexpat1 libgdbm-compat4 libgdbm5 libicu60 liblua5.2-0 libmagic-mgc libmagic1 libperl5.26 libxml2 mime-support netbase perl perl-modules-5.26 ssl-cert xz-utils
...
啟動這個 apache 服務,然後使用 curl 來測試本地訪問。
root@7d93de07bf76:/# service apache2 start
* Starting web server apache2 AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 172.17.0.2. Set the 'ServerName' directive globally to suppress this message
*
root@7d93de07bf76:/# curl 127.0.0.1
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
Modified from the Debian original for Ubuntu
Last updated: 2016-11-16
See: https://launchpad.net/bugs/1288690
-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Apache2 Ubuntu Default Page: It works</title>
<style type="text/css" media="screen">
...
配合使用 -p 引數對外對映服務埠,可以允許容器外來訪問該服務。
20.3.3 相關資源
Debian官網:https://www.debian.org/Neuro Debian官網:http://neuro.debian.net/Debian官方倉庫:https://github.com/DebianDebian官方映像檔:https://hub.docker.com/_/debian/Debian官方映像檔倉庫:https://github.com/tianon/docker-brew-debian/Ubuntu官網:https://ubuntu.comUbuntu官方倉庫:https://github.com/ubuntuUbuntu官方映像檔:https://hub.docker.com/_/ubuntu/Ubuntu官方映像檔倉庫:https://github.com/tianon/docker-brew-ubuntu-core
20.4 CentOS Fedora
20.4.1 CentOS 系統簡介
CentOS 和 Fedora 都是基於 Redhat 的常見 Linux 分支。傳統 CentOS Linux 曾是企業級伺服器的常用作業系統,但相關 Docker 官方映像檔已停止維護;新部署通常應評估 Rocky Linux、AlmaLinux、CentOS Stream 或直接使用 RHEL/UBI 等替代方案。Fedora 則主要面向個人桌面使用者。

CentOS (Community Enterprise Operating System,中文意思是:社群企業作業系統),它是基於 Red Hat Enterprise Linux 原始碼編譯而成。由於 CentOS 與 Redhat Linux 源於相同的程式碼基礎,所以很多成本敏感且需要高穩定性的公司就使用 CentOS 來替代商業版 Red Hat Enterprise Linux。CentOS 自身不包含閉源軟體。
使用 CentOS 官方映像檔
CentOS 官方映像檔的使用非常簡單。
注意:CentOS 8 已於 2021 年 12 月 31 日停止維護 (EOL),CentOS 7 已於 2024 年 6 月 30 日停止維護。對於新部署,推薦使用 Rocky Linux、AlmaLinux 或 CentOS Stream 等替代發行版。
使用 docker run 直接執行 CentOS 7 映像檔,並登入 bash。
$ docker run -it centos:7 bash
Unable to find image 'centos:7' locally
7: Pulling from library/centos
3d8673bd162a: Pull complete
Digest: sha256:a66ffcb73930584413de83311ca11a4cb4938c9b2521d331026dad970c19adf4
Status: Downloaded newer image for centos:7
[root@43eb3b194d48 /]# cat /etc/redhat-release
CentOS Linux release 7.9.2009 (Core)
20.4.2 Fedora 系統簡介
下圖直觀地展示了本節內容:

Fedora 由 Fedora Project 社群開發,紅帽公司贊助的 Linux 發行版。它的目標是建立一套新穎、多功能並且自由和開源的作業系統。Fedora 的功能對於使用者而言,它是一套功能完備的,可以更新的免費作業系統,而對贊助商 Red Hat 而言,它是許多新技術的測試平臺。被認為可用的技術最終會加入到 Red Hat Enterprise Linux 中。
使用 Fedora 官方映像檔
使用 docker run 命令直接執行 Fedora 官方映像檔,並登入 bash。
$ docker run -it fedora bash
Unable to find image 'fedora:latest' locally
latest: Pulling from library/fedora
2bf01635e2a0: Pull complete
Digest: sha256:64a02df6aac27d1200c2572fe4b9949f1970d05f74d367ce4af994ba5dc3669e
Status: Downloaded newer image for fedora:latest
[root@196ca341419b /]# cat /etc/redhat-release
Fedora release 43 (Forty Three)
20.4.3 相關資源
Fedora官網:https://getfedora.org/Fedora官方倉庫:https://github.com/fedora-infraFedora官方映像檔:https://hub.docker.com/_/fedora/Fedora官方映像檔倉庫:https://github.com/fedora-cloud/docker-brew-fedoraCentOS官網:https://www.centos.orgCentOS官方倉庫:https://github.com/CentOSCentOS官方映像檔:https://hub.docker.com/_/centos/CentOS官方映像檔倉庫:https://github.com/CentOS/CentOS-Dockerfiles
第二十一章 實戰案例 - DevOps
第二十一章 實戰案例 - DevOps
版本說明
本章示例中使用的映像檔版本遵循以下原則:
- GitHub Actions (
actions/checkout@v6等) 採用大版本標籤,代表最新的功能版本與穩定性的平衡 - 程式語言映像檔 (
golang:1.26-alpine、rust:1.95-alpine等) 採用次版本標籤,確保 API 穩定性同時獲得安全更新 - 資料庫映像檔 (
postgres:16-alpine、redis:8-alpine等) 採用大版本標籤,便於獲得修復和改進 - 基礎映像檔 (
alpine:3.21、debian:12等) 採用大版本或次版本標籤,避免latest以確保可再現性 - 實際專案中應根據需求調整版本,生產環境建議定期更新以獲取安全補丁
DevOps 背景介紹
DevOps 是一種重要的開發和運維文化,強調開發團隊和運維團隊之間的協作和自動化。它致力於透過自動化和流程最佳化,加快軟體交付速度,同時提高系統的穩定性和可靠性。Docker 作為容器化技術的領導者,已成為現代 DevOps 工作流中不可或缺的工具。透過容器化應用,開發團隊可以確保“一次構建,處處執行”,消除開發、測試和生產環境的差異,大大簡化了部署流程。
Docker 在 DevOps 中的角色
Docker 在 DevOps 工作流中承擔多個關鍵角色。首先,它標準化了應用的開發和部署環境,使得團隊成員在相同的 Docker 容器中工作,避免了“在我的機器上可以執行”的問題。其次,Docker 與 CI/CD 流程無縫整合,透過自動化的映像檔構建、測試和部署,實現快速的迭代週期。此外,Docker 還支援微服務架構和容器編排,使團隊能夠更靈活地擴充套件應用和管理基礎設施。
CI/CD 管道的重要性
持續整合與持續部署 (CI/CD) 是現代 DevOps 的核心。透過自動化的程式碼檢測、測試、構建和部署流程,團隊可以更加頻繁地釋出新版本,同時保持系統的穩定性和可靠性。Docker 在 CI/CD 中扮演了重要角色:
- 標準化構建環境 - Docker 確保開發、測試和生產環境完全一致,消除了環境差異帶來的問題
- 加速流水線 - 容器的快速啟動和輕量級特性,大幅加快了 CI/CD 流程的執行效率
- 靈活的測試框架 - 可以輕鬆建立短生命週期的測試容器,並行執行多個測試
- 自動化映像檔釋出 - CI/CD 工具可以自動構建、掃描、標記和推送 Docker 映像檔到倉庫
- 藍綠部署和金絲雀釋出 - 利用容器的隔離性和可重複性,實現高階釋出策略
本章將透過介紹 GitHub Actions、Drone 等流行的 CI/CD 工具,展示如何在實際專案中構建完整的自動化流水線。我們還將演示如何在本地開發環境中整合 Docker,使用 IDE 的容器開發外掛,加快本地迭代週期。
本章學習目標
透過學習本章內容,你將能夠:
- 理解 DevOps 文化、CI/CD 流程和容器化的緊密關係
- 掌握完整的 Docker 工作流,從程式碼提交到線上部署的每一個環節
- 學習如何使用 GitHub Actions 實現自動化 CI/CD,以及工作流的編寫和最佳化
- 瞭解 Drone 等第三方 CI/CD 工具的架構、部署和配置方式
- 學會在本地 IDE (VS Code) 中整合 Docker,利用容器開發工具提升開發效率
- 掌握實戰中常見的 DevOps 情境、最佳實務和故障排查方法
章節內容導航
- DevOps 完整工作流 — 從程式碼到部署的全流程
- GitHub Actions — 使用 GitHub Actions 實現 CI/CD
- Drone — Drone CI/CD 平臺簡介和配置
- Drone Demo — Drone 實戰演示和應用
- 在 IDE 中使用 Docker — IDE 與 Docker 整合的好處
- VS Code — Visual Studio Code 容器開發指南
- 實戰例子 — 真實專案中的 DevOps 應用案例
- 本章小結
21.1 DevOps 完整工作流
本章將演示一個基於 Docker、Kubernetes 和 Jenkins/GitLab CI 的完整 DevOps 工作流。
21.1.1 工作流概覽
- Code:開發人員提交程式碼到 GitLab。
- Build:GitLab CI 觸發構建任務。
- Test:執行單元測試和整合測試。
- Package:構建 Docker 映像檔並推送到 Harbor/Registry。
- Deploy (Staging):自動部署到測試環境 Kubernetes 叢集。
- Verify:人工或自動化驗證。
- Release (Production):審批後自動部署到生產環境。
21.1.2 關鍵配置示例
本節透過一組最小可用的片段,展示典型 DevOps 流程中與 Docker 相關的關鍵配置。
1. Dockerfile 多階段構建
使用 Docker 多階段構建可以有效減小映像檔體積。
## Build stage
FROM golang:1.26 AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
## Final stage
FROM alpine:3.21
WORKDIR /app
COPY --from=builder /app/main .
CMD ["./main"]
2. GitLab CI 配置
GitLab CI(.gitlab-ci.yml)配置如下:
stages:
- test
- build
- deploy
unit_test:
stage: test
image: golang:1.26
script:
- go test ./...
build_image:
stage: build
image: docker:29
services:
- docker:29-dind
script:
- echo "$CI_REGISTRY_PASSWORD" | docker login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
deploy_staging:
stage: deploy
image: dtzar/helm-kubectl
script:
- printf '%s' "$KUBE_CA_PEM" > kube-ca.crt
- kubectl config set-cluster k8s --server=$KUBE_URL --certificate-authority=kube-ca.crt --embed-certs=true
- kubectl config set-credentials admin --token=$KUBE_TOKEN
- kubectl config set-context default --cluster=k8s --user=admin
- kubectl config use-context default
- kubectl set image deployment/myapp myapp=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
-n staging
only:
- develop
21.1.3 最佳實務
- 不可變基礎設施:一旦映像檔構建完成,在各個環境(Dev、Staging、Prod)中都應該使用同一個映像檔 tag(通常是 commit hash),而不是重新構建。
- 配置分離:使用 ConfigMap 和 Secret 管理環境特定的配置,不要打包進映像檔。
- 應對 Docker Hub 限額 (Rate Limits):
- Docker Hub 對匿名拉取實施了嚴格的限制 (6 小時內約 100 次)。若在 CI/CD 中頻繁構建,極易觸發
toomanyrequests錯誤。 - 最佳策略:- 在流水線開頭始終執行安全的身份認證 (使用 PAT,而非密碼)。
- 將常用的基礎映像檔快取到自建的 Harbor/Nexus,使用 Pull-Through Cache。
- 開啟 Docker 構建引擎 (BuildKit) 的 inline 或 registry 快取功能,以降低全量拉取頻率。
- GitOps:考慮引入 ArgoCD,將部署配置也作為程式碼儲存在 Git 中,實現 Git 驅動的部署同步。
21.2 GitHub Actions
GitHub Actions 是 GitHub 推出的一款 CI/CD 工具。
我們可以在每個 job 的 step 中使用 Docker 執行構建步驟。
21.2.1 最小可用示例
更多語法、許可權模型和可用 action,請以 GitHub Actions 官方文件 為準。
在倉庫根目錄建立 .github/workflows/ci.yml:
name: CI
on:
push:
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
with:
context: .
push: false
tags: local/test:ci
該示例會在 GitHub Actions 中構建當前倉庫的 Docker 映像檔(不推送到 registry)。
21.2.2 構建並推送到 Registry
實際專案中通常需要在 CI 中構建映像檔並推送到容器 Registry。以下示例展示了多階段構建 + 登入 + 推送的完整流程:
name: Build and Push
on:
push:
branches: [main]
permissions:
contents: read
packages: write
jobs:
build-push:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/setup-buildx-action@v4
- uses: docker/build-push-action@v7
with:
context: .
push: true
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
關鍵說明:
docker/login-action負責認證,支援 Docker Hub、GHCR、ECR 等主流 Registry。cache-from/cache-to使用 GitHub Actions 原生快取(type=gha),無需額外配置即可加速增量構建。- 標籤同時使用 commit hash 和
latest,兼顧版本追溯與部署便利。
21.2.3 最佳實務
- 固定 action 的主版本(例如
@v4/@v6),避免使用@master這類浮動引用。 - 設定最小許可權(例如
contents: read),需要寫入許可權時再開啟。 - 需要依賴快取時,優先使用官方支援的快取方案(例如針對語言包管理器的 cache 或 BuildKit cache)。
- 敏感憑據(Registry 密碼、Deploy Key 等)一律透過
secrets注入,禁止硬編碼。 - 多平臺構建可在
build-push-action中新增platforms: linux/amd64,linux/arm64。
如果你需要在某個步驟裡直接執行容器映像檔(而不是構建映像檔),可以使用 docker:// 語法:
- name: Run container step
uses: docker://golang:alpine
with:
args: go version
21.3 Drone
基於 Docker 的 CI/CD 工具 Drone,所有編譯、測試的流程都在容器中進行。
開發者只需在專案中包含 .drone.yml 檔案,將程式碼推送到 git 倉庫,Drone 就能夠自動化地進行編譯、測試、釋出。
本小節以 GitHub + Drone 來演示 Drone 的工作流程。
當然在實際開發過程中,你的程式碼也許不在 GitHub 託管,那麼你可以嘗試使用 Gogs + Drone 來進行 CI/CD。
21.3.1 關聯專案
在 GitHub 新建一個名為 drone-demo 的倉庫。
開啟我們已經部署好的 Drone 網站或者 Drone Cloud,
使用 GitHub 賬號登入,在介面中關聯剛剛新建的 drone-demo 倉庫。
21.3.2 編寫專案原始碼
初始化一個 git 倉庫:
mkdir drone-demo
cd drone-demo
git init
git remote add origin [email protected]:username/drone-demo.git
這裡以一個簡單的 Go 程式為例,該程式輸出 Hello World!
編寫 app.go 檔案:
package main
import "fmt"
func main() {
fmt.Printf("Hello World!\n")
}
編寫 .drone.yml 檔案:
kind: pipeline
type: docker
name: build
steps:
- name: build
image: golang:alpine
pull: if-not-exists
environment:
KEY: VALUE
commands:
- echo $KEY
- pwd
- ls
- CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
- ./app
trigger:
branch:
- master
現在目錄結構如下:
.
├── .drone.yml
└── app.go
21.3.3 推送專案原始碼到 GitHub
git add .
git commit -m "test drone ci"
git push origin master
21.3.4 檢視專案構建過程及結果
開啟我們部署好的 Drone 網站或者 Drone Cloud,即可看到構建結果。

當然我們也可以把構建結果上傳到 GitHub、Docker Registry、 雲服務商提供的物件儲存,或者生產環境中。
21.4 Drone Demo
21.4.1 Demo 專案說明
這是一個基於 Go 語言編寫的簡單 Web 應用示例,用於演示 Drone CI 的持續整合流程。
21.4.2 目錄結構
drone_demo.app.go:簡單的 Go Web 伺服器程式碼。drone_demo.drone.yml:Drone CI 的配置檔案,定義了構建和測試流程。
21.4.3 如何使用
- 確保本地已安裝 Docker 環境。
- 將示例檔案重新命名為 Drone 期望的檔名:
bash
cp drone_demo.app.go app.go
cp drone_demo.drone.yml .drone.yml
- 將
app.go與.drone.yml推送到你的drone-demo倉庫,即可在 Drone 中看到構建結果。
21.5 在 IDE 中使用 Docker
使用 IDE 進行開發,往往要求本地安裝好工具鏈。一些 IDE 支援 Docker 容器中的工具鏈,這樣充分利用了 Docker 的優點,而無需在本地安裝。
本節關注一個核心目標:把“開發依賴”放進容器,把“原始碼編輯體驗”留在本地 IDE。
21.5.1 適用情境
- 團隊希望統一開發環境(Go/Node/Python 版本、系統依賴、編譯鏈)。
- 本地系統不方便安裝依賴(例如 Windows、公司管控環境)。
- 專案依賴較重(例如需要
gcc、資料庫用戶端、特定系統庫)。
不太適合的情境:強依賴本機 GPU/USB 裝置、或需要非常低延遲檔案 IO 的工程(此時可能需要額外調優掛載/同步策略)。
21.5.2 最小可用模式:docker compose + 開發容器
下面用一個“長期執行的開發容器”作為例子(以 Go 為例,你可以替換為 Node/Python)。
- 在專案中建立
compose.yaml(或複用你已有的 compose 檔案):
yaml
services:
dev:
image: golang:1.26
working_dir: /work
volumes:
- ./:/work
command: sleep infinity
- 啟動開發容器:
bash
docker compose up -d
- 進入容器安裝依賴/執行命令:
bash
docker compose exec dev bash
go version
go test ./...
這個模式的優點是“簡單直接、IDE 無關”,缺點是 IDE 需要額外配置 (例如配置遠端直譯器/語言服務,或使用 VS Code Dev Containers)。
21.5.3 目錄掛載與許可權建議
-
Linux 下如果遇到容器內寫檔案許可權問題,優先確保容器內使用者與宿主機 UID/GID 對齊。 VS Code Dev Containers 支援自動處理;手寫 Dockerfile/compose 時也可以顯式設定使用者。
-
如果遇到檔案變更監聽不生效(常見於 macOS/Windows 的虛擬化檔案系統), 優先使用語言/工具支援的輪詢模式或提高 watcher 限制。
21.6 VS Code
VS Code 的 Dev Containers 可以把“開發環境”放進容器,同時保留 VS Code 的編輯、補全、除錯體驗。
本節提供一個最小可用示例:把任意專案(以 Go 為例)變成“開啟即開發”的容器化環境。
21.6.1 前置條件
- 安裝 Docker Desktop(或 Linux 上的 Docker Engine)。
- VS Code 安裝擴充套件:Dev Containers(
ms-vscode-remote.remote-containers)。
21.6.2 最小示例:.devcontainer/devcontainer.json
在專案根目錄建立 .devcontainer/devcontainer.json:
{
"name": "docker-practice-dev",
"image": "golang:1.26",
"workspaceFolder": "/work",
"workspaceMount": "source=${localWorkspaceFolder},target=/work,type=bind",
"customizations": {
"vscode": {
"extensions": [
"golang.Go"
]
}
},
"postCreateCommand": "go version"
}
然後在 VS Code 命令面板選擇:
Dev Containers: Reopen in Container
VS Code 會拉取映像檔並啟動容器,隨後你就可以在容器內執行:
go test ./...
21.6.3 結合 Docker Compose(可選)
如果專案同時依賴資料庫/快取(例如 Postgres/Redis),可以使用 dockerComposeFile
把依賴一起拉起。
示例(devcontainer.json 片段):
{
"name": "compose-dev",
"dockerComposeFile": [
"../docker-compose.yml"
],
"service": "dev",
"workspaceFolder": "/work"
}
注意:service 需要對應 compose 裡的服務名。
21.7 實戰案例:Go/Rust/資料庫/微服務
本節透過實際專案案例演示如何為不同型別的應用構建最最佳化的 Docker 映像檔,以及如何使用 Docker Compose 構建完整的開發和生產環境。
21.7.1 Go 應用的最小化映像檔構建
Go 語言因其編譯為靜態二進位制和快速啟動而特別適合容器化。以下展示如何構建極小的 Go 應用映像檔。
超小 Go Web 服務
應用程式碼(main.go):
package main
import (
"fmt"
"log"
"net/http"
"os"
)
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"status":"healthy","version":"1.0.0"}`)
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
hostname, _ := os.Hostname()
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"message":"Hello from %s","version":"1.0.0"}`, hostname)
}
func main() {
http.HandleFunc("/health", healthHandler)
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/", helloHandler)
port := ":8080"
log.Printf("Server starting on %s", port)
if err := http.ListenAndServe(port, nil); err != nil {
log.Fatalf("Server failed: %v", err)
}
}
多階段 Dockerfile:
# Stage 1: 構建階段
FROM golang:1.26-alpine AS builder
WORKDIR /build
# 安裝構建依賴
RUN apk add --no-cache git ca-certificates tzdata
# 複製模組檔案(利用快取)
COPY go.mod go.sum ./
RUN go mod download
# 複製原始碼
COPY . .
# 構建靜態二進位制
# -ldflags="-w -s" 去除除錯符號減小體積
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
-a -installsuffix cgo \
-ldflags="-w -s -X main.Version=1.0.0 -X main.BuildTime=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" \
-o app .
# Stage 2: 執行階段(scratch 映像檔)
FROM scratch
# 複製 CA 證書(用於 HTTPS)
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
# 複製時區資料(用於時間處理)
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# 複製應用二進位制
COPY --from=builder /build/app /app
EXPOSE 8080
# 使用絕對路徑作為 ENTRYPOINT
ENTRYPOINT ["/app"]
構建和測試:
# 構建映像檔
docker build -t go-app:latest .
# 檢查映像檔大小
docker images go-app
# 執行容器
docker run -d -p 8080:8080 --name go-demo go-app:latest
# 測試應用
curl http://localhost:8080/health | jq .
# 進入容器驗證
docker exec go-demo ls -la /
# 只包含 /app 和系統必要檔案
# 映像檔大小通常 < 10MB(相比 golang:1.26 基礎映像檔的 ~900MB)
docker history go-app:latest
go.mod 和 go.sum 示例:
module github.com/example/go-app
go 1.26
require (
// 如果需要依賴
)
帶依賴的 Go 應用
應用程式碼(使用 Gin 框架):
package main
import (
"github.com/gin-gonic/gin"
"log"
)
func main() {
router := gin.Default()
router.GET("/health", func(c *gin.Context) {
c.JSON(200, gin.H{
"status": "ok",
})
})
router.GET("/api/users", func(c *gin.Context) {
c.JSON(200, gin.H{
"users": []string{"alice", "bob"},
})
})
log.Fatal(router.Run(":8080"))
}
最佳化的 Dockerfile:
FROM golang:1.26-alpine AS builder
WORKDIR /src
RUN apk add --no-cache git ca-certificates tzdata
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -a -installsuffix cgo \
-ldflags="-w -s" \
-o app .
# 最終映像檔
FROM alpine:3.21
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /root/
COPY --from=builder /src/app .
EXPOSE 8080
CMD ["./app"]
21.7.2 Rust 應用的最小化映像檔構建
Rust 因其效能和安全性在系統級應用中備受青睞。
應用程式碼(main.rs):
use actix_web::{web, App, HttpServer, HttpResponse};
use std::sync::Mutex;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
println!("Starting server on 0.0.0.0:8080");
HttpServer::new(|| {
App::new()
.route("/health", web::get().to(health))
.route("/hello", web::get().to(hello))
})
.bind("0.0.0.0:8080")?
.run()
.await
}
async fn health() -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"status": "healthy"
}))
}
async fn hello() -> HttpResponse {
HttpResponse::Ok().json(serde_json::json!({
"message": "Hello from Rust"
}))
}
Cargo.toml:
[package]
name = "rust-app"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "rust-app"
path = "src/main.rs"
[dependencies]
actix-web = "4.13"
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
多階段構建 Dockerfile:
# Stage 1: 編譯
FROM rust:1.95-alpine AS builder
RUN apk add --no-cache musl-dev
WORKDIR /src
COPY Cargo.* ./
COPY src ./src
# 構建最佳化的釋出版本
RUN cargo build --release
# Stage 2: 執行映像檔
FROM alpine:3.21
RUN apk add --no-cache ca-certificates
COPY --from=builder /src/target/release/rust-app /app
EXPOSE 8080
CMD ["/app"]
構建和驗證:
docker build -t rust-app:latest .
docker run -d -p 8080:8080 rust-app:latest
curl http://localhost:8080/health | jq .
# Rust 應用通常比 Go 更小:5-20MB(取決於依賴)
docker images rust-app
21.7.3 資料庫容器化最佳實務
PostgreSQL 生產部署
自定義 PostgreSQL 映像檔:
FROM postgres:16-alpine
# 安裝額外工具
RUN apk add --no-cache \
postgresql-contrib \
pg-stat-monitor \
curl
# 複製初始化指令碼
COPY init-db.sql /docker-entrypoint-initdb.d/
COPY health-check.sh /
RUN chmod +x /health-check.sh
HEALTHCHECK --interval=10s --timeout=5s --start-period=40s --retries=3 \
CMD /health-check.sh
EXPOSE 5432
初始化指令碼(init-db.sql):
-- 建立自定義使用者
CREATE USER appuser WITH PASSWORD 'secure_password';
-- 建立資料庫
CREATE DATABASE myappdb OWNER appuser;
-- 建立擴充套件
\c myappdb
CREATE EXTENSION IF NOT EXISTS uuid-ossp;
CREATE EXTENSION IF NOT EXISTS hstore;
CREATE EXTENSION IF NOT EXISTS pg_trgm;
-- 建立表
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username VARCHAR(255) NOT NULL UNIQUE,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 建立索引
CREATE INDEX idx_users_username ON users (username);
CREATE INDEX idx_users_email ON users (email);
-- 授予許可權
GRANT CONNECT ON DATABASE myappdb TO appuser;
GRANT USAGE ON SCHEMA public TO appuser;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO appuser;
GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO appuser;
健康檢查指令碼(health-check.sh):
#!/bin/bash
PGPASSWORD=$POSTGRES_PASSWORD pg_isready \
-h localhost \
-U $POSTGRES_USER \
-d $POSTGRES_DB \
-p 5432 > /dev/null 2>&1
exit $?
Docker Compose 配置:
services:
postgres:
build:
context: .
dockerfile: Dockerfile.postgres
container_name: postgres-db
environment:
POSTGRES_DB: myappdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./backups:/backups
ports:
# 只暴露給本機除錯;生產環境優先不釋出資料庫埠
- "127.0.0.1:5432:5432"
networks:
- backend
restart: unless-stopped
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# 備份服務
backup:
image: postgres:16-alpine
depends_on:
- postgres
environment:
PGPASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
volumes:
- ./backups:/backups
command: |
sh -c 'while true; do
pg_dump -h postgres -U postgres -d myappdb > /backups/backup_$$(date +%Y%m%d_%H%M%S).sql
echo "Backup completed at $$(date)"
sleep 86400
done'
networks:
- backend
volumes:
postgres_data:
driver: local
networks:
backend:
driver: bridge
效能最佳化配置:
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_DB: myappdb
command:
- "postgres"
- "-c"
- "max_connections=200"
- "-c"
- "shared_buffers=256MB"
- "-c"
- "effective_cache_size=1GB"
- "-c"
- "maintenance_work_mem=64MB"
- "-c"
- "checkpoint_completion_target=0.9"
- "-c"
- "wal_buffers=16MB"
- "-c"
- "default_statistics_target=100"
- "-c"
- "random_page_cost=1.1"
- "-c"
- "effective_io_concurrency=200"
- "-c"
- "work_mem=1310kB"
- "-c"
- "min_wal_size=1GB"
- "-c"
- "max_wal_size=4GB"
- "-c"
- "max_worker_processes=4"
- "-c"
- "max_parallel_workers_per_gather=2"
- "-c"
- "max_parallel_workers=4"
- "-c"
- "max_parallel_maintenance_workers=2"
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
volumes:
postgres_data:
MySQL/MariaDB 部署
FROM mariadb:11
# 複製自定義配置
COPY my.cnf /etc/mysql/conf.d/custom.cnf
# 初始化指令碼
COPY init.sql /docker-entrypoint-initdb.d/
EXPOSE 3306
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD mariadb-admin ping -h localhost || exit 1
自定義 my.cnf:
[mysqld]
# 效能最佳化
max_connections = 200
default_storage_engine = InnoDB
innodb_buffer_pool_size = 1GB
innodb_log_file_size = 256MB
query_cache_type = 0
query_cache_size = 0
# 日誌配置
log_error = /var/log/mysql/error.log
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# 複製配置
server_id = 1
log_bin = mysql-bin
binlog_format = ROW
Redis 快取部署
FROM redis:8-alpine
# 複製 Redis 配置
COPY redis.conf /usr/local/etc/redis/redis.conf
# 使用配置檔案啟動
CMD ["redis-server", "/usr/local/etc/redis/redis.conf"]
EXPOSE 6379
HEALTHCHECK --interval=5s --timeout=3s --retries=5 \
CMD redis-cli ping || exit 1
redis.conf 配置:
# 繫結地址
bind 0.0.0.0
# 埠
port 6379
# 密碼保護
requirepass your_secure_password
# 記憶體管理
maxmemory 512mb
maxmemory-policy allkeys-lru
# 持久化
save 900 1
save 300 10
save 60 10000
# AOF 持久化
appendonly yes
appendfsync everysec
# 日誌
loglevel notice
logfile ""
# 用戶端輸出緩衝限制
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit replica 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
21.7.4 微服務架構的 Docker Compose 編排
三層微服務架構示例:
services:
# 前端服務
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
container_name: frontend
ports:
- "3000:3000"
environment:
REACT_APP_API_URL: http://localhost:8000
NODE_ENV: production
depends_on:
- api
networks:
- frontend-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 30s
timeout: 10s
retries: 3
# API 服務
api:
build:
context: ./api
dockerfile: Dockerfile
container_name: api
ports:
- "8000:8000"
environment:
DATABASE_URL: postgresql://appuser:password@postgres:5432/myappdb
REDIS_URL: redis://redis:6379
LOG_LEVEL: info
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
networks:
- frontend-network
- backend-network
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
deploy:
resources:
limits:
cpus: '1'
memory: 512M
reservations:
cpus: '0.5'
memory: 256M
# PostgreSQL 資料庫
postgres:
image: postgres:16-alpine
container_name: postgres
environment:
POSTGRES_DB: myappdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- backend-network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d myappdb"]
interval: 10s
timeout: 5s
retries: 5
# Redis 快取
redis:
image: redis:8-alpine
container_name: redis
command: redis-server --appendonly yes --requirepass redispass
volumes:
- redis_data:/data
networks:
- backend-network
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
interval: 10s
timeout: 5s
retries: 5
# Nginx 反向代理
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/conf.d:/etc/nginx/conf.d:ro
- ./ssl:/etc/nginx/ssl:ro
depends_on:
- frontend
- api
networks:
- frontend-network
restart: unless-stopped
healthcheck:
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost/health"]
interval: 30s
timeout: 10s
retries: 3
volumes:
postgres_data:
driver: local
redis_data:
driver: local
networks:
frontend-network:
driver: bridge
backend-network:
driver: bridge
nginx.conf 配置:
upstream frontend {
server frontend:3000;
}
upstream api {
server api:8000;
}
server {
listen 80;
server_name localhost;
client_max_body_size 100M;
# 健康檢查端點
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# 前端應用
location / {
proxy_pass http://frontend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# API 介面
location /api/ {
proxy_pass http://api/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_redirect off;
# WebSocket 支援
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# 靜態資源快取
location ~* ^.+\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
proxy_pass http://frontend;
expires 30d;
add_header Cache-Control "public, immutable";
}
}
21.7.5 使用 VS Code Dev Containers
Dev Containers 讓整個開發環境容器化,提升團隊一致性。
.devcontainer/devcontainer.json:
{
"name": "Python Dev Environment",
"image": "mcr.microsoft.com/devcontainers/python:3.14",
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/devcontainers/features/git:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.pylint",
"charliermarsh.ruff",
"ms-vscode-remote.remote-containers"
],
"settings": {
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.provider": "black",
"[python]": {
"editor.formatOnSave": true,
"editor.defaultFormatter": "ms-python.python"
}
}
}
},
"postCreateCommand": "pip install -r requirements.txt && pip install pytest black pylint",
"forwardPorts": [8000, 5432, 6379],
"portsAttributes": {
"8000": {
"label": "Application",
"onAutoForward": "notify"
},
"5432": {
"label": "PostgreSQL",
"onAutoForward": "ignore"
},
"6379": {
"label": "Redis",
"onAutoForward": "ignore"
}
},
"mounts": [
"source=${localEnv:HOME}/.ssh,target=/home/vscode/.ssh,readonly"
],
"remoteUser": "vscode"
}
.devcontainer/Dockerfile:
FROM mcr.microsoft.com/devcontainers/python:3.14
# 安裝額外工具
RUN apt-get update && apt-get install -y \
postgresql-client \
redis-tools \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# 建立虛擬環境
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
WORKDIR /workspace
Docker Compose 用於 Dev Containers:
# .devcontainer/docker-compose.yml
services:
app:
build:
context: .
dockerfile: Dockerfile
environment:
DATABASE_URL: postgresql://dev:dev@postgres:5432/myapp
REDIS_URL: redis://redis:6379
volumes:
- ..:/workspace:cached
ports:
- "8000:8000"
depends_on:
- postgres
- redis
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: myapp
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:8-alpine
volumes:
postgres_data:
本章小結
本章透過一個完整的 DevOps 工作流示例,串聯了從程式碼提交、自動化測試、映像檔構建、 到部署釋出的一整套實踐路徑。
在落地時,建議重點把握以下原則:
- 不可變交付物:同一份產物(映像檔)在不同環境中流轉,使用 commit hash 等方式標識版本。
- 最小許可權與金鑰管理:CI 平臺許可權盡量收斂,敏感資訊使用 Secret 管理並定期輪換。
- 流水線可觀測與可回滾:為關鍵步驟保留日誌與製品,釋出失敗時能快速定位並回滾。
- 開發環境一致性:透過 Dev Containers / compose 開發容器減少“在我電腦上可以”的問題。
下一步你可以根據團隊現狀選擇一條主線深入:
- 已在用 GitHub:優先補全 Actions 的快取、製品、釋出策略。
- 自建體系:結合私有 Registry、Kubernetes 與 GitOps 工具完善部署與審計。
附錄
附錄
本附錄彙總了 Docker 相關的常見問題、映像檔參考、命令速查、最佳實務、除錯方法、官方資源、術語表與學習路線,便於按需查閱。
目錄
- 附錄一:常見問題與錯誤速查:彙總學習和使用 Docker 過程中的常見問題與錯誤解決方案。
- 附錄二:熱門映像檔介紹:介紹常用官方映像檔的用途、基本用法與適用情境。
- 附錄三:Docker 命令查詢:速查 Docker 用戶端和伺服器端的常用命令。
- 附錄四:Dockerfile 最佳實務:提供編寫高效、安全 Dockerfile 的指導原則。
- 附錄五:如何除錯 Docker:介紹 Docker 除錯技巧和工具。
- 附錄六:資源連結:提供官方文件、釋出頁和參考入口。
- 附錄七:術語表:統一全書中英文術語、縮寫與命令寫法。
- 附錄八:Docker 學習路線圖與知識體系:給出階段化學習路徑和知識地圖。
附錄一:常見問題與錯誤速查
更多錯誤排查條目見 常見錯誤處理。
映像檔相關
如何批次清理臨時映像檔檔案?
答:可以使用 docker image prune 命令。
如何檢視映像檔支援的環境變數?
答:可以使用 docker run IMAGE env 命令。
本地的映像檔檔案都存放在哪裡?
答:與 Docker 相關的本地資源預設存放在 /var/lib/docker/ 目錄下,以 overlay2 檔案系統為例,其中 containers 目錄存放容器資訊,image 目錄存放映像檔資訊,overlay2 目錄下存放具體的映像檔層檔案。
構建 Docker 映像檔應該遵循哪些原則?
答:整體原則上,儘量保持映像檔功能的明確和內容的精簡,要點包括
- 儘量選取滿足需求但較小的基礎系統映像檔,例如大部分時候可以選擇
alpine映像檔,僅有不足六兆大小; - 清理編譯生成檔案、安裝包的快取等臨時檔案;
- 安裝各個軟體時候要指定準確的版本號,並避免引入不需要的依賴;
- 從安全形度考慮,應用要儘量使用系統的庫和依賴;
- 如果安裝應用時候需要配置一些特殊的環境變數,在安裝後要還原不需要保持的變數值;
- 使用 Dockerfile 建立映像檔時候要新增。dockerignore 檔案或使用乾淨的工作目錄。
更多內容請檢視 Dockerfile 最佳實務
碰到網路問題,無法 pull 映像檔,命令列指定 http_proxy 無效?
答:先區分代理要作用在哪一層。Docker daemon 拉取映像檔時,推薦在 daemon.json 的 proxies 欄位或 systemd drop-in 中配置 HTTP_PROXY / HTTPS_PROXY / NO_PROXY,然後重啟 Docker 服務。Docker CLI、構建過程和容器內應用的代理應分別使用 ~/.docker/config.json、--build-arg 或 docker run --env 配置;不要把 export http_proxy=... 當作 daemon.json 內容寫入。
容器相關
容器退出後,透過 docker container ls 命令檢視不到,資料會丟失麼?
答:容器退出後會處於終止 (exited) 狀態,此時可以透過 docker container ls -a 檢視。其中的資料也不會丟失,還可以透過 docker start 命令來啟動它。只有刪除掉容器才會清除所有資料。
如何停止所有正在執行的容器?
答:可以使用 docker stop $(docker container ls -q) 命令。
如何批次清理已經停止的容器?
答:可以使用 docker container prune 命令。
如何獲取某個容器的 PID 資訊?
答:可以使用
docker inspect --format '{{ .State.Pid }}' <CONTAINER ID or NAME>
如何獲取某個容器的 IP 地址?
答:可以使用
docker inspect --format '{{ .NetworkSettings.IPAddress }}' <CONTAINER ID or NAME>
如何給容器指定一個固定 IP 地址,而不是每次重啟容器 IP 地址都會變?
答:使用以下命令啟動容器可以使容器 IP 固定不變
$ docker network create -d bridge --subnet 172.25.0.0/16 my-net
$ docker run --network=my-net --ip=172.25.3.3 -itd --name=my-container busybox
如何臨時退出一個正在互動的容器的終端,而不終止它?
答:按 Ctrl-p Ctrl-q。如果按 Ctrl-c 往往會讓容器內應用程序終止,進而會終止容器。
使用 docker port 命令對映容器的埠時,系統報錯 “Error:No public port ‘80’ published for xxx”?
答:
- 建立容器時用
-p HOST_PORT:CONTAINER_PORT或--publish顯式釋出埠,例如docker run -p 8080:80 nginx; - 只想把映像檔宣告的
EXPOSE埠隨機發布到宿主機埠時,用-P/--publish-all,之後透過docker ps檢視實際埠; Dockerfile中的EXPOSE只是映像檔後設資料,不會自動釋出埠。
可以在一個容器中同時執行多個應用程序麼?
答:一般並不推薦在同一個容器內執行多個應用程序。如果有類似需求,可以透過一些額外的程序管理機制,比如 supervisord 來管理所執行的程序。可以參考 Docker 官方說明。
如何控制容器佔用 CPU、記憶體等系統資源的份額?
答:在使用 docker create 命令建立容器或使用 docker run 建立並啟動容器的時候,可以使用 -c|--cpu-shares[=0] 引數來調整容器使用 CPU 的權重;使用 -m|--memory[=MEMORY] 引數來調整容器使用記憶體的大小。
倉庫相關
倉庫、註冊伺服器、註冊索引有何關係?
首先,倉庫是存放一組關聯映像檔的集合,比如同一個應用的不同版本的映像檔。
註冊伺服器是存放實際的映像檔檔案的地方。註冊索引則負責維護使用者的賬號、許可權、搜尋、標籤等的管理。因此,註冊伺服器利用註冊索引來實現認證等管理。
配置相關
Docker 的配置檔案放在哪裡,如何修改配置?
答:使用 systemd 的系統 (如 Ubuntu 22.04+、Debian 12+、Rocky/Alma/CentOS Stream 9+) 的配置檔案在 /etc/docker/daemon.json。
如何更改 Docker 的預設儲存位置?
答:Docker 的預設儲存位置是 /var/lib/docker,如果希望將 Docker 的本地檔案儲存到其他分割槽,可以使用 Linux 軟連線的方式來完成,或者修改配置檔案 /etc/docker/daemon.json 的 data-root 項。可以使用 docker info | grep "Docker Root Dir" 檢視當前使用的儲存位置。
例如,如下操作將預設儲存位置遷移到 /storage/docker。
[root@s26 ~]# df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root 50G 5.3G 42G 12% /
tmpfs 48G 228K 48G 1% /dev/shm
/dev/sda1 485M 40M 420M 9% /boot
/dev/mapper/VolGroup-lv_home 222G 188M 210G 1% /home
/dev/sdb2 2.7T 323G 2.3T 13% /storage
[root@s26 ~]# service docker stop
[root@s26 ~]# cd /var/lib/
[root@s26 lib]# mv docker /storage/
[root@s26 lib]# ln -s /storage/docker/ docker
[root@s26 lib]# ls -la docker
lrwxrwxrwx. 1 root root 15 11月 17 13:43 docker -> /storage/docker
[root@s26 lib]# service docker start
使用記憶體和 swap 限制啟動容器時候報核心不支援警告?
答:如果遇到 WARNING: Your kernel does not support cgroup swap limit 等警告,這是因為系統預設沒有開啟對記憶體和 swap 使用的統計功能,引入該功能會帶來效能的下降。要開啟該功能,可以採取如下操作:
- 編輯
/etc/default/grub檔案 (Ubuntu 系統為例),配置GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" - 更新 grub:
$ sudo update-grub - 重啟系統,即可。
Docker 與虛擬化
Docker 與 LXC 有何不同?
答:LXC 利用 Linux 上相關技術實現了容器。Docker 則在如下的幾個方面進行了改進:
- 移植性:透過抽象容器配置,容器可以實現從一個平臺移植到另一個平臺;
- 映像檔系統:基於 OverlayFS 的映像檔系統為容器的分發帶來了很多的便利,同時共同的映像檔層只需要儲存一份,實現高效率的儲存;
- 版本管理:類似於 Git 的版本管理理念,使用者可以更方便的建立、管理映像檔檔案;
- 倉庫系統:倉庫系統大大降低了映像檔的分發和管理的成本;
- 周邊工具:各種現有工具 (配置管理、雲平臺) 對 Docker 的支援,以及基於 Docker 的 PaaS、CI 等系統,讓 Docker 的應用更加方便和多樣化。
Docker 與 Vagrant 有何不同?
答:兩者的定位完全不同。
- Vagrant 類似 Boot2Docker (一款執行 Docker 的最小核心),是一套虛擬機器的管理環境。Vagrant 可以在多種系統上和虛擬機器軟體中執行,可以在 Windows,Mac 等非 Linux 平臺上為 Docker 提供支援,自身具有較好的包裝性和移植性。
- 原生的 Docker 自身只能執行在 Linux 平臺上,但啟動和執行的效能都比虛擬機器要快,往往更適合快速開發和部署應用的情境。
簡單說:Vagrant 適合用來管理虛擬機器,而 Docker 適合用來管理應用環境。
開發環境中 Docker 和 Vagrant 該如何選擇?
答:Docker 不是虛擬機器,而是程序隔離,對於資源的消耗很少,但是目前需要 Linux 環境支援。Vagrant 是虛擬機器上做的封裝,虛擬機器本身會消耗資源。
如果本地使用的 Linux 環境,推薦都使用 Docker。
如果本地使用的是 macOS 或者 Windows 環境,那就需要開虛擬機器,單一開發環境下 Vagrant 更簡單;多環境開發下推薦在 Vagrant 裡面再使用 Docker 進行環境隔離。
其它
Docker 能在非 Linux 平臺上執行麼?比如 Windows 或 macOS
答:完全可以。安裝方法請檢視安裝 Docker 一節
如何將一臺宿主主機的 Docker 環境遷移到另外一臺宿主主機?
答:停止 Docker 服務。將整個 Docker 儲存資料夾複製到另外一臺宿主主機,然後調整另外一臺宿主主機的配置即可。
如何進入 Docker 容器的網路名稱空間?
答:Docker 在建立容器後,刪除了宿主主機上 /var/run/netns 目錄中的相關的網路名稱空間檔案。因此,在宿主主機上是無法看到或訪問容器的網路名稱空間的。
使用者可以透過如下方法來手動恢復它。
首先,使用下面的命令檢視容器程序資訊,比如這裡的 1234。
$ docker inspect --format='{{ .State.Pid }}' $container_id
1234
接下來,在 /proc 目錄下,把對應的網路名稱空間檔案連結到 /var/run/netns 目錄。
$ sudo ln -s /proc/1234/ns/net /var/run/netns/
然後,在宿主主機上就可以看到容器的網路名稱空間資訊。例如
$ sudo ip netns show
1234
此時,使用者可以透過正常的系統命令來檢視或操作容器的名稱空間了。例如修改容器的 IP 地址資訊為 172.17.0.100/16。
$ sudo ip netns exec 1234 ifconfig eth0 172.17.0.100/16
如何獲取容器繫結到本地那個 veth 介面上?
答:Docker 容器啟動後,會透過 veth 介面對連線到本地網橋,veth 介面命名跟容器命名毫無關係,十分難以找到對應關係。
最簡單的一種方式是透過檢視介面的索引號,在容器中執行 ip a 命令,檢視到本地介面最前面的介面索引號,如 205,將此值加上 1,即 206,然後在本地主機執行 ip a 命令,查詢介面索引號為 206 的介面,兩者即為連線的 veth 介面對。
常見錯誤速查表
| 錯誤資訊 / 現象 | 可能原因 | 解決方案 |
|---|---|---|
Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? |
Docker 服務未啟動 | Linux: sudo systemctl start dockerMac/Win: 啟動 Docker Desktop |
permission denied while trying to connect to the Docker daemon socket |
當前使用者不在 docker 使用者組 |
sudo usermod -aG docker $USER (需重新登入) |
manifest for ... not found: manifest unknown |
映像檔 tag 不存在 | 檢查 Docker Hub 該映像檔是否存在該 tag,或拼寫是否正確 |
connection refused (pull image) |
網路不通或映像檔源無法訪問 | 檢查網路,配置映像檔加速器 |
Bind for 0.0.0.0:8080 failed: port is already allocated |
埠被佔用 | 檢查佔用埠的程序 (lsof -i:8080) 並殺掉,或換個埠對映 (-p 8081:80) |
exec user process caused "exec format error" |
架構不匹配 (如在 x86 上跑 ARM 映像檔) | 使用 docker buildx 構建多架構映像檔,或拉取對應架構的映像檔 |
standard_init_linux.go:211: exec user process caused "no such file or directory" |
找不到直譯器或依賴庫 | 檢查 ENTRYPOINT/CMD 指令碼開頭的 shebang (#!/bin/sh vs #!/bin/bash),或確認二進位制檔案是否依賴缺失 (Alpine 常見缺少 glibc) |
iptables: No chain/target/match by that name |
防火牆規則缺失或衝突 | 重啟 Docker 服務重置 iptables 鏈: sudo systemctl restart docker |
| 容器內無法訪問外網 | DNS 配置或轉發問題 | 檢查 /etc/docker/daemon.json 中的 DNS 配置 |
附錄二:熱門映像檔介紹
本附錄介紹常用官方映像檔的用途、基本用法與適用情境,便於快速查閱。
目錄
Ubuntu
基本資訊
Ubuntu 是流行的 Linux 發行版,其自帶軟體版本往往較新一些。
該倉庫位於 Docker Hub 的 Ubuntu 官方映像檔頁。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
預設會啟動一個最小化的 Ubuntu 環境。
$ docker run --name some-ubuntu -it ubuntu:24.04
root@523c70904d54:/#
Dockerfile
請到 Ubuntu 官方映像檔文件目錄 檢視。
CentOS
基本資訊
CentOS 是流行的 Linux 發行版,其軟體包大多跟 RedHat 系列保持一致。
⚠️ 重要提示:CentOS 8 已於 2021 年 12 月 31 日停止維護 (EOL),CentOS 7 也已於 2024 年 6 月 30 日 完全結束支援。Docker Hub 上的 CentOS 官方映像檔 已停止更新 且存在未修復的安全漏洞。
2026 年了,對於任何新專案,強烈建議 使用以下生產級替代方案: - Rocky Linux:CentOS 原創始人發起的社群驅動專案,目前主流為 Rocky Linux 9。 - AlmaLinux:由 CloudLinux 支援的企業級發行版,提供長期支援。 - CentOS Stream:RHEL 的上游開發分支,映像檔已遷移至 Quay.io (適合開發測試,不建議用於生產環境)。
該倉庫位於 Docker Hub 的 CentOS 官方映像檔頁,提供了 CentOS 從 5 ~ 8 各個版本的映像檔(僅作為歷史歸檔,不再更新)。
使用方法
使用 Rocky Linux 9 替代 (推薦):
$ docker run --name rocky -it rockylinux:9 bash
使用舊版 CentOS 7 (僅用於維護舊專案,不推薦):
$ docker run --name centos -it centos:7 bash
Dockerfile
請到 CentOS 官方映像檔文件目錄 檢視。
Nginx
基本資訊
Nginx 是開源的高效的 Web 伺服器實現,支援 HTTP、HTTPS、SMTP、POP3、IMAP 等協議。
該倉庫位於 Docker Hub 的 Nginx 官方映像檔頁。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
下面的命令將作為一個靜態頁面伺服器啟動。
$ docker run --name some-nginx -v /some/content:/usr/share/nginx/html:ro -d nginx
使用者也可以不使用這種對映方式,透過利用 Dockerfile 來直接將靜態頁面內容放到映像檔中,內容為
FROM nginx
COPY static-html-directory /usr/share/nginx/html
之後生成新的映像檔,並啟動一個容器。
$ docker build -t some-content-nginx .
$ docker run --name some-nginx -d some-content-nginx
開放埠,並對映到本地的 8080 埠。
$ docker run --name some-nginx -d -p 8080:80 some-content-nginx
Nginx 的預設配置檔案路徑為 /etc/nginx/nginx.conf,可以透過對映它來使用本地的配置檔案,例如
$ docker run -d \
--name some-nginx \
-p 8080:80 \
-v /path/nginx.conf:/etc/nginx/nginx.conf:ro \
nginx
Dockerfile
請到 Nginx 官方映像檔文件目錄 檢視。
PHP
基本資訊
PHP (Hypertext Preprocessor 超文字前處理器的字母縮寫) 是一種被廣泛應用的開放原始碼的多用途指令碼語言,它可嵌入到 HTML 中,尤其適合 web 開發。
該倉庫位於 Docker Hub 的 PHP 官方映像檔頁。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
下面的命令將執行一個已有的 PHP 指令碼。
$ docker run -it --rm -v "$PWD":/app -w /app php:alpine php your-script.php
Dockerfile
請到 PHP 官方映像檔文件目錄 檢視。
Node.js
基本資訊
Node.js 是基於 JavaScript 的可擴充套件伺服器端和網路軟體開發平臺。
該倉庫位於 https://hub.docker.com/_/node/。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
在專案中建立一個 Dockerfile。
FROM node:22
## replace this with your application's default port
EXPOSE 8888
然後建立映像檔,並啟動容器。
$ docker build -t my-nodejs-app
$ docker run -it --rm --name my-running-app my-nodejs-app
也可以直接執行一個簡單容器。
$ docker run -it --rm \
--name my-running-script \
# -v "$ ":/usr/src/myapp \
--mount type=bind,src="$(pwd)",target=/usr/src/myapp \
-w /usr/src/myapp \
node:22-alpine \
node your-daemon-or-script.js
Dockerfile
請到 Node 官方映像檔文件目錄 檢視。
MySQL
基本資訊
MySQL 是開源的關聯式資料庫實現。
該倉庫位於 Docker Hub 的 MySQL 官方映像檔頁。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
預設會在 3306 埠啟動資料庫。
$ docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=mysecretpassword -d mysql
之後就可以使用其它應用來連線到該容器。
首先建立網路
$ docker network create my-mysql-net
然後啟動 MySQL 容器
$ docker run --name some-mysql -d --network my-mysql-net -e MYSQL_ROOT_PASSWORD=mysecretpassword mysql
最後啟動應用容器
$ docker run --name some-app -d --network my-mysql-net application-that-uses-mysql
或者透過 mysql 命令列連線。
$ docker run -it --rm \
--network my-mysql-net \
mysql \
sh -c 'exec mysql -hsome-mysql -P3306 -uroot -pmysecretpassword'
Dockerfile
請到 MySQL 官方映像檔文件目錄 檢視。
WordPress
基本資訊
WordPress 是開源的 Blog 和內容管理系統框架,它基於 PHP 和 MySQL。
該倉庫位於 https://hub.docker.com/_/wordpress/。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
啟動容器需要 MySQL 的支援,預設埠為 80。
首先建立網路
$ docker network create my-wordpress-net
啟動 MySQL 容器
$ docker run --name some-mysql -d --network my-wordpress-net -e MYSQL_ROOT_PASSWORD=mysecretpassword mysql
啟動 WordPress 容器
$ docker run --name some-wordpress -d --network my-wordpress-net -e WORDPRESS_DB_HOST=some-mysql -e WORDPRESS_DB_PASSWORD=mysecretpassword wordpress
啟動 WordPress 容器時可以指定的一些環境變數包括:
WORDPRESS_DB_HOST:MySQL 服務的主機名WORDPRESS_DB_USER:MySQL 資料庫的使用者名稱WORDPRESS_DB_PASSWORD:MySQL 資料庫的密碼WORDPRESS_DB_NAME:WordPress 要使用的資料庫名
Dockerfile
請到 WordPress 官方映像檔文件目錄 檢視。
MongoDB
基本資訊
MongoDB 是開源的 NoSQL 資料庫實現。
該倉庫位於 https://hub.docker.com/_/mongo/。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
預設會在 27017 埠啟動資料庫。
$ docker run --name mongo -d mongo
使用其他應用連線到容器,首先建立網路
$ docker network create my-mongo-net
然後啟動 MongoDB 容器
$ docker run --name some-mongo -d --network my-mongo-net mongo
最後啟動應用容器
$ docker run --name some-app -d --network my-mongo-net application-that-uses-mongo
或者透過 mongosh(MongoDB 6.0+ 已移除舊版 mongo 命令列工具)
$ docker run -it --rm \
--network my-mongo-net \
mongo \
mongosh "some-mongo:27017/test"
Dockerfile
請到 Mongo 官方映像檔文件目錄 檢視。
Redis
基本資訊
Redis 是開源的記憶體 Key-Value 資料庫實現。
該倉庫位於 Docker Hub 的 Redis 官方映像檔頁。具體可用版本以 Docker Hub 上的 tags 列表為準。
使用方法
預設會在 6379 埠啟動資料庫。
$ docker run --name some-redis -d -p 6379:6379 redis
另外還可以啟用持久儲存。
$ docker run --name some-redis -d -p 6379:6379 redis redis-server --appendonly yes
預設資料儲存位置在 VOLUME/data。可以使用 --volumes-from some-volume-container 或 -v /docker/host/dir:/data 將資料存放到本地。
使用其他應用連線到容器,首先建立網路
$ docker network create my-redis-net
然後啟動 redis 容器
$ docker run --name some-redis -d --network my-redis-net redis
最後啟動應用容器
$ docker run --name some-app -d --network my-redis-net application-that-uses-redis
或者透過 redis-cli
$ docker run -it --rm \
--network my-redis-net \
redis \
sh -c 'exec redis-cli -h some-redis'
Dockerfile
請到 Redis 官方映像檔文件目錄 檢視。
Minio
MinIO 是一個基於 Apache License v2.0 開源協議的物件儲存服務。它相容亞馬遜 S3 雲端儲存服務介面,非常適合於儲存大容量非結構化的資料,例如圖片、影片、日誌檔案、備份資料和容器/虛擬機器映像檔等,而一個物件檔案可以是任意大小,從幾 kb 到最大 5T 不等。
MinIO 是一個非常輕量的服務,可以很簡單的和其他應用的結合,類似 NodeJS,Redis 或者 MySQL。
簡單使用
測試、開發環境下不考慮資料儲存的情況下可以使用下面的命令快速開啟服務。
$ docker run -d -p 9000:9000 -p 9090:9090 minio/minio server /data --console-address ':9090'
離線部署
許多生產環境是一般是沒有公網資源的,這就需要從有公網資源的伺服器上把映像檔匯出,然後匯入到需要執行映像檔的內網伺服器。
匯出映像檔
在有公網資源的伺服器上下載好 minio/minio 映像檔
$ docker save -o minio.tar minio/minio:latest
使用 docker save 的時候,也可以使用 image id 來匯出,但是那樣匯出的時候,就會丟失原來的映像檔名稱,推薦,還是使用映像檔名字+tag 來匯出映像檔
匯入映像檔
把壓縮檔案複製到內網伺服器上,使用下面的命令匯入映像檔
$ docker load -i minio.tar
執行 minio
- 把
/mnt/data改成要替換的資料目錄 - 替換
MINIO_ROOT_USER的值 - 替換
MINIO_ROOT_PASSWORD的值 - 替換 name,minio1 (可選)
- 如果 9000、9090 埠衝突,替換埠前面的如
9009:9000
$ sudo docker run -d -p 9000:9000 -p 9090:9090 --name minio1 \
-e "MINIO_ROOT_USER=改成自己需要的" \
-e "MINIO_ROOT_PASSWORD=改成自己需要的" \
-v /mnt/data:/data \
--restart=always \
minio/minio server /data --console-address ':9090'
訪問 web 管理頁面
開啟 http://<server-ip>:9090 訪問 Web 控制檯。
附錄三:Docker 命令查詢
基本語法
Docker 命令有兩大類,用戶端命令和伺服器端命令。前者是主要的操作介面,後者用來啟動 Docker Daemon。
-
用戶端命令:基本命令格式為
docker [OPTIONS] COMMAND [arg...]; -
伺服器端命令:基本命令格式為
dockerd [OPTIONS]。
可以透過 man docker 或 docker help 來檢視這些命令。
接下來的小節對這兩個命令進行介紹。
用戶端命令 - docker
用戶端命令選項
--config="":指定用戶端配置檔案,預設為~/.docker;-D=true|false:是否使用 debug 模式。預設不開啟;-H, --host=[]:指定命令對應 Docker 守護程序的監聽介面,可以為 unix 套接字unix:///path/to/socket,檔案控制代碼fd://socketfd或 tcp 套接字tcp://[host[:port]],預設為unix:///var/run/docker.sock;-l, --log-level="debug|info|warn|error|fatal":指定日誌輸出級別;--tls=true|false:是否對 Docker 守護程序啟用 TLS 安全機制,預設為否;--tlscacert=/.docker/ca.pem:TLS CA 簽名的可信證書檔案路徑;--tlscert=/.docker/cert.pem:TLS 可信證書檔案路徑;--tlskey=/.docker/key.pem:TLS 金鑰檔案路徑;--tlsverify=true|false:啟用 TLS 校驗,預設為否。
用戶端命令
可以透過 docker COMMAND --help 來檢視這些命令的具體用法。
attach:依附到一個正在執行的容器中;build:從一個 Dockerfile 建立一個映像檔;commit:從一個容器的修改中建立一個新的映像檔;cp:在容器和本地宿主系統之間複製檔案中;create:建立一個新容器,但並不執行它;diff:檢查一個容器內檔案系統的修改,包括修改和增加;events:從伺服器端獲取實時的事件;exec:在執行的容器內執行命令;export:匯出容器內容為一個tar包;history:顯示一個映像檔的歷史資訊;images:列出存在的映像檔;import:匯入一個檔案 (典型為tar包) 路徑或目錄來建立一個本地映像檔;info:顯示一些相關的系統資訊;inspect:顯示一個容器的具體配置資訊;kill:關閉一個執行中的容器 (包括程序和所有相關資源);load:從一個 tar 包中載入一個映像檔;login:註冊或登入到一個 Docker 的倉庫伺服器;logout:從 Docker 的倉庫伺服器登出;logs:獲取容器的 log 資訊;network:管理 Docker 的網路,包括檢視、建立、刪除、掛載、解除安裝等;node:管理 swarm 叢集中的節點,包括檢視、更新、刪除、提升/取消管理節點等;pause:暫停一個容器中的所有程序;port:查詢一個 nat 到一個私有網口的公共口;ps:列出主機上的容器;pull:從一個 Docker 的倉庫伺服器下拉一個映像檔或倉庫;push:將一個映像檔或者倉庫推送到一個 Docker 的註冊伺服器;rename:重新命名一個容器;restart:重啟一個執行中的容器;rm:刪除給定的若干個容器;rmi:刪除給定的若干個映像檔;run:建立一個新容器,並在其中執行給定命令;save:儲存一個映像檔為 tar 包檔案;search:在 Docker index 中搜尋一個映像檔;service:管理 Docker 所啟動的應用服務,包括建立、更新、刪除等;start:啟動一個容器;stats:輸出 (一個或多個) 容器的資源使用統計資訊;stop:終止一個執行中的容器;swarm:管理 Docker swarm 叢集,包括建立、加入、退出、更新等;tag:為一個映像檔打標籤;top:檢視一個容器中的正在執行的程序資訊;unpause:將一個容器內所有的程序從暫停狀態中恢復;update:更新指定的若干容器的配置資訊;version:輸出 Docker 的版本資訊;volume:管理 Docker volume,包括檢視、建立、刪除等;wait:阻塞直到一個容器終止,然後輸出它的退出符。
一張圖總結 Docker 的命令
如圖 16-1 所示,Docker 常用用戶端命令可按功能分組理解。

圖 A-1:Docker 用戶端命令分類示意圖
參考
伺服器端命令 - dockerd
使用說明
dockerd 引數會隨版本變化。建議優先在目標機器上執行 dockerd --help,並以 daemon.json 為主進行持久化配置。
常用選項:Docker Engine 29.x
--config-file="/etc/docker/daemon.json":指定 daemon 配置檔案路徑;--data-root="":Docker 資料目錄 (預設/var/lib/docker);-H, --host=[]:指定 daemon 監聽地址 (Unix socket / TCP);-D, --debug:開啟除錯日誌;-l, --log-level="debug|info|warn|error|fatal":日誌級別;--group="":Unix socket 所屬使用者組 (預設docker);--containerd="":指定 containerd socket;--exec-opt=[]:執行時執行選項 (如 cgroup 驅動);--default-ulimit=[]:設定容器預設 ulimit;--dns=[]/--dns-search=[]/--dns-opt=[]:DNS 配置;--registry-mirror=[]:映像檔加速地址;--insecure-registry=[]:允許訪問不安全倉庫;--iptables=true|false/--ip-forward=true|false/--ip-masq=true|false:網路轉發與 NAT 規則控制;--ipv6=true|false:啟用 IPv6;--storage-driver=""/--storage-opt=[]:儲存驅動及引數;--log-driver=""/--log-opt=[]:容器日誌驅動與引數;--authorization-plugin=[]:鑑權外掛;--selinux-enabled=true|false:啟用 SELinux 整合 (依賴發行版策略);--userns-remap=...:使用者名稱空間對映;--tls/--tlscacert/--tlscert/--tlskey/--tlsverify:TLS 安全配置。
歷史引數提示
以下引數已移除或不建議繼續使用:
--graph:請改用--data-root;--cluster-store/--cluster-advertise/--cluster-store-opt:已移除;--disable-legacy-registry:已移除。
參考
附錄四:Dockerfile 最佳實務
本附錄是筆者對 Docker 官方文件中 Best practices for writing Dockerfiles 的理解與翻譯。
一般性的指南和建議
容器應該是短暫的
透過 Dockerfile 構建的映像檔所啟動的容器應該儘可能短暫 (生命週期短)。“短暫” 意味著可以停止和銷燬容器,並且建立一個新容器並部署好所需的設定和配置工作量應該是極小的。
使用 .dockerignore 檔案
使用 Dockerfile 構建映像檔時最好是將 Dockerfile 放置在一個新建的空目錄下。然後將構建映像檔所需要的檔案新增到該目錄中。為了提高構建映像檔的效率,你可以在目錄下新建一個 .dockerignore 檔案來指定要忽略的檔案和目錄。.dockerignore 檔案的排除模式語法和 Git 的 .gitignore 檔案相似。
使用多階段構建
在現代 Docker 版本中,你可以使用多階段構建來減少所構建映像檔的大小。該能力最早在 Docker 17.05 引入,如今已是編寫生產映像檔的預設實踐之一;新專案還應結合 BuildKit 快取掛載、secret 掛載和多平臺構建能力一起評估。
避免安裝不必要的包
為了降低複雜性、減少依賴、減小檔案大小、節約構建時間,你應該避免安裝任何不必要的包。例如,不要在資料庫映像檔中包含一個文字編輯器。
一個容器只執行一個程序
應該保證在一個容器中只執行一個程序。將多個應用解耦到不同容器中,保證了容器的橫向擴充套件和複用。例如 web 應用應該包含三個容器:web 應用、資料庫、快取。
如果容器互相依賴,你可以使用 Docker 自定義網路來把這些容器連線起來。
映像檔層數儘可能少
你需要在 Dockerfile 可讀性 (也包括長期的可維護性) 和減少層數之間做一個平衡。
將多行引數排序
將多行引數按字母順序排序 (比如要安裝多個包時)。這可以幫助你避免重複包含同一個包,更新包列表時也更容易。也便於 PRs 閱讀和審查。建議在反斜槓符號 \ 之前新增一個空格,以增加可讀性。
下面是來自 buildpack-deps 映像檔的例子:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
構建快取
在映像檔的構建過程中,Docker 會遍歷 Dockerfile 檔案中的指令,然後按順序執行。在執行每條指令之前,Docker 都會在快取中查詢是否已經存在可重用的映像檔,如果有就使用現存的映像檔,不再重複建立。如果你不想在構建過程中使用快取,你可以在 docker build 命令中使用 --no-cache=true 選項。
但是,如果你想在構建的過程中使用快取,你得明白什麼時候會,什麼時候不會找到匹配的映像檔,遵循的基本規則如下:
- 從一個基礎映像檔開始 (
FROM指令指定),下一條指令將和該基礎映像檔的所有子映像檔進行匹配,檢查這些子映像檔被建立時使用的指令是否和被檢查的指令完全一樣。如果不是,則快取失效。 - 在大多數情況下,只需要簡單地對比
Dockerfile中的指令和子映像檔。然而,有些指令需要更多的檢查和解釋。 - 對於
ADD和COPY指令,映像檔中對應檔案的內容也會被檢查,每個檔案都會計算出一個校驗和。檔案的最後修改時間和最後訪問時間不會納入校驗。在快取的查詢過程中,會將這些校驗和與已存在映像檔中的檔案校驗和進行對比。如果檔案有任何改變,比如內容和後設資料,則快取失效。 - 除了
ADD和COPY指令,快取匹配過程不會檢視臨時容器中的檔案來決定快取是否匹配。例如,當執行完RUN apt-get -y update指令後,容器中一些檔案被更新,但 Docker 不會檢查這些檔案。這種情況下,只有指令字串本身被用來匹配快取。
一旦快取失效,所有後續的 Dockerfile 指令都將產生新的映像檔,快取不會被使用。
Dockerfile 指令
下面針對 Dockerfile 中各種指令的最佳編寫方式給出建議。
FROM
儘可能使用當前官方倉庫作為你構建映像檔的基礎。推薦使用 Alpine 映像檔,因為它被嚴格控制並保持最小尺寸 (目前小於 5 MB),但它仍然是一個完整的發行版。
LABEL
你可以給映像檔新增標籤來幫助組織映像檔、記錄許可資訊、輔助自動化構建等。每個標籤一行,由 LABEL 開頭加上一個或多個標籤對。下面的示例展示了各種不同的可能格式。# 開頭的行是註釋內容。
注意:如果你的字串中包含空格,必須將字串放入引號中或者對空格使用轉義。如果字串內容本身就包含引號,必須對引號使用轉義。
## Set one or more individual labels
LABEL com.example.version="0.0.1-beta"
LABEL vendor="ACME Incorporated"
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""
一個映像檔可以包含多個標籤,但建議將多個標籤放入到一個 LABEL 指令中。
## Set multiple labels at once, using line-continuation characters to break long lines
LABEL vendor=ACME\ Incorporated \
com.example.is-beta= \
com.example.is-production="" \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"
關於標籤可以接受的鍵值對,參考 Understanding object labels。關於查詢標籤資訊,參考 Managing labels on objects。
RUN
為了保持 Dockerfile 檔案的可讀性,可理解性,以及可維護性,建議將長的或複雜的 RUN 指令用反斜槓 \ 分割成多行。
apt-get
RUN 指令最常見的用法是安裝包用的 apt-get。因為 RUN apt-get 指令會安裝包,所以有幾個問題需要注意。
不要使用 RUN apt-get upgrade 或 dist-upgrade,因為許多基礎映像檔中的 “必須” 包不會在一個非特權容器中升級。如果基礎映像檔中的某個包過時了,你應該聯絡它的維護者。如果你確定某個特定的包,比如 foo,需要升級,使用 apt-get install -y foo 就行,該指令會自動升級 foo 包。
永遠將 RUN apt-get update 和 apt-get install 組合成一條 RUN 宣告,例如:
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo
將 apt-get update 放在一條單獨的 RUN 宣告中會導致快取問題以及後續的 apt-get install 失敗。比如,假設你有一個 Dockerfile 檔案:
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y curl
構建映像檔後,所有的層都在 Docker 的快取中。假設你後來又修改了其中的 apt-get install 新增了一個包:
FROM ubuntu:24.04
RUN apt-get update
RUN apt-get install -y curl nginx
Docker 發現修改後的 RUN apt-get update 指令和之前的完全一樣。所以,apt-get update 不會執行,而是使用之前的快取映像檔。因為 apt-get update 沒有執行,後面的 apt-get install 可能安裝的是過時的 curl 和 nginx 版本。
使用 RUN apt-get update && apt-get install -y 可以確保你的 Dockerfiles 每次安裝的都是包的最新的版本,而且這個過程不需要進一步的編碼或額外干預。這項技術叫作 cache busting。你也可以顯示指定一個包的版本號來達到 cache-busting,這就是所謂的固定版本,例如:
RUN apt-get update && apt-get install -y \
package-bar \
package-baz \
package-foo=1.3.*
固定版本會迫使構建過程檢索特定的版本,而不管快取中有什麼。這項技術也可以減少因所需包中未預料到的變化而導致的失敗。
下面是一個 RUN 指令的示例模板,展示了所有關於 apt-get 的建議。
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
git \
redis-server \
&& rm -rf /var/lib/apt/lists/*
其中 redis-server 是示例包。確保安裝的是最新版本。
另外,清理掉 apt 快取 var/lib/apt/lists 可以減小映像檔大小。因為 RUN 指令的開頭為 apt-get update,包快取總是會在 apt-get install 之前重新整理。
注意:官方的 Debian 和 Ubuntu 映像檔會自動執行 apt-get clean,所以不需要顯式的呼叫 apt-get clean。
CMD
CMD 指令用於執行目標映像檔中包含的軟體,可以包含引數。CMD 大多數情況下都應該以 CMD ['executable', 'param1', 'param2'...] 的形式使用。因此,如果建立映像檔的目的是為了部署某個服務 (比如 Apache),你可能會執行類似於 CMD ['apache2', '-DFOREGROUND'] 形式的命令。我們建議任何服務映像檔都使用這種形式的命令。
多數情況下,CMD 都需要一個互動式的 shell (bash,Python,perl 等),例如 CMD ['perl', '-de0'],或者 CMD ['PHP', '-a']。使用這種形式意味著,當你執行類似 docker run -it python 時,你會進入一個準備好的 shell 中。CMD 應該在極少的情況下才能以 CMD ['param', 'param'] 的形式與 ENTRYPOINT 協同使用,除非你和你的映像檔使用者都對 ENTRYPOINT 的工作方式十分熟悉。
EXPOSE
EXPOSE 指令用於指定容器將要監聽的埠。因此,你應該為你的應用程式使用常見的埠。例如,提供 Apache web 服務的映像檔應該使用 EXPOSE 80,而提供 MongoDB 服務的映像檔使用 EXPOSE 27017。
對於外部訪問,使用者可以在執行 docker run 時使用一個標誌來指示如何將指定的埠對映到所選擇的埠。
ENV
為了方便新程式執行,你可以使用 ENV 來為容器中安裝的程式更新 PATH 環境變數。例如使用 ENV PATH /usr/local/nginx/bin:$PATH 來確保 CMD ["nginx"] 能正確執行。
ENV 指令也可用於為你想要容器化的服務提供必要的環境變數,比如 Postgres 需要的 PGDATA。
最後,ENV 也能用於設定常見的版本號,比如下面的示例:
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgres && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
類似於程式中的常量,這種方法可以讓你只需改變 ENV 指令來自動的改變容器中的軟體版本。
ADD 和 COPY
雖然 ADD 和 COPY 功能類似,但一般優先使用 COPY。因為它比 ADD 更透明。COPY 只支援簡單將本地檔案複製到容器中,而 ADD 有一些並不明顯的功能 (比如本地 tar 提取和遠端 URL 支援)。因此,ADD 的最佳用例是將本地 tar 檔案自動提取到映像檔中,例如 ADD rootfs.tar.xz。
如果你的 Dockerfile 有多個步驟需要使用上下文中不同的檔案。單獨 COPY 每個檔案,而不是一次性的 COPY 所有檔案,這將保證每個步驟的構建快取只在特定的檔案變化時失效。例如:
COPY requirements.txt /tmp/
RUN pip install --requirement /tmp/requirements.txt
COPY . /tmp/
如果將 COPY . /tmp/ 放置在 RUN 指令之前,只要 . 目錄中任何一個檔案變化,都會導致後續指令的快取失效。
為了讓映像檔儘量小,最好不要使用 ADD 指令從遠端 URL 獲取包,而是使用 curl 和 wget。這樣你可以在檔案提取完之後刪掉不再需要的檔案來避免在映像檔中額外新增一層。比如儘量避免下面的用法:
ADD http://example.com/big.tar.xz /usr/src/things/
RUN tar -xJf /usr/src/things/big.tar.xz -C /usr/src/things
RUN make -C /usr/src/things all
而是應該使用下面這種方法:
RUN mkdir -p /usr/src/things \
&& curl -SL http://example.com/big.tar.xz \
| tar -xJC /usr/src/things \
&& make -C /usr/src/things all
上面使用的管道操作,所以沒有中間檔案需要刪除。
對於其他不需要 ADD 的自動提取功能的檔案或目錄,你應該使用 COPY。
ENTRYPOINT
ENTRYPOINT 的最佳用處是設定映像檔的主命令,允許將映像檔當成命令本身來執行 (用 CMD 提供預設選項)。
例如,下面的示例映像檔提供了命令列工具 s3cmd:
ENTRYPOINT ["s3cmd"]
CMD ["--help"]
現在直接執行該映像檔建立的容器會顯示命令幫助:
$ docker run s3cmd
或者提供正確的引數來執行某個命令:
$ docker run s3cmd ls s3://mybucket
這樣映像檔名可以當成命令列的參考。
ENTRYPOINT 指令也可以結合一個輔助指令碼使用,和前面命令列風格類似,即使啟動工具需要不止一個步驟。
例如,Postgres 官方映像檔使用下面的指令碼作為 ENTRYPOINT:
#!/bin/bash
set -e
if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi
exec gosu postgres "$@"
fi
exec "$@"
注意:該指令碼使用了 Bash 的內建命令 exec,所以最後執行的程序就是容器的 PID 為 1 的程序。這樣,程序就可以接收到任何傳送給容器的 Unix 訊號了。
該輔助指令碼被複製到容器,並在容器啟動時透過 ENTRYPOINT 執行:
COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
該指令碼可以讓使用者用幾種不同的方式和 Postgres 互動。
你可以很簡單地啟動 Postgres:
$ docker run postgres
也可以執行 Postgres 並傳遞引數:
$ docker run postgres postgres --help
最後,你還可以啟動另外一個完全不同的工具,比如 Bash:
$ docker run --rm -it postgres bash
VOLUME
VOLUME 指令用於暴露任何資料庫儲存檔案,配置檔案,或容器建立的檔案和目錄。強烈建議使用 VOLUME 來管理映像檔中的可變部分和使用者可以改變的部分。
USER
如果某個服務不需要特權執行,建議使用 USER 指令切換到非 root 使用者。先在 Dockerfile 中使用類似 RUN groupadd -r postgres && useradd -r -g postgres postgres 的指令建立使用者和使用者組。
注意:在映像檔中,使用者和使用者組每次被分配的 UID/GID 都是不確定的,下次重新構建映像檔時被分配到的 UID/GID 可能會不一樣。如果要依賴確定的 UID/GID,你應該顯式的指定一個 UID/GID。
你應該避免使用 sudo,因為它不可預期的 TTY 和訊號轉發行為可能造成的問題比它能解決的問題還多。如果你真的需要和 sudo 類似的功能 (例如,以 root 許可權初始化某個守護程序,以非 root 許可權執行它),你可以使用 gosu。
最後,為了減少層數和複雜度,避免頻繁地使用 USER 來回切換使用者。
WORKDIR
為了清晰性和可靠性,你應該總是在 WORKDIR 中使用絕對路徑。另外,你應該使用 WORKDIR 來替代類似於 RUN cd ... && do-something 的指令,後者難以閱讀、排錯和維護。
官方映像檔示例
這些官方映像檔的 Dockerfile 都是參考典範,詳見 docker-library/docs。
附錄五:如何除錯 Docker
開啟 Debug 模式
在 dockerd 配置檔案 daemon.json (預設位於 /etc/docker/) 中新增
{
"debug": true
}
重啟守護程序。
$ sudo kill -SIGHUP $(pidof dockerd)
此時 dockerd 會在日誌中輸出更多資訊供分析。
檢查核心日誌
$ sudo dmesg |grep dockerd
$ sudo dmesg |grep runc
Docker 不響應時處理
可以殺死 dockerd 程序檢視其堆疊呼叫情況。
$ sudo kill -SIGUSR1 $(pidof dockerd)
重置 Docker 本地資料
注意,本操作會移除所有的 Docker 本地資料,包括映像檔和容器等。
更安全的替代方式是優先使用以下命令進行清理:
$ docker system prune
如果你只是想 “恢復出廠設定”,在 Docker Desktop 裡也提供了相應入口。
$ sudo rm -rf /var/lib/docker
常見故障排查
容器啟動失敗
如果容器啟動後立即退出,可以使用 docker logs 檢視原因。
- Exit Code 1:應用程式錯誤。通常是配置錯誤或依賴缺失。
- Exit Code 137:OOM (Out Of Memory)。容器記憶體不足被核心殺掉。
- 檢查宿主機記憶體。
- 調整容器記憶體限制 (
--memory)。 - Exit Code 127:命令未找到。可能是
ENTRYPOINT或CMD指定的命令不存在。
網路連線問題
容器內部無法聯網
- 檢查 Docker DNS 配置 (
/etc/docker/daemon.json)。 - 檢查宿主機防火牆 (iptables/firewalld) 是否攔截了轉發。
- 容器內測試:
ping 8.8.8.8(測試連通性),nslookup google.com(測試 DNS)。
埠對映不通
- 檢查容器埠是否正確監聽:
netstat -tunlp(宿主機) 或docker exec <container> netstat -tunlp。 - 確認應用監聽地址是
0.0.0.0而不是127.0.0.1。 * 如果應用監聽在127.0.0.1,只有容器內部能訪問,對映到宿主機外部也無法被外部請求訪問。
映像檔拉取失敗
- connection refused:檢查網路或代理設定。
- image not found:檢查映像檔名稱和 Tag 拼寫。
- EOF / timeout:網路不穩定,嘗試配置映像檔加速器。
附錄六:資源連結
本頁只保留適合作為一手核驗入口的官方資源,便於查版本、查命令、查釋出說明和查最佳實務。
官方入口
- Docker 文件
- Docker 入門指南
- Docker 發行說明
- Docker Desktop 發行說明
- Docker Compose 安裝
- Docker Hub 文件
- Docker Blog
- Docker Roadmap
- Kubernetes 文件
- Kubernetes 釋出頁
- kubeadm 安裝文件
- containerd 文件
- GitHub Actions 文件
- Drone 文件
參考文件
原始碼倉庫
附錄七:術語表
本附錄整理了本書中常見的一些專業術語及其解釋。
A
- Alpine:一個輕量級的 Linux 發行版,常作為基礎映像檔用於構建體積較小的 Docker 映像檔。
- API (Application Programming Interface):應用程式程式設計介面,Docker Daemon 提供 RESTful API 供用戶端或外部程式與之互動。
B
- Base Image (基礎映像檔):沒有父映像檔的映像檔,通常是作業系統的最小安裝集合(如
ubuntu或alpine)。 - BuildKit:Docker 下一代的構建引擎,提供了更高的構建效能、更好的快取處理和併發構建支援。
- Buildx:Docker CLI 的一個外掛,擴充套件了構建功能,支援 BuildKit 的所有高階特性,例如多系統架構映像檔構建。
C
- Cgroups (Control Groups):控制組,Linux 核心特性,用於限制、記錄、隔離程序組使用的物理資源(如 CPU、記憶體、磁碟 I/O 等)。
- Cluster (叢集):一組協同工作的節點(如主機、虛擬機器等),在容器領域常指 Kubernetes 叢集。
- Compose (Docker Compose):用於定義和執行多容器 Docker 應用程式的工具,透過 YAML 檔案配置應用服務。
- Container (容器):映像檔的執行例項,帶有額外的可寫檔案層,具有獨立性。
- Containerd:行業標準的容器執行時,核心功能是管理宿主機上容器的生命週期(建立、啟動、停止、銷燬)。
D
- Daemon (守護程序):Docker 的後臺守護程序,負責接收和處理 Docker API 請求,並管理映像檔、容器、網路和資料卷等物件。
- Docker:開源的應用容器引擎,讓開發者可以打包應用程式及其依賴包到一個可移植的容器中,然後釋出到任何流行的 Linux 或 Windows 機器上。
- Docker Desktop:包含 Docker Engine、Docker CLI 用戶端、Docker Compose 和 Kubernetes 等的桌面應用程式,適用於 macOS 和 Windows。
- Docker Hub:Docker 官方的公共映像檔倉庫服務,提供容器映像檔的儲存和分發。
- Dockerfile:包含用於組合映像檔的命令的文字檔案,Docker 透過讀取
Dockerfile中的指令即可自動完成映像檔構建。
E
- Etcd:一個高可用、強一致性的分散式鍵值儲存系統,常用於容器叢集(如 Kubernetes)的服務發現和狀態配置管理。
I
- Image (映像檔):Docker 映像檔是一個只讀模板,帶有建立 Docker 容器的說明。
K
- Kubernetes (K8s):開源的容器編排引擎,用於自動化容器化應用程式的部署、擴充套件和管理。
L
- Layer (映像檔層):Docker 映像檔由多個只讀層疊合而成,每一層通常代表 Dockerfile 中的一條指令的操作結果,透過聯合檔案系統(UFS)疊加在一起形成完整的檔案系統。
M
- Multistage Build (多階段構建):Dockerfile 中的特性,允許在同一個 Dockerfile 中使用多個
FROM語句,從一個階段複製所需的構建產物到另一個階段,從而大幅減小最終映像檔的體積。
N
- Namespace (名稱空間):Linux 核心特性,用於隔離各種系統資源,如程序、網路、掛載點等,使容器看起來就像是一個獨立的作業系統。
- Node (節點):容器叢集(如 Kubernetes)中的一臺工作機器,可以是物理機或虛擬機器。
O
- OCI (Open Container Initiative):開放容器規範,由多家行業領頭企業共同制定的容器執行時和映像檔格式的行業標準。
- Orchestration (編排):自動化部署、管理、擴充套件和網路配置容器的系統和技術(如 Kubernetes)。
P
- Pod:Kubernetes 中最小的、可部署的計算單元,包含一個或多個緊密相關的容器,共享相同的網路名稱空間和儲存。
- Prometheus:開源的系統監控和告警工具包,廣泛應用於雲原生的監控體系中。
R
- Registry (註冊伺服器):提供 Docker 映像檔下載和上傳等儲存分發服務的伺服器。
- Repository (倉庫):集中存放某個應用的所有映像檔的地方,通常由映像檔名定義。一個 Registry 中可以包含多個 Repository。
S
- Swarm (Docker Swarm):Docker 原生的叢集和編排管理工具,可將多個 Docker 主機組合成一個統一的虛擬 Docker 主機池。維護節點時通常將節點可用性設為
Drain,這隻影響 Swarm service 排程,不會停止該節點上獨立執行的容器。
U
- UFS (Union File System):聯合檔案系統,一種分層、輕量級並且高效能的檔案系統,它支援對檔案系統的修改一層層疊加。
V
- Volume (資料卷):專為繞過聯合檔案系統而設計的特殊目錄,用於實現容器資料的持久化,或在多個容器之間提供檔案共享。
附錄八:Docker 學習路線圖與知識體系
本附錄為學習者提供清晰的學習路線、知識點依賴關係、認證指南和常見面試題,幫助快速成長為 Docker 和 DevOps 專家。
學習階段劃分
Docker 學習可分為四個遞進階段,每個階段都有明確的學習目標和時間投入。
第一階段:基礎入門(0-2 周)
學習目標:
- 理解容器化的基本概念
- 能夠執行、管理基本的容器
- 瞭解映像檔和倉庫的基本操作
核心內容:
Docker 簡介
├── 為什麼需要 Docker
├── 容器 vs 虛擬機器 vs 雲端計算
└── Docker 的三大核心概念
├── 映像檔(Image)
├── 容器(Container)
└── 倉庫(Repository)
基礎命令
├── docker run / create / start / stop / rm
├── docker ps / logs / exec / inspect
├── docker pull / push / tag
└── docker build -t
Docker 安裝配置
├── Linux 平臺安裝
├── macOS 和 Windows 安裝
├── 映像檔加速器配置
└── 許可權和使用者配置
學習資源:
- 官方教程
- 本書第 1-3 章:入門篇基礎概念
- Docker CLI 參考
時間投入:
- 理論學習:3-4 小時
- 實作練習:8-10 小時
- 總計:2 周
驗證學習成果:
# 完成以下任務說明基礎入門完成
1. 執行官方 nginx 映像檔,訪問 http://localhost
2. 使用 docker exec 進入容器修改首頁
3. 提交修改為新映像檔
4. 推送映像檔到 Docker Hub(需建立賬戶)
第二階段:核心開發(2-6 周)
學習目標:
- 掌握 Dockerfile 編寫
- 能夠構建自己的應用映像檔
- 理解資料管理和網路配置
- 熟悉 Docker Compose 編排
核心內容:
Dockerfile 指令詳解
├── FROM / RUN / COPY / ADD
├── WORKDIR / ENV / ARG
├── EXPOSE / CMD / ENTRYPOINT
├── VOLUME / USER / HEALTHCHECK
└── 最佳實務和效能最佳化
├── 分層快取機制
├── 減少映像檔體積
├── 多階段構建
└── 安全最佳實務
容器資料管理
├── 資料卷(Volume)
│ ├── 命名卷
│ ├── 匿名卷
│ └── 卷掛載最佳實務
├── 繫結掛載(Bind Mount)
│ ├── 宿主機路徑對映
│ └── 許可權和隔離
└── tmpfs 掛載
└── 臨時檔案系統
容器網路
├── 網路型別
│ ├── bridge(預設)
│ ├── host
│ ├── overlay
│ └── macvlan
├── 埠對映
├── 容器互聯
├── DNS 配置
└── 自定義網路
Docker Compose
├── compose.yml/docker-compose.yml 編寫
├── services 定義
├── volumes 配置
├── networks 配置
├── 依賴關係
├── 環境變數
└── 命令操作
├── up / down / ps / logs
├── exec / run
└── build / push
學習資源:
- 本書第 4-11 章:進階篇
- Docker 官方最佳實務
- Dockerfile 參考
時間投入:
- 理論學習:8-10 小時
- 實作練習:30-40 小時(多個實戰專案)
- 總計:4-6 周
專案實戰:
專案 1: Python Web 應用(Flask/Django)
- 編寫多階段 Dockerfile
- 使用 Compose 配置資料庫
- 實現熱過載開發環境
專案 2: Node.js 微服務
- 最佳化映像檔大小
- 配置 Compose 多個服務
- 設定網路和環保境變數
專案 3: 資料庫容器化
- PostgreSQL/MySQL 配置
- 資料持久化
- 備份恢復策略
第三階段:生產最佳化(6-12 周)
學習目標:
- 掌握容器安全最佳實務
- 理解效能監控和最佳化
- 學會容器編排(Kubernetes 基礎)
- 熟悉 CI/CD 整合
核心內容:
容器安全
├── 映像檔安全
│ ├── 漏洞掃描(Trivy/Grype/Snyk)
│ ├── 映像檔簽名和驗證(Cosign)
│ ├── SBOM 生成和管理
│ └── 供應鏈安全
├── 執行時安全
│ ├── 使用者和許可權
│ ├── Linux 能力機制
│ ├── AppArmor 和 SELinux
│ ├── Rootless 容器
│ └── 安全的 Docker socket 訪問
└── 宿主機安全
├── API 訪問控制
├── TLS 認證
└── 審計日誌
效能監控和最佳化
├── 監控指標體系
│ ├── CPU / 記憶體 / 網路 / I/O
│ └── 應用級指標
├── 監控工具
│ ├── docker stats
│ ├── cAdvisor
│ ├── Prometheus
│ └── Grafana
├── 效能最佳化
│ ├── 映像檔大小最佳化
│ ├── 記憶體和 CPU 限制
│ ├── OOM 診斷和處理
│ └── 網路效能最佳化
└── 日誌管理
├── 日誌驅動配置
├── ELK Stack
└── 日誌聚合
容器編排基礎
├── Kubernetes 核心概念
│ ├── Pod / Deployment / Service
│ ├── ConfigMap / Secret
│ └── 健康檢查和自動恢復
├── 容器執行環境
│ ├── containerd
│ ├── CRI-O
│ └── Docker
├── 網路外掛
│ ├── CNI 標準
│ ├── Calico / Flannel / Cilium
│ └── 網路策略
└── 儲存和有狀態應用
├── PV / PVC
├── StorageClass
└── StatefulSet
CI/CD 整合
├── GitHub Actions
│ ├── 映像檔構建和推送
│ ├── 安全掃描
│ └── 自動化測試
├── GitLab CI
├── Jenkins Docker 整合
└── Drone
生態工具
├── Buildx(多架構構建)
├── Skopeo(映像檔管理)
├── Podman(替代方案)
├── Buildah(映像檔構建)
└── Kollabot
學習資源:
- 本書第 12-21 章:深入篇和實戰篇
- Kubernetes 官方文件
- CNCF 學習路線
時間投入:
- 理論學習:15-20 小時
- 實作練習:60-80 小時(多個生產級專案)
- 總計:6-12 周
專案實戰:
專案 1: 安全映像檔構建流程
- 整合 Trivy 掃描
- 映像檔簽名和驗證
- 生成 SBOM 文件
專案 2: 完整監控棧
- 搭建 Prometheus + Grafana
- 配置告警規則
- 效能資料採集和分析
專案 3: CI/CD 流程
- GitHub Actions 或 GitLab CI 配置
- 自動化映像檔構建
- 安全掃描和合規檢查
- 自動化部署到 Kubernetes
專案 4: Kubernetes 叢集部署
- 本地 K3s/Kind 叢集
- 部署有狀態應用
- 配置持久化儲存
第四階段:專家深造(12+ 周)
學習目標:
- 掌握 Kubernetes 高階特性
- 理解容器執行時底層實現
- 能夠設計和最佳化大規模容器平臺
- 貢獻開源社群
核心內容:
Kubernetes 高階特性
├── 叢集管理
│ ├── 節點管理和驅逐
│ ├── 叢集自動擴縮容
│ └── 節點親和性和汙點容忍
├── 儲存編排
│ ├── 動態儲存配置
│ ├── 有狀態應用管理(StatefulSet)
│ └── 備份和災難恢復
├── 服務網格(Service Mesh)
│ ├── Istio / Linkerd / Cilium
│ ├── 流量管理
│ └── 可觀測性增強
├── 安全和多租戶
│ ├── RBAC(角色訪問控制)
│ ├── Network Policy 深入
│ ├── Pod Security Policy
│ └── 准入控制器(Admission Controller)
└── 效能和擴充套件性
├── 大規模叢集最佳化
├── 自定義 Operator
└── 叢集聯邦
容器執行時底層
├── Linux 核心機制
│ ├── Namespace 詳解
│ ├── Cgroup v1 和 v2
│ ├── OverlayFS 和 UnionFS
│ └── SELinux 和 AppArmor
├── 容器執行時
│ ├── containerd 原始碼閱讀
│ ├── runc 實現
│ ├── gVisor 和 Kata
│ └── Firecracker
└── OCI 標準
├── Image Spec
└── Runtime Spec
DevOps 工程化
├── 大規模叢集管理
│ ├── Helm / Kustomize
│ ├── GitOps(Flux / ArgoCD)
│ └── 配置管理
├── 災難恢復和高可用
│ ├── 多叢集部署
│ ├── 故障轉移
│ └── 備份策略
├── 成本最佳化
│ ├── 資源申請和限制
│ ├── 自動擴縮容
│ └── 成本監控
└── 團隊協作
├── GitFlow 工作流
├── 程式碼審查
└── 文件和最佳實務傳播
貢獻機會:
知識點依賴關係
基礎概念 (Week 0-2)
├── 容器 vs 虛擬機器
├── Docker 三大概念
└── 基礎命令
↓
Dockerfile 和映像檔構建 (Week 2-4)
├── Dockerfile 指令
├── 多階段構建
└── 映像檔最佳化
↓ ↓ ↓
資料管理 ← 網路配置 ← Docker Compose (Week 4-6)
├── Volume ├── Bridge ├── YAML 編寫
├── Bind Mount├── Overlay ├── 多容器編排
└── tmpfs └── 自定義網路└── 開發工作流
↓ ↓ ↓
└─────────────────────────┘
實戰專案開發 (Week 6-10)
├── Web 應用容器化
├── 資料庫容器化
├── 微服務架構
└── 本地開發環境
↓
容器安全 ← 效能最佳化 ← 監控和日誌 (Week 10-14)
├── 映像檔掃描 ├── 大小最佳化 ├── Prometheus
├── 漏洞管理 ├── 記憶體最佳化 ├── Grafana
├── 映像檔簽名 ├── CPU 最佳化 └── ELK Stack
└── SBOM └── 診斷工具
↓ ↓ ↓
└─────────────────────┘
安全生產環境 (Week 14-18)
├── CI/CD 流程
├── 映像檔倉庫
├── 日誌集中
└── 告警系統
↓
Kubernetes 基礎 (Week 18-24)
├── Pod / Service / Deployment
├── 資源管理
├── 儲存管理
└── 網路策略
↓
Kubernetes 進階 (Week 24-36)
├── StatefulSet / DaemonSet
├── Operator 開發
├── 叢集管理
└── 服務網格
↓
企業級平臺設計 (Week 36+)
├── 多叢集管理
├── GitOps 工作流
├── 成本最佳化
└── 開源貢獻
推薦學習資源
官方文件
| 資源 | URL | 推薦程度 |
|---|---|---|
| Docker 官方文件 | docs.docker.com | ⭐⭐⭐⭐⭐ |
| Docker Hub | hub.docker.com | ⭐⭐⭐⭐⭐ |
| Kubernetes 官方 | kubernetes.io/docs | ⭐⭐⭐⭐⭐ |
| CNCF 景觀 | landscape.cncf.io | ⭐⭐⭐⭐ |
線上課程
- Udemy:Docker 和 Kubernetes 完整課程(70-100 小時)
- Linux Academy:Linux 和容器管理
- A Cloud Guru:AWS/Azure 容器服務
- Pluralsight:Docker 和容器生態系統
書籍推薦
- 《Docker 深入淺出》- 本書的原版
- 《Kubernetes 權威指南》- 深入 Kubernetes 的必讀書
- 《容器技術核心技術與應用》- 理解底層實現
- 《SRE Google 運維之道》- 生產環境最佳實務
部落格和社群
認證指南
Docker 認證
Docker Certified Associate (DCA)
考試資訊:
- 題目數:55 道
- 時間限制:90 分鐘
- 及格分數:73%(約 41 道題)
- 費用:$199 USD
- 有效期:2 年
考試內容比例:
映像檔和倉庫(20%)
- 映像檔構建和管理
- 映像檔層和快取
- 私有倉庫配置
容器執行(15%)
- 容器生命週期
- 資源限制
- 容器隔離
網路(15%)
- 網路驅動
- 容器通訊
- 埠對映
儲存(10%)
- Volume 管理
- 資料持久化
- 繫結掛載
編排(20%)
- Docker Compose
- Docker Swarm 基礎
安全(15%)
- 使用者和許可權
- 金鑰管理
- 映像檔安全
- 守護程序安全
和日誌(5%)
- Logging drivers
- 事件處理
準備建議:
# 1. 學習本書第 1-11 章(基礎到中級)
# 2. 完成 20+ 個實戰專案
# 3. 參考官方學習指南
# 參考 KodeKloud DCA 認證指南:https://kodekloud.com/blog/docker-certified-associate-guide/
# 4. 模擬考試
- Linux Academy DCA 練習題
- Whizlabs DCA 模擬考試
# 5. 重點掌握的命令
docker build / push / pull / tag
docker run / exec / logs / inspect / ps
docker volume / network / service
docker compose up / down / logs / ps
docker stats / events / inspect
Kubernetes 認證
認證路徑:
-
CKA - Certified Kubernetes Administrator - 難度:高 - 時間:3 小時(實作) - 費用:$395 - 內容:叢集安裝、管理、故障排查
-
CKAD - Certified Kubernetes Application Developer - 難度:中 - 時間:2 小時(實作) - 費用:$395 - 內容:應用開發和部署
-
CKS - Certified Kubernetes Security Specialist - 難度:很高 - 時間:2 小時(實作) - 費用:$395 - 內容:安全最佳實務
常見面試題與答案要點
基礎概念面試題
Q1: Docker 容器和虛擬機器有什麼區別?
A(要點):
虛擬機器:
- 完整的作業系統環境(GB 級)
- 啟動時間:分鐘級
- 隔離級別:完全硬體隔離
- 效能開銷:高(5-20%)
容器:
- 共享核心,包含應用和依賴(MB 級)
- 啟動時間:秒級
- 隔離級別:程序級隔離(Namespace/Cgroup)
- 效能開銷:低(1-5%)
總結:容器更輕量、更快、密度更高
Q2: 什麼是 Docker 映像檔?它如何儲存的?
A(要點):
映像檔本質:
- 只讀的檔案系統快照
- 分層儲存結構
- 每一層是前一層的增量
儲存方式:
- Union FS:多個只讀層 + 一個可寫層
- 每個 RUN/COPY/ADD 指令建立一層
- 層之間透過 diff 增量儲存,節省空間
優點:
- 共享基礎層減少儲存
- 層級快取加快構建
- 支援高效分發
Q3: 容器如何實現隔離?
A(要點):
技術手段:
1. Namespace(資源隔離):
- PID Namespace:程序隔離
- Network Namespace:網路隔離
- Mount Namespace:檔案系統隔離
- UTS Namespace:主機名隔離
- IPC Namespace:程序間通訊隔離
2. Cgroup(資源限制):
- 限制 CPU 使用
- 限制記憶體使用
- 限制磁碟 I/O
- 限制網路頻寬
3. Linux 能力機制(許可權控制):
- 削減不必要的 root 許可權
- 限制容器能力
4. SELinux / AppArmor(強制訪問控制)
Dockerfile 面試題
Q4: 如何最佳化 Docker 映像檔大小?
A(要點):
1. 選擇合適的基礎映像檔:
scratch < alpine:3.21 < python:3.14-slim < python:3.14
2. 多階段構建:
- 構建階段只保留編譯工具
- 執行階段只包含最終二進位制
- 典型情境:Go、Node.js、Java
3. 清理包管理器快取:
apt-get clean && rm -rf /var/lib/apt/lists/*
yum clean all && rm -rf /var/cache/yum
pip install --no-cache-dir
4. 合併 RUN 指令:
減少映像檔層數
5. 使用 .dockerignore:
排除不必要的構建上下文
6. 去除除錯符號:
Go: -ldflags="-w -s"
C/C++: strip binary
7. 壓縮資源:
gzip 靜態檔案,壓縮圖片
Q5: CMD 和 ENTRYPOINT 有什麼區別?
A(要點):
CMD:
- 定義容器預設命令
- 容器執行時可被覆蓋:docker run image_name custom_cmd
- 可以有多個 CMD,只有最後一個生效
ENTRYPOINT:
- 定義容器的可執行程式
- 容器執行時引數追加而非覆蓋
- 與 CMD 配合使用
推薦用法:
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8000"]
# 執行 docker run image --debug 會執行:
# python app.py --debug
網路和儲存面試題
Q6: Docker 網路驅動的區別?
A(要點):
Bridge(預設):
- 虛擬網橋,容器間透過網橋通訊
- 支援埠對映
- 隔離性好,效能適中
Host:
- 使用宿主機網路棧
- 效能最優,隔離性最差
- 容器埠直接對映到宿主機
Overlay:
- 跨主機通訊,基於 VXLAN
- Docker overlay 網路預設使用 UDP 4789 傳輸資料
- Swarm 標準;Kubernetes 通常由 CNI 外掛實現跨主機網路
- 效能略低,支援分散式
macvlan:
- 容器獲得 MAC 地址
- 表現為物理機,效能好
- 用於物理網路整合
None:
- 無網路,完全隔離
Q7: Volume 和 Bind Mount 有什麼區別?
A(要點):
Volume:
- Docker 管理,儲存位置:/var/lib/docker/volumes/
- 跨平臺相容,隔離性好
- 支援驅動,可擴充套件
- 推薦在生產環境使用
Bind Mount:
- 宿主機管理,任意位置
- 跨平臺相容性一般
- 效能好,用於開發環境
- 許可權管理複雜
tmpfs:
- 記憶體檔案系統,不持久化
- 用於臨時檔案、敏感資料
- 效能最好,重啟丟失
安全和生產面試題
Q8: 如何提高 Docker 安全性?
A(要點):
映像檔安全:
- 使用官方映像檔或可信映像檔源
- 定期掃描漏洞(Trivy/Grype)
- 映像檔簽名驗證(Cosign)
- 生成和管理 SBOM
容器執行:
- 以非 root 使用者執行
- 使用 read-only 檔案系統
- 限制 Linux 能力
- 使用 AppArmor 或 SELinux
宿主機安全:
- 啟用 TLS 認證 API
- 不暴露 /var/run/docker.sock
- 使用 Rootless 容器
- 定期更新 Docker
網路安全:
- 使用自定義網路隔離
- 配置網路策略
- 限制出入站流量
Q9: 容器被 OOM 殺死,如何診斷和解決?
A(要點):
診斷:
1. 檢查容器是否被 OOM 殺死:
docker inspect <container> | grep OOMKilled
2. 檢視宿主機日誌:
dmesg | grep -i oom
journalctl -u docker | grep -i oom
3. 監控記憶體使用:
docker stats <container>
docker exec <container> ps aux --sort=-%mem
解決:
1. 增加記憶體限制:
docker update -m 2g <container>
2. 檢查記憶體洩漏:
使用記憶體分析工具(heapdump、pprof)
3. 最佳化應用:
- 增加垃圾回收頻率
- 減少快取大小
- 使用物件池模式
4. 使用記憶體交換(最後手段):
docker run -m 512m --memory-swap 1g
Q10: 如何在 CI/CD 中整合 Docker?
A(要點):
構建階段:
- 觸發器:Push / PR 事件
- 構建映像檔:docker build
- 標記:git sha、版本號
- 掃描:Trivy 漏洞掃描
- 簽名:Cosign 映像檔簽名
儲存階段:
- 推送到映像檔倉庫:docker push
- 記錄 SBOM 和掃描報告
部署階段:
- 驗證映像檔簽名
- 獲取映像檔摘要
- 更新部署配置
- 觸發 GitOps 工作流
監控階段:
- 收集應用日誌
- 監控效能指標
- 告警異常情況
示例工作流:
1. GitHub Actions / GitLab CI 監聽 push
2. 執行單元測試
3. 構建 Docker 映像檔
4. 推送到 Docker Hub / ECR
5. 觸發 ArgoCD / Flux 自動部署
6. 監控部署狀態
學習進度跟蹤模板
# Docker 學習進度跟蹤
## 第一階段:基礎入門(目標:2 周)
- [ ] 學完第 1-3 章(6 小時)
- [ ] 完成基礎命令練習(10 小時)
- [ ] 執行官方映像檔
- [ ] 建立和推送第一個映像檔到 Docker Hub
- [ ] 完成度:___%
## 第二階段:核心開發(目標:4-6 周)
- [ ] 學完第 4-11 章(15 小時)
- [ ] 完成 3 個 Dockerfile 最佳實務專案
- [ ] 掌握 Docker Compose(5 個專案)
- [ ] 學習資料管理和網路(8 小時)
- [ ] 完成度:___%
## 第三階段:生產最佳化(目標:6-12 周)
- [ ] 學完第 12-21 章(25 小時)
- [ ] 映像檔安全掃描和簽名
- [ ] 搭建完整監控棧
- [ ] 配置 CI/CD 流程
- [ ] Kubernetes 基礎(30 小時)
- [ ] 完成度:___%
## 第四階段:專家深造(目標:12+ 周)
- [ ] Kubernetes 高階特性
- [ ] 服務網格學習
- [ ] 底層實現研究
- [ ] 貢獻開源專案
- [ ] 完成度:___%
## 證書目標
- [ ] Docker DCA 認證
- [ ] CKA 認證
- [ ] CKAD 認證
## 實戰專案清單
- [ ] Python Web 應用容器化
- [ ] Node.js 微服務
- [ ] 資料庫容器化
- [ ] 完整微服務架構
- [ ] 監控和日誌系統
- [ ] CI/CD 流程實現
快速參考速查表
常用命令速查:
# 映像檔管理
docker build -t image:tag . # 構建映像檔
docker images # 列出映像檔
docker rmi image:tag # 刪除映像檔
docker tag source:tag target:tag # 標記映像檔
docker push registry/image:tag # 推送映像檔
docker pull image:tag # 拉取映像檔
docker history image:tag # 檢視映像檔歷史
docker inspect image:tag # 檢視映像檔詳情
# 容器管理
docker run [OPTIONS] image # 執行容器
docker ps [-a] # 列出容器
docker stop/start/restart container # 容器生命週期
docker rm container # 刪除容器
docker logs [-f] container # 檢視日誌
docker exec -it container cmd # 進入容器
docker inspect container # 檢視容器詳情
docker stats [container] # 檢視資源使用
# 網路管理
docker network ls # 列出網路
docker network create name # 建立網路
docker network connect/disconnect # 連線/斷開網路
docker network inspect name # 檢視網路詳情
# 卷管理
docker volume ls # 列出卷
docker volume create name # 建立卷
docker volume rm name # 刪除卷
docker volume inspect name # 檢視卷詳情
# Docker Compose
docker compose up [-d] # 啟動服務
docker compose down # 停止服務
docker compose ps # 列出服務
docker compose logs [-f] [service] # 檢視日誌
docker compose exec service cmd # 在服務中執行命令
docker compose build # 構建服務映像檔