4. 节点亲和性(Node Affinity):requiredDuringSchedulingIgnoredDuringExecution 与 preferredDuringSchedulingIgnoredDuringExecution 规则详解

节点亲和性(Node Affinity)是 Kubernetes 调度器将 Pod 绑定到特定节点集合的核心策略。与简单的 nodeSelector 相比,Node Affinity 提供了更丰富的匹配表达式(In、NotIn、Exists、DoesNotExist、Gt、Lt)以及两种截然不同的执行语义:硬性要求(required)与软性偏好(preferred)。

4.1 两种执行语义:硬约束与软偏好

Kubernetes 通过 requiredDuringSchedulingIgnoredDuringExecution 和 preferredDuringSchedulingIgnoredDuringExecution 两个字段区分调度阶段的强制性与倾向性规则。它们的核心差异如下表所示:

特性 requiredDuringSchedulingIgnoredDuringExecution preferredDuringSchedulingIgnoredDuringExecution
调度阶段行为 Pod 必须满足所有匹配规则,否则无法调度 调度器尽力满足规则,但不保证;不满足时仍可调度到其他节点
执行阶段行为 节点标签变更后,已运行的 Pod 不受影响(IgnoredDuringExecution) 节点标签变更后,已运行的 Pod 不受影响(IgnoredDuringExecution)
权重支持 不支持权重,所有规则必须同时满足 支持权重(weight,1–100),调度器按加权评分选择最优节点
典型场景 GPU 节点、合规区域、专用硬件 性能优先、成本优化、可用区均衡
失败后果 Pod 处于 Pending 状态,无法调度 Pod 仍可调度到不匹配的节点,评分较低
关键理解: 两种规则名称中的 "IgnoredDuringExecution" 意味着——一旦 Pod 成功运行在某个节点上,即使该节点的标签后续发生变化,已经运行的 Pod 也不会被驱逐或重新调度。这是 Kubernetes 调度设计中的"一次绑定,终身有效"原则。

4.2 requiredDuringSchedulingIgnoredDuringExecution:硬性亲和规则

该规则使用 nodeSelectorTerms 定义一组匹配条件。多个 nodeSelectorTerms 之间是 OR 关系(满足任一 Term 即可);每个 Term 内的多个 matchExpressions 之间是 AND 关系(必须全部满足)。

apiVersion: v1
kind: Pod
metadata:
  name: gpu-inference-pod
spec:
  containers:
  - name: inference
    image: nvidia/cuda:12.0-runtime
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: accelerator
            operator: In
            values:
            - nvidia-tesla-v100
            - nvidia-a100
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - us-east-1a

上述配置的含义:Pod 必须调度到同时满足 accelerator 为 V100 或 A100 且 可用区为 us-east-1a 的节点上。如果集群中没有这样的节点,Pod 将一直处于 Pending 状态。

4.2.1 操作符详解

操作符 行为 示例
In 标签值在给定的 values 列表中 key: gpu, operator: In, values: [true]
NotIn 标签值不在给定的 values 列表中 key: disktype, operator: NotIn, values: [hdd]
Exists 节点上存在该标签键(不关心值) key: nvidia.com/gpu, operator: Exists
DoesNotExist 节点上不存在该标签键 key: spot-instance, operator: DoesNotExist
Gt 标签值(数值字符串)大于给定值 key: memory-gb, operator: Gt, values: [128]
Lt 标签值(数值字符串)小于给定值 key: cpu-cores, operator: Lt, values: [8]
注意: Gt 和 Lt 操作符要求标签值必须是可以解析为数字的字符串。如果标签值是非数字字符串(如 large),调度器会忽略该表达式,可能导致意外的调度行为。

4.3 preferredDuringSchedulingIgnoredDuringExecution:软性偏好规则

软性规则通过 preferences 字段定义,每个偏好项包含一个 weight(1–100)和一组 matchExpressions。调度器会计算所有可调度节点的加权总分,选择得分最高的节点。

apiVersion: v1
kind: Pod
metadata:
  name: web-frontend
