深刻剖析k8s之默認調度器調度策略解析

本篇專一在調度過程當中 Predicates 和 Priorities 這兩個調度策略主要發生做用的階段。node

Predicates

首先,咱們一塊兒看看 Predicates。nginx

Predicates 在調度過程當中的做用,能夠理解爲 Filter,即:它按照調度策略,從當前集羣的全部節點中,「過濾」出一系列符合條件的節點。這些節點,都是能夠運行待調度 Pod 的宿主機。算法

而在 Kubernetes 中,默認的調度策略有以下三種。docker

第一種類型,叫做 GeneralPredicates。

顧名思義,這一組過濾規則,負責的是最基礎的調度策略。api

PodFitsResources

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。dom

而在 PodFitsResources 裏面,調度器其實並不知道這個字段 Key 的含義是 GPU,而是直接使用後面的 Value 進行計算。固然,在 Node 的 Capacity 字段裏,你也得相應地加上這臺宿主機上 GPU 的總數,好比:alpha.kubernetes.io/nvidia-gpu=4。這些流程,我在後面講解 Device Plugin 的時候會詳細介紹到。優化

PodFitsHost

而 PodFitsHost 檢查的是,宿主機的名字是否跟 Pod 的 spec.nodeName 一致。ui

PodFitsHostPorts

PodFitsHostPorts 檢查的是,Pod 申請的宿主機端口(spec.nodePort)是否是跟已經被使用的端口有衝突。

PodMatchNodeSelector

PodMatchNodeSelector 檢查的是,Pod 的 nodeSelector 或者 nodeAffinity 指定的節點,是否與待考察節點匹配,等等。

能夠看到,像上面這樣一組 GeneralPredicates,正是 Kubernetes 考察一個 Pod 能不能運行在一個 Node 上最基本的過濾條件。因此,GeneralPredicates 也會被其餘組件(好比 kubelet)直接調用。

我在上一篇文章中已經提到過,kubelet 在啓動 Pod 前,會執行一個 Admit 操做來進行二次確認。這裏二次確認的規則,就是執行一遍 GeneralPredicates。

第二種類型,是與 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 才能被調度到該節點上。

而 NodeMemoryPressurePredicate,檢查的是當前節點的內存是否是已經不夠充足,若是是的話,那麼待調度 Pod 就不能被調度到該節點上。<

第四種類型,是 Pod 相關的過濾規則

這一組規則,跟 GeneralPredicates 大多數是重合的。而比較特殊的,是 PodAffinityPredicate。這個規則的做用,是檢查待調度 Pod 與 Node 上的已有 Pod 之間的親密(affinity)和反親密(anti-affinity)關係。好比下面這個例子:

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 這個關鍵詞的做用。

而與 podAntiAffinity 相反的,就是 podAffinity,好比下面這個例子:

apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity: 
      requiredDuringSchedulingIgnoredDuringExecution: 
      - labelSelector:
          matchExpressions:
          - key: security 
            operator: In 
            values:
            - S1 
        topologyKey: failure-domain.beta.kubernetes.io/zone
  containers:
  - name: with-pod-affinity
    image: docker.io/ocpqe/hello-pod

這個例子裏的 Pod,就只會被調度到已經有攜帶了 security=S1 標籤的 Pod 運行的 Node 上。而這條規則的做用域,則是全部攜帶 Key 是failure-domain.beta.kubernetes.io/zone標籤的 Node。

此外,上面這兩個例子裏的 requiredDuringSchedulingIgnoredDuringExecution 字段的含義是:這條規則必須在 Pod 調度時進行檢查(requiredDuringScheduling);可是若是是已經在運行的 Pod 發生變化,好比 Label 被修改,形成了該 Pod 再也不適合運行在這個 Node 上的時候,Kubernetes 不會進行主動修正(IgnoredDuringExecution)。

上面這四種類型的 Predicates,就構成了調度器肯定一個 Node 能夠運行待調度 Pod 的基本策略。

在具體執行的時候, 當開始調度一個 Pod 時,Kubernetes 調度器會同時啓動 16 個 Goroutine,來併發地爲集羣裏的全部 Node 計算 Predicates,最後返回能夠運行這個 Pod 的宿主機列表。

