Featured image of post Pod 为什么没有按预期分散?从 anti-affinity 到 topologySpreadConstraints

Pod 为什么没有按预期分散?从 anti-affinity 到 topologySpreadConstraints

记录 Kubernetes Pod 分散调度的两个常见方案:podAntiAffinity 和 topologySpreadConstraints,以及 maxSkew、DoNotSchedule、ScheduleAnyway 的取舍。

有次看一个服务的副本分布,Deployment 配了 6 个副本,预期是每个节点差不多分一点。但实际看下来,有两个节点各跑了 2 个,有的节点一个都没有。

第一反应是加 podAntiAffinity,但后来发现这个场景更适合 topologySpreadConstraints

这里的关键不是“能不能分散”,而是“分散到什么程度算可以接受”。anti-affinity 表达的是排斥关系,topology spread 表达的是倾斜度。前者更像硬规则,后者更像分布预算。

先确认实际分布

先不要改 YAML,先看当前 Pod 落点:

1
2
3
kubectl get pod -n default -l app=web -o wide

kubectl get nodes -L kubernetes.io/hostname,topology.kubernetes.io/zone

如果 Pod 已经 Pending,再看调度事件:

1
kubectl describe pod -n default web-xxx

anti-affinity 能解决什么

最直接的写法是用 podAntiAffinity,让同一个应用的 Pod 不要放在同一个节点:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            - labelSelector:
                matchLabels:
                  app: web
              topologyKey: kubernetes.io/hostname
      containers:
        - name: web
          image: nginx:1.27

这个配置比较硬:同一个节点上不能有两个 app=web 的 Pod。

它适合副本数小于等于可用节点数的场景。比如 3 个节点跑 3 个副本,很干净。

但如果 replicas 是 6,集群只有 3 个节点,后面 3 个 Pod 就会 Pending。因为 hard anti-affinity 已经把节点都排除掉了。

所以很多时候我不会直接上 required,而是先用 preferred:

1
2
3
4
5
6
7
8
9
affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchLabels:
              app: web
          topologyKey: kubernetes.io/hostname

这个配置会让调度器尽量分散,但它不是强保证。资源紧张的时候,还是可能放在一起。

topologySpreadConstraints 更像控制倾斜度

如果目标不是“绝对不能放一起”,而是“尽量均匀”,topologySpreadConstraints 更合适。

比如希望 app=web 按节点均匀分布:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 6
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: kubernetes.io/hostname
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: web
      containers:
        - name: web
          image: nginx:1.27

几个字段的理解:

  • 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 个。

如果更关心可用区,而不是节点,可以这样写:

1
2
3
4
5
6
7
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: web

这要求节点必须有 topology.kubernetes.io/zone 这个 label。排查时可以直接看:

1
kubectl get nodes -L topology.kubernetes.io/zone

怎么选

如果诉求是“同一个节点绝对不能放两个副本”,用 hard podAntiAffinity

如果诉求是“尽量均匀,不要太偏”,优先用 topologySpreadConstraints

如果集群节点数不固定,服务还会扩缩容,topologySpreadConstraints 通常更好维护。因为它表达的是分布倾斜度,而不是简单地排斥某个拓扑域。

一个容易踩的坑是 labelSelector 没匹配到自己。topologySpreadConstraintslabelSelector 要能匹配 Pod template 里的 labels。否则调度器统计的就不是你以为的那组 Pod。

还有一个排查点是节点缺 topology label。比如你按 topology.kubernetes.io/zone 分散,但某些节点没有这个 label,结果就会很奇怪。生产里最好把节点标签一致性当成集群初始化的一部分,而不是等 Pending 以后再补。

改完后我一般这样看:

1
2
3
4
5
kubectl rollout restart deployment/web -n default
kubectl rollout status deployment/web -n default

kubectl get pod -n default -l app=web -o wide
kubectl describe pod -n default -l app=web

如果还是 Pending,直接看 Events。对于拓扑分布来说,Pending 不一定是资源不够,也可能是 maxSkew 太小、DoNotSchedule 太硬、节点缺 topology label,或者多个约束叠加后没有节点能满足。

我现在的默认倾向是:关键服务节点级别可以用 DoNotSchedule,可用区级别先用 ScheduleAnyway。除非业务真的不能接受某个 zone 副本偏多,否则不要让一个分布策略直接阻断发布。

参考资料

记录工程实践和排障过程。
使用 Hugo 构建
主题 StackJimmy 设计