深度解析Kubernetes核心原理之Scheduler

Kubernetes是一個容器編排引擎,它被設計爲在被稱爲集羣的節點上運行容器化應用。經過系統建模的方法,本系列文章的目的是爲了可以深刻了解Kubernetes以及它的深層概念。node

Kubernetes Scheduler是Kubernetes的一個核心組件:在用戶或者控制器建立一個Pod後,Scheduler在對象存儲數據裏監控未被分配的Pod,並將Pod分配到某個節點。而後Kubelet在對象存儲數據裏監控已分配的Pod,並運行該Pod。ide

本文提供了一個Kubernetes Scheduler的更簡潔、更詳細的模型表述。該模型部分基於TLA+規範。
圖片描述函數

圖 1. Pod處理流程優化

調度ui

Kubernetes Scheduler的任務是選擇一個placement(位置)。一個placement是一個部分的,非內射的Pod集合到節點集合的分配。
圖片描述spa

圖 2. 調度示例設計

調度是一個最優化問題:首先,Scheduler肯定feasible placements(可用的位置),這些是知足給定約束的placement集合。而後,Scheduler肯定viable placements(可行的位置),這些是得分最高的feasible placements集合。3d

圖片描述

圖 3. Possible(可能), Feasible(可用)和Viable(可行)的調度code

Kubernetes Scheduler是一個保證局部最優解的多步調度器,而不是一個保證全局最優解的單步調度器。對象

圖片描述

圖 4. 多步 vs. 單步

Kubernetes Scheduler

圖片描述

圖 5. Kubernetes Pod對象和Node對象

圖5描述了Kubernetes Scheduler所感興趣的Kubernetes對象和屬性。在Kubernetes裏: 一個Pod表示爲一個Kubernetes Pod對象 一個Node表示爲一個Kubernetes Node對象 * 一個Pod分配給一個Node表示爲Pod的Spec.NodeName屬性

BoundTo(Pod, Node, Snapshot)≝ 
∧ Pod ∈Snapshot
∧Pod.Kind = "Pod"
∧ Node∈ Snapshot
∧Node.Kind = "Node"
∧Pod.Spec.NodeName = Node.Name

Bound(Pod, Snapshot) ≝ 
∃ Node∈ Snapshot:
BoundTo(Pod, Node, Snapshot)

若是一個Pod的Spec.NodeName等於一個Node的Name,則表示這個Pod對象綁定到了這個Node對象。

Kubernetes Scheduler的任務如今能夠更規範地表述爲:對於一個Pod p,Kubernetes Scheduler選擇一個Node n,且更新(*)這個Pod的Spec.NodeName使得BoundTo(p, n)爲true。

控制循環邏輯

Scheduler ≝
LETUnbound ≝ {Pod \in Objects : Pod.Kind = "Pod" ∧ ~ Bound(Pod,Objects)} IN
∃ Pod∈ { Pod ∈ Unbound : ∀ Other ∈ Unbound : Other.Spec.Priority ≤ Pod.Spec.Priority}:
    CASE SchedulingEnabled(Pod) ⟶ Scheduling(Pod)
      [] PreemptionEnabled(Pod) ⟶ Preemption(Pod)
      [] OTHER ⟶ UNCHANGED(Objects)

Kubernetes Scheduler監控Kubernetes對象存儲而且選擇一個未綁定的最高優先級的Pod來執行調度流程或者搶佔流程。

調度流程

SchedulingEnabled(Pod) ≝
∃ Node∈ {Node ∈ Objects : Node.Kind = "Node"}: 
Feasibility(Pod, Node, Objects)

Scheduling(Pod) ≝
LETFeasibile ≝ {Node ∈ Objects : n.Kind = "Node" ∧ Feasibility(Pod, n,Objects)} IN
∃Node ∈ Feasibile : 
  ∧ ∀Other ∈ Feasibile : Viability(Pod, Other, Objects) ≤ Viability(Pod, Node,Objects)
  ∧Objects' = {
   IF Pod = Object THEN 
     [Pod EXCEPT !["Spec"] = [Pod.Spec EXCEPT!["NodeName"] = Node.Name]] 
   ELSE 
     Object : Object ∈ Objects}

對於一個給定的Pod,若是存在至少一個Node能夠運行該Pod,則啓用調度流程。

