域安全 | K8s調度策略
調度
在K8s中,調度是指將Pod放置到合適的節點上。調度器通過 K8s 的監測機制來發現集群中新創建且尚未被調度到節點上的Pod。調度器會將所發現的每一個未調度的Pod調度到一個合適的節點上來運行。
kube-scheduler調度器
kube-scheduler組件是K8s集群的默認調度器,并且是集群控制面的一部分。對每一個新創建的Pod或者是未被調度的 Pod,kube-scheduler 會選擇一個最優的節點去運行這個Pod。然而,Pod內的每一個容器對資源都有不同的需求,而且Pod本身也有不同的需求。因此,Pod 在被調度到節點上之前,根據這些特定的調度需求,需要對集群中的節點進行一次過濾。在一個集群中滿足一個 Pod調度請求的所有節點稱之為可調度節點。如果沒有任何一個節點能滿足 Pod 的資源請求,那么這個 Pod 將一直停留在未調度狀態直到調度器能夠找到合適的Node節點。kube-scheduler調度器先在集群中找到一個Pod的所有可調度節點,然后根據一系列函數對這些可調度節點打分,選出其中得分最高的節點來運行 Pod。之后,調度器將這個調度決定通知給 kube-apiserver,這個過程叫做綁定。
在做調度決定時需要考慮的因素包括:單獨和整體的資源請求、硬件/軟件/策略限制、 親和以及反親和要求、數據局部性、負載間的干擾等等。
kube-scheduler調度流程
kube-scheduler 給一個 Pod 做調度選擇時包含兩個步驟:
- 過濾
- 打分
過濾階段會將所有滿足Pod調度需求的節點選出來。PodFitsResources 過濾函數會檢查候選節點的可用資源能否滿足 Pod 的資源請求。在過濾之后,得出一個節點列表,里面包含了所有可調度節點;通常情況下, 這個節點列表包含不止一個節點。如果這個列表是空的,代表這個 Pod 不可調度。
在打分階段,調度器會為 Pod 從所有可調度節點中選取一個最合適的節點。根據當前啟用的打分規則,調度器會給每一個可調度節點進行打分。最后,kube-scheduler 會將 Pod 調度到得分最高的節點上。如果存在多個得分最高的節點,kube-scheduler 會從中隨機選取一個。
支持以下兩種方式配置調度器的過濾和打分行為:
- 調度策略:允許你配置過濾所用的 斷言(Predicates) 和打分所用的 優先級(Priorities)。
- 調度配置:允許你配置實現不同調度階段的插件, 包括:QueueSort、Filter、Score、Bind、Reserve、Permit 等等。你也可以配置 kube-scheduler 運行不同的配置文件。
將Pod分配給指定節點
可以通過一些手段約束一個Pod以便限制其只能在特定的節點上運行,或優先在特定的節點上運行。有幾種方法可以實現這點:
- 節點標簽
- 親和性與反親和性
- nodeName字段
- Pod拓撲分布約束
- 污點和容忍度
節點標簽
標簽(Labels)是附加到 K8s 對象(比如 Pod)上的鍵值對。標簽旨在用于指定對用戶有意義且相關的對象的標識屬性,但不直接對核心系統有語義含義。標簽可以用于組織和選擇對象的子集。標簽可以在創建時附加到對象,隨后可以隨時添加和修改。每個對象都可以定義一組鍵/值標簽。每個鍵對于給定對象必須是唯一的,如下:
"metadata": { "labels": { "key1" : "value1", "key2" : "value2" }}
與很多其他 K8s 對象類似,節點也有標簽(Labels),也可以手動給節點添加標簽。K8s 也會為集群中所有節點添加一些標準的標簽。
給節點添加標簽
執行如下命令給指定的k8s-node1節點添加標簽 key1=value1。
#給指定的節點打標簽kubectl label nodes k8s-node1 key1=value1#移除標簽kubectl label nodes k8s-node1 key1-

