------ TopologyKey 分类、节点打标、亲和性/反亲和性配置与空域调度深度解析
在 Kubernetes 中,topologyKey 是控制 Pod 分布的核心杠杆。它决定了调度器如何划分"故障域",以及如何在这些域之间实现高可用(分散) 、高性能(集中/亲和)或均匀分布。
本教程将系统性地讲解:
- TopologyKey 的分类与选择逻辑。
- 不同场景下的节点打标最佳实践及配置方法。
- 核心应用 :**Pod 亲和性(Affinity)与 反亲和性(Anti-Affinity)**如何配合
topologyKey使用。 - 核心难点:当节点缺少标签(即处于"空"拓扑域)时,Pod 能否被调度?
- 完整配置模板与避坑指南。
第一部分:TopologyKey 的分类与选择
topologyKey 本质上引用的是节点上的一个 Label Key。Kubernetes 调度器根据这个 Key 的值将节点分组。
1. 标准内置拓扑键(最常用)
云厂商和 K8s 默认注入的标签,适用于 90% 的场景。
| Topology Key | 含义 | 粒度 | 适用场景 |
|---|---|---|---|
kubernetes.io/hostname |
主机名 | 节点级 | 防单点故障。确保同一服务的副本不跑在同一台物理机/VM 上。这是最基础的分散策略。 |
topology.kubernetes.io/zone |
可用区 (AZ) | 机房级 | 跨可用区容灾。确保副本分散在不同的可用区。即使整个机房断电,服务仍可用。 |
topology.kubernetes.io/region |
地域 (Region) | 地域级 | 跨地域容灾。极少用于普通应用,因为跨地域延迟高。 |
2. 自定义拓扑键(高级玩法)
你可以给节点打上任何自定义标签,并将其作为 topologyKey。
| 自定义 Key | 示例值 | 适用场景 |
|---|---|---|
rack |
rack-01, rack-02 |
机架感知。防止单个机架断电导致服务全挂。常用于自建 IDC 集群。 |
disk-type |
ssd, hdd |
硬件隔离。确保 IO 密集型应用只调度到 SSD 节点。 |
gpu-model |
a100, v100 |
算力隔离。确保 AI 训练任务分散在不同型号的 GPU 节点上。 |
原则 :选择
topologyKey时,问自己一个问题:"如果这个'域'挂了,我的服务会受影响吗?"
第二部分:节点打标策略与使用方法
为了让 topologyKey 生效,节点必须拥有对应的标签。
场景 1:公有云集群
操作 :通常无需手动打标,CCM 会自动注入 topology.kubernetes.io/zone。
检查:
kubectl get nodes -L topology.kubernetes.io/zone
场景 2:自建物理集群(必须手动打标)
假设你有 2 个机架,需实现机架级感知。
1. 执行打标
# 为 Rack A 的节点打标
kubectl label node node-a1 rack=rack-a
kubectl label node node-a2 rack=rack-a
# 为 Rack B 的节点打标
kubectl label node node-b1 rack=rack-b
kubectl label node node-b2 rack=rack-b
2. 验证
kubectl get nodes -L rack
第三部分:Pod 亲和性与反亲和性如何使用 TopologyKey
这是本教程的核心补充部分。topologyKey 本身不产生动作,它必须配合 Affinity(亲和性) 或 Anti-Affinity(反亲和性) 才能发挥作用。
1. Pod 亲和性 (Pod Affinity) ------ "我想靠近"
目的 :让新 Pod 尽量调度到与特定目标 Pod 相同的拓扑域中。
典型场景:
- 缓存加速:Web 应用 Pod 尽量靠近 Redis Cache Pod,减少网络延迟。
- 数据本地性:计算任务尽量靠近存储数据所在的节点/机架。
配置示例:Web 应用靠近 Redis
假设 Redis Pod 运行在 zone=az-1 的节点上,我们希望 Web Pod 也尽量去 az-1。
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
affinity:
podAffinity:
# 软策略:尽量靠近,如果不行也没关系
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: redis-cache # 目标 Pod 的标签
topologyKey: "topology.kubernetes.io/zone" # 关键:在"可用区"级别靠近
containers:
- name: web
image: nginx
调度逻辑:
- 调度器找到所有运行着
app=redis-cache的节点。 - 获取这些节点的
topology.kubernetes.io/zone值(例如az-1)。 - 优先将
web-app调度到同样拥有zone=az-1标签的其他节点上。 - 如果
az-1资源不足,由于是preferred,它会调度到其他 Zone,但得分较低。
2. Pod 反亲和性 (Pod Anti-Affinity) ------ "我想远离"
目的 :让新 Pod 尽量避免 与特定目标 Pod 调度到同一个拓扑域中。
典型场景:
- 高可用:同一服务的多个副本分散在不同节点或可用区,防止单点故障。
- 资源隔离:CPU 密集型任务不要和内存密集型任务挤在同一台机器。
配置示例:Prometheus 实例分散部署
我们希望 prometheus-manager 的每个副本都跑在不同的节点上。
apiVersion: apps/v1
kind: Deployment
metadata:
name: prometheus-manager
spec:
replicas: 3
template:
spec:
affinity:
podAntiAffinity:
# 硬策略:严禁两个副本在同一节点
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: prometheus-manager # 目标 Pod 就是我自己
topologyKey: "kubernetes.io/hostname" # 关键:在"主机"级别远离
containers:
- name: prometheus
image: prom/prometheus
调度逻辑:
- 调度器检查集群中所有已存在的
app=prometheus-managerPod。 - 获取它们所在节点的
kubernetes.io/hostname。 - 禁止将新 Pod 调度到这些 hostname 对应的节点上。
- 如果只有 2 个节点,而你要部署 3 个副本,第 3 个 Pod 将永远 Pending (因为是
required硬限制)。
第四部分:核心难点------"空"拓扑域的调度行为
什么是"空"拓扑域?
指节点上没有配置 该 topologyKey 对应的标签。
关键问题:Pod 能调度到这种"无标签"节点上吗?
1. Pod 亲和性 (Affinity) 下的空域行为
| 策略类型 | 目标 Pod 位置 | 新节点状态 (空域) | 能否调度? | 原因解析 |
|---|---|---|---|---|
| Required (硬) | Node A (zone=az-1) |
Node C (无标签) | ❌ 否 | 硬匹配要求值相等。null != az-1。无法进入目标域。 |
| Preferred (软) | Node A (zone=az-1) |
Node C (无标签) | ✅ 是 | 软偏好不满足仅降低得分,不阻塞调度。 |
结论 :如果你想让 Pod 靠近 某些服务,且使用了硬亲和性,那么无标签节点是不可用的,因为它们无法匹配目标域。
2. Pod 反亲和性 (Anti-Affinity) 下的空域行为
| 策略类型 | 目标 Pod 位置 | 新节点状态 (空域) | 能否调度? | 原因解析 |
|---|---|---|---|---|
| Required (硬) | Node A (zone=az-1) |
Node C (无标签) | ✅ 是 | null != az-1。不在同一个域,所以不违反"远离"规则。 |
| Required (硬) | Node A (无标签) | Node B (无标签) | ❌ 否 | 陷阱! 所有无标签节点被视为同一个"空域"。如果 A 占了空域,B 就不能再进。 |
| Preferred (软) | 任意 | 任意 | ✅ 是 | 软策略永不阻塞。 |
结论 :如果你想让 Pod 远离 某些服务:
- 如果目标在有标签节点,无标签节点是安全的(可以调度)。
- 如果目标也在无标签节点,无标签节点是危险的(互斥)。
3. 拓扑分布约束 (Topology Spread Constraints) 下的空域行为
| 策略类型 | 当前分布情况 | 新节点状态 (空域) | 能否调度? | 原因解析 |
|---|---|---|---|---|
| DoNotSchedule (硬) | Az-1: 2, Empty: 0 | Node C (无标签) | ✅ 是 | 调度到 Empty 有助于平衡(2->1),若 maxSkew>=1 则允许。 |
| ScheduleAnyway (软) | 任意 | 任意 | ✅ 是 | 总是允许。 |
第五部分:完整配置模板汇总
1. 场景:Web 应用靠近 Redis 缓存(亲和性)
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: redis
topologyKey: "topology.kubernetes.io/zone" # 在同一可用区内靠近
2. 场景:数据库主从严格分散(反亲和性)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: mysql
topologyKey: "kubernetes.io/hostname" # 严禁同一主机
3. 场景:通用微服务均匀分布(拓扑分布约束 - 推荐)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: ScheduleAnyway # 软限制,保证可用性
labelSelector:
matchLabels:
app: my-service
第六部分:调试与避坑指南
🕳️ 坑 1:亲和性导致 Pod 无法调度
- 现象 :使用
required亲和性,目标 Pod 在zone-1,但zone-1资源耗尽。 - 结果:新 Pod Pending,即使其他 Zone 有空闲资源也不会用。
- 解决 :改为
preferred,或者确保目标 Zone 有足够资源。
🕳️ 坑 2:混合标签导致的"隐形"互斥
- 现象:使用反亲和性,部分节点有标签,部分没有。
- 结果:无标签节点之间互相排斥,导致调度成功率低于预期。
- 解决 :统一打标。给所有节点打上默认的拓扑标签。
🛠️ 调试命令
# 1. 查看节点标签
kubectl get nodes -L <your-topology-key>
# 2. 查看 Pod 事件
kubectl describe pod <pod-name>
# 3. 查看调度器日志(高级)
kubectl logs -n kube-system <scheduler-pod-name>
总结
- TopologyKey 是基础:它定义了"域"。
- Affinity/Anti-Affinity 是动作 :
- Affinity + TopologyKey = 聚拢(靠近目标域)。
- Anti-Affinity + TopologyKey = 分散(远离目标域)。
- 空域行为需谨慎 :
- 亲和性 :硬模式下,空域不可用。
- 反亲和性 :硬模式下,空域可用(除非目标也在空域)。
- 最佳实践 :
- 优先使用
TopologySpreadConstraints进行均匀分布。 - 使用
preferred/ScheduleAnyway保证服务可用性。 - 确保所有节点都有统一的拓扑标签,避免空域带来的不确定性。
- 优先使用