若是調度流程啓用,Scheduler將綁定該Pod到一個可選的Node,使得綁定能達到最優的可行性。

若是調度流程未啓用,則Sheduler將嘗試執行搶佔流程。

搶佔流程

PreemptionEnabled(Pod) ≝
∃ Node∈ {Node \in Objects : Node.Kind = "Node"}:
∃Pods ∈ SUBSET(Jeopardy(Pod, Node, Objects)):
 Feasibility(Pod, Node, Objects \ Pods)

Preemption(Pod) ==
LETPreemptable == {Node ∈ Objects : Node.Kind = "Node" ∧ ∃ Pods ∈SUBSET(Jeopardy(p, Node, Objects)): Feasibility(Pod, Node, Objects \ Pods)} IN
∃Node ∈ Preemptable:
  ∃Pods ∈ SUBSET(Jeopardy(Pod, Node, Objects)): 
    ∀OtherNode ∈ qualified: 
     ∀ OtherPods ∈ SUBSET(Jeopardy(Pod, OtherNode, Objects)): 
       ∧ Casualty(Pods) ≤ Casualty(OtherPods)
       ∧ Objects' = (Objects \ Pods)

對於一個給定的Pod,若是存在至少一個Node,在刪除綁定到該Node的較低優先級Pod子集後能夠運行該Pod,則啓用搶佔流程。

若是搶佔流程啓用,Scheduler將觸發綁定到Node的低優先級Pod子集的刪除操做,使得搶佔流程形成的損害最小。

(搶佔損害是用Pod Disruption Budget來評估的,超出了本文的主題)

注意的是Scheduler不保證觸發搶佔流程的Pod在後續的調度流程中能綁定到Node。

  1. 可用性(Feaisbility)

對於每個Pod,Kubernetes Scheduler肯定可用的Node集合,這些Node知足了該Pod的約束。

從概念上講,Kubernetes Scheduler定義了一個過濾函數集合。給定一個Pod和一個Node,過濾函數決定該Node是否知足該Pod的約束。全部過濾函數都必須返回true才表示該Node能夠運行該Pod。
Feasibility(Pod, Node,Snapshot) ==
(Filter_1(Pod, Node, Snapshot) ∧ Filter_2(Pod, Node, Snapshot) ∧ ...)

下面小節詳細描述了目前一些可用的過濾函數:

1.1 可調度性和生命週期階段(Schedulability and LifecyclePhase)

該過濾函數基於Node的可調度性和生命週期階段來肯定Node的可用。Nodeconditions經過taints和tolerations來講明(以下所示)。

圖片描述

圖 1.1 可調度性和生命週期階段

Filter(Pod, Node) ≝
\* Onlyconsider Nodes that accept new Pods
∧Node.Spec.Unschedulable = False
\* Onlyconsider Nodes that are ready to accept new Pods (Lifecycle Phase)
∧Node.Status.Phase = "Running"

1.2 資源需求和資源可用性

該過濾函數基於Pod的資源需求和Node的資源可用性來肯定Node的可用。

圖片描述

圖 1.2 資源需求和資源可用性

Resources(Pod, Node) ≝
∧ 1 ≤Node.Status.Allocatable["pods"]
\* Usethe maximum resource requirements of init containers
∧ Max({i \in DOMAIN p.Spec.InitContainer :p.Spec.InitContainer[i].Resources.Required["cpu"] }) ≤Node.Status.Allocatable["cpu"]
∧ Max({i \in DOMAIN p.Spec.InitContainer :p.Spec.InitContainer[i].Resources.Required["mem"] }) ≤Node.Status.Allocatable["mem"]
∧ ...
\* Usethe sum of resource requirements of main containers
∧ Sum({i \in DOMAIN p.Spec.Container :p.Spec.Container[i].Resources.Required["cpu"] }) ≤Node.Status.Allocatable["cpu"]
∧ Sum({i \in DOMAIN p.Spec.Container :p.Spec.Container[i].Resources.Required["mem"] }) ≤Node.Status.Allocatable["mem"]
∧ ...

1.3 Node Selector

該過濾函數基於Pod的node selector值和Node的label值來肯定Node的可用。
圖片描述

圖 1.3 Node Selector

Filter(Pod, Node) ==
∀ Label∈ DOMAIN(Pod.Spec.NodeSelector): 
  ∧Label ∈ DOMAIN(Node.Labels) 
  ∧Pod.Spec.NodeSelector[Label] = Node.Labels[Label]

