主题
使用 Pod Topology Spread Constraints 实现工作负载强制分散到所有节点
在多节点 Kubernetes 集群中,Deployment 默认调度策略并不保证副本均匀分布,经常出现副本集中在少数节点上的情况:单节点故障会一次性影响过多副本,节点间负载也不均衡。Pod Topology Spread Constraints(拓扑分布约束)可以按节点、可用区等拓扑域控制副本的最大分布差值(maxSkew),实现工作负载在所有节点上的强制分散。本文以将一个 20 副本的 Deployment 均匀分散到 8 个节点为例,介绍 apply/patch 两种配置方式、分布验证方法、新增节点后的强制重调度手段及关键参数说明。
一、创建/更新 Deployment
1.1 方式一:通过 kubectl apply 直接部署 YAML
bash
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: job-task-deploy
spec:
replicas: 20
selector:
matchLabels:
app: job-task-deploy
template:
metadata:
labels:
app: job-task-deploy
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
# whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: job-task-deploy
containers:
- name: main
image: nginx:alpine # 替换为实际业务镜像
EOF1.2 方式二:通过 kubectl patch 更新已有 Deployment
bash
kubectl patch deployment job-task-deploy --patch '
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: job-task-deploy
'patch 方式只更新 Pod 模板中的调度约束,业务容器配置不受影响;patch 后 Deployment 会触发滚动更新,新创建的 Pod 按新约束调度。
二、验证调度结果
2.1 查看 Pod 分布情况
bash
kubectl get pods -l app=job-task-deploy -o wide输出示例:
text
NAME READY STATUS NODE
job-task-deploy-abcd1 1/1 Running node1
job-task-deploy-efgh2 1/1 Running node2
... (每个节点至少 2-3 个 Pod)2.2 统计每个节点的 Pod 数量
bash
kubectl get pods -l app=job-task-deploy -o jsonpath='{.items[*].spec.nodeName}' | tr ' ' '\n' | sort | uniq -c输出示例:
text
3 node1
3 node2
2 node3
2 node4
3 node5
3 node6
2 node7
2 node8maxSkew: 1 生效时,任意两个节点上的 Pod 数量差不超过 1。
三、强制触发重新调度(适用于新增节点后)
拓扑分布约束只在 Pod 调度时生效,已运行的 Pod 不会因为新节点加入而自动迁移。如果新增了 3 个节点但 Pod 未自动均衡到新节点,可以手动触发重建:
bash
# 方法1:缩容再扩容
kubectl scale deployment job-task-deploy --replicas=10
kubectl scale deployment job-task-deploy --replicas=20
# 方法2:删除旧 Pod 触发重建
kubectl delete pods -l app=job-task-deploy两种方法都会让 Pod 重新走调度流程,从而应用拓扑分布约束。生产环境优先使用方法 2(Deployment 按滚动策略逐个重建,服务不中断);方法 1 会造成副本数瞬时下降,需评估业务承载能力。
四、关键参数说明
| 参数 | 作用 | 示例值 |
|---|---|---|
maxSkew | 允许的最大分布不平衡数(任意两个拓扑域间 Pod 数量差值上限) | 1(严格均衡) |
topologyKey | 分散的拓扑域(节点标签键) | kubernetes.io/hostname(节点级)、topology.kubernetes.io/zone(可用区级) |
whenUnsatisfiable | 无法满足约束时的行为 | DoNotSchedule(强制,不满足则 Pending)或 ScheduleAnyway(宽松,尽量满足) |
labelSelector | 匹配需要分散统计的 Pod 标签 | matchLabels: {app: job-task-deploy} |
补充说明:
labelSelector必须与 Pod 自身标签匹配,否则调度器找不到同组 Pod,约束不生效。maxSkew必须是大于 0 的整数;whenUnsatisfiable: DoNotSchedule时取值越大,允许的分布越不均匀。whenUnsatisfiable缺省值为DoNotSchedule,追求强约束时可显式写出以增强可读性。
五、注意事项
- 资源不足问题:如果节点资源不足,部分 Pod 会处于
Pending状态。检查节点可分配资源:
bash
kubectl describe nodes | grep -A 5 "Allocatable"- 优先级调整:若需与其他调度规则(如资源请求、节点亲和性)共存,可使用强制约束保证分布不被打破:
yaml
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector: {...}- 多维度分散:如需同时按节点和可用区分散,可添加多条约束(多条约束之间是 AND 关系,必须同时满足):
yaml
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector: {...}
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector: {...}- 与 HPA 配合:副本数动态变化时调度器会自动维持约束,无需额外配置。
故障速查与注意事项表
| 现象/事项 | 说明 |
|---|---|
| Pod 大量 Pending | 使用 DoNotSchedule 且节点数少于副本需求、或节点资源不足;改用 ScheduleAnyway 或扩容节点 |
| 新节点加入后分布未变化 | 约束只在调度时生效,需删除 Pod 或缩扩容触发重建 |
| 约束看起来不生效 | 检查 labelSelector 是否与 Pod 标签一致;检查 topologyKey 对应的节点标签是否在所有节点上存在 |
| 副本数不能被节点数整除 | 属正常情况,maxSkew: 1 保证各节点数量差不超过 1(如 20 副本 8 节点,分布为 3/3/2/2/3/3/2/2) |
| 节点打污点(taint)后副本集中 | 污点会缩小可调度节点范围,拓扑约束只在可调度节点内生效,需结合容忍(tolerations)评估 |
通过以上配置,20 个副本将强制分散到全部 8 个节点上,每个节点运行 2-3 个 Pod,单节点故障影响的副本数被控制在最小范围。