调度策略基础:节点选择与亲和性/反亲和性

Kubernetes 调度器负责将 Pod 分配到合适的节点上。默认调度器基于资源请求(Requests)和可用资源进行打分与选择,但在实际业务中,我们往往需要更精细的控制:将 Pod 调度到特定硬件(如 GPU 节点)、将高流量 Pod 分散到不同节点、或将相关服务部署在同一台机器上以减少网络延迟。Kubernetes 提供了三种核心调度约束机制:nodeSelector、节点亲和性(nodeAffinity) 以及 Pod 亲和性/反亲和性(podAffinity / podAntiAffinity)。

1. nodeSelector:最简单的节点选择

nodeSelector 是 Kubernetes 中最直接、最古老的节点选择方式。它通过 Pod 定义中的 spec.nodeSelector 字段,要求 Pod 只能调度到包含指定标签(Label)的节点上。

适用场景: 快速将工作负载绑定到具有特定标签的节点池,例如“所有 GPU 任务必须运行在带有 gpu=true 标签的节点上”。

工作原理:

  • 运维人员预先为节点打上标签(如 disktype=ssd、region=us-east)。
  • Pod 的 YAML 中声明 nodeSelector,键值必须完全匹配节点标签。
  • 调度器在过滤阶段(Predicates)直接剔除不满足标签条件的节点。
apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  nodeSelector:
    gpu: "true"          # 节点必须包含 gpu=true 标签
  containers:
  - name: cuda-container
    image: nvidia/cuda:12.0
特性 说明
匹配逻辑 精确匹配(AND 逻辑),所有键值对必须同时满足
表达能力 弱,仅支持等值匹配,不支持 In、NotIn、Exists 等操作符
灵活性 低,无法表达“优先选择 SSD 节点,但如果没有也可以接受”这类软约束
维护成本 低,适合简单固定的标签匹配
注意: nodeSelector 是硬约束(Required)。如果集群中没有节点满足标签条件,Pod 将一直处于 Pending 状态。对于需要软偏好或复杂逻辑的场景,请使用 nodeAffinity。

2. nodeAffinity:增强的节点选择

nodeAffinity 是 nodeSelector 的升级版,属于节点亲和性(Node Affinity)功能。它提供了更丰富的匹配操作符(In、NotIn、Exists、DoesNotExist、Gt、Lt),并支持硬约束(required)和软偏好(preferred)两种语义。

两种类型:

  • requiredDuringSchedulingIgnoredDuringExecution:硬约束。调度时必须满足,否则 Pod 无法调度。节点运行时标签变化不影响已运行的 Pod。
  • preferredDuringSchedulingIgnoredDuringExecution:软偏好。调度器会尽量满足,但不强制。可以设置 weight(权重,1-100)来控制优先级。
apiVersion: v1
kind: Pod
metadata:
  name: affinity-pod
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values:
            - us-east-1a
            - us-east-1b
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
  containers:
  - name: app
    image: nginx
操作符 示例 说明
In key: zone, values: [a, b] 节点标签值在给定列表中
NotIn key: zone, values: [c] 节点标签值不在给定列表中
Exists key: gpu 节点存在该标签(不关心值)
DoesNotExist key: spot 节点不存在该标签
Gt / Lt key: memory, values: [32] 标签值大于/小于给定数值(标签值需为数字字符串)
最佳实践: 使用 preferredDuringScheduling 时,建议将权重设置为 10 的倍数(如 10、20、50、100),便于后期调整优先级。多个偏好规则之间权重可以叠加。
nodeSelector 与 nodeAffinity 调度流程对比 nodeSelector(精确匹配) Pod 声明 nodeSelector 调度器过滤:标签必须完全匹配 结果:调度到匹配节点 / Pending nodeAffinity(丰富语义) Pod 声明 nodeAffinity 硬约束(required) + 软偏好(preferred) 结果:强制匹配 + 权重打分调度

3. Pod 亲和性与反亲和性

节点亲和性关注的是 Pod 与节点标签的关系,而 Pod 亲和性(podAffinity)与反亲和性(podAntiAffinity)关注的是 Pod 与其他 Pod 的拓扑关系。它们基于已经运行在节点上的 Pod 的标签来决定调度位置。

核心概念:

  • podAffinity:将 Pod 调度到与某些 Pod 相同的拓扑域中(例如:同一个节点、同一个可用区)。常用于将前端与后端服务部署在一起以减少延迟。
  • podAntiAffinity:将 Pod 调度到与某些 Pod 不同的拓扑域中。常用于将同一服务的多个副本分散到不同节点或可用区,提高可用性。

关键参数:

  • topologyKey:定义拓扑域的键。例如 kubernetes.io/hostname 表示节点级别,topology.kubernetes.io/zone 表示可用区级别。
  • labelSelector:选择目标 Pod 的标签。
  • namespaces:指定命名空间范围(默认是当前命名空间)。
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - web
            topologyKey: "kubernetes.io/hostname"
      containers:
      - name: nginx
        image: nginx
实战解读: 上述配置强制要求同一 Deployment 的 3 个副本必须调度到不同的节点上(topologyKey: kubernetes.io/hostname)。如果集群只有 2 个节点,第 3 个 Pod 将处于 Pending 状态,直到有新的节点加入。

4. 三种调度策略对比总结

策略 作用对象 约束类型 操作符 典型场景
nodeSelector 节点标签 硬约束 等值匹配 GPU 节点、SSD 节点
nodeAffinity 节点标签 硬约束 + 软偏好 In、NotIn、Exists、Gt、Lt 优先选择高性能节点、排除故障域
podAffinity Pod 标签 硬约束 + 软偏好 In、NotIn、Exists 将缓存与计算服务部署在同一节点
podAntiAffinity Pod 标签 硬约束 + 软偏好 In、NotIn、Exists 分散副本、避免单点故障
Pod 亲和性(同节点)与反亲和性(分散节点) podAffinity:亲和部署 节点 A (hostname: node-1) Pod: cache Pod: app 节点 B (hostname: node-2) Pod: db podAntiAffinity:分散部署 节点 C (hostname: node-3) Pod: web-v1 节点 D (hostname: node-4) Pod: web-v2 左图:app Pod 通过 podAffinity 与 cache Pod 调度到同一节点;右图:web 副本通过 podAntiAffinity 分散到不同节点

5. 调度策略选择决策树

在实际业务中,如何选择合适的调度策略?可以参考以下决策逻辑:

  1. 是否需要绑定到特定硬件或区域?
    • 是 → 使用 nodeSelector(简单场景)或 nodeAffinity(复杂场景)。
  2. 是否需要将 Pod 部署到与其他 Pod 相同的位置?
    • 是 → 使用 podAffinity,并指定 topologyKey(如 kubernetes.io/hostname 或 topology.kubernetes.io/zone)。
  3. 是否需要将 Pod 副本分散开以避免单点故障?
    • 是 → 使用 podAntiAffinity,配合 requiredDuringScheduling 实现强制分散。
  4. 是否需要同时满足多种约束?
    • 可以组合使用 nodeAffinity 和 podAntiAffinity,调度器会同时评估所有规则。
性能注意: 使用 podAntiAffinity 的 requiredDuringScheduling 时,如果集群规模较大(数百节点),调度器需要遍历所有节点上的 Pod 进行匹配,可能增加调度延迟。对于大规模集群,建议优先使用软偏好(preferred)或配合 topologyKey 缩小范围。