Pod 拓撲分散約束
您可以使用拓撲分散約束來控制 Pod 在叢集中如何在故障網域(例如區域、可用區、節點和其他使用者定義的拓撲網域)之間分散。這有助於實現高可用性以及有效的資源利用率。
您可以設定叢集層級預設約束作為預設值,或為個別工作負載設定拓撲分散約束。
動機
假設您有一個最多二十個節點的叢集,並且想要執行一個工作負載,該工作負載會自動調整其使用的副本數量。副本數量可能少至兩個 Pod,也可能多至十五個。當只有兩個 Pod 時,您會希望這兩個 Pod 都不要在同一個節點上執行:您會承擔單一節點故障導致工作負載離線的風險。
除了這個基本用法之外,還有一些進階用法範例,可讓您的工作負載在高可用性和叢集利用率方面受益。
當您擴展規模並執行更多 Pod 時,另一個考量變得重要。假設您有三個節點,每個節點執行五個 Pod。這些節點有足夠的容量來執行那麼多副本;但是,與此工作負載互動的用戶端分散在三個不同的資料中心(或基礎架構區域)中。現在您比較不擔心單一節點故障,但您注意到延遲高於您的期望,而且您正在為不同區域之間傳送網路流量而支付網路成本。
您決定在正常運作下,您希望在每個基礎架構區域中排程相似數量的副本排程,並且您希望叢集在出現問題時能夠自我修復。
Pod 拓撲分散約束為您提供了一種宣告式方法來設定該行為。
topologySpreadConstraints
欄位
Pod API 包含一個欄位 spec.topologySpreadConstraints
。此欄位的使用方式如下:
---
apiVersion: v1
kind: Pod
metadata:
name: example-pod
spec:
# Configure a topology spread constraint
topologySpreadConstraints:
- maxSkew: <integer>
minDomains: <integer> # optional
topologyKey: <string>
whenUnsatisfiable: <string>
labelSelector: <object>
matchLabelKeys: <list> # optional; beta since v1.27
nodeAffinityPolicy: [Honor|Ignore] # optional; beta since v1.26
nodeTaintsPolicy: [Honor|Ignore] # optional; beta since v1.26
### other Pod fields go here
您可以執行 kubectl explain Pod.spec.topologySpreadConstraints
或參考 Pod 的 API 參考的排程章節,以閱讀更多關於此欄位的資訊。
分散約束定義
您可以定義一個或多個 topologySpreadConstraints
條目,以指示 kube-scheduler 如何根據叢集中現有的 Pod 來放置每個傳入的 Pod。這些欄位為:
maxSkew 描述 Pod 可能不均勻分佈的程度。您必須指定此欄位,且數字必須大於零。其語意會根據
whenUnsatisfiable
的值而有所不同。- 如果您選擇
whenUnsatisfiable: DoNotSchedule
,則maxSkew
定義目標拓撲中符合條件的 Pod 數量與全域最小值(符合條件的網域中符合條件的 Pod 最小數量,如果符合條件的網域數量少於 MinDomains,則為零)之間允許的最大差異。例如,如果您有 3 個區域,分別有 2 個、2 個和 1 個符合條件的 Pod,則MaxSkew
設定為 1,則全域最小值為 1。 - 如果您選擇
whenUnsatisfiable: ScheduleAnyway
,則排程器會優先考慮有助於減少偏差的拓撲。
- 如果您選擇
minDomains 指出符合條件的網域的最小數量。此欄位為選用欄位。網域是拓撲的特定執行個體。符合條件的網域是其節點符合節點選擇器的網域。
注意
在 Kubernetes v1.30 之前,只有在啟用MinDomainsInPodTopologySpread
功能閘道(自 v1.28 以來為預設值)時,minDomains
欄位才可用。在較舊的 Kubernetes 叢集中,可能會明確停用它,或者該欄位可能不可用。- 指定時,
minDomains
的值必須大於 0。您只能將minDomains
與whenUnsatisfiable: DoNotSchedule
一起指定。 - 當符合拓撲鍵的符合條件網域的數量少於
minDomains
時,Pod 拓撲分散會將全域最小值視為 0,然後執行skew
的計算。全域最小值是符合條件的網域中符合條件的 Pod 最小數量,如果符合條件的網域數量少於minDomains
,則為零。 - 當符合拓撲鍵的符合條件網域的數量等於或大於
minDomains
時,此值對排程沒有影響。 - 如果您未指定
minDomains
,則約束的行為就像minDomains
為 1 一樣。
- 指定時,
topologyKey 是節點標籤的鍵。具有此鍵且值相同的標籤的節點被視為在相同的拓撲中。我們將拓撲的每個執行個體(換句話說,<鍵, 值> 對)稱為網域。排程器將嘗試將平衡數量的 Pod 放入每個網域。此外,我們將符合條件的網域定義為其節點符合 nodeAffinityPolicy 和 nodeTaintsPolicy 要求的網域。
whenUnsatisfiable 指出如果 Pod 不滿足分散約束時該如何處理
DoNotSchedule
(預設值)告訴排程器不要排程它。ScheduleAnyway
告訴排程器仍然排程它,同時優先考慮使偏差最小化的節點。
labelSelector 用於尋找符合條件的 Pod。符合此標籤選擇器的 Pod 會被計數,以判斷其對應拓撲網域中的 Pod 數量。請參閱標籤選擇器以取得更多詳細資訊。
matchLabelKeys 是一個 Pod 標籤鍵的清單,用於選取將在哪些 Pod 上計算擴散。這些鍵用於從 Pod 標籤中查找值,這些鍵值標籤會與
labelSelector
進行 AND 運算,以選取現有的 Pod 群組,並針對即將到來的 Pod 計算擴散。同一個鍵禁止同時存在於matchLabelKeys
和labelSelector
中。當labelSelector
未設定時,不能設定matchLabelKeys
。Pod 標籤中不存在的鍵將被忽略。空值或空清單表示僅根據labelSelector
進行比對。透過
matchLabelKeys
,您不需要在不同版本之間更新pod.spec
。控制器/運算子只需要為不同版本設定相同的標籤鍵的不同值。排程器將根據matchLabelKeys
自動假定這些值。例如,如果您正在設定 Deployment,您可以使用標記了 pod-template-hash 的標籤,該標籤由 Deployment 控制器自動新增,以區分單一部署中的不同版本。topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: foo matchLabelKeys: - pod-template-hash
注意
matchLabelKeys
欄位是一個 Beta 級別的欄位,在 1.27 版本中預設啟用。您可以透過停用MatchLabelKeysInPodTopologySpread
特性閘門 來停用它。nodeAffinityPolicy 指出在計算 Pod 拓撲擴散傾斜時,我們將如何處理 Pod 的 nodeAffinity/nodeSelector。選項如下:
- Honor:僅在計算中包含符合 nodeAffinity/nodeSelector 的節點。
- Ignore:忽略 nodeAffinity/nodeSelector。所有節點都包含在計算中。
如果此值為空值,則行為等同於 Honor 策略。
注意
nodeAffinityPolicy
是一個 Beta 級別的欄位,在 1.26 版本中預設啟用。您可以透過停用NodeInclusionPolicyInPodTopologySpread
特性閘門 來停用它。nodeTaintsPolicy 指出在計算 Pod 拓撲擴散傾斜時,我們將如何處理節點污點。選項如下:
- Honor:包含沒有污點的節點,以及即將到來的 Pod 具有容忍度的受污節點。
- Ignore:忽略節點污點。所有節點都包含在內。
如果此值為空值,則行為等同於 Ignore 策略。
注意
nodeTaintsPolicy
是一個 Beta 級別的欄位,在 1.26 版本中預設啟用。您可以透過停用NodeInclusionPolicyInPodTopologySpread
特性閘門 來停用它。
當 Pod 定義多個 topologySpreadConstraint
時,這些約束會使用邏輯 AND 運算組合在一起:kube-scheduler 會為即將到來的 Pod 尋找一個滿足所有已配置約束的節點。
節點標籤
拓撲擴散約束依賴節點標籤來識別每個 節點 所屬的拓撲網域。例如,節點可能具有標籤
region: us-east-1
zone: us-east-1a
注意
為了簡潔起見,此範例未使用 廣為人知 的標籤鍵 topology.kubernetes.io/zone
和 topology.kubernetes.io/region
。然而,仍然建議使用這些已註冊的標籤鍵,而不是此處使用的私有(未限定的)標籤鍵 region
和 zone
。
您無法對不同情境之間私有標籤鍵的含義做出可靠的假設。
假設您有一個 4 節點的叢集,具有以下標籤
NAME STATUS ROLES AGE VERSION LABELS
node1 Ready <none> 4m26s v1.16.0 node=node1,zone=zoneA
node2 Ready <none> 3m58s v1.16.0 node=node2,zone=zoneA
node3 Ready <none> 3m17s v1.16.0 node=node3,zone=zoneB
node4 Ready <none> 2m43s v1.16.0 node=node4,zone=zoneB
然後,叢集在邏輯上被視為如下
一致性
您應該在群組中的所有 Pod 上設定相同的 Pod 拓撲擴散約束。
通常,如果您正在使用工作負載控制器(例如 Deployment),則 Pod 範本會為您處理此問題。如果您混合使用不同的擴散約束,則 Kubernetes 會遵循欄位的 API 定義;但是,行為更可能變得混亂,且疑難排解也不那麼直接。
您需要一種機制來確保拓撲網域(例如雲端供應商區域)中的所有節點都以一致的方式標記。為了避免您需要手動標記節點,大多數叢集會自動填入廣為人知的標籤,例如 kubernetes.io/hostname
。檢查您的叢集是否支援此功能。
拓撲擴散約束範例
範例:一個拓撲擴散約束
假設您有一個 4 節點的叢集,其中 3 個標記為 foo: bar
的 Pod 分別位於 node1、node2 和 node3 中
如果您希望即將到來的 Pod 與現有的 Pod 均勻分佈在各個區域,您可以使用類似於以下的 Manifest
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
從該 Manifest 中,topologyKey: zone
表示均勻分佈將僅應用於標記為 zone: <任何值>
的節點(沒有 zone
標籤的節點將被跳過)。欄位 whenUnsatisfiable: DoNotSchedule
告訴排程器,如果排程器找不到滿足約束的方法,則讓即將到來的 Pod 保持 Pending 狀態。
如果排程器將此即將到來的 Pod 放置到 zone A
中,則 Pod 的分佈將變為 [3, 1]
。這表示實際的傾斜度為 2(計算為 3 - 1
),這違反了 maxSkew: 1
。為了滿足此範例的約束和情境,即將到來的 Pod 只能放置在 zone B
中的節點上
或
您可以調整 Pod spec 以滿足各種需求
- 將
maxSkew
變更為更大的值(例如2
),以便即將到來的 Pod 也可以放置到 zoneA
中。 - 將
topologyKey
變更為node
,以便在節點而不是區域之間均勻分佈 Pod。在上述範例中,如果maxSkew
保持為1
,則即將到來的 Pod 只能放置到節點node4
上。 - 將
whenUnsatisfiable: DoNotSchedule
變更為whenUnsatisfiable: ScheduleAnyway
,以確保即將到來的 Pod 始終可排程(假設滿足其他排程 API)。但是,最好將其放置在匹配 Pod 較少的拓撲網域中。(請注意,此偏好設定會與其他內部排程優先順序(例如資源使用率)共同標準化)。
範例:多個拓撲擴散約束
這建立在先前的範例之上。假設您有一個 4 節點的叢集,其中 3 個現有的 Pod 標記為 foo: bar
,分別位於 node1、node2 和 node3 上
您可以組合兩個拓撲擴散約束,以同時控制 Pod 按節點和按區域的擴散
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
- maxSkew: 1
topologyKey: node
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
containers:
- name: pause
image: registry.k8s.io/pause:3.1
在這種情況下,為了符合第一個約束,即將到來的 Pod 只能放置在 zone B
中的節點上;而就第二個約束而言,即將到來的 Pod 只能排程到節點 node4
。排程器僅考慮滿足所有已定義約束的選項,因此唯一有效的放置位置是節點 node4
。
範例:衝突的拓撲擴散約束
多個約束可能會導致衝突。假設您有一個跨越 2 個區域的 3 節點叢集
如果您要將 two-constraints.yaml
(來自先前範例的 Manifest)應用於此叢集,您會看到 Pod mypod
保持在 Pending
狀態。發生這種情況是因為:為了滿足第一個約束,Pod mypod
只能放置在 zone B
中;而就第二個約束而言,Pod mypod
只能排程到節點 node2
。兩個約束的交集返回一個空集合,並且排程器無法放置 Pod。
為了克服這種情況,您可以增加 maxSkew
的值,或修改其中一個約束以使用 whenUnsatisfiable: ScheduleAnyway
。根據情況,您也可能決定手動刪除現有的 Pod,例如,如果您正在疑難排解為什麼錯誤修復的推出沒有進展。
與節點親和性和節點選擇器的互動
如果即將到來的 Pod 已定義 spec.nodeSelector
或 spec.affinity.nodeAffinity
,則排程器將從傾斜度計算中跳過不符合的節點。
範例:具有節點親和性的拓撲擴散約束
假設您有一個跨越區域 A 到 C 的 5 節點叢集
並且您知道必須排除區域 C
。在這種情況下,您可以撰寫如下的 Manifest,以便 Pod mypod
將放置在區域 B
而不是區域 C
中。同樣地,Kubernetes 也會尊重 spec.nodeSelector
。
kind: Pod
apiVersion: v1
metadata:
name: mypod
labels:
foo: bar
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
foo: bar
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: zone
operator: NotIn
values:
- zoneC
containers:
- name: pause
image: registry.k8s.io/pause:3.1
隱含慣例
這裡有一些值得注意的隱含慣例
只有與即將到來的 Pod 具有相同命名空間的 Pod 才能成為匹配的候選者。
排程器僅考慮同時具有所有
topologySpreadConstraints[*].topologyKey
的節點。缺少任何這些topologyKey
的節點將被繞過。這表示- 位於這些繞過節點上的任何 Pod 都不會影響
maxSkew
計算 - 在上述 範例 中,假設節點node1
沒有 "zone" 標籤,則這 2 個 Pod 將被忽略,因此即將到來的 Pod 將排程到 zoneA
。 - 即將到來的 Pod 沒有機會排程到這類節點上 - 在上述範例中,假設節點
node5
具有拼寫錯誤的標籤zone-typo: zoneC
(並且沒有設定zone
標籤)。在節點node5
加入叢集後,它將被繞過,並且此工作負載的 Pod 不會排程到那裡。
- 位於這些繞過節點上的任何 Pod 都不會影響
請注意,如果即將到來的 Pod 的
topologySpreadConstraints[*].labelSelector
與其自身的標籤不符,會發生什麼情況。在上述範例中,如果您移除即將到來的 Pod 的標籤,它仍然可以放置在 zoneB
中的節點上,因為約束仍然滿足。但是,在放置之後,叢集的不平衡程度仍然不變 - 仍然是 zoneA
有 2 個標記為foo: bar
的 Pod,而 zoneB
有 1 個標記為foo: bar
的 Pod。如果這不是您期望的,請更新工作負載的topologySpreadConstraints[*].labelSelector
以符合 Pod 範本中的標籤。
叢集層級的預設約束
可以為叢集設定預設的拓撲擴散約束。預設的拓撲擴散約束僅在以下情況下應用於 Pod:
- Pod 的
.spec.topologySpreadConstraints
中未定義任何約束。 - Pod 屬於 Service、ReplicaSet、StatefulSet 或 ReplicationController。
預設約束可以設定為 排程設定檔 中 PodTopologySpread
外掛程式引數的一部分。約束以與上述 API 相同的方式 指定,但 labelSelector
必須為空。選擇器是從 Pod 所屬的 Service、ReplicaSet、StatefulSet 或 ReplicationController 計算而來。
範例組態可能如下所示
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: PodTopologySpread
args:
defaultConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
defaultingType: List
內建預設約束
Kubernetes v1.24 [stable]
如果您未針對 Pod 拓撲擴散設定任何叢集層級的預設約束,則 kube-scheduler 的行為就像您指定了以下預設拓撲約束一樣
defaultConstraints:
- maxSkew: 3
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway
- maxSkew: 5
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: ScheduleAnyway
此外,提供等效行為的舊版 SelectorSpread
外掛程式預設為停用。
注意
PodTopologySpread
外掛程式不會對沒有在擴散約束中指定的拓撲鍵的節點進行評分。與使用預設拓撲約束時的舊版 SelectorSpread
外掛程式相比,這可能會導致不同的預設行為。
如果您的節點預期不會同時設定 kubernetes.io/hostname
和 topology.kubernetes.io/zone
標籤,請定義您自己的約束,而不是使用 Kubernetes 預設值。
如果您不想為您的叢集使用預設的 Pod 擴散約束,您可以透過將 defaultingType
設定為 List
並在 PodTopologySpread
外掛程式組態中將 defaultConstraints
留空來停用這些預設值
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: PodTopologySpread
args:
defaultConstraints: []
defaultingType: List
與 podAffinity 和 podAntiAffinity 的比較
在 Kubernetes 中,Pod 間親和性和反親和性 控制 Pod 如何彼此相關地排程 - 更密集或更分散。
podAffinity
- 吸引 Pod;您可以嘗試將任意數量的 Pod 封裝到符合條件的拓撲網域中。
podAntiAffinity
- 排斥 Pod。如果您將其設定為
requiredDuringSchedulingIgnoredDuringExecution
模式,則只有一個 Pod 可以排程到單個拓撲網域中;如果您選擇preferredDuringSchedulingIgnoredDuringExecution
,則您將失去強制執行約束的能力。
為了更精細的控制,您可以指定拓撲擴散約束,以在不同的拓撲網域之間分佈 Pod - 以實現高可用性或節省成本。這也有助於滾動更新工作負載和平穩地擴展副本。
有關更多背景資訊,請參閱關於 Pod 拓撲擴散約束的增強提案的 Motivation 章節。
已知限制
無法保證在移除 Pod 時約束仍然滿足。例如,縮減 Deployment 可能會導致 Pod 分佈不平衡。
您可以使用諸如 Descheduler 之類的工具來重新平衡 Pod 分佈。
與受污節點匹配的 Pod 將受到尊重。請參閱 Issue 80921。
排程器不事先知道叢集擁有的所有區域或其他拓撲網域。它們是從叢集中的現有節點確定的。當節點池(或節點群組)縮減為零節點,並且您期望叢集擴展時,這可能會導致問題,因為在這種情況下,在其中至少有一個節點之前,這些拓撲網域將不會被考慮在內。
您可以透過使用叢集自動擴展工具來解決此問題,該工具知道 Pod 拓撲擴散約束,並且也知道拓撲網域的整體集合。
下一步
- 部落格文章 Introducing PodTopologySpread 詳細解釋了
maxSkew
,並涵蓋了一些進階使用範例。 - 閱讀 Pod 的 API 參考文件的 排程 章節。