Kubernetes 系列(一):建立 K8s 心智模型 - 從 kubectl apply 到 Pod 啟動
前言
如果你已經熟悉 Docker,應該很習慣用 docker run 啟動容器、用映像檔部署應用程式。但 Kubernetes 的難點不在於「如何再寫一份更長的 YAML」,而在於它把一個應用程式拆成多個控制迴圈、API 物件、節點元件與網路層,讓整個叢集持續朝你宣告的狀態靠近。
這篇文章的目標,是先建立一張可以用來排錯的地圖:當你執行 kubectl apply 後,誰儲存設定、誰建立 Pod、誰選擇 Node、誰啟動容器,以及 Pod 為什麼可能一直停在 Pending 或 Ready=False。讀完後,你應該能解釋 Kubernetes 的控制平面、節點元件與資料平面各自負責什麼,而不是只背一張元件名稱表。
本文刻意不把 StatefulSet、DaemonSet、Job 等 Workload 類型逐一展開,也不在這裡完整拆解 Pod-to-Pod 的封包路徑。前者會在下一篇 Workload 實戰篇處理,後者會在網路篇從 Network Namespace、CNI、Service 與 CoreDNS 開始拆開。本篇先回答更基礎、也更常影響排錯方向的問題:Kubernetes 到底如何把一份宣告變成正在執行的 Pod?
一、先建立 Kubernetes 的心智模型
這一節先不急著記元件名稱,而是先理解 Kubernetes 的核心工作方式:你提交的是「想要的狀態」,叢集中的控制器則持續觀察「目前的狀態」,並採取動作讓兩者逐漸一致。
1.1 Kubernetes 是宣告式系統
在命令式操作中,你會告訴系統「現在執行這個動作」;在 Kubernetes 中,你通常描述的是「最後希望叢集長什麼樣子」。例如下面的 Deployment 不是一段依序執行的腳本,而是一筆持續存在的 API 物件:
1 | spec: |
這段 spec 表達的意思是:「我希望有兩個符合這個模板的 Pod」。它沒有指定「先在哪台主機建立容器,再呼叫哪個指令啟動」;具體的執行步驟由 Kubernetes 的元件協作完成。
可以把叢集中的狀態分成三個詞:
| 詞彙 | 意義 | 常見來源 |
|---|---|---|
| Desired state(期望狀態) | 使用者希望系統最後達到的樣子 | API 物件的 spec,例如 Deployment 的 replicas: 2 |
| Current state(目前狀態) | 叢集現在實際觀察到的樣子 | Pod 的 status、Node 的條件、容器狀態 |
| Reconciliation(調和) | 把目前狀態往期望狀態推進的過程 | Controller、Scheduler、Kubelet 等控制迴圈 |
因此,kubectl apply 成功不代表容器已經啟動。它通常只代表 API Server 已經接受並儲存這筆宣告;後續控制迴圈仍需要建立 ReplicaSet、建立 Pod、安排 Node、拉取映像檔、啟動容器,最後才會回報 status。
1.2 控制迴圈像一個不斷運作的自動駕駛
Kubernetes 的 Controller 可以類比成一個不斷運作的溫控器:它讀取目標溫度與目前溫度,必要時開啟或關閉設備,然後再次觀察結果。Controller 不會假設「我下過一次指令,事情就永遠完成」,而是持續面對 Pod 被刪除、Node 故障、映像檔拉取失敗與設定變更等情況。
flowchart LR
D["YAML / kubectl\n描述 desired state"] --> API["kube-apiserver\nAPI、驗證與授權"]
API <--> ETCD["etcd\n儲存 API objects"]
API --> OBS["list / watch\n觀察物件變化"]
OBS --> CTRL["Controllers\n調和資源"]
OBS --> SCHED["Scheduler\n選擇 Node"]
OBS --> KUBELET["Kubelet\n執行已分派的 Pod"]
CTRL --> API
SCHED --> API
KUBELET --> API
KUBELET --> ACTUAL["Node 上的 Runtime、Pod、網路"]
ACTUAL --> STATUS["觀察到的 status"]
STATUS --> KUBELET
KUBELET --> API
這張圖有兩個容易被忽略的重點:
- 多數控制元件透過 Kubernetes API 交換叢集狀態,而不是彼此直接呼叫私有 API。
- 真正的容器程序與 Pod 網路在 Node 上運作;一般 Pod-to-Pod 流量不會繞到 API Server。API Server 儲存與傳遞「應該怎麼做」的資訊,並不是所有資料流量的代理。
1.3 Kubernetes API 物件是持續存在的紀錄
Kubernetes API 物件不是一次性的命令。當你建立一個 Deployment,這個物件會保留在 API Server 中,並由控制器持續引用。metadata 說明物件是誰,spec 說明想要什麼,status 則由叢集元件回報目前結果:
1 | apiVersion: apps/v1 |
實際的 status 欄位會依資源種類與 Kubernetes 版本而不同,且更新是非同步的。這也是為什麼排錯時不能只看 kubectl apply 的回應,還要搭配 get、describe、事件與容器日誌觀察每一層的進度。
二、把叢集分成 Control Plane、Node Components 與 Data Plane
Kubernetes 的元件常被畫成一張大圖,初學者容易因此誤以為每個元件都做同一件事。本節把它們依責任拆開:Control Plane 做決策與保存叢集狀態,Node Components 把被分派的 Pod 變成實際程序,Data Plane 承載應用程式與網路流量。
2.1 整體拓樸
flowchart TB
USER["kubectl / CI/CD / 使用者"] --> API["kube-apiserver"]
subgraph CP["Control Plane 控制平面"]
API
ETCD["etcd\nAPI objects 的持久化儲存"]
CONTROLLER["kube-controller-manager\n多個 Controllers"]
SCHEDULER["kube-scheduler\nPod 到 Node 的綁定"]
end
API <--> ETCD
CONTROLLER <--> API
SCHEDULER <--> API
subgraph NODE["Node 節點"]
KUBELET["kubelet"]
RUNTIME["Container Runtime\ncontainerd / CRI-O"]
CNI["CNI plugin\nPod 網路設定"]
PROXY["kube-proxy 或其他 Service data path"]
PODS["Pods / containers\n實際應用程序"]
end
API <--> KUBELET
KUBELET -->|CRI gRPC| RUNTIME
RUNTIME -->|建立 sandbox 時呼叫| CNI
RUNTIME --> PODS
CNI --> PODS
PODS <-->|"Data Plane:應用程式流量"| PODS
PROXY --> PODS
這裡的 Node Components 和 Data Plane 不是互斥的分類:kubelet、Runtime、CNI 都在 Node 上,並共同支撐資料平面;分開寫是為了區分「負責管理與執行的元件」和「真正承載請求的 Pod、網路與轉送規則」。
2.2 Control Plane:做決策、保存狀態、推動調和
Control Plane 不直接在每個 Node 上執行你的 Web Server。它維護 Kubernetes API 所代表的叢集狀態,並透過控制器與 Scheduler 產生下一步應該發生的變更。
API Server:所有 Kubernetes API 操作的入口
kube-apiserver 是 Kubernetes HTTP API 的核心服務。kubectl、Controller、Scheduler、Kubelet,以及使用 Kubernetes API 的其他客戶端,通常都透過它讀取或修改 API 物件。
API Server 的責任包括:
- 驗證請求者身分(authentication)與權限(authorization)。
- 驗證 API schema、套用預設值,並執行 admission 相關檢查。
- 將 API 物件讀寫到 etcd。
- 提供
list、watch等機制,讓其他元件觀察物件變化。 - 對特定操作代理到 Kubelet,例如取得日誌、
exec、attach與port-forward。
但 API Server 不是容器執行引擎,也不是所有網路流量的中介。例如 Pod A 連到 Pod B 時,封包由 Node 上的網路元件處理,不會因為 Kubernetes API 的存在而每一跳都經過 API Server。
etcd:API 物件的持久化資料庫
etcd 是一致性鍵值儲存,保存 Kubernetes API Server 的叢集資料,例如 Deployment、Pod、Node、ConfigMap 與 Service 等物件。通常只有 API Server 直接對外提供 Kubernetes API;其他控制元件透過 API Server 讀寫叢集狀態。
把 etcd 想成「Kubernetes 的唯一真相來源」很有用,但要注意它不是應用程式資料庫,也不會替你保存容器內的應用程式資料。正式環境還要考慮 etcd 的多副本、備份、還原與磁碟延遲。
Controller:把目前狀態推向期望狀態
Controller 是一種控制迴圈,不是單一固定元件。它會觀察特定 API 物件,並透過 API Server 建立或更新其他物件,使目前狀態更接近期望狀態。
以 Deployment 為例,可以先記住這個責任分工:
- Deployment Controller 觀察 Deployment,管理不同版本的 ReplicaSet 與 rollout。
- ReplicaSet Controller 觀察 ReplicaSet,確保符合模板的 Pod 數量足夠。
- 其他 Controllers 則處理 Node、Job、EndpointSlice 等不同領域。
Controller 自己不會在 Node 上呼叫 docker run,也不會直接把應用程式程序啟動起來。它透過 API Server 建立「應該存在的 Pod 物件」,後續由 Scheduler、Kubelet 與 Runtime 接手。
Scheduler:只決定 Pod 要去哪裡
kube-scheduler 會觀察還沒有 spec.nodeName 的 Pod,從可行的 Node 中依資源需求、taint/toleration、affinity、拓樸與其他規則進行篩選與評分,最後把 Pod bind 到某個 Node。
Scheduler 的工作到「選擇與綁定 Node」為止;它不負責拉映像檔、不建立容器,也不負責判斷應用程式的 HTTP readiness。若沒有符合條件的 Node,Pod 會維持未排程狀態,kubectl describe pod 的 Events 通常比盯著一個固定的 Pending 文字更有用。
2.3 Node Components:把被分派的 Pod 實際跑起來
Node 上的元件接收已經被分派的 Pod,建立執行環境,並將實際狀態回報到 API Server。它們和 Control Plane 有 API 層的互動,但容器啟動與資料流量是 Node 本地的工作。
Kubelet:每台 Node 的 Pod 生命週期代理人
Kubelet 是 Node 上的主要代理程式。它會取得被指派到自己 Node 的 Pod 規格,持續執行本地同步迴圈,並負責:
- 透過 Container Runtime 建立或停止 Pod sandbox 與容器。
- 執行 startup、liveness、readiness 等 probes。
- 將容器狀態、Pod conditions 與 Node heartbeat 回報給 API Server。
- 掛載由 Kubernetes 指定的 Volume,並處理容器日誌等本地生命週期工作。
Kubelet 不負責替 Pod 選 Node;那是 Scheduler 的責任。它也不會把每個 Pod-to-Pod 封包送到 API Server。
CRI 與 Container Runtime:Kubelet 如何請 Runtime 工作
熟悉 Docker 的人容易把 kubelet 想成一直執行 docker run。實際上,Kubelet 透過 CRI(Container Runtime Interface) 使用 gRPC 與 Container Runtime 溝通。CRI 是介面與協定,Runtime 才是實際負責映像檔、Pod sandbox、容器建立、啟動、停止與狀態查詢的程式。
常見 Runtime 包括 containerd 與 CRI-O。Docker 建立的 OCI 映像檔通常可以被這些 Runtime 使用,但 Kubernetes 不需要透過 Docker CLI 才能執行容器;「建立映像檔」和「在 Kubernetes Node 上執行映像檔」是兩件不同的事。
1 | kubelet --(CRI / gRPC)--> containerd 或 CRI-O |
CNI:替 Pod 建立網路環境
CNI(Container Network Interface)不是一個固定的 Kubernetes 控制平面服務,而是一套介面規格與 plugins。 當 Runtime 建立 Pod sandbox 時,會依叢集配置呼叫 CNI plugin,為該 Pod 的 network namespace 設定介面、IP、路由或其他網路能力。
CNI 主要回答「這個 Pod 如何接上叢集網路」;它不是用來啟動應用程式,也不是 CoreDNS。具體的 IPAM、跨 Node 傳輸、NetworkPolicy 與 eBPF 實作,會依 CNI 方案不同而改變,留到網路篇再拆解。
kube-proxy 與其他資料平面實作
許多叢集會在 Node 上運行 kube-proxy,維護 Service 相關的轉送規則;也有其他方案使用 eBPF 等資料平面實作。這些元件影響 Service 流量如何找到後端 Pod,但不是 Scheduler 或 Kubelet 的替代品。
2.4 Data Plane:真正承載應用程式請求的地方
Data Plane 指的是實際承載工作負載與流量的部分,包括 Node 上的 Pod、容器程序、network namespace、介面、路由,以及 Service 流量的轉送規則。Control Plane 可以決定「應該有哪幾個 Pod」,但 HTTP、TCP 或 UDP 封包通常在 Data Plane 中直接流動。
可以用下面的責任表快速定位問題:
| 區域 | 代表元件 | 主要問題 |
|---|---|---|
| Control Plane | API Server、etcd、Controllers、Scheduler | 叢集應該有哪些物件?Pod 應該綁到哪個 Node? |
| Node Components | Kubelet、CRI Runtime、CNI、kube-proxy | 被指派的 Pod 是否能在這台 Node 建立與運作? |
| Data Plane | Pod、容器程序、Pod 網路、Service 轉送規則 | 實際請求是否能抵達應用程式? |
對應到常見的通信路徑:
| 通信雙方 | 用途 | 是否是一般應用程式資料流量 |
|---|---|---|
kubectl → API Server |
建立、查詢、更新 API 物件 | 否,是控制操作 |
Controller / Scheduler → API Server |
讀取狀態、建立物件、寫入 binding 或 status | 否,是控制平面協作 |
Kubelet ↔ API Server |
取得 Pod 規格、回報 Node/Pod 狀態 | 否,是管理與回報 |
Kubelet → Runtime |
透過 CRI 建立 sandbox 與容器 | 否,是 Node 本地執行 |
Runtime → CNI |
建立 Pod 網路介面與路由 | 否,是 Node 本地網路設定 |
Pod A → Pod B |
應用程式互相呼叫 | 是,走 Data Plane,不經 API Server |
這張表可以避免一個很常見的錯誤模型:「Kubernetes 的所有元件只和 API Server 溝通,所以應用程式流量也會經過 API Server。」 API Server 是叢集 API 的 hub,但不是應用程式網路的 router。
三、從 API 物件理解「宣告」與「回報」
理解 spec、status 與物件之間的關係,能讓你在看到一份 YAML 或一串 kubectl 輸出時知道應該問哪個控制迴圈,而不是把所有問題都歸咎於「Kubernetes 還在處理」。
3.1 spec 是我要什麼,status 是現在怎樣
以 Deployment 為例:
1 | apiVersion: apps/v1 |
metadata提供名稱、Namespace、labels 與 owner references 等識別資訊。spec是使用者或上層控制器宣告的期望狀態。status是 Kubernetes 元件根據實際觀察結果寫回的狀態,例如目前副本數、可用副本數與條件。
status 不一定會立即反映最新的 Node 或容器狀態,因為它必須經過 Kubelet 回報、API Server 儲存,以及上層 Controller 再次觀察的非同步流程。排錯時,status 是重要線索,但不應脫離 Events、describe 與 Logs 單獨解讀。
3.2 Controller 不會「執行 YAML」,而是產生下一個物件
可以把 Deployment 的一段調和理解成:
1 | Deployment.spec.replicas = 2 |
每一個箭頭之間都可能有延遲或失敗。Deployment Controller 建立 ReplicaSet 成功,不代表 ReplicaSet 已經建立 Pod;Pod 建立成功,也不代表它已經有 Node 或容器程序。這個分層正是 kubectl get deployment,rs,pod 很有價值的原因。
3.3 Container restart、Pod replacement 與 reconciliation 不同
Part 2 會完整介紹 Workload 的生命週期;這裡只先建立排錯所需的區分:
| 現象 | 主要處理者 | 你應該先問什麼 |
|---|---|---|
| 同一個 Pod 的容器反覆重啟 | Kubelet、Runtime | 程序是否退出?liveness probe 是否失敗?看 restartCount、Logs 與 Events |
| Pod 物件被替換 | 擁有它的 Workload Controller | 是誰擁有這個 Pod?新 Pod 是否被重新排程?看 ownerReferences 與 ReplicaSet |
spec 與 status 持續靠近 |
對應的 Controller、Scheduler、Kubelet | 哪一個控制迴圈還沒有完成工作?看下一層物件與 Events |
因此,CrashLoopBackOff 通常表示同一個 Pod 內的容器反覆失敗並逐漸延長重試間隔;它不等於 Kubernetes 已經建立了很多個新 Pod。相反地,直接建立的裸 Pod 沒有 Deployment 或 ReplicaSet 負責在 Pod 物件消失後補回它。
四、完整流程:kubectl apply 一個 Deployment 後發生什麼?
這是本篇最重要的流程。下面以一個兩副本的 Nginx Deployment 為例,依照責任邊界追蹤從 API 請求到 Pod Ready 的過程;實際事件是非同步的,時間上可能交錯,但因果關係可以用這個順序理解。
4.1 使用者宣告 Deployment
先準備一個最小但包含 readiness probe 的 Deployment。這裡沒有建立 Service,因為本節只追蹤「Pod 如何被建立並啟動」;Service 與 Pod 之間的流量會在後續文章處理。
1 | apiVersion: apps/v1 |
執行:
1 | kubectl apply -f deployment-demo.yaml |
kubectl apply 的回應只代表 API 請求被接受;rollout status 才是在等待 Deployment 的 rollout 條件逐漸達成。不要把這兩個命令當成同一件事。
4.2 階段一:API Server 驗證並儲存 Deployment
kubectl讀取目前 context 的憑證與 API Server 位址,送出建立或更新 Deployment 的 HTTPS 請求。- API Server 進行 authentication、authorization、schema validation、defaulting 與 admission 檢查。
- 通過後,API Server 將 Deployment 物件寫入 etcd,並把請求結果回傳給
kubectl。
此時可以說「Deployment 這筆宣告已經進入叢集」,但還不能說「兩個 Nginx 容器已經在運行」。API Server 不會在同一個 HTTP 請求中同步等待後續所有控制迴圈完成。
1 | kubectl --HTTPS--> kube-apiserver --讀寫--> etcd |
4.3 階段二:Deployment Controller 建立 ReplicaSet
Deployment Controller 觀察到新的 Deployment,將它和目前已存在的 ReplicaSet 比對。因為目前沒有相同 Pod template 的 ReplicaSet,它會透過 API Server 建立一個新的 ReplicaSet,並以 owner reference 表示這個 ReplicaSet 由 Deployment 管理。
ReplicaSet 通常會帶有能識別 Pod template 版本的 label。這讓 Deployment 可以在更新映像檔時建立新 ReplicaSet,並逐步調整新舊版本;本篇先不展開 rolling update 的細節。
1 | Deployment Controller |
4.4 階段三:ReplicaSet Controller 建立未排程的 Pod 物件
ReplicaSet Controller 觀察到 ReplicaSet 的期望副本數是 2,但目前符合 selector 的 Pod 數量不足,因此透過 API Server 建立兩個 Pod 物件。
這兩個 Pod 物件一開始通常還沒有 spec.nodeName。它們是「希望某台 Node 執行的宣告」,不是已經在某台機器上存在的 Linux process。此時你可能看到 Pod 為 Pending,而這個階段尚未代表映像檔或應用程式本身有問題。
1 | ReplicaSet Controller |
4.5 階段四:Scheduler 選擇 Node 並完成 binding
Scheduler 透過 API Server 觀察到未排程的 Pod,為每個 Pod 執行排程流程:
- Filtering:排除資源不足、taint 不相容、node affinity 不符合等 Node。
- Scoring:在可行 Node 中依規則評分。
- Binding:把選定的 Node 寫入 Pod 的
spec.nodeName。
Scheduler 只負責「這個 Pod 應該去哪一台 Node」,不會直接操作 Node 上的 Runtime。若所有 Node 都不符合條件,Pod 會繼續留在未排程狀態;此時應從 kubectl describe pod 的 Events 找出原因,而不是只重複執行 kubectl apply。
1 | Pod A(nodeName 空白) ─┐ |
4.6 階段五:Kubelet 透過 CRI 要求 Runtime 建立 Pod
被綁定的 Node 上,Kubelet 透過 API Server 的 watch 或同步流程取得這些 Pod 規格。Kubelet 接著與該 Node 的 Container Runtime 溝通,通常會經過以下工作:
- 建立 Pod sandbox 與對應的 network namespace。
- 在建立 sandbox 的過程中,讓 Runtime 依設定呼叫 CNI plugin,配置 Pod 的介面、IP 與路由。
- 檢查本地是否已有映像檔;沒有時從 registry 拉取。
- 依 Pod spec 建立並啟動容器。
- 監控容器、執行 probes、收集狀態與事件。
這裡可以看到 CRI、CNI 與 Kubelet 的邊界:Kubelet 負責 Pod 生命週期協調,CRI 是 Kubelet 和 Runtime 的協定,Runtime 負責容器執行,CNI 負責把 Pod 接上網路。任何一層失敗,都可能讓 Pod 卡在 ContainerCreating 或反覆重試,但原因要靠 Events 與 Node 上的日誌確認。
4.7 階段六:Kubelet 回報 status,readiness 決定是否 Ready
容器程序啟動後,Kubelet 會持續執行 readiness probe。對上面的 Nginx 而言,Kubelet 會在 Pod 的網路環境中檢查 HTTP / 是否成功;probe 成功後,Kubelet 將容器與 Pod 的狀態回報給 API Server。
這裡要區分兩個概念:
Running通常表示容器已經被啟動,但不代表應用程式已經可以接收流量。Ready=True表示這個 Pod 通過目前設定的 readiness 條件,之後 Service 等流量管理元件才會把它視為可用後端。
Deployment Controller 會再觀察 Pod 的狀態,更新 Deployment 的 status,例如目前副本數、updated replicas 或 available replicas。當實際狀態符合 Deployment 的期望,kubectl rollout status 才會結束等待。
sequenceDiagram
participant U as kubectl
participant A as API Server
participant E as etcd
participant D as Deployment Controller
participant R as ReplicaSet Controller
participant S as Scheduler
participant K as Kubelet
participant RT as Runtime / CNI
U->>A: apply Deployment
A->>E: 儲存 Deployment
A-->>U: 請求已接受
D->>A: 建立 ReplicaSet
R->>A: 建立未排程的 Pods
S->>A: 讀取未綁定 Pods
S->>A: 寫入 spec.nodeName
K->>A: 取得被指派的 Pod spec
K->>RT: CRI:建立 sandbox、容器
RT->>RT: 呼叫 CNI、拉取映像檔、啟動容器
K->>K: 執行 readiness probe
K->>A: 回報 Pod status / Ready
A->>E: 儲存 status
這個流程是「控制器逐步調和」而不是一個大型函式呼叫。各元件可能重試、並行處理或因為 API、資源、映像檔與網路問題暫停,因此實際 Events 的先後順序可能與示意圖略有不同。
五、用心智模型判讀 kubectl 輸出
當狀態不如預期時,最有效的做法是沿著物件與控制迴圈逐層縮小範圍。先判斷「哪一個物件沒有出現」或「哪個狀態沒有往前走」,再找負責該轉換的元件。
5.1 從 Deployment 一路往下查
| 觀察到的情況 | 可能卡住的邊界 | 第一批觀察指令 |
|---|---|---|
| Deployment 存在,但沒有 ReplicaSet | API Server 或 Deployment Controller | kubectl describe deployment mental-model-demo、kubectl get events |
| ReplicaSet 存在,但沒有足夠 Pod | API Server 或 ReplicaSet Controller | kubectl describe rs、kubectl get events |
Pod 存在但沒有 nodeName |
Scheduler 或排程條件 | kubectl describe pod、kubectl get events |
Pod 已有 Node,但長時間 ContainerCreating |
Kubelet、Runtime、CNI、映像檔或 Volume | kubectl describe pod、Node/Kubelet/Runtime 日誌 |
Pod Running 但 Ready=False |
readiness probe 或應用程式啟動 | kubectl describe pod、kubectl logs、probe 設定 |
| Pod Ready 但請求失敗 | Service、DNS、網路政策或應用程式 | 留到網路篇按封包路徑排查 |
這張表的重點不是把每個錯誤都背起來,而是建立「資源層級 → 負責元件 → 觀察證據」的關聯。例如看到 Pending 時,先問它是不是根本還沒有 Node;看到 ContainerCreating 時,則已經表示 Scheduler 大致完成工作,排查焦點應往 Kubelet、Runtime、CNI 或映像檔移動。
5.2 get、describe、events、watch 各自回答什麼
1 | # get:快速看物件是否存在,以及狀態是否持續變化 |
kubectl get 是快照,kubectl get -w 才能看見非同步調和的過程;describe 通常提供資源與事件的上下文;Events 適合回答「最近哪個元件嘗試做什麼」。它們不能取代容器 Logs,也不能取代 Node 上的 Runtime/CNI 日誌,但能幫你先定位責任層。
六、etcd 不可用時,為什麼不是「整個叢集立即癱瘓」?
這個問題很適合用來檢查自己的架構模型。etcd 對 Kubernetes 的控制面非常重要,但「控制面無法繼續改變狀態」和「所有已經存在的程序在同一秒停止」不是同一件事。
6.1 etcd 故障會先影響哪些事情?
當 API Server 無法從 etcd 讀寫資料時,常見影響包括:
- 新的
kubectl apply、刪除、更新與許多查詢可能失敗或無法取得最新資料。 - Controllers 與 Scheduler 無法可靠地讀取或寫入它們需要的 API 狀態,因此新的調和、排程與 status 更新會停滯。
- Kubelet 的 Node heartbeat 與 Pod status 可能無法回報;Control Plane 看到的狀態會變舊。
- 已經在 Node 上運行的容器,可能暫時仍由 Kubelet 與 Runtime 依本地狀態繼續運作。
- 已經存在的部分網路轉送規則,可能在沒有新變更時繼續處理流量,但不能把這當成所有叢集、所有 CNI 與所有故障情境的保證。
最重要的限制是:「現有程序暫時還活著」不代表叢集仍然健康。 如果容器之後崩潰、Node 重啟、Pod 需要被替換、Service 規則需要更新,控制面無法持久化與調和就可能無法恢復預期狀態。
6.2 高可用與恢復的正確理解
正式環境通常會讓 etcd 以奇數個成員形成 quorum,並配合定期快照、備份還原演練與磁碟效能監控。這是為了降低「單一 etcd 成員故障」對 API Server 的影響,不是讓 etcd 變成可以隨意遺失資料的快取。
因此,比「etcd 掛掉等於叢集立即停止」更精確的說法是:
etcd 不可用時,Kubernetes 控制面通常無法可靠地讀寫與調和叢集狀態;既有 Node 上的部分工作負載可能暫時繼續運作,但新變更、故障修復、狀態回報與控制面決策都可能停滯。實際影響取決於故障範圍、持續時間、Node 與資料平面當時的狀態。
這個區分也說明了為什麼 Kubernetes 的備份重點不只是 YAML:YAML 可以重建宣告,但不能取代 etcd 中的叢集 metadata、Secret、RBAC、Lease 與其他 API 物件,也不能取代應用程式資料的獨立備份。
七、本地實驗:觀察一個 Deployment 如何被調和
這個實驗把前面的元件放到一個可重建的 kind 多節點叢集中。它不會假裝貼出固定的 Pod 名稱、Node 名稱或事件輸出;請以你本機實際觀察到的結果為準,並用 get、describe、Events 與 watch 對照每個階段。
7.1 建立 kind 多節點叢集
請先準備 Docker、kubectl 與 kind。kind 的 Node 是 Docker 容器,適合做控制流程與排錯練習,但不等同於正式環境的硬體隔離或高可用架構。
1 | cat <<'EOF' | kind create cluster --name k8s-mental-model --config=- |
kubectl get pods -n kube-system 可以讓你看到這個環境中的控制面與系統元件;名稱與版本會依 kind 與 Kubernetes 版本變化,不要把某次輸出硬編碼成判斷標準。
7.2 建立並套用 Deployment
把前面 4.1 節的 YAML儲存為 deployment-demo.yaml,然後套用:
1 | kubectl apply -f deployment-demo.yaml |
如果你想完全不建立本地檔案,也可以把同一份 YAML 透過標準輸入傳給 kubectl apply -f -;本實驗後續指令假設資源名稱是 mental-model-demo。
7.3 用 watch 看物件逐層出現
開啟兩到三個終端機,分別執行以下觀察。這些指令只要求 Kubernetes 回報當下狀態,不預設固定輸出:
1 | # 終端機 A:Deployment、ReplicaSet、Pod 的物件關係與狀態變化 |
1 | # 終端機 B:觀察建立、排程、拉取映像檔與 probe 相關事件 |
1 | # 終端機 C:特別注意 Pod 被分派到哪個 Node,以及 READY 欄位何時變化 |
你可以把觀察到的變化對回前面的流程:Deployment 先存在,接著出現 ReplicaSet,再出現沒有 Node 或仍在等待的 Pod;Scheduler 綁定後,Pod 的 NODE 欄位才會有值;Kubelet 與 Runtime 完成工作後,容器狀態與 readiness 才會逐步更新。
7.4 用 describe 與 JSONPath 找出每一層的證據
先取得一個實際 Pod 名稱,再查看它的 owner、Node 與 Ready condition:
1 | POD=$(kubectl get pod -l app=mental-model-demo -o jsonpath='{.items[0].metadata.name}') |
請特別查看 describe 的 Events,而不是只截取 kubectl get pod 的一行狀態。你應該能從實際物件確認:Pod 的 owner 是哪個 ReplicaSet、spec.nodeName 是否已填入,以及 Ready condition 是由哪個階段之後才出現。
7.5 刪除 Pod,觀察 Controller 如何補回
這一步用來驗證「Deployment 是持續存在的期望狀態,不是一次性命令」:
1 | kubectl delete pod -l app=mental-model-demo --wait=false |
刪除的是 Pod 物件,不是 Deployment。ReplicaSet Controller 會再次比較副本數,並建立新的 Pod;新 Pod 的名稱、UID 或 Node 可能和原本不同。這也正是為什麼正式服務通常由 Workload Controller 管理,而不是只建立裸 Pod。
7.6 清理實驗
完成觀察後刪除資源與 kind 叢集,避免本地環境留下不必要的容器:
1 | kubectl delete -f deployment-demo.yaml |
八、小結:遇到問題時,先問「誰應該做下一步?」
這一篇的重點不是記住更多元件名稱,而是用責任邊界理解 Kubernetes:API Server 接受並提供 API,etcd 持久化 API 狀態,Controllers 調和資源,Scheduler 選擇 Node,Kubelet 協調 Node 本地生命週期,CRI 讓 Kubelet 使用 Runtime,CNI 建立 Pod 網路,而 Data Plane 才承載應用程式流量。
可以把整篇濃縮成這條鏈:
1 | 宣告 desired state |
下次看到 Pending、ContainerCreating、Running 或 Ready=False 時,先不要把它們當成單一錯誤類型;先問:哪個物件已經存在?哪個轉換尚未完成?負責這個轉換的元件是哪一個?我可以用哪個 kubectl 證據驗證? 這套問題比背命令更能把 Kubernetes 的行為串起來。
系列文章導覽
以下連結使用目前專案文章的實際 permalink,方便沿著概念、網路、流量與排錯路徑閱讀。
- Part 1:建立 K8s 心智模型 - 從
kubectl apply到 Pod 啟動(本篇) - Part 2:Workload 實戰入門 - 從 Pod 到 Job
- Part 3:Pod 網路如何實際運作 - 從 localhost 到 Service VIP
- Part 4:配置與存儲篇 - ConfigMap、Secret 與 Volume
- Part 5:進階篇 - 資源管理與自動擴展
- Part 6:實戰篇 - 部署完整微服務應用
- Part 7:服務發現實戰 - CoreDNS、Service 與 EndpointSlice
- Part 8:L4/L7 負載平衡實戰 - 從 ClusterIP 到 Gateway API
- Part 9:故障排除篇 - 從症狀到根因的實戰診斷
- Part 10:Production Baseline - 把測試叢集變成可運維環境
- Part 11:資源治理與排程 - Requests、QoS、PDB 與 Topology
- Part 12:Autoscaling 與容量規劃 - HPA、VPA 與 Node Autoscaler
- Part 13:Release Engineering - 安全發布、回滾與漸進式交付
- Part 14:Observability 實戰 - Metrics、Logs、Traces 與 SLO
參考資源
本文的元件責任、控制迴圈與本地實驗以官方文件為準;實際叢集的 Runtime、CNI、雲端整合與 Kubernetes 版本可能改變細節。