創建一個將被調度到你選擇的節點的 Pod
”
如下Pod 配置文件描述了一個擁有節點選擇器 key1: value1 的 Pod。這表明該 Pod 將被調度到有 key1: value1 標簽的節點上。
apiVersion: v1kind: Podmetadata: name: nginx labels: env: testspec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent nodeSelector: key1: value1
可以看到該pod確實創建在具有該標簽的node節點上。

親和性和反親和性
nodeSelector 提供了一種最簡單的方法來將 Pod 約束到具有特定標簽的節點上,而親和性和反親和性擴展了你可以定義的約束類型。使用親和性與反親和性的一些好處有如下:
- 親和性、反親和性語言的表達能力更強。nodeSelector 只能選擇擁有所有指定標簽的節點,而親和性、反親和性為你提供對選擇邏輯的更強控制能力。
- 你可以標明某規則是“軟需求”或者“偏好”,這樣調度器在無法找到匹配節點時仍然調度該 Pod。
- 你可以使用節點上(或其他拓撲域中)運行的其他 Pod 的標簽來實施調度約束, 而不是只能使用節點本身的標簽。這個能力讓你能夠定義規則允許哪些 Pod 可以被放置在一起。
親和性功能由兩種類型的親和性組成:
- 節點親和性功能類似于 nodeSelector 字段,但它的表達能力更強,并且允許你指定軟規則。
- Pod 間親和性、反親和性允許你根據其他 Pod 的標簽來約束 Pod。
節點親和性
”
節點親和性概念上類似于 nodeSelector, 它使你可以根據節點上的標簽來約束 Pod 可以調度到哪些節點上。節點親和性有兩種:
- requiredDuringSchedulingIgnoredDuringExecution:調度器只有在規則被滿足的時候才能執行調度。此功能類似于 nodeSelector, 但其語法表達能力更強。
- preferredDuringSchedulingIgnoredDuringExecution:調度器會嘗試尋找滿足對應規則的節點。如果找不到匹配的節點,調度器仍然會調度該 Pod。
注:在上述類型中,IgnoredDuringExecution 意味著如果節點標簽在 Kubernetes 調度 Pod 后發生了變更,Pod 仍將繼續運行。
Pod 間親和性與反親和性
”
Pod間親和性與反親和性使你可以基于已經在節點上運行的Pod的標簽來約束Pod可以調度到的節點,而不是基于節點上的標簽。
Pod間親和性與反親和性的規則格式為“如果 X 上已經運行了一個或多個滿足規則 Y 的 Pod, 則這個 Pod 應該(或者在反親和性的情況下不應該)運行在 X 上”。這里的 X 可以是節點、機架、云提供商可用區或地理區域或類似的拓撲域, Y 則是 K8s 嘗試滿足的規則。
你可以通過標簽選擇算符的形式來表達規則(Y),并可根據需要指定關聯的名字空間列表。Pod 在 K8s 中是名字空間作用域的對象,因此 Pod 的標簽也隱式地具有名字空間屬性。針對 Pod 標簽的所有標簽選擇算符都要指定名字空間,K8s 會在指定的名字空間內尋找標簽。
你會通過 topologyKey 來表達拓撲域(X)的概念,其取值是系統用來標示域的節點標簽鍵。
注:Pod 間親和性和反親和性都需要相當的計算量,因此會在大規模集群中顯著降低調度速度。我們不建議在包含數百個節點的集群中使用這類設置。Pod 反親和性需要節點上存在一致性的標簽。換言之, 集群中每個節點都必須擁有與 topologyKey 匹配的標簽。如果某些或者所有節點上不存在所指定的 topologyKey 標簽,調度行為可能與預期的不同。
用節點親和性把Pod分配到節點
”
執行如下命令給指定的k8s-node1節點添加標簽 key1=value1。
#給指定的節點打標簽kubectl label nodes k8s-node1 key1=value1

下面清單描述了一個 Pod,它有一個節點親和性配置 requiredDuringSchedulingIgnoredDuringExecution,key1=value1。這意味著 pod 只會被調度到具有 key1=value1 標簽的節點上。
apiVersion: v1kind: Podmetadata: name: nginx4spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: key1 operator: In values: - value1 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent

