4. 故障域隔离策略设计:PodAntiAffinity、PodTopologySpreadConstraints、NodeSelector 与 Toleration
在 Kubernetes 集群中,故障域(Failure Domain)通常指物理机架、可用区(Availability Zone)或区域(Region)。当 Helm 应用进行故障域隔离演练时,必须通过调度策略确保工作负载副本分散到不同故障域,避免单点故障导致整个服务不可用。本章详细阐述四种核心调度策略的设计原则、配置方法与基准测试要点。
4.1 策略对比与适用场景
| 策略 | 控制粒度 | 强制程度 | 典型场景 | 对容量规划的影响 |
|---|---|---|---|---|
| PodAntiAffinity | Pod 级别 | required / preferred | 确保同一应用副本不调度到同一节点或同一可用区 | required 模式可能因节点不足导致 Pod 无法调度 |
| PodTopologySpreadConstraints | 拓扑域级别 | 软约束(maxSkew) | 均匀分布副本到多个可用区或节点组 | 需预留 buffer 节点以容忍 maxSkew 偏差 |
| NodeSelector | 节点标签 | 硬约束 | 将 Pod 固定到特定硬件类型或网络区域 | 严格限制可用节点池,需精确计算标签覆盖 |
| Toleration + Taint | 节点污点 | 容忍则允许,无容忍则排斥 | 隔离特殊节点(GPU、高内存、专用故障域) | 需确保每个故障域有足够容忍节点承载副本 |
4.2 PodAntiAffinity:硬性反亲和与软性反亲和
PodAntiAffinity 通过 topologyKey 定义故障域范围。常见拓扑键包括 kubernetes.io/hostname(节点级)、topology.kubernetes.io/zone(可用区级)。
4.2.1 requiredDuringSchedulingIgnoredDuringExecution(硬约束)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: my-helm-app
topologyKey: topology.kubernetes.io/zone
此配置强制每个可用区最多只能调度一个副本。若副本数超过可用区数,超出副本将处于 Pending 状态。在容量规划时,必须确保 副本数 ≤ 可用区数 × 每区最大副本数。
4.2.2 preferredDuringSchedulingIgnoredDuringExecution(软约束)
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: my-helm-app
topologyKey: kubernetes.io/hostname
软约束优先将副本分散到不同节点,但允许在节点不足时调度到同一节点。适用于演练场景中需要容忍部分故障域重叠的情况。
.Values.global.replicaCount 与 .Values.global.failureDomainCount 做动态校验,确保副本数不超过故障域总数。
4.3 PodTopologySpreadConstraints:均匀分布与 maxSkew 控制
PodTopologySpreadConstraints 提供更灵活的分布控制,通过 maxSkew 参数允许一定程度的分布不均衡。在故障域隔离演练中,通常将 maxSkew 设为 1 以实现最严格均匀分布。
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: my-helm-app
当 whenUnsatisfiable: DoNotSchedule 时,若无法满足 maxSkew=1,Pod 将不调度。容量规划时需保证每个可用区至少有 ceil(副本数 / 可用区数) 的节点容量。
4.3.1 与 PodAntiAffinity 的协同策略
| 组合方式 | 效果 | 容量规划建议 |
|---|---|---|
| PodAntiAffinity 硬约束 + TopologySpread 硬约束 | 最严格隔离,副本完全均匀分布 | 副本数 ≤ 可用区数 × 每区最小节点数 |
| PodAntiAffinity 软约束 + TopologySpread 硬约束 | 优先均匀,允许少量偏差 | 副本数 ≤ 可用区数 × 每区节点数 + 1~2 个 buffer |
| PodAntiAffinity 硬约束 + TopologySpread 软约束 | 强制反亲和,分布尽量均匀 | 副本数 ≤ 可用区数 × 每区节点数,但允许 maxSkew 放宽 |
4.4 NodeSelector:固定故障域边界
NodeSelector 用于将 Pod 限定到特定标签的节点组。在故障域隔离演练中,常配合自定义标签如 failure-domain=zone-a 使用。
nodeSelector:
topology.kubernetes.io/zone: us-east-1a
此配置将 Pod 强制调度到 us-east-1a 可用区。容量规划时需确保该可用区节点资源总和 ≥ 副本资源需求。若多个 Helm release 使用相同 NodeSelector,需计算资源叠加。
4.5 Toleration 与 Taint:隔离特殊故障域
通过为特定故障域节点添加 Taint(如 dedicated=disaster-recovery:NoSchedule),只有带有对应 Toleration 的 Pod 才能调度到这些节点。这在演练中用于保留“逃生舱”节点。
tolerations:
- key: "dedicated"
operator: "Equal"
value: "disaster-recovery"
effect: "NoSchedule"
容量规划时,需统计所有带有该 Toleration 的 Pod 总资源请求,确保不超过对应 Taint 节点的总容量。同时,应预留至少 20% 的节点资源用于演练期间的突发调度。
4.6 故障域隔离策略组合架构
下图展示了一个典型的四层故障域隔离策略组合,从集群级到 Pod 级逐层约束。
4.7 容量规划基准测试清单
在进行故障域隔离演练前,需完成以下容量规划基准测试项:
- 故障域计数验证:确认集群中可用区数、节点组数、每区节点数,与 Helm values 中配置的拓扑键匹配。
- 副本分布模拟:使用
kubectl get pods -o wide或调度模拟工具,验证当前副本分布是否符合预期 skew。 - 资源预留计算:每个故障域至少预留
ceil(副本数 / 故障域数) × 单副本资源请求的节点容量。 - Taint 节点容量审计:统计所有 Toleration 匹配的 Pod 总资源请求,确保不超过 Taint 节点总容量。
- 调度失败测试:故意减少一个故障域节点,观察 Pod 是否按预期进入 Pending 状态,验证告警与自动扩缩容机制。
• 调度成功率:≥ 99.5%(排除预期 Pending 场景)
• 副本分布标准差:≤ 0.5(跨故障域副本数偏差)
• 故障域切换时间:从故障发生到 Pod 重新调度 ≤ 120 秒
4.8 Helm Chart 中的策略模板化
将上述策略抽象为 Helm 模板,通过 values.yaml 动态控制,便于不同环境复用:
{{- if .Values.global.failureDomain.enabled }}
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: {{ .Values.global.appName }}
topologyKey: {{ .Values.global.failureDomain.topologyKey }}
topologySpreadConstraints:
- maxSkew: {{ .Values.global.failureDomain.maxSkew }}
topologyKey: {{ .Values.global.failureDomain.topologyKey }}
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: {{ .Values.global.appName }}
{{- end }}
nodeSelector:
{{ .Values.global.failureDomain.nodeSelectorKey }}: {{ .Values.global.failureDomain.nodeSelectorValue }}
tolerations:
- key: {{ .Values.global.failureDomain.taintKey }}
operator: Equal
value: {{ .Values.global.failureDomain.taintValue }}
effect: NoSchedule
{{- end }}
通过 values.yaml 配置不同环境的故障域参数:
global:
failureDomain:
enabled: true
topologyKey: "topology.kubernetes.io/zone"
maxSkew: 1
nodeSelectorKey: "topology.kubernetes.io/zone"
nodeSelectorValue: "us-east-1a"
taintKey: "dedicated"
taintValue: "disaster-recovery"