有次看一个服务的副本分布,Deployment 配了 6 个副本,预期是每个节点差不多分一点。但实际看下来,有两个节点各跑了 2 个,有的节点一个都没有。
第一反应是加 podAntiAffinity,但后来发现这个场景更适合 topologySpreadConstraints。
这里的关键不是“能不能分散”,而是“分散到什么程度算可以接受”。anti-affinity 表达的是排斥关系,topology spread 表达的是倾斜度。前者更像硬规则,后者更像分布预算。
先确认实际分布
先不要改 YAML,先看当前 Pod 落点:
如果 Pod 已经 Pending,再看调度事件:
| |
anti-affinity 能解决什么
最直接的写法是用 podAntiAffinity,让同一个应用的 Pod 不要放在同一个节点:
| |
这个配置比较硬:同一个节点上不能有两个 app=web 的 Pod。
它适合副本数小于等于可用节点数的场景。比如 3 个节点跑 3 个副本,很干净。
但如果 replicas 是 6,集群只有 3 个节点,后面 3 个 Pod 就会 Pending。因为 hard anti-affinity 已经把节点都排除掉了。
所以很多时候我不会直接上 required,而是先用 preferred:
这个配置会让调度器尽量分散,但它不是强保证。资源紧张的时候,还是可能放在一起。
topologySpreadConstraints 更像控制倾斜度
如果目标不是“绝对不能放一起”,而是“尽量均匀”,topologySpreadConstraints 更合适。
比如希望 app=web 按节点均匀分布:
| |
几个字段的理解:
topologyKey:按哪个节点 label 划分拓扑域。按节点分散一般用kubernetes.io/hostname,按可用区分散一般用topology.kubernetes.io/zone。labelSelector:统计哪些 Pod。这里统计同一个应用app=web。maxSkew:允许的最大倾斜度。设为 1 时,调度器会尽量让不同拓扑域之间的匹配 Pod 数量差距不超过 1。whenUnsatisfiable:不能满足时怎么办。DoNotSchedule表示不调度,ScheduleAnyway表示仍然调度,但优先选择能降低倾斜的节点。
maxSkew 很容易被误解。它不是“每个节点最多几个 Pod”,而是不同拓扑域之间的数量差距。比如 3 个节点跑 7 个副本,maxSkew: 1 的理想结果大概是 3/2/2,不是每个节点只能跑 1 个。
如果更关心可用区,而不是节点,可以这样写:
这要求节点必须有 topology.kubernetes.io/zone 这个 label。排查时可以直接看:
| |
怎么选
如果诉求是“同一个节点绝对不能放两个副本”,用 hard podAntiAffinity。
如果诉求是“尽量均匀,不要太偏”,优先用 topologySpreadConstraints。
如果集群节点数不固定,服务还会扩缩容,topologySpreadConstraints 通常更好维护。因为它表达的是分布倾斜度,而不是简单地排斥某个拓扑域。
一个容易踩的坑是 labelSelector 没匹配到自己。topologySpreadConstraints 的 labelSelector 要能匹配 Pod template 里的 labels。否则调度器统计的就不是你以为的那组 Pod。
还有一个排查点是节点缺 topology label。比如你按 topology.kubernetes.io/zone 分散,但某些节点没有这个 label,结果就会很奇怪。生产里最好把节点标签一致性当成集群初始化的一部分,而不是等 Pending 以后再补。
改完后我一般这样看:
如果还是 Pending,直接看 Events。对于拓扑分布来说,Pending 不一定是资源不够,也可能是 maxSkew 太小、DoNotSchedule 太硬、节点缺 topology label,或者多个约束叠加后没有节点能满足。
我现在的默认倾向是:关键服务节点级别可以用 DoNotSchedule,可用区级别先用 ScheduleAnyway。除非业务真的不能接受某个 zone 副本偏多,否则不要让一个分布策略直接阻断发布。