Kubernetes 高级调度完全指南(适用于 Kubernetes v1.35)

文档说明

  • 适用版本:Kubernetes v1.35.4
  • 前置知识:了解 Pod、Node、Deployment 的基本概念
  • 目标:从零掌握 Kubernetes 节点管理、亲和性、反亲和性、污点与容忍度的原理与使用

第一章:为什么需要高级调度?

1.1 默认调度的局限性

在默认情况下,Pod 运行在哪个 Node 上,是由 Scheduler 组件经过一系列算法计算得出的,这个过程不受人工控制。但在实际生产中,我们往往有更精细的控制需求:

场景需求
硬件差异化GPU 节点只跑 AI 训练任务,SSD 节点只跑数据库
高可用同一个应用的多个副本不能跑在同一台机器上
低延迟前端 Pod 和后端 Pod 要尽量靠近(同一节点/可用区)
节点隔离业务 Pod 不能调度到 Master 节点
节点维护维护节点前要驱逐所有 Pod,且不允许新 Pod 调度上来

一句话总结:Kubernetes 提供了亲和性(Attraction)污点(Repulsion) 两套机制,让你可以精确控制 Pod 该往哪儿跑、不该往哪儿跑。

1.2 调度机制全景图

Kubernetes 提供了四大类调度方式

调度方式说明强制程度
自动调度Scheduler 自动计算,不受人工控制-
定向调度nodeName / nodeSelector强制
亲和性调度NodeAffinity / PodAffinity / PodAntiAffinity强制/偏好
污点(容忍)调度Taints / Tolerations强制/偏好

第二章:Node(节点)管理基础

2.1 查看节点

# 查看所有节点
kubectl get nodes

# 查看节点详细信息(含标签、污点)
kubectl describe node <node-name>

# 查看节点标签
kubectl get nodes --show-labels

2.2 节点标签(Labels)—— 调度的基础

亲和性和反亲和性都依赖于 Label(标签) 机制。标签是键值对,可以附加到 Node 或 Pod 上。

给节点打标签

# 给节点添加标签
kubectl label nodes <node-name> disktype=ssd
kubectl label nodes <node-name> gpu=true
kubectl label nodes <node-name> zone=us-west-1a

# 查看节点标签
kubectl get nodes --show-labels

# 删除标签(在键名后加 -)
kubectl label nodes <node-name> disktype-

常用的节点标签(Kubernetes 自动添加):

标签说明
kubernetes.io/hostname节点主机名
topology.kubernetes.io/zone可用区
topology.kubernetes.io/region区域
kubernetes.io/os操作系统
kubernetes.io/archCPU 架构

2.3 节点维护操作:Cordon 与 Drain

在维护节点之前,需要先将节点上的 Pod 迁移走,并阻止新 Pod 调度上来。

Cordon(隔离) :将节点标记为不可调度,新 Pod 不会调度上来,但已有 Pod 不受影响。

# 隔离节点
kubectl cordon <node-name>

# 取消隔离
kubectl uncordon <node-name>

# 查看节点状态(SchedulingDisabled 表示被 cordon)
kubectl get nodes

Drain(排空) :驱逐节点上的所有 Pod(优雅终止),并将节点标记为不可调度。

# 排空节点(驱逐所有 Pod)
kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data

# 参数说明:
# --ignore-daemonsets: 忽略 DaemonSet 管理的 Pod
# --delete-emptydir-data: 删除 emptyDir 中的数据
# --force: 强制驱逐(即使 Pod 不受 Deployment 管理)

典型维护流程

  1. kubectl cordon <node> —— 阻止新 Pod 调度
  2. kubectl drain <node> —— 驱逐现有 Pod
  3. 执行维护操作(升级内核、更换硬件等)
  4. kubectl uncordon <node> —— 恢复调度

第三章:定向调度 —— 最简单粗暴的方式

3.1 nodeName —— 指定节点名称

nodeName 用于强制将 Pod 调度到指定名称的节点上。这种方式直接跳过 Scheduler 的调度逻辑,即使目标节点不存在,Pod 也会被创建(只是会失败)。

apiVersion: v1
kind: Pod
metadata:
  name: pod-nodename
spec:
  nodeName: node-01              # 直接指定节点名称
  containers:
  - name: nginx
    image: nginx:1.25
# 查看节点名称
kubectl get nodes

3.2 nodeSelector —— 节点选择器

nodeSelector 通过标签匹配来选择节点,是强制约束

apiVersion: v1
kind: Pod
metadata:
  name: pod-selector
spec:
  nodeSelector:                  # 必须匹配的标签
    disktype: ssd                # 只调度到有 disktype=ssd 标签的节点
  containers:
  - name: nginx
    image: nginx:1.25

