Pod拓扑分布约束
FEATURESTATE:Kubernetesv1.19[stable]
你可以使用拓扑分布约束(TopologySpreadConstraints)来控制Pods在集群内故障域之间的分布,例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域。这样做有助于实现高可用并提升资源利用率。
先决条件 节点标签
拓扑分布约束依赖于节点标签来标识每个节点所在的拓扑域。例如,某节点可能具有标签:node=node1,zone=us-east-1a,region=us-east-1
假设你拥有具有以下标签的一个4节点集群:
NAME STATUS ROLES AGE VERSION LABELS
node1 Ready 4m26s v1.16.0 node=node1,zone=zoneA
node2 Ready 3m58s v1.16.0 node=node2,zone=zoneA
node3 Ready 3m17s v1.16.0 node=node3,zone=zoneB
node4 Ready 2m43s v1.16.0 node=node4,zone=zoneB
那么,从逻辑上看集群如下:
你可以复用在大多数集群上自动创建和填充的常用标签,而不是手动添加标签。
Pod的分布约束 API
pod.spec.topologySpreadConstraints字段定义如下所示:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
topologySpreadConstraints:
- maxSkew:
topologyKey:
whenUnsatisfiable:
labelSelector:
你可以定义一个或多个topologySpreadConstraint来指示kube-scheduler如何根据与现有的Pod的关联关系将每个传入的Pod部署到集群中。字段包括:
topologyKey是节点标签的键。如果两个节点使用此键标记并且具有相同的标签值,则调度器会将这两个节点视为处于同一拓扑域中。调度器试图在每个拓扑域中放置数量均衡的Pod。whenUnsatisfiable指示如果Pod不满足分布约束时如何处理:labelSelector用于查找匹配的pod。匹配此标签的Pod将被统计,以确定相应拓扑域中Pod的数量。
当Pod定义了不止一个topologySpreadConstraint,这些约束之间是逻辑与的关系。kube-scheduler会为新的Pod寻找一个能够满足所有约束的节点。
你可以执行kubectlexplainPod.spec.topologySpreadConstraints命令以了解关于topologySpreadConstraints的更多信息。
例子:单个TopologySpreadConstraint
假设你拥有一个4节点集群,其中标记为foo:bar的3个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
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”上:
或者
你可以调整Pod规约以满足各种要求:
例子:多个TopologySpreadConstraints
下面的例子建立在前面例子的基础上。假设你拥有一个4节点集群,其中3个标记为foo:bar的Pod分别位于node1、node2和node3上:
可以使用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节点集群:
如果对集群应用“two-constraints.yaml”,会发现“mypod”处于Pending状态。这是因为:为了满足第一个约束,”mypod”只能放在“zoneB”中,而第二个约束要求“mypod”只能放在“node2”上。Pod调度无法满足两种约束。
为了克服这种情况,你可以增加maxSkew或修改其中一个约束,让其使用whenUnsatisfiable:ScheduleAnyway。
节点亲和性与节点选择器的相互作用
如果Pod定义了spec.nodeSelector或spec.affinity.nodeAffinity,调度器将在偏差计算中跳过不匹配的节点。
示例:TopologySpreadConstraints与NodeAffinity
假设你有一个跨越zoneA到zoneC的5节点集群:
而且你知道“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上:
你可以在调度方案(SchedulingProfile)中将默认约束作为PodTopologySpread插件参数的一部分来设置。约束的设置采用如前所述的API,只是labelSelector必须为空。选择算符是根据Pod所属的服务、副本控制器、ReplicaSet或StatefulSet来设置的。
配置的示例可能看起来像下面这个样子:
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
默认调度约束所生成的评分可能与SelectorSpread插件所生成的评分有冲突。建议你在为PodTopologySpread设置默认约束是禁用调度方案中的该插件。
内部默认约束
FEATURESTATE:Kubernetesv1.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插件也会被禁用。
对于分布约束中所指定的拓扑键而言,
PodTopologySpread插件不会为不包含这些主键的节点评分。这可能导致在使用默认拓扑约束时,其行为与原来的
SelectorSpread插件的默认行为不同,
如果你的节点不会同时设置
kubernetes.io/hostname和
topology.kubernetes.io/zone标签,你应该定义自己的约束而不是使用Kubernetes的默认约束。
如果你不想为集群使用默认的Pod分布约束,你可以通过设置defaultingType参数为List并将PodTopologySpread插件配置中的defaultConstraints参数置空来禁用默认Pod分布约束。
apiVersion: kubescheduler.config.k8s.io/v1beta3
kind: KubeSchedulerConfiguration
profiles:
- schedulerName: default-scheduler
pluginConfig:
- name: PodTopologySpread
args:
defaultConstraints: []
defaultingType: List
与PodAffinity/PodAntiAffinity相比较
在Kubernetes中,与“亲和性”相关的指令控制Pod的调度方式(更密集或更分散)。
要实现更细粒度的控制,你可以设置拓扑分布约束来将Pod分布到不同的拓扑域下,从而实现高可用性或节省成本。这也有助于工作负载的滚动更新和平稳地扩展副本规模。
已知局限性