Kubernetes 調度器解析

kube-scheduler是 kubernetes 系統的核心組件之一,主要負責整個集羣資源的調度功能,根據特定的調度算法和策略,將 Pod 調度到最優的工做節點上面去,從而更加合理、更加充分的利用集羣的資源,這也是咱們選擇使用 kubernetes 一個很是重要的理由。若是一門新的技術不能幫助企業節約成本、提供效率,我相信是很難推動的。nginx

調度流程

默認狀況下,kube-scheduler 提供的默認調度器可以知足咱們絕大多數的要求,咱們前面和你們接觸的示例也基本上用的默認的策略,均可以保證咱們的 Pod 能夠被分配到資源充足的節點上運行。可是在實際的線上項目中,可能咱們本身會比 kubernetes 更加了解咱們本身的應用,好比咱們但願一個 Pod 只能運行在特定的幾個節點上,或者這幾個節點只能用來運行特定類型的應用,這就須要咱們的調度器可以可控。算法

kube-scheduler 是 kubernetes 的調度器,它的主要做用就是根據特定的調度算法和調度策略將 Pod 調度到合適的 Node 節點上去,是一個獨立的二進制程序,啓動以後會一直監聽 API Server,獲取到 PodSpec.NodeName 爲空的 Pod,對每一個 Pod 都會建立一個 binding。數據庫

kube-scheduler structrue

kube-scheduler structrueapi

這個過程在咱們看來好像比較簡單,但在實際的生產環境中,須要考慮的問題就有不少了:app

  • 如何保證所有的節點調度的公平性?要知道並非說有節點資源配置都是同樣的
  • 如何保證每一個節點都能被分配資源?
  • 集羣資源如何可以被高效利用?
  • 集羣資源如何才能被最大化使用?
  • 如何保證 Pod 調度的性能和效率?
  • 用戶是否能夠根據本身的實際需求定製本身的調度策略?

考慮到實際環境中的各類複雜狀況,kubernetes 的調度器採用插件化的形式實現,能夠方便用戶進行定製或者二次開發,咱們能夠自定義一個調度器並以插件形式和 kubernetes 進行集成。ide

kubernetes 調度器的源碼位於 kubernetes/pkg/scheduler 中,大致的代碼目錄結構以下所示:(不一樣的版本目錄結構可能不太同樣)函數

kubernetes/pkg/scheduler
-- scheduler.go         //調度相關的具體實現
|-- algorithm
|   |-- predicates      //節點篩選策略
|   |-- priorities      //節點打分策略
|-- algorithmprovider
|   |-- defaults         //定義默認的調度器

其中 Scheduler 建立和運行的核心程序,對應的代碼在 pkg/scheduler/scheduler.go,若是要查看kube-scheduler的入口程序,對應的代碼在 cmd/kube-scheduler/scheduler.go。工具

調度主要分爲如下幾個部分:性能

  • 首先是預選過程,過濾掉不知足條件的節點,這個過程稱爲Predicates
  • 而後是優選過程,對經過的節點按照優先級排序,稱之爲Priorities
  • 最後從中選擇優先級最高的節點,若是中間任何一步驟有錯誤,就直接返回錯誤

Predicates階段首先遍歷所有節點,過濾掉不知足條件的節點,屬於強制性規則,這一階段輸出的全部知足要求的 Node 將被記錄並做爲第二階段的輸入,若是全部的節點都不知足條件,那麼 Pod 將會一直處於 Pending 狀態,直到有節點知足條件,在這期間調度器會不斷的重試。優化

因此咱們在部署應用的時候,若是發現有 Pod 一直處於 Pending 狀態,那麼就是沒有知足調度條件的節點,這個時候能夠去檢查下節點資源是否可用。

Priorities階段即再次對節點進行篩選,若是有多個節點都知足條件的話,那麼系統會按照節點的優先級(priorites)大小對節點進行排序,最後選擇優先級最高的節點來部署 Pod 應用。

下面是調度過程的簡單示意圖:kube-scheduler filter

更詳細的流程是這樣的:

  • 首先,客戶端經過 API Server 的 REST API 或者 kubectl 工具建立 Pod 資源
  • API Server 收到用戶請求後,存儲相關數據到 etcd 數據庫中
  • 調度器監聽 API Server 查看爲調度(bind)的 Pod 列表,循環遍歷地爲每一個 Pod 嘗試分配節點,這個分配過程就是咱們上面提到的兩個階段:
    • 預選階段(Predicates),過濾節點,調度器用一組規則過濾掉不符合要求的 Node 節點,好比 Pod 設置了資源的 request,那麼可用資源比 Pod 須要的資源少的主機顯然就會被過濾掉
    • 優選階段(Priorities),爲節點的優先級打分,將上一階段過濾出來的 Node 列表進行打分,調度器會考慮一些總體的優化策略,好比把 Deployment 控制的多個 Pod 副本分佈到不一樣的主機上,使用最低負載的主機等等策略
  • 通過上面的階段過濾後選擇打分最高的 Node 節點和 Pod 進行 binding 操做,而後將結果存儲到 etcd 中
  • 最後被選擇出來的 Node 節點對應的 kubelet 去執行建立 Pod 的相關操做

