深色模式
节点/Pod 亲和性与反亲和性
摘要:本文面向生产 SRE / 平台工程师,区分
nodeSelector、nodeAffinity、podAffinity/podAntiAffinity的能力边界,解释requiredDuringSchedulingIgnoredDuringExecution与preferredDuringSchedulingIgnoredDuringExecution的差异、操作符语义、评分加权和大规模集群性能陷阱。适用 Kubernetes v1.28+。
适用版本与前提
- Kubernetes:v1.28+
- 工具:
kubectl、kubectl explain pod.spec.affinity - 前提:集群节点已打预期 label(如
topology.kubernetes.io/zone)
背景与问题
nodeSelector 只能表达"节点必须同时拥有这些 label"的硬约束,缺乏表达力与软约束能力。当我们需要:
- "优先"落在某类节点(不满足也能跑);
- "尽量和某服务靠近"以降低延迟;
- "避免和某服务同节点/同可用区"以提高可用性;
就需要亲和性(affinity)。亲和性用更丰富的 label selector 语言解决"吸引"问题;反亲和性解决"排斥"问题。
核心概念
亲和性分两大类,每类都有"硬"与"软"两种形态:
| 类型 | 字段 | 硬约束 | 软约束 |
|---|---|---|---|
| 节点亲和 | spec.affinity.nodeAffinity | requiredDuringSchedulingIgnoredDuringExecution | preferredDuringSchedulingIgnoredDuringExecution |
| Pod 间亲和 | spec.affinity.podAffinity | 同上 | 同上 |
| Pod 间反亲和 | spec.affinity.podAntiAffinity | 同上 | 同上 |
IgnoredDuringExecution 的含义:Pod 调度后若节点 label 变化,已运行的 Pod 不会被驱逐(兼容旧行为)。[版本相关] 社区曾讨论 RequiredDuringExecution 以在 label 变化后驱逐 Pod,但截至 v1.28+ 尚未 GA,请勿依赖。
架构与原理
节点亲和在 Filter 阶段由 NodeAffinity 插件处理硬约束,在 Score 阶段为 preferred 规则累加权重(每项 weight 1–100)。Pod 间亲和/反亲和由 InterPodAffinity 插件处理,开销显著更高(见"性能")。
节点亲和 nodeAffinity
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 50
preference:
matchExpressions:
- key: node-type
operator: In
values:
- high-memory
containers:
- name: web
image: nginx:1.271
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
要点:
- 多个
nodeSelectorTerms之间是 OR 关系;同一 term 内多个matchExpressions是 AND 关系。 - 若同时写
nodeSelector与nodeAffinity,两者都必须满足。 preferred的weight1–100,调度器把命中的权重累加进节点总分。
支持的操作符:In、NotIn、Exists、DoesNotExist、Gt、Lt。NotIn / DoesNotExist 可用于表达节点反亲和,但隔离场景更推荐用 taint(见 taint-toleration.md)。
Pod 间亲和与反亲和
yaml
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:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: topology.kubernetes.io/zone
containers:
- name: web
image: nginx:1.271
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
topologyKey定义"域":常用kubernetes.io/hostname(每节点)、topology.kubernetes.io/zone(每可用区)、topology.kubernetes.io/region。required反亲和确保同topologyKey域下不出现第二个匹配 Pod——常用于"每节点至多一个副本"。preferred亲和把 web 尽量靠近 cache 同可用区以降低延迟。
注意
Pod 间反亲和要求集群每个节点都必须有对应的 topologyKey label,否则可能出现未预期行为。若部分节点缺失该 label,调度结果会偏离预期。
生产危险
requiredDuringSchedulingIgnoredDuringExecution 的 Pod 反亲和在节点数少于副本数时会导致剩余副本永久 Pending。例如 3 副本 + topologyKey: kubernetes.io/hostname 反亲和,但集群仅 2 个节点——第 3 个副本永远无法调度。上线前务必核算 副本数 ≤ 域数量。
性能、容量与成本
InterPodAffinity是调度器中最昂贵的插件之一:它需要扫描目标域内的现有 Pod 来计算匹配。官方文档明确指出,不建议在超过数百节点的集群中使用 Pod 间亲和/反亲和,否则会显著拖慢调度吞吐。- 替代思路:跨可用区打散优先用
topologySpreadConstraints(见 topology-spread.md),它专精于"均匀分布",复杂度远低于反亲和,且支持ScheduleAnyway软约束避免 Pending。
生产实践
- 优先
topologySpreadConstraints做跨域打散,仅在需要"严格互斥同节点"时用podAntiAffinity.required。 - 软约束兜底:把反亲和设为
preferred,允许在资源紧张时仍可调度,避免雪崩式 Pending。 - 保证 label 一致性:用 DaemonSet / 启动脚本 / 云厂商 controller 确保所有节点都带
topologyKeylabel。 - GPU/特殊硬件:结合
ExtendedResourceTolerationadmission controller 自动加 toleration,比手写 affinity 更省心。
操作步骤
bash
# 校验字段含义
kubectl explain pod.spec.affinity.nodeAffinity
# 查看节点 topology label
kubectl get nodes -L topology.kubernetes.io/zone,kubernetes.io/hostname
# 应用并观察分布
kubectl apply -f web.yaml
kubectl get pods -o wide1
2
3
4
5
6
7
2
3
4
5
6
7
验证
bash
# 确认 Pod 落在预期 zone
kubectl get pods -o custom-columns=NAME:.metadata.name,NODE:.spec.nodeName,ZONE:.metadata.labels.'topology\.kubernetes\.io/zone'
# 检查亲和/反亲和是否生效(无 FailedScheduling)
kubectl describe pod <pod> | grep -A5 Events1
2
3
4
2
3
4
回滚与清理
bash
kubectl rollout undo deployment/web
kubectl delete -f web.yaml1
2
2
故障排查
| 现象 | 根因 | 处理 |
|---|---|---|
| 副本数 < 期望且 Pending | required 反亲和后域数量不足 | 放宽 preferred 或扩域 / 降副本 |
| 调度变慢 | 大量 Pod 间亲和 | 改用 topologySpread |
| 部分节点被忽略 | 缺失 topologyKey label | 补齐节点 label |
didn't match pod anti-affinity | 同域已存在匹配 Pod | 检查 topologyKey 与 labelSelector |
安全与合规
- 反亲和不阻止恶意同节点共置(攻击者若控制 labelSelector 仍可绕过)。硬隔离应使用
Namespace+PodSecurity+ 网络策略组合。 topologyKey当前不限制为已知 label,理论上可用任意节点 label;请仅使用受信任、稳定、全员一致的 label。
替代方案与权衡
| 需求 | 推荐 | 不推荐 |
|---|---|---|
| 跨可用区均匀 | topologySpreadConstraints | podAntiAffinity(贵) |
| 每节点至多一个 | podAntiAffinity.required | 手写脚本 |
| 专属硬件节点 | taint + toleration | nodeName |
FAQ
Q:preferred 一定会被满足吗? A:不会。它只影响评分权重,节点分数最高者胜出;若所有节点都不满足偏好,Pod 仍会在最不差节点上运行。
Q:Pod 反亲和的 topologyKey 能用自定义 label 吗? A:可以,但需确保所有节点都带该 label,否则行为未定义。
参考资料
- Kubernetes 官方文档 - Assigning Pods to Nodes(Affinity 章节),访问日期:2026-10-08。
- Kubernetes 官方文档 - Pod Topology Spread Constraints,访问日期:2026-10-08。
- Kubernetes 官方文档 - Scheduler Framework,访问日期:2026-10-08。