spec:
  containers:
  - name: nginx
    image: nginx:1.25
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - us-east-1a
      - weight: 20
        preference:
          matchExpressions:
          - key: node-type
            operator: In
            values:
            - ssd-optimized

在这个例子中,调度器优先选择 us-east-1a 可用区的节点(权重 80),如果同时满足 ssd-optimized 标签则额外加分(权重 20)。即使没有任何节点满足这些条件,Pod 仍然可以调度到集群中的任意节点。

4.3.1 评分机制

调度器对每个可调度节点执行以下评分流程:

  1. 遍历所有偏好项:对每个 preference,检查节点是否满足其 matchExpressions。
  2. 累加权重:如果节点满足该偏好,将对应的 weight 加入总分。
  3. 归一化:最终得分 = 总分 / 所有偏好项权重之和 × 100(百分比形式)。
  4. 选择最高分节点:得分最高的节点被选中;如果多个节点得分相同,调度器按其他策略(如 LeastRequestedPriority)进一步筛选。
最佳实践: 将关键业务 Pod 的软性偏好权重设置在 70–100 之间,非关键偏好设置在 10–30 之间。权重差距过小(如 50 和 55)会导致调度器难以区分优先级,失去偏好的意义。

4.4 节点亲和性调度流程

下图展示了调度器在处理 Node Affinity 时的完整决策路径:

节点亲和性调度决策流程 Pod 创建请求 required 规则? 是 检查所有节点标签 匹配节点? 否 Pod Pending 是 候选节点列表 preferred 规则? 否 随机选择节点 是 加权评分选择 Pod 绑定到目标节点

4.5 组合使用:硬约束 + 软偏好的典型模式

生产环境中,通常将 requiredDuringScheduling 和 preferredDuringScheduling 组合使用,形成"底线保障 + 优化选择"的调度策略:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ml-training
spec:
  replicas: 3
  selector:
    matchLabels:
      app: ml-training
  template:
    metadata:
      labels:
        app: ml-training
    spec:
      containers:
      - name: trainer
        image: pytorch/pytorch:2.0-cuda12
        resources:
          requests:
            nvidia.com/gpu: 1
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: nvidia.com/gpu.present
                operator: Exists
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 90
            preference:
              matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values:
                - us-east-1a
                - us-east-1b
          - weight: 10
            preference:
              matchExpressions:
              - key: node-type
                operator: NotIn
                values:
                - spot-instance
策略解读:
  • 硬约束:Pod 必须调度到有 GPU 的节点上(nvidia.com/gpu.present: Exists),这是运行训练任务的前提条件。
  • 软偏好:优先选择 us-east-1a 或 us-east-1b 可用区(权重 90),同时尽量避免 spot 实例(权重 10)。如果所有 GPU 节点都在 us-east-1c,Pod 仍然可以调度过去,只是评分较低。

4.6 常见陷阱与避坑指南

  • 标签不存在导致调度失败:使用 In 操作符时,如果节点上根本没有该标签键,表达式视为不匹配。建议先用 Exists 检查标签存在性,再用 In 筛选具体值。
  • 权重值超出范围:weight 必须在 1–100 之间。设置为 0 会被调度器忽略,设置为大于 100 会导致 Pod 创建失败。
  • 多个 Term 的 OR 语义误用:nodeSelectorTerms 列表中的每个 Term 是 OR 关系,容易误写成 AND。如果需要 AND 语义,将所有表达式放在同一个 Term 内。
  • 忽略执行阶段的影响:即使节点标签在 Pod 运行后发生变化,已运行的 Pod 也不会被重新调度。如果需要动态响应标签变化,需结合 Pod 拓扑分布约束或自定义控制器。
  • 软偏好权重过于平均:多个偏好项的权重差距过小(如 50、55、45),调度器难以形成有效优先级,建议至少保持 2 倍以上的权重差距。
调试技巧: 使用 kubectl describe pod <pod-name> 查看 Events 字段,可以获取调度器对 Node Affinity 规则的匹配结果。如果 Pod 处于 Pending 状态,Events 中会明确显示 "0/3 nodes are available: 1 node(s) didn't match node selector, 2 node(s) didn't match Pod's node affinity" 等详细信息。