其中Predicates過濾有一系列的算法可使用,咱們這裏簡單列舉幾個:

  • PodFitsResources:節點上剩餘的資源是否大於 Pod 請求的資源
  • PodFitsHost:若是 Pod 指定了 NodeName,檢查節點名稱是否和 NodeName 匹配
  • PodFitsHostPorts:節點上已經使用的 port 是否和 Pod 申請的 port 衝突
  • PodSelectorMatches:過濾掉和 Pod 指定的 label 不匹配的節點
  • NoDiskConflict:已經 mount 的 volume 和 Pod 指定的 volume 不衝突,除非它們都是隻讀的
  • CheckNodeDiskPressure:檢查節點磁盤空間是否符合要求
  • CheckNodeMemoryPressure:檢查節點內存是否夠用

除了這些過濾算法以外,還有一些其餘的算法,更多更詳細的咱們能夠查看源碼文件:k8s.io/kubernetes/pkg/scheduler/algorithm/predicates/predicates.go。

Priorities優先級是由一系列鍵值對組成的,鍵是該優先級的名稱,值是它的權重值,一樣,咱們這裏給你們列舉幾個具備表明性的選項:

  • LeastRequestedPriority:經過計算 CPU 和內存的使用率來決定權重,使用率越低權重越高,固然正常確定也是資源是使用率越低權重越高,能給別的 Pod 運行的可能性就越大
  • SelectorSpreadPriority:爲了更好的高可用,對同屬於一個 Deployment 或者 RC 下面的多個 Pod 副本,儘可能調度到多個不一樣的節點上,當一個 Pod 被調度的時候,會先去查找該 Pod 對應的 controller,而後查看該 controller 下面的已存在的 Pod,運行 Pod 越少的節點權重越高
  • ImageLocalityPriority:就是若是在某個節點上已經有要使用的鏡像節點了,鏡像總大小值越大,權重就越高
  • NodeAffinityPriority:這個就是根據節點的親和性來計算一個權重值,後面咱們會詳細講解親和性的使用方法

除了這些策略以外,還有不少其餘的策略,一樣咱們能夠查看源碼文件:k8s.io/kubernetes/pkg/scheduler/algorithm/priorities/ 瞭解更多信息。每個優先級函數會返回一個0-10的分數,分數越高表示節點越優,同時每個函數也會對應一個表示權重的值。最終主機的得分用如下公式計算得出:

finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn)

自定義調度

上面就是 kube-scheduler 默認調度的基本流程,除了使用默認的調度器以外,咱們也能夠自定義調度策略。

調度器擴展

kube-scheduler在啓動的時候能夠經過 --policy-config-file參數來指定調度策略文件,咱們能夠根據咱們本身的須要來組裝PredicatesPriority函數。選擇不一樣的過濾函數和優先級函數、控制優先級函數的權重、調整過濾函數的順序都會影響調度過程。

下面是官方的 Policy 文件示例:

{
    "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}
    ]
}

多調度器

若是默認的調度器不知足要求,還能夠部署自定義的調度器。而且,在整個集羣中還能夠同時運行多個調度器實例,經過podSpec.schedulerName 來選擇使用哪個調度器(默認使用內置的調度器)。

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  schedulerName: my-scheduler  # 選擇使用自定義調度器 my-scheduler
  containers:
  - name: nginx
    image: nginx:1.10

要開發咱們本身的調度器也是比較容易的,好比咱們這裏的 my-scheduler:

  • 首先須要經過指定的 API 獲取節點和 Pod
  • 而後選擇phase=PendingschedulerName=my-scheduler的pod
  • 計算每一個 Pod 須要放置的位置以後,調度程序將建立一個Binding對象
  • 而後根據咱們自定義的調度器的算法計算出最適合的目標節點

優先級調度

與前面所講的調度優選策略中的優先級(Priorities)不一樣,前面所講的優先級指的是節點優先級,而咱們這裏所說的優先級 pod priority 指的是 Pod 的優先級,高優先級的 Pod 會優先被調度,或者在資源不足低狀況犧牲低優先級的 Pod,以便於重要的 Pod 可以獲得資源部署。

要定義 Pod 優先級,就須要先定義PriorityClass對象,該對象沒有 Namespace 的限制:

apiVersion: v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This priority class should be used for XYZ service pods only."

其中:

  • value爲 32 位整數的優先級,該值越大,優先級越高
  • globalDefault用於未配置 PriorityClassName 的 Pod,整個集羣中應該只有一個PriorityClass將其設置爲 true

而後經過在 Pod 的spec.priorityClassName中指定已定義的PriorityClass名稱便可:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: IfNotPresent
  priorityClassName: high-priority

另一個值得注意的是當節點沒有足夠的資源供調度器調度 Pod,致使 Pod 處於 pending 時,搶佔(preemption)邏輯就會被觸發。Preemption會嘗試從一個節點刪除低優先級的 Pod,從而釋放資源使高優先級的 Pod 獲得節點資源進行部署。

如今咱們經過下面的圖再去回顧下 kubernetes 的調度過程是否是就清晰不少了:kube-scheduler

相關文章
相關標籤/搜索