前言
在 Kubernetes 叢集中,kube-scheduler 扮演著至關重要的角色。它負責監聽 kube-apiserver,找出尚未被分配節點的 Pod,並依據一套完整的調度策略,把這些 Pod 放到最適合的 Node 上運行。今天我想用比較白話的方式,帶大家走一遍 K8s 調度器的整個運作邏輯,以及如何透過權重調整來影響調度結果。
調度的完整流程
整個調度流程大致是這樣的:當我們透過 kubectl 或 API 建立一個 Pod 時,kube-apiserver 會把這筆請求存進 etcd。接著 kube-scheduler 內部的 Informer 元件透過 list-watch 機制,以 spec.nodeName="" 條件篩選出還沒被調度的 Pod,然後依序進行以下步驟:
- 預選(Predicate / Filter):先把不符合條件的節點通通過濾掉
- 優選(Priority / Score):針對通過預選的節點進行評分,選出得分最高的那個
- 綁定(Bind):把 Pod 的
spec.nodeName設為選中的節點名稱,完成調度

小提醒:如果你直接在 Pod spec 裡指定了
nodeName,調度器會被完全跳過,不會做任何資源檢查與過濾,請謹慎使用。
Scheduler Framework 深入解析
Kubernetes 的調度框架叫做 Scheduler Framework,Pod 在調度過程中會依序經過多個階段,每個階段都有對應的插件來實現調度邏輯。你也可以在特定階段開發自己的插件來客製化調度行為。

以下是各個階段的簡要說明:
- PreFilter:預處理 Pod 相關資訊,若回傳錯誤則整個調度週期終止
- Filter:過濾無法運行該 Pod 的節點,任一 Filter 插件標記不可行就直接排除
- PostFilter:僅在 Filter 階段找不到可用節點時觸發,典型用途是搶佔(Preemption)
- PreScore:為後續評分插件準備共享狀態
- Score:對每個可調度節點進行評分
- NormalizeScore:將各插件評分標準化到 [0, 100] 區間
- Reserve:在正式綁定前先保留選中的節點
- Permit:最終批准或拒絕調度結果
- PreBind:綁定前的準備工作(例如掛載網路磁碟區)
- Bind:正式將 Pod 綁定到節點
- PostBind:綁定完成後的清理作業
簡而言之,預選階段對應的是 Filter,負責篩掉不合格的節點;優選階段對應 Score,負責為每個節點打分。最終分數的計算方式是:節點分數 = 插件評分 × 插件權重,分數最高的節點獲勝。
Filter 與 Score 階段的內建插件
Filter 階段插件
| 插件名稱 | 功能說明 |
|---|---|
| PodTopologySpread | 檢查節點是否滿足 Pod 的拓撲分佈約束 |
| InterPodAffinity | 檢查節點是否符合 Pod 間的親和性設定 |
| NodePorts | 檢查節點的連接埠是否能滿足 Pod 需求 |
| NodeAffinity | 檢查節點是否符合 Pod 的節點親和性規則 |
| VolumeBinding | 檢查 PV 的節點親和性,並保存可動態建立 PVC 的節點 |
| TaintToleration | 依據 NoSchedule 和 NoExecute 污點過濾節點 |
Score 階段插件
| 插件名稱 | 功能說明 | 預設權重 |
|---|---|---|
| NodeAffinity | 依據節點親和性權重計算得分 | 2 |
| NodeResourcesBalancedAllocation | 依各資源(CPU、記憶體、磁碟區)的佔比與權重計算 | 1 |
| ImageLocality | 依映像檔大小與在各節點的分佈情況評分 | 1 |
| InterPodAffinity | 依據 Pod 間親和性權重計算得分 | 2 |
| TaintToleration | 依 PreferNoSchedule 策略計算得分 | 3 |
| NodeResourcesFit | 三種策略:LeastAllocated / MostAllocated / RequestedToCapacityRatio | 1 |
| PodTopologySpread | 依拓撲匹配度和權重計算得分 | 2 |
實戰:透過調整權重來控制調度目標
接下來用一個實際範例,示範如何透過修改 Score 插件的權重,讓 Pod 被調度到我們期望的節點上。
環境設定
假設叢集中有兩個節點 k8s-0001 和 k8s-0002,已經有一個 nginx 工作負載跑在 k8s-0002 上。我們建立一個 test Deployment,同時設定了節點親和性(偏好 k8s-0001)和 Pod 親和性(偏好跟 nginx 同節點,即 k8s-0002):
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
selector:
matchLabels:
app: test
template:
metadata:
labels:
app: test
spec:
containers:
- name: container-1
image: nginx
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-0001
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
namespaces:
- default
topologyKey: kubernetes.io/hostname
這時候兩種親和性互相「打架」,最終調度結果就取決於各插件的權重高低。
情境一:提高 InterPodAffinity 權重 -- 調度到 k8s-0002
透過 ConfigMap 修改 kube-scheduler 設定,把 InterPodAffinity 權重拉到 100,NodeAffinity 維持 1:
apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-config
namespace: kube-system
data:
scheduler-config.yaml: |
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
score:
disabled:
- name: InterPodAffinity
- name: NodeAffinity
enabled:
- name: InterPodAffinity
weight: 100
- name: NodeAffinity
weight: 1
結果:k8s-0002 在 InterPodAffinity 拿到 100 分 x 權重 100 = 10000 分,輕鬆勝出,Pod 調度到 k8s-0002。
情境二:提高 NodeAffinity 權重 -- 調度到 k8s-0001
反過來,把 NodeAffinity 權重拉高到 100,InterPodAffinity 設為 1:
apiVersion: v1
kind: ConfigMap
metadata:
name: scheduler-config
namespace: kube-system
data:
scheduler-config.yaml: |
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
plugins:
score:
disabled:
- name: InterPodAffinity
- name: NodeAffinity
enabled:
- name: InterPodAffinity
weight: 1
- name: NodeAffinity
weight: 100
結果:k8s-0001 在 NodeAffinity 拿到高分 x 權重 100,Pod 調度到 k8s-0001。
結語
Kubernetes 調度器的設計其實非常精巧,透過 Scheduler Framework 的插件化架構,讓我們可以在不修改核心程式碼的情況下,靈活調整調度策略。實務上最常見的做法就是透過調整 Score 插件的權重來影響調度結果,這比自己開發調度器插件要簡單得多。希望這篇文章能幫助大家更深入理解 K8s 調度器的運作機制。

發佈留言