Kubernetes 調度器淺析

1、概述

Kubernetes 是 Google 開源的容器集羣管理系統(谷歌內部:Borg),而今天要介紹的 kube-scheduler 是 k8s 系統的核心組件之一,其主要職責就是經過自身的調度算法,爲新建立的 Pod 尋找一個最合適的 Node。
主要包含以下幾個步驟:node

  • 經過一組叫作謂詞 predicates 的過濾算法,先挑出知足條件的 Node;
  • 經過一組叫作優先級 priorities 的打分算法,來給上一步符合條件的每一個 Node 進行打分排名;
  • 最終選擇得分最高的節點,固然若是得分同樣就隨機一個節點,填回 Pod 的 spec.nodeName 字段。

官方流程圖以下:git

For given pod:

    +---------------------------------------------+
    |               Schedulable nodes:            |
    |                                             |
    | +--------+    +--------+      +--------+    |
    | | node 1 |    | node 2 |      | node 3 |    |
    | +--------+    +--------+      +--------+    |
    |                                             |
    +-------------------+-------------------------+
                        |
                        |
                        v
    +-------------------+-------------------------+

    Pred. filters: node 3 doesn't have enough resource

    +-------------------+-------------------------+
                        |
                        |
                        v
    +-------------------+-------------------------+
    |             remaining nodes:                |
    |   +--------+                 +--------+     |
    |   | node 1 |                 | node 2 |     |
    |   +--------+                 +--------+     |
    |                                             |
    +-------------------+-------------------------+
                        |
                        |
                        v
    +-------------------+-------------------------+

    Priority function:    node 1: p=2
                          node 2: p=5

    +-------------------+-------------------------+
                        |
                        |
                        v
            select max{node priority} = node 2

scheduler 的工做看似很簡單,但其實否則。考慮的問題很是多,好比要保證每一個節點被公平調度,提升資源利用率,提升 pod 調度效率,提高調度器擴展能力等等。
可涉及的內容很是多,接下來會圍繞兩個核心步驟對 k8s 的 默認調度策略 深刻了解。github

參考 Kubernetes 版本: v1.12

2、Predicates

Predicates 在調度過程當中的做用就是先進行過濾,過濾掉全部不符合條件的節點後,剩下的全部節點就都是能夠運行帶調度 Pod。算法

Predicates 的能夠分爲以下四類:api

  • GeneralPredicates:負責最基礎的調度策略,好比 PodFitsResources 計算宿主機資源是否夠用。
  • 與 Volume 相關的過濾規則:負責與容器持久化 Volume 相關的調度策略。
  • 與宿主機相關的過濾規則:負責考察待調度 Pod 是否知足 Node 自己的一些條件。
  • 與已運行 Pod 相關的過濾規則:負責檢查待調度 Pod 與 Node 上已有 Pod 之間的親和性關係。
具體的 Predicates 默認策略,能夠參考: 默認調度策略

當開始調度一個 Pod 的時候,調度器會同時開啓多個協程併發的進行 Node Predicates 過濾,最後返回一個能夠運行 Pod 的節點列表。每一個協程都是按照固定的順序進行計算過濾的。緩存

接下來,咱們看下四大類具體運行的調度策略內容。併發

1. GeneralPredicates

看字面意思就知道 GeneralPredicates 負責的是最基礎的調度策略,其包含的具體策略以下:ide

  • PodFitsResources: 計算宿主機的 CPU、內存、擴展資源(如 GPU)等是否夠用。
  • PodFitsHost: 檢查宿主機的名字是否跟 Pod 的 spec.nodeName 匹配。
  • PodFitsHostPorts: 檢查 Pod 申請的宿主機端口有沒有衝突。
  • PodMatchNodeSelector: 檢查節點是否能匹配 Pod 的 nodeSelector 和 nodeAffinity。

由於 GeneralPredicates 是最基礎的調度策略,因此該接口也會被別的組件直接調用,好比 kubelet、daemonSet controller。kubelet 在啓動 pod 以前,還會再執行一遍 GeneralPredicates,用於二次確認。函數

2. 與 Volume 相關的過濾規則

不廢話就直接列舉具體的策略了:命令行

  • NoDiskConflict:檢查該節點上全部的 Pods 是否與待調度的 Pod 的 Volume 有衝突,好比 AWS、GCE 的 Volume 是不容許被兩個 Pod 同時使用的。
  • VolumeZonePredicate:檢查 Pod Volume 的 zone 標籤是否與節點的 zone 標籤匹配。若是 Node 沒有 zone 標籤則認定爲匹配。
  • MaxPDVolumeCountPredicate:檢查節點上某種類型的 Volume 是否已經超過指定數目。
  • CSIMaxVolumeLimitPredicate:檢查 csi volume 相關的限制
  • VolumeBindingPredicate:檢查 Pod 對應的 Local PV 的 nodeAffinity 字段,是否跟某個節點的標籤相匹配。若是該 Pod PVC 尚未綁定 PV 的話,則調度器還要負責檢查全部待綁定的 PV,且該 PV 的 nodeAffinity 是否與節點標籤匹配。

