天天看點

【K8s概念】Pod 拓撲分布限制

參考:​​https://kubernetes.io/zh/docs/concepts/workloads/pods/pod-topology-spread-constraints/​​

你可以使用 拓撲分布限制(Topology Spread Constraints) 來控制 Pods 在叢集内故障域 之間的分布,例如區域(Region)、可用區(Zone)、節點和其他使用者自定義拓撲域。 這樣做有助于實作高可用并提升資源使用率。

說明: 在 v1.18 之前的 Kubernetes 版本中,如果要使用 Pod 拓撲擴充限制,你必須在 API 伺服器 和排程器 中啟用 EvenPodsSpread 特性門控。

先決條件

節點标簽

拓撲分布限制依賴于節點标簽來辨別每個節點所在的拓撲域。 例如,某節點可能具有标簽:node=node1,zone=us-east-1a,region=us-east-1

假設你擁有具有以下标簽的一個 4 節點叢集:

【K8s概念】Pod 拓撲分布限制

然後從邏輯上看叢集如下:

【K8s概念】Pod 拓撲分布限制

你可以複用在大多數叢集上自動建立和填充的 常用标簽, 而不是手動添加标簽。

​​https://kubernetes.io/zh/docs/reference/labels-annotations-taints/​​

Pod 的分布限制

API

pod.spec.topologySpreadConstraints 字段定義如下所示:

apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  topologySpreadConstraints:
    - maxSkew: <integer>
      topologyKey: <string>
      whenUnsatisfiable: <string>
      labelSelector: <object>
      

你可以定義一個或多個 topologySpreadConstraint 來訓示 kube-scheduler 如何根據與現有的 Pod 的關聯關系将每個傳入的 Pod 部署到叢集中。字段包括:

  • maxSkew 描述 Pod 分布不均的程度。這是給定拓撲類型中任意兩個拓撲域中 比對的 pod 之間的最大允許內插補點。它必須大于零。取決于 whenUnsatisfiable 的 取值,其語義會有不同。
  • 當 whenUnsatisfiable 等于 "DoNotSchedule" 時,maxSkew 是目标拓撲域 中比對的 Pod 數與全局最小值之間可存在的差異。
  • 當 whenUnsatisfiable 等于 "ScheduleAnyway" 時,排程器會更為偏向能夠降低 偏內插補點的拓撲域。
  • topologyKey 是節點标簽的鍵。如果兩個節點使用此鍵标記并且具有相同的标簽值, 則排程器會将這兩個節點視為處于同一拓撲域中。排程器試圖在每個拓撲域中放置數量 均衡的 Pod。
  • whenUnsatisfiable 訓示如果 Pod 不滿足分布限制時如何處理:
  • DoNotSchedule(預設)告訴排程器不要排程。
  • ScheduleAnyway 告訴排程器仍然繼續排程,隻是根據如何能将偏差最小化來對 節點進行排序。
  • labelSelector 用于查找比對的 pod。比對此标簽的 Pod 将被統計,以确定相應 拓撲域中 Pod 的數量。 有關詳細資訊,請參考标簽選擇算符。

你可以執行 kubectl explain Pod.spec.topologySpreadConstraints 指令以 了解關于 topologySpreadConstraints 的更多資訊。

例子:單個 TopologySpreadConstraint

假設你擁有一個 4 節點叢集,其中标記為 foo:bar 的 3 個 Pod 分别位于 node1、node2 和 node3 中:

【K8s概念】Pod 拓撲分布限制

如果希望新來的 Pod 均勻分布在現有的可用區域,則可以按如下設定其規約:

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: k8s.gcr.io/pause:3.1
      

topologyKey: zone 意味着均勻分布将隻應用于存在标簽鍵值對為 "zone:" 的節點。 whenUnsatisfiable: DoNotSchedule 告訴排程器如果新的 Pod 不滿足限制, 則讓它保持懸決狀态。

