Pod 拓扑传播约束
您可以使用 拓扑传播约束 来控制 Pod 如何在您的集群中跨越故障域(如区域、区域、节点和其他用户定义的拓扑域)分布。这有助于实现高可用性和高效的资源利用率。
您可以设置 集群级约束 作为默认值,或为单个工作负载配置拓扑传播约束。
动机
假设您有一个最多有 20 个节点的集群,并且您想运行一个 工作负载,该工作负载会自动扩展其使用的副本数量。副本数量可能只有两个 Pod,也可能多达 15 个。当只有两个 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。您只能与whenUnsatisfiable: DoNotSchedule
结合使用minDomains
。 - 当具有匹配拓扑键的合格域的数量小于
minDomains
时,Pod 拓扑传播将全局最小值视为 0,然后执行skew
的计算。全局最小值是合格域中匹配 Pod 的最小数量,或者如果合格域的数量小于minDomains
则为零。 - 当具有匹配拓扑键的合格域的数量等于或大于
minDomains
时,此值对调度没有影响。 - 如果您没有指定
minDomains
,则约束的行为与minDomains
为 1 时相同。
- 当指定时,
topologyKey 是 节点标签 的键。具有此键且值相同的标签的节点被认为在同一个拓扑中。我们称拓扑的每个实例(换句话说,一个 <key, value> 对)为一个域。调度程序会尝试将均衡数量的 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 控制器自动添加)来区分单个 Deployment 中的不同修订版本。topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: foo matchLabelKeys: - pod-template-hash
nodeAffinityPolicy 指示我们在计算 pod 拓扑传播偏差时如何处理 Pod 的 nodeAffinity/nodeSelector。选项包括
- Honor:仅包含与 nodeAffinity/nodeSelector 匹配的节点。
- Ignore:忽略 nodeAffinity/nodeSelector。所有节点都包含在内。
如果此值为 null,则行为等效于 Honor 策略。
注意
nodeAffinityPolicy
是处于 Beta 阶段的字段,在 1.26 中默认启用。您可以通过禁用NodeInclusionPolicyInPodTopologySpread
特性门控 来禁用它。nodeTaintsPolicy 指示我们在计算 pod 拓扑传播偏差时如何处理节点污点。选项包括
- Honor:包含没有污点的节点,以及传入 pod 具有容忍度的污点节点。
- Ignore:忽略节点污点。所有节点都包含在内。
如果此值为 null,则行为等效于 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 均匀分布,您可以使用类似于以下的清单
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
从该清单中,topologyKey: zone
意味着均匀分布仅适用于标记为zone: <any value>
的节点(没有zone
标签的节点将被跳过)。字段whenUnsatisfiable: DoNotSchedule
告诉调度程序如果调度程序找不到满足约束的方法,则让传入的 Pod 保持挂起状态。
如果调度程序将此传入 Pod 放入区域A
,则 Pod 的分布将变为[3, 1]
。这意味着实际偏差为 2(计算为3 - 1
),这违反了maxSkew: 1
。为了满足此示例的约束和上下文,传入的 Pod 只能放置在区域B
中的节点上
或
您可以调整 Pod 规范以满足各种需求
- 将
maxSkew
更改为更大的值(例如2
),以便传入的 Pod 也可以放置到区域A
中。 - 将
topologyKey
更改为node
,以便在节点而不是区域之间均匀分布 Pod。在上面的示例中,如果maxSkew
保持为1
,则传入的 Pod 只能放置到节点node4
上。 - 将
whenUnsatisfiable: DoNotSchedule
更改为whenUnsatisfiable: ScheduleAnyway
以确保传入的 Pod 始终可调度(假设其他调度 API 已满足)。但是,最好将其放置在具有较少匹配 Pod 的拓扑域中。(请注意,此优先级与其他内部调度优先级(例如资源使用率)一起进行标准化)。
示例:多个拓扑传播约束
这建立在前面的示例之上。假设您有一个 4 节点集群,其中 3 个标记为foo: bar
的现有 Pod 分别位于 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 只能放置到区域B
中的节点上;而就第二个约束而言,传入的 Pod 只能调度到节点node4
上。调度程序只考虑满足所有定义约束的选项,因此唯一有效的放置是到节点node4
上。
示例:冲突的拓扑传播约束
多个约束会导致冲突。假设您在 2 个区域中拥有一个 3 节点集群
如果您要将two-constraints.yaml
(来自上一个示例的清单)应用于**此**集群,您将看到 Pod mypod
处于Pending
状态。这是因为:为了满足第一个约束,Pod mypod
只能放置到区域B
中;而就第二个约束而言,Pod mypod
只能调度到节点node2
上。这两个约束的交集返回一个空集,调度程序无法放置 Pod。
为了克服这种情况,您可以增加maxSkew
的值或修改其中一个约束以使用whenUnsatisfiable: ScheduleAnyway
。根据情况,您可能还决定手动删除现有 Pod - 例如,如果您正在排查为什么错误修复发布没有进展。
与节点亲和性和节点选择器的交互
如果传入的 Pod 定义了spec.nodeSelector
或spec.affinity.nodeAffinity
,调度程序将跳过偏差计算中的不匹配节点。
示例:具有节点亲和性的拓扑传播约束
假设您有一个跨越区域 A 到 C 的 5 节点集群
并且您知道区域C
必须被排除。在这种情况下,您可以像下面这样编写清单,以便 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
的节点。这意味着- 位于这些绕过节点上的任何 Pod 不会影响
maxSkew
的计算 - 在上面的示例中,假设节点node1
没有标签 "zone",那么这两个 Pod 将被忽略,因此传入的 Pod 将被调度到区域A
中。 - 传入的 Pod 没有机会被调度到此类节点上 - 在上面的示例中,假设节点
node5
具有错误类型的标签zone-typo: zoneC
(并且没有设置zone
标签)。在节点node5
加入集群后,它将被绕过,并且此工作负载的 Pod 不会调度到那里。
- 位于这些绕过节点上的任何 Pod 不会影响
注意,如果传入 Pod 的
topologySpreadConstraints[*].labelSelector
不匹配它自己的标签,将会发生什么。在上面的示例中,如果您删除传入 Pod 的标签,它仍然可以放置到区域B
中的节点上,因为约束仍然得到满足。但是,在该放置之后,集群的不平衡程度保持不变 - 区域A
仍然有 2 个标记为foo: bar
的 Pod,区域B
有 1 个标记为foo: bar
的 Pod。如果您不希望这样,请更新工作负载的topologySpreadConstraints[*].labelSelector
以匹配 pod 模板中的标签。
集群级默认约束
可以为集群设置默认的拓扑传播约束。如果满足以下条件,则默认的拓扑传播约束将应用于 Pod:
- 它在
.spec.topologySpreadConstraints
中没有定义任何约束。 - 它属于 Service、ReplicaSet、StatefulSet 或 ReplicationController。
默认约束可以作为调度配置文件 中PodTopologySpread
插件参数的一部分进行设置。约束使用与上面的 API相同的 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 [稳定]
如果您没有为 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
与 pod 亲和性和 pod 反亲和性的比较
在 Kubernetes 中,Pod 间亲和性和反亲和性控制 Pod 相互之间如何调度 - 既可以更紧密地打包,也可以更分散地分布。
pod 亲和性
- 吸引 Pod;您可以尝试将任意数量的 Pod 打包到合格的拓扑域中。
pod 反亲和性
- 排斥 Pod。如果您将其设置为
requiredDuringSchedulingIgnoredDuringExecution
模式,那么只有一个 Pod 可以调度到单个拓扑域中;如果您选择preferredDuringSchedulingIgnoredDuringExecution
,那么您将失去强制执行约束的能力。
为了进行更精细的控制,您可以指定拓扑传播约束以在不同的拓扑域之间分布 Pod - 以实现高可用性或节省成本。这还有助于在滚动更新工作负载和顺利扩展副本时实现。
有关更多背景信息,请参见关于 Pod 拓扑传播约束的增强提案的 动机 部分。
已知限制
无法保证在删除 Pod 后约束仍然满足。例如,缩减部署可能会导致 Pod 分布不均衡。
您可以使用诸如 Descheduler 之类的工具重新平衡 Pod 分布。
在受污染节点上匹配的 Pod 会得到尊重。请参见 问题 80921。
调度程序事先不知道集群拥有的所有区域或其他拓扑域。它们是从集群中现有的节点中确定的。这可能会导致自动扩展集群出现问题,当节点池(或节点组)缩放到零个节点时,并且您希望集群扩展,因为在这种情况下,这些拓扑域将不会被考虑,直到它们中至少有一个节点。
您可以通过使用了解 Pod 拓扑传播约束并了解所有拓扑域的集群自动扩展工具来解决此问题。
下一步
- 博文文章 介绍 PodTopologySpread 详细解释了
maxSkew
,以及一些高级使用示例。 - 阅读 API 参考中有关 Pod 的 调度 部分。