Skip to content

使用 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  # 替换为实际业务镜像
EOF

1.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 node8

maxSkew: 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,追求强约束时可显式写出以增强可读性。

五、注意事项

  1. 资源不足问题:如果节点资源不足,部分 Pod 会处于 Pending 状态。检查节点可分配资源:
bash
kubectl describe nodes | grep -A 5 "Allocatable"
  1. 优先级调整:若需与其他调度规则(如资源请求、节点亲和性)共存,可使用强制约束保证分布不被打破:
yaml
topologySpreadConstraints:
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: DoNotSchedule
  labelSelector: {...}
  1. 多维度分散:如需同时按节点和可用区分散,可添加多条约束(多条约束之间是 AND 关系,必须同时满足):
yaml
topologySpreadConstraints:
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: ScheduleAnyway
  labelSelector: {...}
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: ScheduleAnyway
  labelSelector: {...}
  1. 与 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,单节点故障影响的副本数被控制在最小范围。