如果排程器将新的 Pod 放入 "zoneA",Pods 分布将變為 [3, 1],是以實際的偏差 為 2(3 - 1)。這違反了 maxSkew: 1 的約定。此示例中,新 Pod 隻能放置在 "zoneB" 上:

【K8s概念】Pod 拓撲分布限制

你可以調整 Pod 規約以滿足各種要求:

  • 将 maxSkew 更改為更大的值,比如 "2",這樣新的 Pod 也可以放在 "zoneA" 上。
  • 将 topologyKey 更改為 "node",以便将 Pod 均勻分布在節點上而不是區域中。 在上面的例子中,如果 maxSkew 保持為 "1",那麼傳入的 Pod 隻能放在 "node4" 上。
  • 将 whenUnsatisfiable: DoNotSchedule 更改為 whenUnsatisfiable: ScheduleAnyway, 以確定新的 Pod 始終可以被排程(假設滿足其他的排程 API)。 但是,最好将其放置在比對 Pod 數量較少的拓撲域中。 (請注意,這一優先判定會與其他内部排程優先級(如資源使用率等)排序準則一起進行标準化。)

例子:多個 TopologySpreadConstraints

下面的例子建立在前面例子的基礎上。假設你擁有一個 4 節點叢集,其中 3 個标記為 foo:bar 的 Pod 分别位于 node1、node2 和 node3 上:

【K8s概念】Pod 拓撲分布限制

可以使用 2 個 TopologySpreadConstraint 來控制 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: k8s.gcr.io/pause:3.1
      

在這種情況下,為了比對第一個限制,新的 Pod 隻能放置在 "zoneB" 中;而在第二個限制中, 新的 Pod 隻能放置在 "node4" 上。最後兩個限制的結果加在一起,唯一可行的選擇是放置 在 "node4" 上。

多個限制之間可能存在沖突。假設有一個跨越 2 個區域的 3 節點叢集:

【K8s概念】Pod 拓撲分布限制

如果對叢集應用 "two-constraints.yaml",會發現 "mypod" 處于 Pending 狀态。 這是因為:為了滿足第一個限制,"mypod" 隻能放在 "zoneB" 中,而第二個限制要求 "mypod" 隻能放在 "node2" 上。Pod 排程無法滿足兩種限制。

為了克服這種情況,你可以增加 maxSkew 或修改其中一個限制,讓其使用 whenUnsatisfiable: ScheduleAnyway。

約定

這裡有一些值得注意的隐式約定:

  • 隻有與新的 Pod 具有相同命名空間的 Pod 才能作為比對候選者。
  • 沒有 topologySpreadConstraints[*].topologyKey 的節點将被忽略。這意味着:

    1.位于這些節點上的 Pod 不影響 maxSkew 的計算。 在上面的例子中,假設 "node1" 沒有标簽 "zone",那麼 2 個 Pod 将被忽略, 是以傳入的 Pod 将被排程到 "zoneA" 中。

    2.新的 Pod 沒有機會被排程到這類節點上。 在上面的例子中,假設一個帶有标簽 {zone-typo: zoneC} 的 "node5" 加入到叢集, 它将由于沒有标簽鍵 "zone" 而被忽略。

  • 注意,如果新 Pod 的 topologySpreadConstraints[].labelSelector 與自身的 标簽不比對,将會發生什麼。 在上面的例子中,如果移除新 Pod 上的标簽,Pod 仍然可以排程到 "zoneB",因為限制仍然滿足。 然而,在排程之後,叢集的不平衡程度保持不變。zoneA 仍然有 2 個帶有 {foo:bar} 标簽的 Pod, zoneB 有 1 個帶有 {foo:bar} 标簽的 Pod。 是以,如果這不是你所期望的,建議工作負載的 topologySpreadConstraints[].labelSelector 與其自身的标簽比對。
  • 如果新 Pod 定義了 spec.nodeSelector 或 spec.affinity.nodeAffinity,則 不比對的節點會被忽略。

