Kubernetes Pod 反亲和:不要把同一模型的副本都放在一个节点 — 方向2 Kubernetes Pod 反亲和不要把同一模型的副本都放在一个节点 — 方向2一、三个副本都在同一台 GPU 节点上的概率有多大在一个有 12 个 GPU 节点的集群中部署 3 个推理副本。Kubernetes 默认调度器的最优解是什么在节点资源充裕的情况下它可能会把 3 个副本全部调度到同一台 GPU 节点上——因为那台节点的资源评分最高、网络跳数最少。然后这台节点因为 GPU 驱动故障重启了。3 个副本全部不可用服务中断。这就是默认调度策略与高可用需求之间的矛盾。Kubernetes 的默认调度目标是最优资源匹配而非分散风险。在一个没有亲和性约束的部署中同一个模型的多个副本被调度到同一个节点上的概率取决于节点数量与副本数量不是可以忽略的小概率事件。生产环境的对比数据印证了这一点。在一个 8 节点、每节点 4 副本的典型推理部署中如果不加反亲和约束约 30% 的节点故障会导致超过 50% 的副本同时不可用。这意味着表面上做了多副本但故障域高度重叠可用性增益远低于预期。二、反亲和策略的层次与调度决策Pod 反亲和通过podAntiAffinity来控制调度器避免将特定标签的 Pod 放在同一拓扑域中。但不同的topologyKey决定了反亲和的粒度节点级反亲和kubernetes.io/hostname是最实用的选择。它确保同一模型的副本分布在不同的物理节点上。当 GPU 节点因为驱动故障、内核崩溃、显存硬件错误而宕机时只影响一个副本。但有一个陷阱如果节点数量少于副本数量requiredDuringScheduling级别的反亲和会导致部分副本无法调度。此时需要使用preferredDuringScheduling让调度器在无法满足反亲和时仍然调度 Pod并配合告警提示运维扩容节点。三、生产级反亲和配置与批量调度的冲突处理以下是针对推理服务的高可用反亲和配置apiVersion: apps/v1 kind: Deployment metadata: name: llama-inference spec: replicas: 3 selector: matchLabels: app: llama-inference model: llama-70b template: metadata: labels: app: llama-inference model: llama-70b spec: affinity: podAntiAffinity: # 硬性约束必须分散在不同节点 requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: model operator: In values: - llama-70b topologyKey: kubernetes.io/hostname # 软性约束优先分散在不同防火墙域如果有机架感知需求 preferredDuringSchedulingIgnoredDuringExecution: - weight: 50 podAffinityTerm: labelSelector: matchExpressions: - key: model operator: In values: - llama-70b topologyKey: topology.kubernetes.io/zone # 配合拓扑分布约束适用于更大规模部署 topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: llama-inference containers: - name: inference image: llama-inference:v2.1 resources: limits: nvidia.com/gpu: 1配置中同时使用了podAntiAffinity和topologySpreadConstraints。两者的区别是反亲和关注不要把相同标签的 Pod 放一起而拓扑分布约束关注各域的 Pod 数量要均衡。在大多数场景下两者可以协同工作。但在 GPU 资源紧张的集群中如果两者都设为硬约束可能导致调度卡死。当多个推理模型的部署同时进行滚动更新时调度器会面临一个复杂问题每个模型的副本都需要分散在不同节点但节点上的 GPU 插槽是有限的。这可能导致调度僵局——所有部署都在等待对方腾出节点。// ScheduleGuard 批量调度时的僵局检测与缓解 type ScheduleGuard struct { client kubernetes.Interface } // DetectStalemate 检测调度僵局 func (g *ScheduleGuard) DetectStalemate(namespace string) ([]string, bool) { // 查找所有因反亲和约束而 Pending 超过 5 分钟的 Pod pods, err : g.client.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{ FieldSelector: status.phasePending, }) if err ! nil { return nil, false } var stalePods []string for _, pod : range pods.Items { if time.Since(pod.CreationTimestamp.Time) 5*time.Minute { continue } // 检查是否因反亲和而 Pending for _, condition : range pod.Status.Conditions { if condition.Type corev1.PodScheduled condition.Reason corev1.PodReasonUnschedulable strings.Contains(condition.Message, anti-affinity) { stalePods append(stalePods, pod.Name) } } } return stalePods, len(stalePods) 2 // 超过2个僵死Pod视为僵局 }僵局检测的目的不是自动化解决——调度僵局通常意味着物理资源不足唯一的出路是扩容节点或降低约束级别。四、反亲和的代价与场景选择反亲和约束并非无成本。调度灵活性降低硬性反亲和约束减少了调度器的选择空间。在资源紧张的情况下这可能导致 Pod 长时间 Pending而一个有反亲和约束的 Pending Pod 比一个调度到同节点的 Pod 更糟糕。故障域与资源利用率的博弈如果只有 2 个 GPU 节点却要求 3 个副本硬约束反亲和至少 1 个副本永远无法调度。这是配置错误但很容易被忽视。GPU 型号差异的复杂性如果集群混部了 A100、A800 和 T4反亲和配置可能导致推理副本被调度到性能差异很大的 GPU 上。这时候需要配合nodeSelector或节点亲和来约束 GPU 型号范围。适用边界适合GPU 节点数 ≥ 副本数 × 2 的集群有足够的冗余空间。适合在线推理服务对单点故障零容忍。不适合GPU 节点数与副本数接近的场景硬性反亲和会导致 Pod Pending。不适合批处理推理任务任务可重试对故障不敏感。五、总结Pod 反亲和的本质是将故障域分散到不同的物理节点。对于推理服务节点级反亲和应用kubernetes.io/hostname是最核心的配置。落地方案第一步使用requiredDuringScheduling强制同模型副本分布在不同节点。第二步叠加topologySpreadConstraints保持整体分布均衡。第三步当节点数不足时降级为preferredDuringScheduling并配套资源不足的告警。基础设施不需要漂亮话需要的是在一台 GPU 节点宕机后其他副本还能接住所有推理请求。