路线图

调度控制

星辉 2026-07-02 阅读 4 min 644 字 路线图
调度控制 封面

概述#

如果说资源管理是"Pod 该拿多少资源",那么调度控制就是"Pod 该跑到哪个节点上"。Kubernetes 默认调度器已经很智能——它会综合考虑节点的资源余量、亲和性规则、污点容忍等因素。但在生产环境中,我们往往需要更精细的控制:把 GPU 任务调度到 GPU 节点、让同一个服务副本分散部署、给运维节点加"污染"防止业务 Pod 误入。这一章覆盖从简单的 nodeSelector 到复杂的亲和性/反亲和性规则,再到弹性伸缩,帮你掌握 Pod 调度的全部手段。


NodeSelector:最简单的节点选择#

基本原理#

NodeSelector 是 Kubernetes 最早的调度控制手段。核心逻辑是:给节点打标签 → 在 Pod 中指定 nodeSelector → Pod 只调度到匹配标签的节点

它的优点是简单直接,缺点是只能做精确匹配(AND 逻辑),不支持"优先选择"这类软约束。

使用步骤#

第一步:给节点打标签

bash
# 给 node1 打上磁盘类型标签
kubectl label node node1 disktype=ssd

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

# 删除标签
kubectl label node node1 disktype-

第二步:在 Pod 中指定 nodeSelector

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

如果集群中没有任何节点带有 disktype=ssd 标签,这个 Pod 会一直处于 Pending 状态,调度器找不到合适节点。

