Pod、Node、Scheduler:Kubernetes 叢集的三個原子
· tech
📑 目錄
上一篇講了 K8s 的靈魂:你宣告期望,reconcile loop 不斷把現實拉過去。但那句「我要 3 個」——3 個什麼?落在哪台機器上?誰決定放哪? 這篇把叢集最基本的三個原子講清楚:Pod、Node、Scheduler。
Pod:最小單位,而且不是「容器」
第一個反直覺的點:K8s 排程與伸縮的最小單位不是容器,是 Pod。 一個 Pod 包住一個(或少數幾個)容器,讓它們同生共死、共享網路與儲存:
為什麼要多這層包裝?因為有些容器天生該綁在一起 —— 例如主服務 + 一個幫它收 log 或當代理的 sidecar,它們要共用網路、一起被排程、一起生死。但別過度用:多數 Pod 就是一個容器。 記住這句話就好:容器是「跑什麼」,Pod 是 K8s 真正搬動、複製、排程的單位。
落成 YAML,一個「app + sidecar 共享一塊暫存」的 Pod 長這樣——containers 是陣列(可以放多個),volumes 定義的盤兩個容器都掛得到:
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: app # 主容器:你的服務
image: myrepo/web:1.0
volumeMounts:
- { name: shared, mountPath: /var/log/app }
- name: log-agent # sidecar:順便幫它收 log
image: fluent-bit:latest
volumeMounts:
- { name: shared, mountPath: /logs } # 掛同一塊盤,就讀得到 app 寫的 log
volumes:
- name: shared
emptyDir: {} # 兩個容器共享的暫存(Pod 消失就沒了)
看得出 Pod 的兩個「共享」怎麼落地:兩個容器掛同一個 shared volume(所以 sidecar 讀得到 app 寫的檔案),又同處一個 Pod、共用同一個網路(彼此用 localhost 就通)。不過實務上你很少單獨寫 Pod——都是讓 Deployment 的 template 幫你生,這裡單獨寫只是為了把 Pod 的結構看清楚。
Node:一台機器
Node 就是一台真正的機器(雲上多半是一台 VM)。上一篇提過叢集分兩半:
- Control Plane(大腦):決定要跑什麼、排到哪、持續修正落差。
- Worker Node(工人):真正跑你 Pod 的機器。每台上面有個 kubelet,負責照顧本機的 Pod、回報狀態給大腦。
所以「我要 3 個 Pod」的實際樣子,就是這 3 個 Pod 被分配到某幾台 Node 上跑。而決定「哪個 Pod 去哪台 Node」的,是第三個原子。
Scheduler:決定 Pod 落哪台
當 reconcile loop 要補一個新 Pod,這個 Pod 一開始是 Pending(還沒有 Node)。Scheduler 的工作,就是幫它挑一台落腳:
過濾與評分背後的旋鈕——requests/limits、node 親和性、taint/toleration——是控制「誰跑在哪」的關鍵,系列裡進階排程那篇會專門講。這裡先知道:Pod 落在哪台不是隨機,是 Scheduler 依資源與規則算出來的。
關鍵心態:Pod 是短命的
最後一個一定要現在就建立的觀念:Pod 是用完即丟的。 它掛掉、被重排、滾動更新換新版時,舊的 Pod 直接消失,reconcile loop 開一個全新的 Pod 補上——新 Pod 有新的 IP。
這代表:你永遠不該記住某個 Pod 的 IP、也別把狀態存在 Pod 裡。 Pod 是牛,不是寵物——壞了就換一頭,不是抱著醫。這帶出兩個之後的主題:既然 Pod 的 IP 會變,別的服務怎麼穩定找到它?(→ Service,系列第 4 篇);Pod 沒了資料就沒了,那資料庫這種有狀態的怎麼辦?(→ Volume 與 StatefulSet,系列第 6 篇)。
反思
「Pod 不是容器」這個區分,想通就順了
我剛學 K8s 時卡最久的,就是「明明是跑容器,為什麼要多一個 Pod?」後來想通:Pod 是 K8s 的排程單位,容器是執行單位。 K8s 要搬動、複製、放置的是 Pod;至於 Pod 裡放一個還是幾個容器,是「這些東西要不要同生共死」的設計選擇。九成情況一個 Pod 一個容器,別為了炫技硬塞 sidecar。把「單位」這件事分清楚,後面 Deployment 管 Pod、Service 指向 Pod、排程排的是 Pod——全部一次順起來。
Pod 短命是特性,不是 bug
「Pod 隨時會消失、IP 會變」聽起來很不安,但這其實是 K8s 自我修復的前提,不是缺陷。正因為 Pod 被當成可拋棄的,壞了才能無痛換一個。這跟我在 Spark、Spark on K8s 那幾篇反覆講的「executor 用完即刪」是同一種思維——把運算單位當成牛而非寵物。一旦接受這點,你就不會去做「記住某個 Pod」這種注定會痛的事,而會轉去用 K8s 給你的穩定抽象(Service、Volume)。
Scheduler 幫你想「放哪」,但你可以介入
預設 Scheduler 會自動挑一台裝得下的 node,多數時候你不用管。但當你有「這批 Pod 要跑在高記憶體機器」「別跟那個吵鄰居擠同一台」這種需求時,requests、affinity、taint 這些旋鈕就是你介入的方式——我在 Airflow + Spark on K8s 那篇用它把 Airflow 核心釘在穩定 node、把 Spark executor 丟去便宜的 spot node,就是這個道理。先讓 Scheduler 自動幫你放,真的有需求再出手調——這也是我對所有 K8s 進階功能的態度:預設夠用,別為了用而用。