1.4 Node Taints和Pod Tolerations

該過濾函數基於Pod的taints鍵值對和Node的tolerations鍵值對來肯定Node的可用。
圖片描述

圖 1.4 Node Taints和Pod Tolerations

Filter(Pod, Node) == 
∀Taint ∈ Node.Spec.Taints:
    ∃Toleration ∈ Pod.Spec.Tolerations: Match(Toleration, Taint)

Match(Toleration, Taint) ==
∧CASE Toleration.Operator = "Exists" 
      ⟶ Toleration.key = Taint.key 
   [] Toleration.Operator = "Equal" 
      ⟶ Toleration.key = Taint.key ∧ Toleration.value = Taint.value
   [] OTHER 
      ⟶ FALSE
∧Toleration.Effect = Taint.Effect

若是某個Node的taints匹配Pod的tolerations, 一個Pod可能被綁定到該Node。若是某個Node的taints不匹配Pod的tolerations, 一個Pod不能被綁定該Node。

1.5 親和性

該過濾函數基於Pod須要的Node親和項,Pod親和項和Pod反親和項來肯定Node的可用。

圖片描述

圖 1.4 Node Taints和Pod Tolerations

Filter(Pod, Node) ≝
\*Node, Affinity
∧ ∃NodeSelectorTerm ∈ Pod.Spec.Affinity.NodeAffinity.Required.NodeSelectorTerms : 
       Match_NS(NodeSelectorTerm, Node)
\*Pod, Affinity
∧ ∀PodAffinityTerm ∈ Pod.Spec.Affinity.PodAffinity.Required : 
       P_Affinity(PodAffinityTerm, Node)
\*Pod, Anit-Affinity
∧ ∀PodAffinityTerm ∈ Pod.Spec.Affinity.AntiPodAffinity.Required : 
     ¬ P_Affinity(PodAffinityTerm, Node)

\* Node, Affinity, Match Node Selector Term
Match_NS(NodeSelectorRequirement, Node) ≝
CASENodeSelectorRequirement.Operator = "In" 
     ⟶   (NodeSelectorRequirement.Key ∈DOMAIN(Node.Labels) ∧ Node.Labels[NodeSelectorRequirement.Key] ∈NodeSelectorRequirement.Value)
  []NodeSelectorRequirement.Operator = "NotIn" 
     ⟶¬ (NodeSelectorRequirement.Key ∈ DOMAIN(Node.Labels) ∧Node.Labels[NodeSelectorRequirement.Key] ∈ NodeSelectorRequirement.Value)
  []NodeSelectorRequirement.Operator = "Exits" 
     ⟶   (NodeSelectorRequirement.Key ∈DOMAIN(Node.Labels))
  []NodeSelectorRequirement.Operator = "DoesNotExist" 
     ⟶¬ (NodeSelectorRequirement.Key ∈ DOMAIN(Node.Labels))
  []_NodeSelectorRequirement.Operator = "Gt" ∧ ∀ Value ∈NodeSelectorRequirement.Value: Value ∈ Int
     ⟶   (NodeSelectorRequirement.Key ∈DOMAIN(Node.Labels) ∧ Node.Labels[NodeSelectorRequirement.Key] ∈ Int ∧Node.Labels[NodeSelectorRequirement.Key] >Max(NodeSelectorRequirement.Value))
  []_NodeSelectorRequirement.Operator = "Lt" ∧ ∀ Value ∈NodeSelectorRequirement.Value: Value ∈ Int
     ⟶   (NodeSelectorRequirement.Key ∈DOMAIN(Node.Labels) ∧ Node.Labels[NodeSelectorRequirement.Key] ∈ Int ∧Node.Labels[NodeSelectorRequirement.Key] <Min(NodeSelectorRequirement.Value))
  []OTHER 
     ⟶FALSE