适用场景#

  • 简单区分节点用途(如 role: worker vs role: gpu
  • 确保 IO 密集型服务调度到 SSD 节点
  • 小型集群、规则简单的场景

Affinity / Anti-Affinity:灵活的亲和性#

当 nodeSelector 不够用的时候,就需要 Affinity。它提供了更丰富的匹配语法(In、NotIn、Exists、Gt、Lt 等),以及软约束(尽量满足,不满足也可以)。

硬亲和 vs 软亲和#

类型YAML 字段含义
硬亲和requiredDuringSchedulingIgnoredDuringExecution必须满足条件才调度,否则 Pod 保持 Pending
软亲和preferredDuringSchedulingIgnoredDuringExecution尽量满足条件,满足不了也调度(配合 weight 权重)

命名里有两个 DuringScheduling / DuringExecution

  • DuringScheduling:调度时起作用
  • IgnoredDuringExecution:运行后不管(即使标签被改了,Pod 也不会被驱逐)

NodeAffinity:让 Pod 跑到特定节点#

比 nodeSelector 更强大,支持条件表达式:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: gpu-job
spec:
  affinity:
    nodeAffinity:
      # 硬要求:必须是 GPU 节点,且在指定区域
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: node-type
                operator: In
                values:
                  - gpu
              - key: topology.kubernetes.io/zone
                operator: In
                values:
                  - us-west-2a
                  - us-west-2b
      # 软偏好:尽量选 SSD 节点,权重 80
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 80
          preference:
            matchExpressions:
              - key: disktype
                operator: In
                values:
                  - ssd
  containers:
    - name: training
      image: pytorch/pytorch:2.1

PodAffinity:让 Pod 聚在一起#

想让某个服务的 Pod 和数据缓存 Pod 部署在同一台机器上(降低网络延迟):

yaml
affinity:
  podAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: redis-cache
        topologyKey: kubernetes.io/hostname   # 必须在同一台宿主机

topologyKey 定义"在一起"的范围:用 kubernetes.io/hostname 表示同一节点;用 topology.kubernetes.io/zone 表示同一可用区。

PodAntiAffinity:让 Pod 分散部署#

这是生产环境最常用的亲和性规则——确保同一服务的多个副本不在同一个节点上:

yaml
affinity:
  podAntiAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: my-api
        topologyKey: kubernetes.io/hostname
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchLabels:
              app: my-api
          topologyKey: topology.kubernetes.io/zone

这段配置的效果:

  • 硬要求:同一应用的两个 Pod 绝不在同一台宿主机(避免单点故障)
  • 软偏好:尽量不在同一可用区(避免整个 AZ 故障影响全部副本)

Taint 与 Toleration:节点"污点"机制#

NodeSelector 和 Affinity 是 Pod 选择节点,Taint/Toleration 是节点拒绝 Pod。

概念对比#

概念作用方含义
Taint(污点)打在节点"这节点有污点,不想被普通 Pod 调度上来"
Toleration(容忍)写在 Pod"我能忍受这个污点,可以调度到有污点的节点"

三种效果#

效果含义
NoSchedule新 Pod 不会调度到此节点,但已运行的 Pod 不受影响
PreferNoSchedule尽量不调度到此节点,但没有绝对保证
NoExecute新 Pod 不调度,已运行的 Pod 也会被驱逐(除非有对应 Toleration)

实战:给运维节点加污点#

bash
# 给运维专用节点加污点,防止业务 Pod 被调度上来
kubectl taint node ops-node1 role=ops:NoSchedule

# 查看污点
kubectl describe node ops-node1 | grep Taints
# 输出:Taints: role=ops:NoSchedule

# 删除污点
kubectl taint node ops-node1 role=ops:NoSchedule-

此时,除非 Pod 写了对应 Toleration,否则调度器会跳过这个节点。

Pod Toleration 配置#

yaml
apiVersion: v1
kind: Pod
metadata:
  name: ops-agent
spec:
  tolerations:
    - key: "role"
      operator: "Equal"
      value: "ops"
      effect: "NoSchedule"
    # 容忍所有污点(不推荐在生产环境用)
    # - operator: "Exists"
  containers:
    - name: agent
      image: ops-agent:1.0

Kubernetes 的 Master 节点(或 control-plane 节点)默认带有 node-role.kubernetes.io/control-plane:NoSchedule 污点,这就是为什么普通 Pod 不会被调度到 Master 节点的原因。


PodTopologySpread:跨拓扑域打散#

这是 Kubernetes 1.19 之后 GA 的高级调度特性,一句话概括:让 Pod 在各个拓扑域中均匀分布

yaml
topologySpreadConstraints:
  - maxSkew: 1              # 各拓扑域之间 Pod 最多差几个
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule   # 或 ScheduleAnyway
    labelSelector:
      matchLabels:
        app: my-api

这段配置的意思是:my-api 的 Pod 在各个可用区之间的数量差异不超过 1。如果 AZ-a 有 3 个 Pod,AZ-b 有 2 个 Pod,maxSkew=1 意味着可以(差 1);但如果是 3 和 1,差 2,新 Pod 会被强制调度到 AZ-b。

它和 PodAntiAffinity 有重叠,但 TopologySpread 更关注"均匀分布",而 AntiAffinity 更关注"不和某些 Pod 在一起"。生产环境中两者常配合使用。


HPA 与 VPA:弹性伸缩#

资源管理和调度控制之后,还有一个终极问题:副本数应该是多少?手动设定的副本数在流量波动时要么不够,要么浪费。HPA 和 VPA 就是解决这个问题。

HPA:水平弹性伸缩#

HPA(Horizontal Pod Autoscaler)根据 CPU/内存或自定义指标自动调整 Pod 副本数。

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-api-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-api
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70    # 所有 Pod 的平均 CPU 利用率目标 70%
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100                # 每次最多翻倍
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300   # 缩容前等待 5 分钟,防抖动
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60

HPA 的核心原则

  • 扩容要快(stabilizationWindowSeconds: 0),缩容要慢(stabilizationWindowSeconds: 300
  • CPU 指标最适合做自动伸缩,内存指标慎用(Java/Go 的内存释放慢,不适合做缩容依据)
  • HPA 依赖 metrics-server 提供指标数据——必须确保 metrics-server 正常运行
bash
# 查看 HPA 状态
kubectl get hpa -n production

# 查看 HPA 事件(为什么不扩/不缩)
kubectl describe hpa my-api-hpa -n production

VPA:垂直弹性伸缩(简介)#

VPA(Vertical Pod Autoscaler)做的事情不同——它不是在水平方向加减 Pod 数量,而是调整每个 Pod 的 Request/Limit 建议值

VPA 有三种模式:

模式行为适用场景
Off只计算推荐值,不修改 Pod生产环境获取 requests 建议
Initial只在 Pod 创建时设置新上线的服务
Auto自动调整并重启 Pod非关键服务、开发环境

VPA 当前仍处于 beta 阶段,生产环境谨慎使用 Auto 模式。推荐用 Off 模式来获取资源建议,然后人工调整。

重要提示:HPA 和 VPA 不要用相同指标同时启用,会因为两者都尝试调整而互相冲突。如果共用,让 VPA 管内存、HPA 管 CPU 是一条可行的策略。


实战要点#

  1. 从 nodeSelector 开始:如果简单的标签匹配够用,就别上复杂的 Affinity。
  2. 生产必备 PodAntiAffinity:同一服务多个副本应分散到不同节点(hostname),避免单点故障。
  3. 污点保护关键节点:GPU 节点、运维节点、有特殊硬件的节点都应加 Taint。
  4. HPA 缩容要保守:缩容太快会导致流量打到剩余 Pod 上引发新的扩容,形成振荡。stabilizationWindowSeconds 至少设 300 秒。
  5. 监控 HPA 指标kubectl describe hpa 的 Events 是排障第一入口。

小结#

调度控制从简单到复杂分为几个层次:NodeSelector(标签匹配)→ Affinity/Anti-Affinity(软硬亲和、灵活表达式)→ Taint/Toleration(节点排斥)→ TopologySpread(均匀分布)。从另一个维度看,HPA/VPA 解决的是"副本数量该多少"的动态调度问题。记住调度设计的核心原则:把关键服务的副本打散,把特殊用途的节点保护起来,让弹性伸缩根据真实负载自动调整