Kubernetes 调度器偏好设置 nodeAffinity 怎么实现 Pod 亲和性调度?

文章导读
看到「nodeAffinity 实现 Pod 亲和性调度」这个问题,我第一反应是先确认你说的是 nodeAffinity 还是 podAffinity。两者名字相近,但作用对象完全不同:nodeAffinity 约束的是 Pod 和节点的关系,podAffinity 才是让 Pod 主动靠近另一组 Pod。nodeAffinity 的典型场景,是把带有 GPU、SSD 或特定可用区标签的节点作为调
📋 目录
  1. 先判断:约束是不是基于节点自身属性
  2. 硬约束和软偏好的配置写法
  3. 检查调度结果的关键动作
  4. 调度完成后的风险边界
  5. 几个容易踩的配置细节
A A

看到「nodeAffinity 实现 Pod 亲和性调度」这个问题,我第一反应是先确认你说的是 nodeAffinity 还是 podAffinity。两者名字相近,但作用对象完全不同:nodeAffinity 约束的是 Pod 和节点的关系,podAffinity 才是让 Pod 主动靠近另一组 Pod。nodeAffinity 的典型场景,是把带有 GPU、SSD 或特定可用区标签的节点作为调度目标,让工作负载固定跑在这些节点上。

先判断:约束是不是基于节点自身属性

nodeAffinity 是 Kubernetes 调度器为 Pod 设置节点偏好的一种机制,它通过节点上的标签来约束 Pod 可以被调度到哪些节点。需要区分的是,节点亲和性解决的是 Pod 与节点的关系,而 Pod 亲和性解决的是 Pod 与 Pod 的关系。如果需求是让某些工作负载固定跑在具有特定硬件或所处可用区的节点上,nodeAffinity 就是直接手段。判断是否采用 nodeAffinity,先看约束是否基于节点自身的属性,比如 GPU 型号、磁盘类型、网络带宽,再看这些属性是否已用 label 标记在 node 对象上。若还没标记,需要先规划好标签体系,否则后续规则很难维护。

实际做的时候,我会先执行 kubectl get nodes --show-labels 查看现有标签,确认没有遗漏的前缀或拼写,再决定用硬约束还是软约束。免得配置写完了,发现规则匹配不到任何节点。

硬约束和软偏好的配置写法

配置写在 Pod 的 spec.affinity.nodeAffinity 里。requiredDuringSchedulingIgnoredDuringExecution 是硬性条件,节点不满足 Pod 就不会被调度;preferredDuringSchedulingIgnoredDuringExecution 是偏好条件,调度器会尽量满足,但不保证。下面是一个同时使用两者的例子:

apiVersion: v1
kind: Pod
metadata:
  name: example-pod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 60
        preference:
          matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - us-east-1

这个配置表示 Pod 必须落在 disktype=ssd 的节点上,同时尽量选择 zone=us-east-1 的节点。weight 范围是 1 到 100,权重越高,调度器在候选节点里排序时越优先。如果只有硬约束,可以把 preferred 整段去掉。

需要提醒的是,nodeSelector 和 nodeAffinity 可以同时写,但两者都会被满足,相当于多个 AND 条件。我的建议是,新配置直接改用 nodeAffinity,把旧的 nodeSelector 统一迁移过来,避免两套描述同时生效。

检查调度结果的关键动作

配置完成后,检查调度效果最直接的方法是运行 kubectl get pods -o wide 查看 Pod 被调度到哪个节点,然后对比该节点是否带有你预期的标签。若 Pod 处于 Pending 且很久未调度,可执行 kubectl describe pod 查看事件,其中会明确提示有节点不满足 nodeAffinity 规则。另一种验证方式是通过 kubectl get nodes --show-labels 确认标签键值是否和 matchExpressions 中的完全一致,包括大小写和前缀。若使用的是软亲和,调度后还需观察多个 Pod 是否均匀分布,避免因 weight 设置不合理导致集中堆积在个别节点。

Kubernetes 调度器偏好设置 nodeAffinity 怎么实现 Pod 亲和性调度?

如果发现标签键值完全一致但 Pod 仍然 Pending,可以再检查一下是否同时配置了多个 nodeSelectorTerms,多个 term 之间是 OR 关系,而一个 term 内的 matchExpressions 是 AND 关系,这个容易搞混。

调度完成后的风险边界

nodeAffinity 只在调度时生效,调度完成后如果节点标签被修改,已运行的 Pod 不会被重新调度或驱逐。因此不要依赖它实现运行时迁移。对于硬亲和的场景,如果节点临时不可用或标签被移除,Pod 会一直处于 Pending 状态,直到条件恢复或人工介入。另外,同时使用 nodeAffinity 和 nodeSelector 时,调度器会要求两者都满足,这会缩小可选节点集,增加调度失败的风险。建议将旧式的 nodeSelector 逐步迁移到 nodeAffinity 中,用统一的规则描述,避免两套配置互相干扰。

如果你的场景需要根据节点状态动态迁移,比如节点标签会经常调整,那么硬亲和会让 Pod 卡住,需要考虑用其它机制配合,比如节点反亲和或污点容忍。

几个容易踩的配置细节

我在配置中遇到过的坑主要有几类。第一,matchExpressions 里的多个表达式是 AND 关系,如果想让 Pod 匹配 ssd 或 gpu,需要写在 nodeSelectorTerms 里用多个 term 来表达,而不是放在一个 matchExpressions 里。第二,operator 为 Exists 和 DoesNotExist 时,values 字段必须省略,写了会报校验错误。第三,标签 key 完全匹配包括前缀,带不带域名前缀是不同标签,比如 node-role.kubernetes.io/control-plane 和 node-role.kubernetes.io/master。第四,软偏好的 weight 值虽然可以调整,但具体调度结果还要看节点资源水位,不能指望这个 weight 实现严格按照比例的分布。

nodeAffinity 的核心是把调度需求拆成明确的节点标签条件,先梳理标签,再写约束,之后通过 describe pod 和节点标签来验证。把这些步骤走完,调度逻辑基本就稳定了。