4. PodAntiAffinity 与故障域:硬性反亲和与软性反亲和的区别、在 Values 中配置 topologyKey 实现跨节点/跨可用区调度

在 Kubernetes 集群中,Pod 的调度策略直接影响应用的可用性与容灾能力。PodAntiAffinity(Pod 反亲和性)是控制同一工作负载的多个副本“不要运行在一起”的核心机制,它与故障域(Failure Domain)结合,可以实现跨节点、跨可用区甚至跨区域的精细化调度隔离。

4.1 硬性反亲和(requiredDuringScheduling)与软性反亲和(preferredDuringScheduling)

PodAntiAffinity 有两种调度模式,它们的约束强度和执行时机不同,适用于不同的故障域隔离场景。

特性 硬性反亲和(required) 软性反亲和(preferred)
调度约束 必须满足,否则 Pod 无法调度 尽量满足,不满足时仍可调度
适用场景 关键业务,必须跨故障域隔离 资源优先,尽量分散但允许集中
集群资源压力 高,可能因资源不足导致 Pod 挂起 低,调度器会尝试但不会阻塞
故障域粒度 严格保证每个副本在不同域 倾向于分散,不保证隔离
权重支持 不支持权重 支持 weight(1-100)
实践建议:对于有状态服务(如数据库、消息队列)的跨可用区部署,优先使用硬性反亲和;对于无状态应用(如 Web Server),在集群资源紧张时可以使用软性反亲和配合高权重,兼顾分散与调度成功率。

4.2 topologyKey:定义故障域的边界

topologyKey 是 PodAntiAffinity 的核心参数,它告诉调度器“以什么维度来判断 Pod 是否在同一个故障域”。Kubernetes 内置了标准的 topologyKey,也可以使用自定义标签。

  • kubernetes.io/hostname:以节点为故障域,实现跨节点调度。
  • topology.kubernetes.io/zone:以可用区为故障域,实现跨可用区调度。
  • topology.kubernetes.io/region:以区域为故障域,实现跨区域调度(通常用于多集群联邦)。
  • 自定义标签:如 rack、datacenter,用于物理机架或数据中心级别的隔离。
注意:topologyKey 必须与节点上的标签匹配。如果节点缺少对应的标签,Pod 将无法正确识别故障域,可能导致调度失败或隔离失效。建议在集群初始化时统一打标。

4.3 在 Helm Values 中配置 PodAntiAffinity

在 Helm Chart 中,通常将 PodAntiAffinity 的配置暴露在 values.yaml 中,让用户根据环境灵活调整。以下是一个标准的设计模式:

# values.yaml
podAntiAffinity:
  # 可选值: required, preferred, disabled
  type: preferred
  # 软性反亲和的权重,仅 type=preferred 时生效
  weight: 80
  # 故障域标签键
  topologyKey: "topology.kubernetes.io/zone"
  # 标签选择器,默认匹配 app.kubernetes.io/name
  labelSelector:
    matchLabels:
      app.kubernetes.io/name: "{{ include \"myapp.name\" . }}"

在 deployment.yaml 模板中,通过条件判断生成对应的 affinity 配置:

{{- if ne .Values.podAntiAffinity.type "disabled" }}
affinity:
  podAntiAffinity:
    {{- if eq .Values.podAntiAffinity.type "required" }}
    requiredDuringSchedulingIgnoredDuringExecution:
      - topologyKey: {{ .Values.podAntiAffinity.topologyKey }}
        labelSelector:
          {{- toYaml .Values.podAntiAffinity.labelSelector | nindent 10 }}
    {{- else if eq .Values.podAntiAffinity.type "preferred" }}
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: {{ .Values.podAntiAffinity.weight }}
        podAffinityTerm:
          topologyKey: {{ .Values.podAntiAffinity.topologyKey }}
          labelSelector:
            {{- toYaml .Values.podAntiAffinity.labelSelector | nindent 12 }}
    {{- end }}
{{- end }}