須要注意的是,在爲每一個 Node 執行 Predicates 時,調度器會按照固定的順序來進行檢查。這個順序,是按照 Predicates 自己的含義來肯定的。好比,宿主機相關的 Predicates 會被放在相對靠前的位置進行檢查。要否則的話,在一臺資源已經嚴重不足的宿主機上,上來就開始計算 PodAffinityPredicate,是沒有實際意義的。

Priorities

接下來,咱們再來看一下 Priorities。

在 Predicates 階段完成了節點的「過濾」以後,Priorities 階段的工做就是爲這些節點打分。這裏打分的範圍是 0-10 分,得分最高的節點就是最後被 Pod 綁定的最佳節點。

Priorities 裏最經常使用到的一個打分規則,是 LeastRequestedPriority。它的計算方法,能夠簡單地總結爲以下所示的公式:

score = (cpu((capacity-sum(requested))10/capacity) + memory((capacity-sum(requested))10/capacity))/2

能夠看到,這個算法實際上就是在選擇空閒資源(CPU 和 Memory)最多的宿主機。

而與 LeastRequestedPriority 一塊兒發揮做用的,還有 BalancedResourceAllocation。它的計算公式以下所示:

score = 10 - variance(cpuFraction,memoryFraction,volumeFraction)*10

其中,每種資源的 Fraction 的定義是 :Pod 請求的資源 / 節點上的可用資源。而 variance 算法的做用,則是計算每兩種資源 Fraction 之間的「距離」。而最後選擇的,則是資源 Fraction 差距最小的節點。

因此說,BalancedResourceAllocation 選擇的,實際上是調度完成後,全部節點裏各類資源分配最均衡的那個節點,從而避免一個節點上 CPU 被大量分配、而 Memory 大量剩餘的狀況。

此外,還有 NodeAffinityPriority、TaintTolerationPriority 和 InterPodAffinityPriority 這三種 Priority。顧名思義,它們與前面的 PodMatchNodeSelector、PodToleratesNodeTaints 和 PodAffinityPredicate 這三個 Predicate 的含義和計算方法是相似的。可是做爲 Priority,一個 Node 知足上述規則的字段數目越多,它的得分就會越高。

在默認 Priorities 裏,還有一個叫做 ImageLocalityPriority 的策略。它是在 Kubernetes v1.12 裏新開啓的調度規則,即:若是待調度 Pod 須要使用的鏡像很大,而且已經存在於某些 Node 上,那麼這些 Node 的得分就會比較高。

固然,爲了不這個算法引起調度堆疊,調度器在計算得分的時候還會根據鏡像的分佈進行優化,即:若是大鏡像分佈的節點數目不多,那麼這些節點的權重就會被調低,從而「對衝」掉引發調度堆疊的風險。

以上,就是 Kubernetes 調度器的 Predicates 和 Priorities 裏默認調度規則的主要工做原理了。

在實際的執行過程當中,調度器裏關於集羣和 Pod 的信息都已經緩存化,因此這些算法的執行過程仍是比較快的。

此外,對於比較複雜的調度算法來講,好比 PodAffinityPredicate,它們在計算的時候不僅關注待調度 Pod 和待考察 Node,還須要關注整個集羣的信息,好比,遍歷全部節點,讀取它們的 Labels。這時候,Kubernetes 調度器會在爲每一個待調度 Pod 執行該調度算法以前,先將算法須要的集羣信息初步計算一遍,而後緩存起來。這樣,在真正執行該算法的時候,調度器只須要讀取緩存信息進行計算便可,從而避免了爲每一個 Node 計算 Predicates 的時候反覆獲取和計算整個集羣的信息。

須要注意的是,除了本篇講述的這些規則,Kubernetes 調度器裏其實還有一些默認不會開啓的策略。你能夠經過爲 kube-scheduler 指定一個配置文件或者建立一個 ConfigMap ,來配置哪些規則須要開啓、哪些規則須要關閉。而且,你能夠經過爲 Priorities 設置權重,來控制調度器的調度行爲。

相關文章
相關標籤/搜索