技术博客

Kubernetes 调度进阶:NodeAffinity、Taint/Toleration、PodTopologySpread 实战

深入讲解 Kubernetes 高级调度机制:NodeSelector/NodeAffinity 精确选节点、Taint/Toleration 实现专属节点池、PodAffinity/AntiAffinity 控制 Pod 间关系、PodTopologySpread 实现跨区域均匀分布,覆盖 GPU 节点隔离、高可用跨 AZ 部署等生产场景。

Kubernetes调度NodeAffinityTaintTolerationPodTopologySpread高可用

Kubernetes 默认调度器(kube-scheduler)通过打分算法把 Pod 分配到合适的节点,但默认算法无法满足“GPU 任务只跑 GPU 节点”、“数据库 Pod 跨 AZ 分散”、“前端 Pod 和缓存 Pod 部署在同一节点”这类精细化需求。本文讲解四类高级调度机制的生产用法。

一、NodeSelector(基础标签筛选)

# 最简单的节点选择:匹配节点标签
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ml-training
spec:
  template:
    spec:
      nodeSelector:
        accelerator: nvidia-gpu    # 只调度到有此标签的节点
        node-type: high-memory
      containers:
      - name: trainer
        image: pytorch/pytorch:2.4
# 给节点打标签
kubectl label node gpu-node-01 accelerator=nvidia-gpu
kubectl label node gpu-node-01 node-type=high-memory

# 查看节点标签
kubectl get nodes --show-labels
kubectl get nodes -l accelerator=nvidia-gpu

NodeSelector 只支持“精确匹配”,不支持 OR 逻辑(多个候选值)。这就需要 NodeAffinity。


二、NodeAffinity:灵活的节点亲和性

NodeAffinity 支持 InNotInExistsDoesNotExistGtLt 六种操作符。

硬亲和(requiredDuringScheduling)

spec:
  affinity:
    nodeAffinity:
      # 硬性要求:不满足则 Pod 一直 Pending
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: kubernetes.io/arch
            operator: In
            values: [amd64, arm64]    # OR 逻辑:amd64 或 arm64 节点都可以
          - key: node.kubernetes.io/instance-type
            operator: NotIn
            values: [spot]            # AND 逻辑:同时要求非 spot 实例

软亲和(preferredDuringScheduling)

spec:
  affinity:
    nodeAffinity:
      # 软性偏好:优先选但不强求
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80                    # 权重 1-100
        preference:
          matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values: [cn-shanghai-a]   # 优先 A 区,没有就去其他区
      - weight: 20
        preference:
          matchExpressions:
          - key: node-type
            operator: In
            values: [high-cpu]        # 次优先高 CPU 节点

生产场景:GPU 任务调度

# GPU 训练任务:必须有 GPU,优先 A100
apiVersion: batch/v1
kind: Job
metadata:
  name: model-training
spec:
  template:
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: accelerator
                operator: In
                values: [nvidia-gpu]
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            preference:
              matchExpressions:
              - key: nvidia.com/gpu.product
                operator: In
                values: [NVIDIA-A100-SXM4-80GB]
          - weight: 60
            preference:
              matchExpressions:
              - key: nvidia.com/gpu.product
                operator: In
                values: [NVIDIA-H100-80GB-HBM3]
      containers:
      - name: trainer
        resources:
          limits:
            nvidia.com/gpu: "4"

三、Taint/Toleration:专属节点池

Taint(污点)打在节点上,阻止普通 Pod 调度;Toleration(容忍)加在 Pod 上,允许调度到有对应污点的节点。

# 给节点打污点
kubectl taint nodes gpu-node-01 dedicated=gpu-only:NoSchedule
# effect 三种:
# NoSchedule    → 不容忍的 Pod 不能调度(已运行的不受影响)
# PreferNoSchedule → 尽量不调度(软限制)
# NoExecute     → 不容忍的 Pod 不能调度,且已运行的会被驱逐

# 查看节点污点
kubectl describe node gpu-node-01 | grep Taint

# 移除污点
kubectl taint nodes gpu-node-01 dedicated=gpu-only:NoSchedule-
# 末尾加 - 表示删除

GPU 专属节点池配置

# GPU 节点污点:dedicated=gpu:NoSchedule
# 普通 Pod 不会调度到 GPU 节点,节省昂贵资源

# GPU 任务 Pod 添加 Toleration
spec:
  tolerations:
  - key: dedicated
    operator: Equal
    value: gpu
    effect: NoSchedule
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: dedicated
            operator: In
            values: [gpu]

系统组件独占节点(master 节点保护)

# K8s master 节点默认有污点,普通 Pod 不会调度上去
kubectl describe node master-01 | grep Taint
# Taints: node-role.kubernetes.io/control-plane:NoSchedule