本清單描述了一個Pod,它有一個節點親和性設置 preferredDuringSchedulingIgnoredDuringExecution,key1=value1。這意味著 pod 將首選具有 key1=value1 標簽的節點。如果沒有找到key1=value1 標簽的節點,則會隨機調度到可用的Node節點上。
apiVersion: v1kind: Podmetadata: name: nginx5spec: affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 1 preference: matchExpressions: - key: key1 operator: In values: - value1 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent

nodeName字段
可以通過設置 nodeName 將某個 Pod 調度到特定的節點,且該 Pod 將只能被調度到特定節點。
apiVersion: v1kind: Podmetadata: name: nginx2spec: nodeName: k8s-node2 containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent

Pod 拓撲分布約束
可以使用拓撲分布約束(Topology Spread Constraints)來控制Pod在集群內故障域之間的分布,故障域的示例有區域(Region)、可用區(Zone)、節點和其他用戶自定義的拓撲域。這樣做有助于提升性能、實現高可用或提升資源利用率。詳細:Pod 拓撲分布約束: https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/topology-spread-constraints/
污點(Taint)和容忍度(Toleration)
節點親和性是Pod的一種屬性,它使Pod被吸引到一類特定的節點(這可能是出于一種偏好,也可能是硬性要求)。而污點(Taint)則相反,污點(Taint)是應用在Node節點之上的它使節點能夠排斥一類特定的Pod,具有污點Taint的node和pod是互斥關系;而容忍度(Toleration)是應用于 Pod 上的,容忍度允許(但不要求)調度器調度帶有對容忍度的Pod到Node節點上,污點和容忍度的目的是優化pod在集群間的調度。作為其功能的一部分, 調度器也會評估其他參數。
污點和容忍度(Toleration)相互配合,可以用來避免 Pod 被分配到不合適的節點上。每個節點上都可以應用一個或多個污點,這表示對于那些不能容忍這些污點的 Pod, 是不會被該節點接受的。
給Node節點打污點
”
給節點k8s-master增加一個污點,它的鍵名是key1,鍵值是value1,效果是NoSchedule。這表示只有擁有和這個污點相匹配的容忍度的 Pod 才有可能夠被分配到k8s-node1這個節點上。
#查看節點信息kubectl describe node k8s-node1#添加污點kubectl taint nodes k8s-node1 key1=value1:NoSchedule#移除污點kubectl taint nodes k8s-node1 key1=value1:NoSchedule-