局限性nodeSelector 只能做简单的“等于”匹配,无法表达更复杂的逻辑(如“不等于”、“存在”、“不在”等)。这些复杂需求需要用到 Node Affinity

第四章:节点亲和性(Node Affinity)

Node Affinity 是 nodeSelector升级版,支持更丰富的匹配表达式(InNotInExistsDoesNotExistGtLt)。

4.1 两种类型

类型关键字说明
硬约束(Required)requiredDuringSchedulingIgnoredDuringExecution必须满足,否则 Pod 不会被调度
软约束(Preferred)preferredDuringSchedulingIgnoredDuringExecution尽量满足,找不到匹配节点时也会调度

4.2 硬约束(Required)示例

apiVersion: v1
kind: Pod
metadata:
  name: node-affinity-required
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
            - nvme
          - key: zone
            operator: In
            values:
            - us-west-1a
  containers:
  - name: nginx
    image: nginx:1.25

逻辑:Pod 必须调度到同时满足以下条件的节点:

  1. 标签 disktype 的值是 ssdnvme
  2. 标签 zone 的值是 us-west-1a

4.3 软约束(Preferred)示例

apiVersion: v1
kind: Pod
metadata:
  name: node-affinity-preferred
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100               # 权重(1-100),越高越优先
        preference:
          matchExpressions:
          - key: disktype
            operator: In
            values:
            - ssd
      - weight: 50
        preference:
          matchExpressions:
          - key: zone
            operator: In
            values:
            - us-west-1a
  containers:
  - name: nginx
    image: nginx:1.25

逻辑:Scheduler 会优先选择有 disktype=ssd 的节点(权重 100),其次选择在 us-west-1a 可用区的节点(权重 50)。但如果所有节点都不满足,Pod 依然会被调度。

4.4 操作符详解