4.4 跨节点与跨可用区调度的 SVG 对比

下图展示了硬性反亲和与软性反亲和分别在跨节点(hostname)和跨可用区(zone)场景下的调度效果:

PodAntiAffinity 调度效果对比:跨节点 vs 跨可用区 跨节点调度(topologyKey: hostname) 节点 A Pod-1 节点 B Pod-2 硬性:必须跨节点 跨可用区调度(topologyKey: zone) 可用区 A Pod-1 可用区 B Pod-2 软性:尽量跨可用区 调度行为对比 • 硬性反亲和(required):调度器强制要求每个 Pod 必须位于不同的故障域。 如果集群只有 2 个可用区,副本数超过 2 时,多余的 Pod 将处于 Pending 状态。 • 软性反亲和(preferred):调度器优先将 Pod 分散到不同故障域, 但如果资源不足,允许将多个 Pod 调度到同一个故障域内。 • 权重(weight)控制软性反亲和的优先级,值越高,调度器越努力分散。

4.5 实战配置示例:跨可用区硬性反亲和

以下是一个完整的 Helm Values 配置,实现 3 副本的跨可用区硬性反亲和:

# values.yaml
replicaCount: 3

podAntiAffinity:
  type: required
  topologyKey: "topology.kubernetes.io/zone"
  labelSelector:
    matchLabels:
      app.kubernetes.io/name: "myapp"
      app.kubernetes.io/instance: "myapp-prod"

对应的调度结果:

  • Pod-1 调度到 可用区 A
  • Pod-2 调度到 可用区 B
  • Pod-3 调度到 可用区 C
  • 如果集群只有 2 个可用区,Pod-3 将无法调度(Pending)
关键点:硬性反亲和 + topologyKey=zone 是实现“三副本跨可用区”的标准模式,适用于生产环境的关键服务。如果集群可用区数量不足,建议结合 PodDisruptionBudget 和 HPA 进行容量规划。

4.6 软性反亲和 + 权重调优

当集群资源有限时,可以使用软性反亲和配合权重,在“尽量分散”和“调度成功”之间取得平衡:

# values.yaml
podAntiAffinity:
  type: preferred
  weight: 90
  topologyKey: "topology.kubernetes.io/zone"
  labelSelector:
    matchLabels:
      app.kubernetes.io/name: "myapp"

调度器会优先将 Pod 分散到不同可用区,但如果某个可用区资源不足,允许将 Pod 调度到已有副本的可用区。权重 90 表示这是一个非常强的倾向,但不会阻塞调度。

场景 硬性反亲和 软性反亲和(weight=90)
3 副本,3 可用区 每个可用区 1 个 Pod 每个可用区 1 个 Pod
3 副本,2 可用区 1 个 Pod Pending 2 个 Pod 在 A,1 个在 B
5 副本,3 可用区 2 个 Pod Pending 尽量均匀分布,可能 2-2-1

4.7 多 topologyKey 组合策略

在大型集群中,可能需要同时考虑节点和可用区两个故障域。Kubernetes 支持在同一个 affinity 中配置多个 topologyKey,但需要注意:

  • 硬性反亲和:每个 topologyKey 独立生效,Pod 必须同时满足所有约束。
  • 软性反亲和:每个 topologyKey 独立计算权重,调度器综合评分。

示例:先跨可用区,再跨节点

affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - topologyKey: "topology.kubernetes.io/zone"
        labelSelector:
          matchLabels:
            app: myapp
      - topologyKey: "kubernetes.io/hostname"
        labelSelector:
          matchLabels:
            app: myapp

这种配置确保每个副本不仅在不同可用区,而且在同一可用区内也分布在不同的节点上,实现双重故障域隔离。

性能提示:多 topologyKey 的硬性反亲和会显著增加调度器的计算压力,建议只在关键有状态服务上使用。对于普通无状态服务,使用单层 topologyKey 配合软性反亲和即可满足大部分需求。