在pod中定義容忍度
”
可以在 PodSpec 中定義 Pod 的容忍度。下面兩個容忍度均與上面例子中使用kubectl taint命令創建的污點相匹配, 因此如果一個 Pod 擁有其中的任何一個容忍度都可能能夠被分配到node1:
tolerations:- key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule"
或
tolerations:- key: "key1" operator: "Exists" effect: "NoSchedule"
實例
apiVersion: v1kind: Podmetadata: name: myapp labels: env: testspec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent tolerations: - key: "key1" operator: "Exists" effect: "NoSchedule"
一個容忍度和一個污點相“匹配”是指它們有一樣的鍵名和效果。
operator 的參數含義:
- 如果 operator 是 Exists (此時容忍度不能指定 value)
- 如果 operator 是 Equal ,則它們的 value 應該相等。
- 如果 operator 不指定,則默認為Equal。
注:如果一個容忍度的 key 為空且 operator 為 Exists, 表示這個容忍度與任意的 key、value 和 effect 都匹配,即這個容忍度能容忍任何污點。
effect 的參數含義:
- 如果effect是NoSchedule,則新的不能容忍的pod不能再調度過來,但是之前運行在node節點中的Pod不受影響。
- 如果effect是NoExecute,則新的不能容忍的pod不能調度過來,老的pod也會被驅逐。
- 如果effect是PreferNoScheduler,則表示盡量不調度到污點節點中去。
注:如果 effect 為空,則可以與所有鍵名 key1 的效果相匹配。
多個污點的匹配規則
”
可以給一個節點添加多個污點,也可以給一個Pod添加多個容忍度。K8s處理多個污點和容忍度的過程就像一個過濾器:從一個節點的所有污點開始遍歷, 過濾掉那些 Pod 中存在與之相匹配的容忍度的污點。余下未被過濾的污點的effect 值決定了 Pod 是否會被分配到該節點,特別是以下情況:
- 如果未被過濾的污點中存在至少一個effect值為NoSchedule的污點, 則 Kubernetes 不會將Pod分配到該節點。
- 如果未被過濾的污點中不存在 effect 值為NoSchedule的污點, 但是存在 effect 值為PreferNoSchedule的污點, 則 Kubernetes 會嘗試不將 Pod 分配到該節點。
- 如果未被過濾的污點中存在至少一個 effect 值為NoExecute 的污點, 則 Kubernetes 不會將 Pod 分配到該節點(如果 Pod 還未在節點上運行), 或者將 Pod 從該節點驅逐(如果 Pod 已經在節點上運行)。
例如,假設您給一個節點添加了如下污點:
kubectl taint nodes node1 key1=value1:NoSchedulekubectl taint nodes node1 key1=value1:NoExecutekubectl taint nodes node1 key2=value2:NoSchedule
假定有一個 Pod,它有兩個容忍度:
tolerations:- key: "key1" operator: "Equal" value: "value1" effect: "NoSchedule"- key: "key1" operator: "Equal" value: "value1" effect: "NoExecute"
上述 Pod 不會被分配到 node1 節點,因為其沒有容忍度和第三個污點相匹配。
但是如果在給節點添加上述污點之前,該 Pod 已經在上述節點運行, 那么它還可以繼續運行在該節點上,因為第三個污點是三個污點中唯一不能被這個 Pod 容忍的。
通常情況下,如果給一個節點添加了一個 effect 值為 NoExecute 的污點, 則任何不能忍受這個污點的 Pod 都會馬上被驅逐,任何可以忍受這個污點的 Pod 都不會被驅逐。但是,如果 Pod 存在一個 effect 值為 NoExecute 的容忍度指定了可選屬性tolerationSeconds 的值,則表示在給節點添加了上述污點之后, Pod 還能繼續在節點上運行的時間。例如:
tolerations:- key: "key1" operator: "Equal" value: "value1" effect: "NoExecute" tolerationSeconds: 3600
這表示如果這個 Pod 正在運行,同時一個匹配的污點被添加到其所在的節點, 那么 Pod 還將繼續在節點上運行 3600 秒,然后被驅逐。如果在此之前上述污點被刪除了,則 Pod 不會被驅逐。
說明
- 默認情況下,只有master節點會有污點(role.kubernetes.io/master:NoSchedule),Node節點默認沒有污點。這也是為什么默認情況下Pod不會被分配到master節點的原因。

- 當只有一個節點沒污點,其他節點都有污點時,創建pod的yml文件的容忍度沒匹配上所有的污點時,創建的pod節點在沒有污點的節點上。
- 當只有一個節點沒污點,其他節點都有污點時,創建pod的yml文件的容忍度匹配上污點時,創建的pod節點也在沒有污點的節點上。
- 當所有node的節點都有污點,創建pod的yml文件的容忍度能匹配上污點時,創建的pod節點在匹配的污點的節點上。
- 當所有node節點都有污點,但是創建pod的yml文件的容忍度沒匹配上所有的污點時,創建的pod節點的NODE這里為空。

3
給master節點分配pod
默認情況下創建的pod是不會被分配到master節點上的,因為master節點默認被打上了node-role.kubernetes.io/master:NoSchedule的污點。
#查詢k8s-master節點的Taintskubectl describe node k8s-master | grep Taints#取消污點kubectl taint nodes k8s-master role.kubernetes.io/master:NoSchedule-
注:其實也可以省略取消master節點污點這一步。
然后使用nodeName字段指定k8s-master
apiVersion: v1kind: Podmetadata: name: nginxspec: nodeName: k8s-master containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent
