揭秘 Kubernetes 排程:深入探討 Predicates 與 Priorities

揭秘 Kubernetes 排程:深入探討 Predicates 與 Priorities

今天的文章主要介紹排程過程中,Predicates 和 Priorities 這兩個排程策略主要作用的階段。

Predicates

讓我們先來看看 Predicates。Predicates 在排程過程中的作用可以理解為一個 Filter,也就是:它會根據排程策略,從當前叢集的所有節點中「過濾」出一系列符合條件的節點。這些節點都是等待排程的 Pod 的潛在主機。在 Kubernetes 中,有四種預設的排程策略。

第一種稱為 GeneralPredicates。

顧名思義,這組過濾規則負責最基本的排程策略。例如,PodFitsResources 計算主機的 CPU 和記憶體資源是否足夠。當然,如同我先前提到的,PodFitsResources 只檢查 Pod 的 requests 欄位。需要注意的是,Kubernetes 排程器並未為 GPU 等硬體資源定義特定的資源類型,而是使用一種稱為 Extended Resource 的 Key-Value 格式來描述。例如:

apiVersion: v1
kind: Pod
metadata:
  name: extended-resource-demo
spec:
  containers:
  - name: extended-resource-demo-ctr
    image: nginx
    resources:
      requests:
        alpha.kubernetes.io/nvidia-gpu: "2"
      limits:
        alpha.kubernetes.io/nvidia-gpu: "2"

可以看到,我們的 Pod 使用定義方式 alpha.kubernetes.io/nvidia-gpu=2 宣告使用兩個 NVIDIA 類型的 GPU。在 PodFitsResources 中,排程器實際上並不知道這個欄位 Key 的意義是 GPU,而是直接使用後面的 Value 進行計算。當然,在 Node 的 Capacity 欄位中,你也需要加入這個主機上的 GPU 總數,例如:alpha.kubernetes.io/nvidia-gpu=4。我之後會在講解 Device Plugin 時詳細介紹這些過程。PodFitsHost 檢查主機名稱是否與 Pod 的 spec.nodeName 相符。PodFitsHostPorts 檢查 Pod 申請的主機連接埠(spec.nodePort)是否與已使用的連接埠衝突。PodMatchNodeSelector 檢查 Pod 指定的 nodeSelectornodeAffinity 是否與正在檢查的節點相符,等等。可以看出,這樣一組 GeneralPredicates 是 Kubernetes 檢查一個 Pod 能否在 Node 上運行的最基本過濾條件。因此,GeneralPredicates 也會被其他元件(例如 kubelet)直接呼叫。

第二種是與 Volume 相關的過濾規則。

這組過濾規則負責與容器的持久化 Volume 相關的排程策略。其中,NoDiskConflict 檢查多個 Pod 宣告的持久化 Volume 之間是否存在衝突。例如,AWS EBS 類型的 Volume 不允許同時被兩個 Pod 使用。因此,當一個名為 A 的 EBS Volume 已經掛載在某個節點上時,另一個也宣告使用這個 A Volume 的 Pod 就無法被排程到這個節點。MaxPDVolumeCountPredicate 檢查某個節點上某一類型的持久化 Volume 是否已經超過一定數量。如果是,那麼宣告使用這類持久化 Volume 的 Pod 就不能再被排程到這個節點。VolumeZonePredicate 檢查持久化 Volume 的 Zone(高可用性域)標籤是否與正在檢查的節點的 Zone 標籤相符。此外,還有一個名為 VolumeBindingPredicate 的規則。它負責檢查 Pod 對應的 PV 的 nodeAffinity 欄位是否與某個節點的標籤相符。Local Persistent Volume(本地持久化卷)必須使用 nodeAffinity 綁定到特定的節點。這實際上意味著,在 Predicates 階段,Kubernetes 必須能夠根據 Pod 的 Volume 屬性進行排程。此外,如果 Pod 的 PVC 尚未綁定到特定的 PV,排程器也負責檢查所有待綁定的 PV。當存在一個可用的 PV,且該 PV 的 nodeAffinity 與正在檢查的節點一致時,此規則將返回「成功」。例如:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-local-pv
spec:
  capacity:
    storage: 500Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: /mnt/disks/vol1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - my-node

可以看到,這個 PV 對應的持久化目錄只會出現在名為 my-node 的主機上。因此,任何透過 PVC 使用這個 PV 的 Pod 都必須被排程到 my-node 才能正常運作。VolumeBindingPredicate 正是排程器做出這個決策的地方。

第三種是與主機相關的過濾規則。

這組規則主要檢查待排程的 Pod 是否滿足 Node 本身的某些條件。例如,PodToleratesNodeTaints 檢查我們經常使用的 Node 的「污點」機制。只有當 Pod 的 Toleration 欄位與 Node 的 Taint 欄位相符時,這個 Pod 才能被排程到該 Node。NodeMemoryPressurePredicate 檢查當前 Node 的記憶體是否不足。如果是,那麼等待排程的 Pod 就不能被排程到這個 Node。