3. 與宿主機相關的過濾規則

這些規則主要考察待調度的 Pod 是否知足 Node 自己的一些條件。
具體的策略以下:

  • NodeConditionPredicate:檢查 Node 是否還未準備好或者處於NodeOutOfDisk、NodeNetworkUnavailable 狀態,又或者 Node spec.Unschedulable 設置爲 true,那該節點都將沒法被調度。
  • PodToleratesNodeTaints:檢查 Node 的 taint(污點)機制。只有當 Pod 的 Toleration 與 Node 的 Taint 匹配時,Pod 才能調度到該節點上。
  • NodeMemoryPressurePredicate:檢查當前節點的內存是否已經不夠使用。
  • NodeDiskPressurePredicate:檢查當前節點的磁盤是否已經不夠使用。
  • NodePIDPressurePredicate:檢查當前節點的 PID 是否已經不夠使用。

4. 與已運行 Pod 相關的過濾規則

該規則主要就是 PodAffinityPredicate,用於檢查待調度 Pod 與 Node 上已有的 Pod 之間的親和性和反親和性關係。
具體的親和性相關的調度,後面會單獨拿一篇文章進行介紹。

3、Priorities

完成了前一個階段的節點 「過濾」 以後,便須要經過 Priorities 爲這些節點打分,選擇得分最高的節點,做爲調度對象。
打分函數不少,總得分能夠參考:
總分 = (權重1 * 打分函數1) + (權重2 * 打分函數2) + … + (權重n * 打分函數n)
每一次打分的範圍是 0 — 10 分。10 表示很是合適,0 表示很是不合適。
而且每一個打分函數均可以配置對應的權重值,下面介紹 調度器策略配置 時,也會涉及權重值的配置。默認權重值是 1,若是以爲某個打分函數特別重要,即可以加大該權重值。

具體的 Priorities 默認策略能夠參考: defaultPriorities

Priorities 最經常使用到的一個打分規則是 LeastRequestedPriority, 該算法用於選出空閒資源(cpu & memory)最多的宿主機。

還有一個常見的是 BalancedResourceAllocation,該規則主要目的是資源平衡。在全部節點裏選擇各類資源分配最均衡的節點,避免出現某些節點 CPU 被大量分配,可是 Memory 大量剩餘的狀況。

此外,還有 InterPodAffinityPriorityNodeAffinityPriorityTaintTolerationPriority,與親和性與污點調度有關,後面會有單獨的文章進行介紹。這裏表示節點知足的規則越多,那得分就越高。

在 K8S v1.12 版本還引入了一個調度策略,即 ImageLocalityPriority。該策略主要目的是優先選擇那些已經存有 Pod 所需 image 的節點,能夠避免實際運行 Pod 時,再去下載 image。

注意: pod 運行時是否會下載 image,還跟 Pod ImagePullPolicy 配置有關。

能夠看到 k8s scheduler 完成一次調度所需的信息很是之多。因此在實際的調度過程當中,大量的信息都事先已經緩存,提升了 Pod 的調度效率。

4、調度策略配置

Kubernetes 調度器有默認的調度策略,具體能夠參考 default 。固然用戶也能夠修改調度策略,能夠經過命令行參數 policy-config-file 指定一個 JSON 文件來描述哪些 predicates 和 priorities 在啓動 k8s 時被使用, 經過這個參數調度就能使用管理者定義的策略了。
示例以下:

{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
    {"name" : "PodFitsHostPorts"},
    {"name" : "PodFitsResources"},
    {"name" : "NoDiskConflict"},
    {"name" : "NoVolumeZoneConflict"},
    {"name" : "MatchNodeSelector"},
    {"name" : "HostName"}
    ],
"priorities" : [
    {"name" : "LeastRequestedPriority", "weight" : 1},
    {"name" : "BalancedResourceAllocation", "weight" : 1},
    {"name" : "ServiceSpreadingPriority", "weight" : 1},
    {"name" : "EqualPriority", "weight" : 1}
    ],
"hardPodAffinitySymmetricWeight" : 10,
"alwaysCheckAllPredicates" : false
}

5、自定義調度器

前面提到了調度器的擴展能力,除了使用 k8s 自帶的調度器,你也能夠編寫本身的調度器。經過修改 Pod 的 spec.schedulername 參數來指定調度器的名字。

參考資料

相關文章
相關標籤/搜索