K8s Pod 拓扑分布约束详解
核心概念 Pod 拓扑分布约束控制 Pod 在拓扑域中的分布,实现高可用和资源均衡。
1. 一、核心定位与解决什么问题
1.1. 1. 定义
**topologySpreadConstraints**(拓扑分散约束)是调度器高级均衡能力,用于强制同业务Pod均匀打散在不同故障域(节点、机架、可用区AZ),相比 Pod 反亲和更灵活精细。
1.2. 2. 对比旧方案缺陷
- podAntiAffinity 硬反亲和**:**
**required**拓扑域最多只能放1个Pod,副本多、节点少时直接Pending;**preferred**仅偏好,无法严格均衡。 - 无约束默认调度:调度器装箱优先填满单节点/单AZ,大量副本堆在同一故障域,一旦节点/机房宕机服务全挂。
1.3. 3. 核心价值(生产高可用必备,你的Agent训练/推理集群强适用)
- 容灾隔离**:多副本均匀分到不同节点、不同可用区,单点故障不整体宕机;**
- 资源均衡**:GPU/CPU负载分散,避免单节点显卡打满;**
- 多AZ流量均衡**:云集群跨区部署,均衡各区实例数量,均衡负载;**
- 支持软硬约束,可配置不满足均衡时直接拒绝调度或尽力调度。
1.4. 基础概念:拓扑域 topology domain
以节点相同标签value划分一组节点,一组节点=一个拓扑域,划分标签键叫 **topologyKey****。
标准内置拓扑键(开箱即用):**
| | | |
|---|---|---|
|topologyKey|拓扑域粒度|用途|
|**kubernetes.io/hostname**|单台物理节点|同业务不扎堆同一服务器|
|**topology.kubernetes.io/zone**|可用区AZ(机房)|跨机房容灾,核心生产配置|
|**topology.kubernetes.io/region**|地域|异地多活极少使用|
|自定义:**rack=rack01** 机架、**gpu-type=a100** 显卡机型。|||
2. 二、五大核心字段完整拆解(必填+可选)
spec:
topologySpreadConstraints:
- maxSkew: 1 # 必选:拓扑域之间允许最大Pod差值
topologyKey: topology.kubernetes.io/zone # 必选:划分拓扑域的节点标签
whenUnsatisfiable: DoNotSchedule # 必选:均衡不达标如何处理
labelSelector: # 必选:统计哪些Pod算同业务副本
matchLabels:
app: llm-agent
minDomains: 2 # 可选:至少分布到几个拓扑域
nodeAffinityPolicy: Honor # 可选:是否遵守节点亲和
nodeTaintsPolicy: Honor # 可选:是否遵守节点污点容忍
2.1. 1. maxSkew 最大偏差(最关键)
控制不同拓扑域内匹配Pod数量允许的最大差值,推荐固定 **maxSkew:1** **达到完全均衡。
计算公式:
任意域Pod数 − 全局最少Pod域数量 ≤ maxSkew
示例:3个可用区A/B/C,现有Pod [2,1,1],差值=1,maxSkew=1 满足;
若现有 [3,1,1],差值=2,maxSkew=1 不满足,无法调度新Pod。**
2.2. 2. whenUnsatisfiable 两种策略(硬约束/软约束)
- DoNotSchedule(硬约束,生产核心业务首选)**
均衡条件无法满足时,直接拒绝调度,Pod长期Pending,强制等待拓扑域负载均衡再创建。
风险:副本扩容、节点故障删除Pod后,容易出现Pending。** - ScheduleAnyway(软约束,测试/低优先级业务)**
尽量往少Pod的拓扑域调度;实在无法均衡,也允许放置,不会阻塞Pod创建。**
2.3. 3. labelSelector
指定哪些Pod算作一组统计副本,必须和Deployment模板Pod标签完全一致,调度器只会统计匹配该标签的Pod来计算skew差值。
2.4. 4. minDomains(可选)最小分布域数量
强制Pod至少分散到N个拓扑域,多AZ容灾场景设 **minDomains:3**,确保3个机房都有实例。
2.5. 5. nodeAffinityPolicy / nodeTaintsPolicy
- Honor:同时遵守节点亲和、污点容忍(默认,生产必用)
- Ignore:忽略亲和/污点,仅做均衡,极少使用
3. 三、两种标准实战模板(GPU Agent集群直接复制)
3.1. 模板1:生产高可用双维度硬约束(跨可用区+跨节点)
需求:LLM推理Agent,副本均匀分散到不同AZ、不同节点,不均衡则不调度
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-infer-agent
spec:
replicas: 6
selector:
matchLabels:
app: llm-infer-agent
template:
metadata:
labels:
app: llm-infer-agent
spec:
# 1. 第一层:跨可用区均衡(硬约束,优先保障机房容灾)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
minDomains: 3
labelSelector:
matchLabels:
app: llm-infer-agent
# 2. 第二层:同可用区内,分散到不同节点(软约束,防止单节点堆太多)
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: llm-infer-agent
# 配套GPU调度:污点容忍+节点机型筛选
tolerations:
- key: gpu
operator: Equal
value: "true"
effect: NoSchedule
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu-type
operator: In
values: ["a100-sxm4-80gb"]
containers:
- name: agent
image: qwen-agent:v1
resources:
limits:
nvidia.com/gpu: 1
cpu: 4
memory: 16Gi
调度效果:3个可用区,每个AZ固定2个Pod;每个AZ内尽量分散到不同GPU节点。
3.2. 模板2:训练任务软均衡(Volcano Job搭配拓扑分散)
批量训练任务不希望阻塞扩容,使用ScheduleAnyway:
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: torch-train-job
4. 四、完整调度计算示例(直观理解maxSkew)
4.1. 场景
3个可用区 zone-a / zone-b / zone-c,maxSkew=1,DoNotSchedule,label匹配app=agent
- 初始0副本,创建第1个Pod:任意区均可
- 第2个Pod:必须放到另外空区,避免差值=2
- 第3个Pod:三区各1个,均衡
- 创建第4个Pod:只能放到现有1个Pod的区,每个区最多2个(差值1)
- 若zone-a已有3个,其他两区各1个:差值=2,新Pod无法调度,Pending
5. 五、与其他调度约束协同优先级
调度器校验顺序(全部同时生效,叠加限制)
- 污点Taints & Tolerations:过滤无资格节点
- nodeSelector / nodeAffinity:筛选机型、GPU标签
- topologySpreadConstraints:均衡拓扑域分布
- podAntiAffinity:同节点禁止重复副本
- 资源(CPU/GPU)配额校验
5.1. 叠加约束极易Pending场景
同时配置:
- zone硬均衡 DoNotSchedule maxSkew=1
- hostname硬反亲和,单节点只能1个Pod
集群节点不足时,两个约束交集为空,Pod永久Pending。
解决方案:其中一层改为 ScheduleAnyway 软约束。
6. 六、常见Pod Pending根因(拓扑约束专属)
- maxSkew=1,拓扑域Pod数量差值超过1,DoNotSchedule拒绝调度**
排查:****kubectl get pods -o wide**统计各AZ/节点副本数量,删除多余Pod或增加节点。 **minDomains**指定3个可用区,但集群只有2个AZ,永远无法满足,持续Pending。- 多套拓扑约束(zone+hostname)均为DoNotSchedule,无节点同时满足两层均衡。
- labelSelector写错标签,调度统计不到现有副本,计算skew错误导致拒绝。
- 节点缺少
**topology.kubernetes.io/zone**标签,无法划分拓扑域。
7. 七、生产最佳实践(AI/GPU集群专用)
- 核心线上推理服务**
两层拓扑约束:zone使用DoNotSchedule硬均衡,hostname使用ScheduleAnyway软均衡,minDomains匹配集群可用区数量。** - 批量训练任务**
全部使用ScheduleAnyway,避免扩容阻塞,保证训练任务正常启动。** - GPU集群搭配规则
拓扑约束 + GPU节点污点容忍 + nodeAffinity筛选显卡型号三位一体;
均衡不同A100/H100节点负载,避免单节点8卡全部占满。 - 副本扩容规划
副本总数 ÷ 可用区数量 尽量整除,保证完美均衡无差值。 - 故障预案
节点宕机删除Pod后,拓扑均衡被打破,新扩容Pod会自动补齐差值;若长期Pending,临时调高maxSkew。
8. 八、拓扑分散 vs Pod反亲和 选型对比
| | | |
|---|---|---|
|特性|topologySpreadConstraints|podAntiAffinity required硬反亲和|
|单拓扑域副本上限|由maxSkew控制,可放多个|最多1个|
|均衡能力|自动均衡各域数量|仅限制单域数量,无法均衡|
|阻塞风险|DoNotSchedule时可能Pending|节点不足直接Pending|
|适用场景|多副本高可用、GPU负载均衡|单副本独占节点、数据库主从隔离|
|推荐优先级|生产首选|仅特殊独占场景使用|
9. 九、高频踩坑总结
- topologyKey必须是节点存在的标签,云集群默认带zone,自建裸机需手动打标签;
- labelSelector必须和Pod模板labels完全一致,否则统计副本数量错误;
- 多层DoNotSchedule约束极易冲突导致Pending,至少一层改为ScheduleAnyway;
- maxSkew不能设0,最小合法值为1;
- minDomains不能超过集群实际拓扑域总数,否则永久无法调度;
- 批量删除节点后,副本分布失衡会触发新Pod Pending,可临时调大maxSkew快速恢复;
- 拓扑分散只控制调度,不会驱逐已运行Pod;存量Pod分布不均不会自动重排,仅影响新创建Pod。
关联文档