# 如果需要在 master 上运行某些系统 Pod(如监控),加容忍:
spec:
  tolerations:
  - key: node-role.kubernetes.io/control-plane
    operator: Exists
    effect: NoSchedule

节点维护驱逐(NoExecute)

# 节点维护时,驱逐 Pod 但给宽限时间
kubectl taint nodes node-01 maintenance=true:NoExecute

# 给关键服务 Pod 设置容忍宽限时间(默认 0 秒立即驱逐)
spec:
  tolerations:
  - key: maintenance
    operator: Equal
    value: "true"
    effect: NoExecute
    tolerationSeconds: 300   # 允许 Pod 继续运行 300 秒,期间可以完成请求处理

四、PodAffinity/AntiAffinity:Pod 间关系调度

控制 Pod 之间是“靠近”还是“远离”。

让 Pod 与特定 Pod 在同一节点(PodAffinity)

# 场景:缓存 Pod 希望和 Web Pod 在同一节点(减少网络延迟)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: cache
spec:
  template:
    spec:
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: web             # 亲近 web Pod
              topologyKey: kubernetes.io/hostname  # 在节点级别匹配

让 Pod 分散到不同节点(PodAntiAffinity)

# 场景:同一个 Deployment 的多个副本必须分散到不同节点(高可用)
spec:
  affinity:
    podAntiAffinity:
      # 硬反亲和:同一节点上不能有相同 app 的 Pod
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: redis-master
        topologyKey: kubernetes.io/hostname

跨 AZ 高可用部署

# 场景:数据库 Pod 必须分布在不同可用区
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: postgres
        topologyKey: topology.kubernetes.io/zone   # AZ 级别分散

五、PodTopologySpread:精确控制分布

PodAffinity 只能“尽量分散”,PodTopologySpread 可以精确控制“最多允许多少个 Pod 在同一域”。

spec:
  topologySpreadConstraints:
  - maxSkew: 1                    # 各域 Pod 数量差异不超过 1
    topologyKey: kubernetes.io/hostname   # 以节点为域
    whenUnsatisfiable: DoNotSchedule     # 不满足则不调度(硬约束)
    labelSelector:
      matchLabels:
        app: web
  - maxSkew: 2
    topologyKey: topology.kubernetes.io/zone  # 以 AZ 为域
    whenUnsatisfiable: ScheduleAnyway         # 软约束(尽力满足)
    labelSelector:
      matchLabels:
        app: web

完整生产示例:跨 AZ 均匀分布

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-frontend
spec:
  replicas: 6
  selector:
    matchLabels:
      app: web-frontend
  template:
    metadata:
      labels:
        app: web-frontend
    spec:
      # 1. 跨 AZ 均匀分布(软约束,AZ 不够时不卡住)
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: web-frontend
      # 2. 同一节点不超过 2 个副本(防止节点故障影响太大)
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: web-frontend
      # 3. 反亲和:硬性保证不在同一节点堆叠(与上面配合)
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchLabels:
                  app: web-frontend
              topologyKey: kubernetes.io/hostname
      containers:
      - name: web
        image: nginx:1.27

六、调度优先级(PriorityClass)

# 创建优先级类
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000          # 数字越大优先级越高
globalDefault: false
description: "关键业务服务"

---
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: low-priority
value: 100
globalDefault: false
description: "批处理任务"
# Pod 使用优先级
spec:
  priorityClassName: high-priority
  # 当集群资源不足时,低优先级 Pod 会被抢占(Preemption)

调度问题排查

# Pod 一直 Pending,查看调度失败原因
kubectl describe pod <pod-name> | grep -A 20 Events
# Events:
#   Warning  FailedScheduling  0/5 nodes are available:
#   3 node(s) had untolerated taint {dedicated: gpu}, 
#   2 node(s) didn't match Pod's node affinity/selector.

# 查看调度器日志
kubectl logs -n kube-system -l component=kube-scheduler --tail=50

# 模拟调度(不实际创建 Pod)
kubectl get nodes -o json | \
  kubectl debug -it --image=bitnami/kubectl -- \
  kubectl explain pod.spec.affinity

# 查看节点可用资源
kubectl describe nodes | grep -A 5 "Allocatable"
kubectl top nodes

小结

四类调度机制的使用场景:NodeAffinity 用于“选什么样的节点”(GPU 节点、高内存节点);Taint/Toleration 用于“节点隔离”(GPU 专属、master 保护、维护驱逐);PodAntiAffinity 用于“同类 Pod 分散”(高可用、防止单点);PodTopologySpread 是最精细的跨域分布控制(精确控制跨 AZ/节点的 Pod 数量差异)。生产中这四类通常组合使用:用 Taint 隔离 GPU 节点,用 NodeAffinity 精确选型,用 PodAntiAffinity + TopologySpread 保证高可用分布。