在日常使用 kubernetes 的过程中中,很多时候我们并没有过多的关心 pod 的到底调度在哪里,只是通过多副本的测试,来提高的我们的业务的可用性,但是当多个相同业务 pod 在分布在相同节点时,一旦节点意外宕机,将会严重影响我们的业务的可用性,鉴于此,kubernetes 中引入了新的 pod 调度策略-topologySpreadConstraints,翻译为拓扑分布约束,今天就让我们揭开其神秘面纱,原来 pod 还可以这么玩。
关键字:topologySpreadConstraints pod拓扑分布约束 kubernetes
拓扑分布约束(Topology Spread Constraints)可以控制 Pods 在集群内故障域 之间的分布,例如区域(Region)、可用区(Zone)、节点和其他用户自定义拓扑域。这样做有助于实现高可用并提升资源利用率。 此项功能在 1.18中将其提升为Beta,1.19 中为 stable 状态,可以在生产环境中使用。
拓扑分布约束依赖于节点标签来标识每个节点所在的拓扑域。例如,某节点可能具有标签:node=node1,zone=us-east-1a,region=us-east-1
假设你拥有具有以下标签的一个 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
在逻辑上看,我们的节点的结构图如下:
由上图可知,逻辑域有两个层次,一是 node 层面,二是 zone 层面,我们可以灵活配置自己的故障域,使我们的业务有更高的可用性。
正如我们日常写 yaml 一样,配置 topologySpreadConstraints,同样只需要在 yaml 中定义即可,路径为: 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 部署到集群中。 字段包括:
你可以执行 kubectl explain Pod.spec.topologySpreadConstraints
命令以了解关于 topologySpreadConstraints 的更多信息。
如果希望新来的 Pod 均匀分布在现有的可用区域,可进行如下配置:
## 示例1-yaml
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
如果调度器将新的 Pod 放入 "zoneA",Pods 分布将变为 [3, 1],因此实际的偏差 为(3 - 1)= 2 。这违反了 maxSkew: 1 的约定。此示例中,新 Pod 只能放置在 "zoneB" 上:
或者
4
你可以调整 Pod 的配置以满足各种要求:
可以使用 2 个 TopologySpreadConstraint 来控制 Pod 在 区域和节点两个维度上的分布:
## 示例2-yaml
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 节点集群:
如果对集群应用示例2-yaml配置,会发现 "mypod" 处于 Pending 状态。这是因为:为了满足第一个约束,"mypod" 只能放在 "zoneB" 中,而第二个约束要求 "mypod" 只能放在 "node2" 上。Pod 调度无法满足两种约束。
为了克服这种情况,你可以增加 maxSkew 或修改其中一个约束,让其使用 whenUnsatisfiable: ScheduleAnyway
假设你有一个跨越 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 上:
你可以在 调度方案(Schedulingg 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
在 Kubernetes 中,与“亲和性”相关的指令控制 Pod 的调度方式(更密集或更分散)。
要实现更细粒度的控制,你可以设置拓扑分布约束来将 Pod 分布到不同的拓扑域下, 从而实现高可用性或节省成本。这也有助于工作负载的滚动更新和平稳地扩展副本规模。
结合NodeSelector/NodeAffinity一起使用
在pod的拓扑分布约束配置中,可以看到我们只有topologyKey的配置选项,并没有任何关于topologyValues的配置字段,也就是并没有规定pod具体安排在哪些拓扑域,默认情况下,它将搜索所有节点并按"topologyKey"对它们进行分组。有时这可能不是我们想要的结果。例如,假设有一个集群,其节点分别用"env = prod","env = staging"和"env = qa"标记,现在您想将Pod均匀地跨区域放置到"qa"环境中,能办到么?
答案是肯定的。您可以利用NodeSelector或NodeAffinity API规范。在幕后,PodTopologySpread功能将兑现这一点,并计算满足选择器的节点之间的传播约束。示意图:
如上所示,您可以指定spec.affinity.nodeAffinity将搜索范围限制为qa环境,并且在该范围内,会将Pod调度到一个满足topologySpreadConstraints的区域。在这种情况下,它是"zone2"。
高阶多拓扑分布约束
了解一个TopologySpreadConstraint的工作原理很直观。多个TopologySpreadConstraints是什么情况?在内部,每个TopologySpreadConstraint都是独立计算的,结果集将合并以生成最终的结果集-即合适的节点。
在以下示例中,我们希望同时将Pod调度到具有2个需求的集群中:
示意图:
对于第一个约束,zone1中有3个Pod,zone2中有2个Pod,因此只能将传入的Pod放入zone2中,以满足"maxSkew = 1"约束。换句话说,结果集是nodeX和nodeY。
对于第二个约束,nodeB和nodeX中的Pod过多,因此只能将传入的Pod放入nodeA和nodeY。
现在我们可以得出结论,唯一合格的节点是nodeY-从集合{nodeX,nodeY}(来自第一个约束)和{nodeA,nodeY}(来自第二个约束)的交集中得出。
多个TopologySpreadConstraints功能强大,但是一定要了解与前面的"NodeSelector/NodeAffinity"示例的区别:一个是独立计算结果集,然后将其互连;另一种是根据节点约束的过滤结果来计算topologySpreadConstraints。
注意
:如果将两个TopologySpreadConstraints应用于同一{topologyKey,whenUnsatisfiable}元组,则Pod的创建将被阻止,并返回验证错误。
- END -