← 返回上一頁
Kubernetes

深入理解 Kubernetes 調度器:從 Filter 到 Score 的權重調整實戰

本頁目錄
Kubernetes Scheduler Framework 各階段擴展點示意圖

前言

在 Kubernetes 叢集中,kube-scheduler 扮演著至關重要的角色。它負責監聽 kube-apiserver,找出尚未被分配節點的 Pod,並依據一套完整的調度策略,把這些 Pod 放到最適合的 Node 上運行。今天我想用比較白話的方式,帶大家走一遍 K8s 調度器的整個運作邏輯,以及如何透過權重調整來影響調度結果。

調度的完整流程

整個調度流程大致是這樣的:當我們透過 kubectl 或 API 建立一個 Pod 時,kube-apiserver 會把這筆請求存進 etcd。接著 kube-scheduler 內部的 Informer 元件透過 list-watch 機制,以 spec.nodeName="" 條件篩選出還沒被調度的 Pod,然後依序進行以下步驟:

  1. 預選(Predicate / Filter):先把不符合條件的節點通通過濾掉
  2. 優選(Priority / Score):針對通過預選的節點進行評分,選出得分最高的那個
  3. 綁定(Bind):把 Pod 的 spec.nodeName 設為選中的節點名稱,完成調度

Kubernetes 調度器節點過濾與評分流程圖

小提醒:如果你直接在 Pod spec 裡指定了 nodeName,調度器會被完全跳過,不會做任何資源檢查與過濾,請謹慎使用。

Scheduler Framework 深入解析

Kubernetes 的調度框架叫做 Scheduler Framework,Pod 在調度過程中會依序經過多個階段,每個階段都有對應的插件來實現調度邏輯。你也可以在特定階段開發自己的插件來客製化調度行為。

Kubernetes Scheduler Framework 各階段擴展點示意圖

以下是各個階段的簡要說明:

  • 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-0001k8s-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 調度器的運作機制。

分享這篇
X LinkedIn Facebook Hacker News Reddit

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

這個網站採用 Akismet 服務減少垃圾留言。進一步了解 Akismet 如何處理網站訪客的留言資料