Pod 拓扑分布约束

    你可以使用 拓扑分布约束(Topology Spread Constraints) 来控制 在集群内故障域 之间的分布,例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域。 这样做有助于实现高可用并提升资源利用率。

    拓扑分布约束依赖于节点标签来标识每个节点所在的拓扑域。 例如,某节点可能具有标签:node=node1,zone=us-east-1a,region=us-east-1

    假设你拥有具有以下标签的一个 4 节点集群:

    然后从逻辑上看集群如下:

    graph TB subgraph “zoneB” n3(Node3) n4(Node4) end subgraph “zoneA” n1(Node1) n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4 k8s; class zoneA,zoneB cluster;

    .mermaid{display:none}

    [JavaScript must be enabled to view content]

    你可以复用在大多数集群上自动创建和填充的 , 而不是手动添加标签。

    API

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

    1. apiVersion: v1
    2. kind: Pod
    3. metadata:
    4. name: mypod
    5. spec:
    6. topologySpreadConstraints:
    7. - maxSkew: <integer>
    8. topologyKey: <string>
    9. 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 的更多信息。

    假设你拥有一个 4 节点集群,其中标记为 foo:bar 的 3 个 Pod 分别位于 node1、node2 和 node3 中:

    graph BT subgraph “zoneB” p3(Pod) —> n3(Node3) n4(Node4) end subgraph “zoneA” p1(Pod) —> n1(Node1) p2(Pod) —> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class zoneA,zoneB cluster;

    .mermaid{display:none}

    [JavaScript must be to view content]

    如果希望新来的 Pod 均匀分布在现有的可用区域,则可以按如下设置其规约:

    pods/topology-spread-constraints/one-constraint.yaml

    topologyKey: zone 意味着均匀分布将只应用于存在标签键值对为 “zone:<any value>” 的节点。 whenUnsatisfiable: DoNotSchedule 告诉调度器如果新的 Pod 不满足约束, 则让它保持悬决状态。

    如果调度器将新的 Pod 放入 “zoneA”,Pods 分布将变为 [3, 1],因此实际的偏差 为 2(3 - 1)。这违反了 maxSkew: 1 的约定。此示例中,新 Pod 只能放置在 “zoneB” 上:

    graph BT subgraph “zoneB” p3(Pod) —> n3(Node3) p4(mypod) —> n4(Node4) end subgraph “zoneA” p1(Pod) —> n1(Node1) p2(Pod) —> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;

    [JavaScript must be to view content]

    或者

    graph BT subgraph “zoneB” p3(Pod) —> n3(Node3) p4(mypod) —> n3 n4(Node4) end subgraph “zoneA” p1(Pod) —> n1(Node1) p2(Pod) —> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;

    .mermaid{display:none}

    [JavaScript must be enabled to view content]

    你可以调整 Pod 规约以满足各种要求:

    • maxSkew 更改为更大的值,比如 “2”,这样新的 Pod 也可以放在 “zoneA” 上。
    • topologyKey 更改为 “node”,以便将 Pod 均匀分布在节点上而不是区域中。 在上面的例子中,如果 maxSkew 保持为 “1”,那么传入的 Pod 只能放在 “node4” 上。
    • whenUnsatisfiable: DoNotSchedule 更改为 whenUnsatisfiable: ScheduleAnyway, 以确保新的 Pod 始终可以被调度(假设满足其他的调度 API)。 但是,最好将其放置在匹配 Pod 数量较少的拓扑域中。 (请注意,这一优先判定会与其他内部调度优先级(如资源使用率等)排序准则一起进行标准化。)

    例子:多个 TopologySpreadConstraints

    下面的例子建立在前面例子的基础上。假设你拥有一个 4 节点集群,其中 3 个标记为 的 Pod 分别位于 node1、node2 和 node3 上:

    graph BT subgraph “zoneB” p3(Pod) —> n3(Node3) n4(Node4) end subgraph “zoneA” p1(Pod) —> n1(Node1) p2(Pod) —> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;

    .mermaid{display:none}

    [JavaScript must be to view content]

    可以使用 2 个 TopologySpreadConstraint 来控制 Pod 在 区域和节点两个维度上的分布:

    pods/topology-spread-constraints/two-constraints.yaml Pod 拓扑分布约束 - 图1

    1. kind: Pod
    2. apiVersion: v1
    3. metadata:
    4. name: mypod
    5. labels:
    6. foo: bar
    7. spec:
    8. topologySpreadConstraints:
    9. - maxSkew: 1
    10. topologyKey: zone
    11. whenUnsatisfiable: DoNotSchedule
    12. labelSelector:
    13. matchLabels:
    14. foo: bar
    15. - maxSkew: 1
    16. topologyKey: node
    17. labelSelector:
    18. matchLabels:
    19. foo: bar
    20. containers:
    21. - name: pause
    22. image: k8s.gcr.io/pause:3.1

    在这种情况下,为了匹配第一个约束,新的 Pod 只能放置在 “zoneB” 中;而在第二个约束中, 新的 Pod 只能放置在 “node4” 上。最后两个约束的结果加在一起,唯一可行的选择是放置 在 “node4” 上。

    多个约束之间可能存在冲突。假设有一个跨越 2 个区域的 3 节点集群:

    graph BT subgraph “zoneB” p4(Pod) —> n3(Node3) p5(Pod) —> n3 end subgraph “zoneA” p1(Pod) —> n1(Node1) p2(Pod) —> n1 p3(Pod) —> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3,p4,p5 k8s; class zoneA,zoneB cluster;

    .mermaid{display:none}

    [JavaScript must be to view content]

    如果对集群应用 “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 定义了 spec.nodeSelectorspec.affinity.nodeAffinity,则 不匹配的节点会被忽略。

      假设你有一个跨越 zoneA 到 zoneC 的 5 节点集群:

      graph BT subgraph “zoneB” p3(Pod) —> n3(Node3) n4(Node4) end subgraph “zoneA” p1(Pod) —> n1(Node1) p2(Pod) —> n2(Node2) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n1,n2,n3,n4,p1,p2,p3 k8s; class p4 plain; class zoneA,zoneB cluster;

      .mermaid{display:none}

      [JavaScript must be enabled to view content]

      graph BT subgraph “zoneC” n5(Node5) end classDef plain fill:#ddd,stroke:#fff,stroke-width:4px,color:#000; classDef k8s fill:#326ce5,stroke:#fff,stroke-width:4px,color:#fff; classDef cluster fill:#fff,stroke:#bbb,stroke-width:2px,color:#326ce5; class n5 k8s; class zoneC cluster;

      .mermaid{display:none}

      [JavaScript must be to view content]

      而且你知道 “zoneC” 必须被排除在外。在这种情况下,可以按如下方式编写 yaml, 以便将 “mypod” 放置在 “zoneB” 上,而不是 “zoneC” 上。同样,spec.nodeSelector 也要一样处理。

      pods/topology-spread-constraints/one-constraint-with-nodeaffinity.yaml

    集群级别的默认约束

    为集群设置默认的拓扑分布约束也是可能的。默认拓扑分布约束在且仅在以下条件满足 时才会应用到 Pod 上:

    • Pod 没有在其 .spec.topologySpreadConstraints 设置任何约束;
    • Pod 隶属于某个服务、副本控制器、ReplicaSet 或 StatefulSet。

    你可以在 中将默认约束作为 PodTopologySpread 插件参数的一部分来设置。 约束的设置采用如前所述的 API,只是 labelSelector 必须为空。 选择算符是根据 Pod 所属的服务、副本控制器、ReplicaSet 或 StatefulSet 来设置的。

    配置的示例可能看起来像下面这个样子:

    1. apiVersion: kubescheduler.config.k8s.io/v1beta1
    2. kind: KubeSchedulerConfiguration
    3. profiles:
    4. - pluginConfig:
    5. - name: PodTopologySpread
    6. args:
    7. defaultConstraints:
    8. - maxSkew: 1
    9. topologyKey: topology.kubernetes.io/zone
    10. whenUnsatisfiable: ScheduleAnyway

    内部默认约束

    FEATURE STATE: Kubernetes v1.19 [alpha]

    当你启用了 DefaultPodTopologySpread 特性门控时,原来的 SelectorSpread 插件会被禁用。 kube-scheduler 会使用下面的默认拓扑约束作为 PodTopologySpread 插件的 配置:

    此外,原来用于提供等同行为的 SelectorSpread 插件也会被禁用。

    • 对于 PodAffinity,你可以尝试将任意数量的 Pod 集中到符合条件的拓扑域中。
    • 对于 PodAntiAffinity,只能将一个 Pod 调度到某个拓扑域中。

    要实现更细粒度的控制,你可以设置拓扑分布约束来将 Pod 分布到不同的拓扑域下, 从而实现高可用性或节省成本。这也有助于工作负载的滚动更新和平稳地扩展副本规模。 有关详细信息,请参考 文档。

    • Deployment 缩容操作可能导致 Pod 分布不平衡。