假設你有一個跨越 zoneA 到 zoneC 的 5 節點叢集:

【K8s概念】Pod 拓撲分布限制

而且你知道 "zoneC" 必須被排除在外。在這種情況下,可以按如下方式編寫 yaml, 以便将 "mypod" 放置在 "zoneB" 上,而不是 "zoneC" 上。同樣,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: k8s.gcr.io/pause:3.1
      

叢集級别的預設限制

為叢集設定預設的拓撲分布限制也是可能的。預設拓撲分布限制在且僅在以下條件滿足 時才會應用到 Pod 上:

  • Pod 沒有在其 .spec.topologySpreadConstraints 設定任何限制;
  • Pod 隸屬于某個服務、副本控制器、ReplicaSet 或 StatefulSet。

你可以在 排程方案(Scheduling Profile) 中将預設限制作為 PodTopologySpread 插件參數的一部分來設定。 限制的設定采用如前所述的 API,隻是 labelSelector 必須為空。 選擇算符是根據 Pod 所屬的服務、副本控制器、ReplicaSet 或 StatefulSet 來設定的。

配置的示例可能看起來像下面這個樣子:

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration

profiles:
  - pluginConfig:
      - name: PodTopologySpread
        args:
          defaultConstraints:
            - maxSkew: 1
              topologyKey: topology.kubernetes.io/zone
              whenUnsatisfiable: ScheduleAnyway
          defaultingType: List
      
說明:預設排程限制所生成的評分可能與 SelectorSpread 插件. 所生成的評分有沖突。 建議你在為 PodTopologySpread 設定預設限制是禁用排程方案中的該插件。

内部預設限制

FEATURE STATE: Kubernetes v1.20 [beta]

當你使用了預設啟用的 DefaultPodTopologySpread 特性門控時,原來的 SelectorSpread 插件會被禁用。 kube-scheduler 會使用下面的預設拓撲限制作為 PodTopologySpread 插件的 配置:

defaultConstraints:
  - maxSkew: 3
    topologyKey: "kubernetes.io/hostname"
    whenUnsatisfiable: ScheduleAnyway
  - maxSkew: 5
    topologyKey: "topology.kubernetes.io/zone"
    whenUnsatisfiable: ScheduleAnyway
      

此外,原來用于提供等同行為的 SelectorSpread 插件也會被禁用。

說明:

如果你的節點不會 同時 設定 kubernetes.io/hostname 和 topology.kubernetes.io/zone 标簽,你應該定義自己的限制而不是使用 Kubernetes 的預設限制。

插件 PodTopologySpread 不會為未設定分布限制中所給拓撲鍵的節點評分。

如果你不想為叢集使用預設的 Pod 分布限制,你可以通過設定 defaultingType 參數為 List 和 将 PodTopologySpread 插件配置中的 defaultConstraints 參數置空來禁用預設 Pod 分布限制。

apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration

profiles:
  - pluginConfig:
      - name: PodTopologySpread
        args:
          defaultConstraints: []
          defaultingType: List
      

與 PodAffinity/PodAntiAffinity 相比較

在 Kubernetes 中,與“親和性”相關的指令控制 Pod 的排程方式(更密集或更分散)。

  • 對于 PodAffinity,你可以嘗試将任意數量的 Pod 集中到符合條件的拓撲域中。
  • 對于 PodAntiAffinity,隻能将一個 Pod 排程到某個拓撲域中。

要實作更細粒度的控制,你可以設定拓撲分布限制來将 Pod 分布到不同的拓撲域下, 進而實作高可用性或節省成本。這也有助于工作負載的滾動更新和平穩地擴充副本規模。 有關詳細資訊,請參考 動機文檔。

已知局限性

  • Deployment 縮容操作可能導緻 Pod 分布不平衡。
  • 具有污點的節點上的 Pods 也會被統計。 參考 Issue 80921。

作者:​​Varden​​