操作符说明示例
In标签值在列表中values: ["ssd", "nvme"]
NotIn标签值不在列表中(反亲和values: ["hdd"]
Exists标签存在(不管值是什么)不需要 values 字段
DoesNotExist标签不存在不需要 values 字段
Gt标签值大于指定值(数值比较)values: ["3"]
Lt标签值小于指定值(数值比较)values: ["3"]

第五章:Pod 亲和性与反亲和性

Node Affinity 控制的是 Pod 往哪些节点跑。而 Pod Affinity / Anti-Affinity 控制的是 Pod 和哪些 Pod 一起跑(或不要一起跑)。

5.1 为什么需要 Pod 级别的调度?

场景解决方案
低延迟前端 Pod 和后端 Pod 部署在同一个节点上(Pod 亲和性)
高可用同一个应用的多个副本不要部署在同一个节点上(Pod 反亲和性)
避免干扰批处理任务不要和在线服务跑在一起(Pod 反亲和性)

5.2 两种类型

与 Node Affinity 一样,Pod Affinity/Anti-Affinity 也分为硬约束(Required)软约束(Preferred)

5.3 Pod 亲和性(Pod Affinity)示例

场景:让 Web 前端 Pod 和后端缓存 Pod(带 app=redis 标签)部署在同一个节点上,降低网络延迟。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - redis
            topologyKey: kubernetes.io/hostname   # 在节点级别亲和
      containers:
      - name: web
        image: nginx:1.25

关键字段 topologyKey

topologyKey含义
kubernetes.io/hostname节点级别亲和(同一台机器)
topology.kubernetes.io/zone可用区级别亲和(同一个机房)
topology.kubernetes.io/region区域级别亲和(同一个地区)

逻辑:Pod 会被调度到已经有带 app=redis 标签的 Pod 在运行的节点上。

5.4 Pod 反亲和性(Pod Anti-Affinity)示例

场景:同一个应用的 3 个副本必须分散到不同节点上,避免单点故障。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payment-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payment
  template:
    metadata:
      labels:
        app: payment
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:   # 硬约束
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - payment
            topologyKey: kubernetes.io/hostname
      containers:
      - name: payment
        image: payment:v1.0.0

效果:3 个副本会分别调度到 3 个不同的节点上。如果集群只有 2 个节点,第 3 个 Pod 会一直 Pending

5.5 软约束反亲和性(Preferred)示例

使用软约束避免调度死锁:

spec:
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - web
          topologyKey: kubernetes.io/hostname

效果:Scheduler 会尽量将 Pod 分散到不同节点,但如果节点不够,也可以接受多个副本在同一节点。

5.6 可用区级别的反亲和性

场景:将 Pod 分散到不同的可用区(Zone),实现机房级别的容灾。

spec:
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - api
          topologyKey: topology.kubernetes.io/zone   # 可用区级别

5.7 组合使用:Node + Zone 双重反亲和

spec:
  affinity:
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - database
          topologyKey: kubernetes.io/hostname      # 节点级别
      - weight: 50
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app
              operator: In
              values:
              - database
          topologyKey: topology.kubernetes.io/zone # 可用区级别

效果:优先分散到不同节点(权重 100),其次分散到不同可用区(权重 50)。

第六章:污点(Taints)与容忍(Tolerations)

6.1 基本概念

污点(Taint)节点的属性,用于排斥一类 Pod。

容忍(Toleration)Pod 的属性,表示 Pod 可以“容忍”某个污点。

类比:污点就像节点上的“刺”,只有携带“防护手套”(容忍)的 Pod 才能靠近。

污点和亲和性的区别

机制作用对象方向效果
亲和性PodPod → NodePod 主动选择 Node(“我想去哪”)
污点NodeNode → PodNode 排斥 Pod(“我不欢迎谁”)

6.2 污点的三种效果(Effect)

Effect对未调度 Pod对已运行 Pod
NoSchedule不容忍则不调度无影响,已运行的保留
PreferNoSchedule尽量不调度(软限制)无影响
NoExecute不容忍则不调度立即驱逐不容忍的 Pod

典型应用场景

  • Master 节点打污点,只允许系统组件运行
  • GPU 节点打污点,只允许需要 GPU 的 Pod 调度
  • 新节点打污点,测试通过后再移除
  • 节点维护时,打 NoExecute 污点驱逐 Pod

6.3 污点操作命令

# 1. 添加污点
kubectl taint nodes <node-name> <key>=<value>:<effect>

# 示例:给 node-01 添加污点,禁止新 Pod 调度
kubectl taint nodes node-01 dedicated=gpu:NoSchedule

# 示例:给 node-02 添加污点,驱逐已有 Pod
kubectl taint nodes node-02 maintenance=true:NoExecute

# 2. 查看节点的污点
kubectl describe node node-01 | grep Taints
# 或
kubectl get nodes -o json | jq '.items[].spec.taints'

# 3. 移除污点(在命令末尾加 -)
kubectl taint nodes node-01 dedicated=gpu:NoSchedule-

6.4 容忍(Toleration)配置

在 Pod 的 YAML 中添加 tolerations 字段

apiVersion: v1
kind: Pod
metadata:
  name: gpu-pod
spec:
  tolerations:
  - key: "dedicated"
    operator: "Equal"
    value: "gpu"
    effect: "NoSchedule"
  containers:
  - name: gpu-app
    image: nvidia/cuda:12.0

容忍匹配规则

operator匹配条件
Equal(默认)key、value、effect 全部相等才匹配
Exists只要 key 和 effect 匹配即可,忽略 value

特殊用法

# 匹配所有 key(operator 必须为 Exists)
tolerations:
- operator: "Exists"
  effect: "NoSchedule"

# 匹配所有 effect(不指定 effect)
tolerations:
- key: "dedicated"
  operator: "Exists"
  # 没有 effect 字段,匹配所有 effect

6.5 NoExecute + tolerationSeconds —— 优雅驱逐

当节点被打上 NoExecute 污点时,不容忍的 Pod 会被立即驱逐。但可以通过 tolerationSeconds 给 Pod 一个优雅退出的时间窗口。

tolerations:
- key: "maintenance"
  operator: "Equal"
  value: "true"
  effect: "NoExecute"
  tolerationSeconds: 60          # 60 秒后才会被驱逐

用途:节点维护时,给 Pod 60 秒时间完成正在处理的请求,然后优雅退出。

6.6 内置污点

Kubernetes 会自动给节点添加一些内置污点:

污点触发条件
node.kubernetes.io/not-ready节点未就绪
node.kubernetes.io/unreachable节点不可达
node.kubernetes.io/memory-pressure节点内存压力
node.kubernetes.io/disk-pressure节点磁盘压力
node.kubernetes.io/pid-pressure节点 PID 压力
node.kubernetes.io/network-unavailable节点网络不可用

默认容忍:kube-system 命名空间下的系统 Pod 通常都带有对这些污点的容忍,确保系统组件能在异常节点上继续运行。

第七章:实战场景组合

7.1 场景一:GPU 节点专用于 AI 训练

需求:集群中有 3 台 GPU 节点,只允许 AI 训练 Pod 调度到这些节点上。

Step 1:给 GPU 节点打标签和污点

# 打标签(用于亲和性)
kubectl label nodes gpu-node-01 gpu=true
kubectl label nodes gpu-node-02 gpu=true
kubectl label nodes gpu-node-03 gpu=true

# 打污点(排斥非 AI 的 Pod)
kubectl taint nodes gpu-node-01 gpu=专用:NoSchedule
kubectl taint nodes gpu-node-02 gpu=专用:NoSchedule
kubectl taint nodes gpu-node-03 gpu=专用:NoSchedule

Step 2:AI 训练 Pod 配置(亲和 + 容忍)

apiVersion: v1
kind: Pod
metadata:
  name: ai-training
spec:
  # 容忍 GPU 节点的污点
  tolerations:
  - key: "gpu"
    operator: "Equal"
    value: "专用"
    effect: "NoSchedule"
  # 只调度到有 gpu=true 标签的节点
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: gpu
            operator: In
            values:
            - "true"
  containers:
  - name: trainer
    image: tensorflow/tensorflow:latest-gpu

7.2 场景二:高可用 —— 3 副本分散到不同节点

需求:关键服务的 3 个副本必须分散到不同节点,避免单点故障。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: critical-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: critical
  template:
    metadata:
      labels:
        app: critical
    spec:
      affinity:
        podAntiAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - critical
            topologyKey: kubernetes.io/hostname
      containers:
      - name: app
        image: myapp:v1

7.3 场景三:低延迟 —— 前端和后端部署在同一节点

需求:Web 前端 Pod 要和 Redis 缓存 Pod 部署在同一个节点上,降低网络延迟。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-frontend
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - redis
              topologyKey: kubernetes.io/hostname
      containers:
      - name: web
        image: nginx:1.25

7.4 场景四:Master 节点隔离

Kubernetes 默认行为:Master 节点默认带有污点 node-role.kubernetes.io/master:NoSchedule(或 control-plane:NoSchedule),业务 Pod 默认不会调度到 Master 节点上。

查看 Master 节点的污点

kubectl describe node master-01 | grep Taints
# 输出:Taints: node-role.kubernetes.io/control-plane:NoSchedule

如果确实需要将某个 Pod 调度到 Master 节点(如监控组件):

spec:
  tolerations:
  - key: "node-role.kubernetes.io/control-plane"
    operator: "Exists"
    effect: "NoSchedule"

第八章:最佳实践总结

8.1 选择指南

需求推荐方案
Pod 必须跑在特定节点nodeSelectorrequired NodeAffinity
Pod 最好跑在特定节点(但不强制)preferred NodeAffinity
多个 Pod 要在一起(低延迟)PodAffinity
多个 Pod 不要在一起(高可用)PodAntiAffinity
节点要排斥某些 PodTaints(污点)
Pod 要“无视”节点的排斥Tolerations(容忍)
节点维护cordon + drain

8.2 核心原则

  1. 硬约束要谨慎required 类规则如果太严格,可能导致 Pod 一直 Pending
  2. 优先使用软约束:能用 preferred 就别用 required,提高调度灵活性
  3. 污点 + 亲和性配合使用:污点负责“排斥”,亲和性负责“吸引”,两者结合效果最佳
  4. 维护节点先 cordon 再 drain:先阻止新 Pod,再驱逐旧 Pod,避免服务中断
  5. 合理设置 tolerationSeconds:给 Pod 足够的优雅退出时间

8.3 常见陷阱

陷阱后果解决方案
硬约束反亲和 + 副本数 > 节点数Pod 一直 Pending改用软约束或减少副本数
忘记给 Pod 加容忍Pod 无法调度到有污点的节点检查 Pod 的 tolerations 配置
给节点打污点后忘记移除节点永远无法接收新 Pod维护完成后移除污点
同时使用 nodeSelector 和亲和性规则叠加,调度更困难尽量只选一种方式

附录:快速索引

需求命令/配置
查看节点kubectl get nodes
查看节点标签kubectl get nodes --show-labels
给节点打标签kubectl label nodes <node> <key>=<value>
隔离节点(Cordon)kubectl cordon <node>
恢复节点(Uncordon)kubectl uncordon <node>
排空节点(Drain)kubectl drain <node> --ignore-daemonsets
添加污点kubectl taint nodes <node> <key>=<value>:<effect>
移除污点kubectl taint nodes <node> <key>=<value>:<effect>-
查看污点kubectl describe node <node> | grep Taints
NodeAffinity 硬约束requiredDuringSchedulingIgnoredDuringExecution
NodeAffinity 软约束preferredDuringSchedulingIgnoredDuringExecution
PodAffinity 硬约束podAffinity.requiredDuringSchedulingIgnoredDuringExecution
PodAntiAffinity 硬约束podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution

文档版本:v1.0
适用环境:Kubernetes v1.35+
前置条件:已部署 Kubernetes 集群并配置 kubectl

更多推荐