\* Pod, (Anti)Affinity, Match Pod Affinity Term
P_Affinity(PodAffinityTerm, Node) ==
IFPodAffinityTerm.TopologyKey \in DOMAIN(Node.Labels) THEN
    ∃Other ∈ {Other ∈ Objects : Other.Kind = "Node" ∧PodAffinityTerm.TopologyKey ∈ DOMAIN(Other.Labels) ∧Other.Labels[PodAffinityTerm.TopologyKey] =Node.Labels[PodAffinityTerm.TopologyKey]}: 
       ∃ Pod ∈ {Pod ∈ objects : Pod.kind = "Pod" ∧ BoundTo(Pod, Node)∧ Pod.Namespace ∈ PodAffinityTerm.Namespaces}:
           Match_LS(PodAffinityTerm.LabelSelector, Pod.Labels)
ELSE
   FALSE

\* Pod, (Anti)Affinity, Match Label Selector
Match_LS(LabelSelector, Labels) ≝
∧ ∀Key ∈ DOMAIN(LabelSelector) : Key ∈ DOMAIN(Labels) ∧ LabelSelector[Key] =Labels[Key]
∧ ∀LabelSelectorRequirement ∈ LabelSelector.MatchExpression:
     CASE LabelSelectorRequirement.Operator = "In"
          ⟶   (LabelSelectorRequirement.Key∈ DOMAIN(Labels) ∧ Labels[LabelSelectorRequirement.Key] ∈LabelSelectorRequirement.Values)
       [] _LabelSelectorRequirement.Operator = "NotIn"
          ⟶ ¬ (LabelSelectorRequirement.Key ∈ DOMAIN(Labels) ∧Labels[LabelSelectorRequirement.key] ∈ LabelSelectorRequirement.Values)
       [] _LabelSelectorRequirement.Operator = "Exists"
          ⟶   (LabelSelectorRequirement.Key∈ DOMAIN(Labels))
       [] _LabelSelectorRequirement.Operator = "DoesNotExist"
          ⟶ ¬ (LabelSelectorRequirement.Key ∈ DOMAIN(Labels))

Node親和
一個Pod必須分配給label匹配Pod的Node親和需求的Node。另外,一個Pod不能分配給label不匹配Pod的Node親和需求的Node。

Pod親和
一個Pod必須分配給匹配TopologyKey的Node, 且該Node上至少有一個Pod匹配Pod的親和需求。

Pod反親和
一個Pod必須分配給匹配TopologyKey的Node, 且該Node上沒有Pod匹配Pod的反親和需求。
可行性(Viability)

對於每個Pod,Kubernetes Scheduler肯定可用的Node集合,這些Node知足了該Pod的約束。而後,Kubernetes Scheduler從可用Node集合中肯定最高可行性的Node。

從概念上講,Kubernetes Scheduler定義了一個評分函數集合。給定一個Pod和一個Node,評分函數肯定Pod和Node配對的可行性。這些結果最後相加。
Viability(Pod, Node,Snapshot) ==
Sum(<<Rating_1(Pod, Node, Snapshot), Rating_2(Pod, Node,Snapshot), ...>>)

下面小節詳細描述了目前一些可用的過濾函數:

2.1 親和偏好

這些過濾函數基於Pod的偏好Node親和項,Pod親和項和Pod反親和項,對Node的可行性進行評分。
圖片描述

圖 1.4 Node Taints和Pod Tolerations

Rating(Pod, Node) ≝
Sum(<<
Sum(LAMBDA Term: Term.Weight, {NodeSelectorTerm ∈Pod.Spec.Affinity.NodeAffinity.Preferred.NodeSelectorTerms :Match_NS(NodeSelectorTerm, Node) }),
Sum(LAMBDA Term: Term.Weight, {PodAffinityTerm ∈Pod.Spec.Affinity.PodAffinity.Preferred : P_Affinity(PodAffinityTerm, Node) }),
Sum(LAMBDA Term: Term.Weight, {PodAffinityTerm ∈Pod.Spec.Affinity.AntiPodAffinity.Preferred : ~ P_Affinity(PodAffinityTerm,Node)})
>>)

最終評分是下列項的總和: 對於每個匹配的Node Selector項的權重的總和 對於每個匹配的Pod親和項的權重的總和 * 對於每個匹配的Pod反親和項的權重的總和

用例分析

圖6描述了包含2個不一樣類型的節點和2個不一樣類型的Pod的例子: 沒有GPU資源的9個節點 有GPU資源的6個節點

這個用例的目標是保證: 不須要GPU的Pod被分配到沒有GPU的節點 須要GPU的Pod被分配到有GPU的節點
圖片描述

相關文章
相關標籤/搜索