第四種是與 Pod 相關的過濾規則。

這組規則與大多數 GeneralPredicates 重疊。比較特殊的是 PodAffinityPredicate。這個規則的作用是檢查待排程的 Pod 與 Node 上現有 Pod 之間的親和性與反親和性關係。例如:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-antiaffinity
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: security
              operator: In
              values:
              - S2
          topologyKey: kubernetes.io/hostname
  containers:
  - name: with-pod-affinity
    image: docker.io/ocpqe/hello-pod

在這個範例中,podAntiAffinity 規則指定了這個 Pod 不希望與任何帶有 security=S2 標籤的 Pod 存在於同一個 Node 上。需要注意的是,PodAffinityPredicate 有一個作用範圍,例如上述規則僅對帶有 Key 為 kubernetes.io/hostname 標籤的 Node 有效。這就是關鍵字 topologyKey 的作用。這四種 Predicates 構成了排程器判斷一個 Node 是否能運行待排程 Pod 的基本策略。執行時,當一個 Pod 被排程時,Kubernetes 排程器會同時啟動 16 個 Goroutine,並發地對叢集中所有 Node 執行 Predicates 計算,最後返回可以運行這個 Pod 的主機列表。需要注意的是,在對每個 Node 執行 Predicates 時,排程器會按照固定的順序進行檢查。這個順序是根據 Predicates 本身的含義決定的。例如,與主機相關的 Predicates 會更早被檢查。否則,在一個資源嚴重不足的主機上計算 PodAffinityPredicate 是沒有意義的。

Priorities

在 Predicates 階段對節點進行「過濾」之後,Priorities 階段的工作就是對這些節點進行評分。這裡的評分範圍是 0-10 分,得分最高的節點就是 Pod 最終綁定的最佳節點。Priorities 中最常用的評分規則是 LeastRequestedPriority。這個演算法實際上會選擇資源(CPU 和 Memory)最空閒的主機。此外,還有另外三個 Priorities:NodeAffinityPriority、TaintTolerationPriority 和 InterPodAffinityPriority。顧名思義,它們在含義和計算方法上與之前的三個 Predicates:PodMatchNodeSelector、PodToleratesNodeTaints 和 PodAffinityPredicate 相似。但是,作為 Priority,一個 Node 滿足上述規則的欄位越多,它的得分就越高。在預設的 Priorities 中,還有一個名為 ImageLocalityPriority 的策略。這是 Kubernetes v1.12 中啟用的一個新排程規則,即:如果待排程 Pod 所需的映像檔很大,並且已經存在於某些 Node 上,那麼這些 Node 將獲得更高的分數。當然,為了避免演算法導致排程堆疊,排程器在計算分數時也會根據映像檔的分佈進行最佳化,也就是說:如果大型映像檔所在節點的數量很少,那麼這些節點的權重就會降低,從而「抵消」導致排程堆疊的風險。總之,這就是 Kubernetes 排程器中預設排程規則的主要工作原理。在實際執行過程中,排程器中的叢集和 Pod 資訊已經被快取,因此這些演算法的執行過程相對較快。此外,對於更複雜的排程演算法,例如 PodAffinityPredicate,在計算時它們不僅關注待排程的 Pod 和正在檢查的 Node,還需要關注整個叢集的資訊,例如遍歷所有節點並讀取它們的 Labels。此時,Kubernetes 排程器會先計算演算法所需的叢集資訊,然後再為每個待排程的 Pod 執行排程演算法,並將其快取。這樣,在實際執行演算法時,排程器只需要讀取快取的資訊進行計算,從而避免在計算 Predicates 時為每個 Node 重複檢索和計算整個叢集的資訊。

總結

總之,本文討論了 Kubernetes 預設排程器內的主要排程演算法。需要注意的是,除了本文涵蓋的規則之外,Kubernetes 排程器中實際還有一些預設未啟用的策略。你可以為 kube-scheduler 指定配置檔,或建立 ConfigMap 來設定啟用或停用哪些規則。此外,你還可以透過設定 Priorities 的權重來控制排程器的行為。以上就是 Kubernetes 排程器中預設排程機制的說明。值得一提的是,雖然由於叢集和 Pod 資訊的快取,排程器運行速度很快,但對於更複雜的演算法,排程器必須考慮整個叢集的狀態,而不僅僅是 Pod 和 Node。這包括在實際執行排程演算法之前,預先計算並快取必要的叢集資訊,以確保排程過程的效率和準確性。

Novita AI 是一個全棧雲端平台,助您實現 AI 抱負。透過無縫整合的 API、無伺服器運算和 GPU 加速,我們提供經濟高效的工具,助您快速建立和擴展 AI 驅動的業務。消除基礎架構煩惱,免費開始使用 – Novita AI 讓您的 AI 夢想成真。

推薦閱讀:

  1. 關於 Docker 你需要知道的事
  2. 為什麼我們需要 Pods