找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3658

积分

0

好友

504

主题
发表于 昨天 18:55 | 查看: 5| 回复: 0

作为 Kubernetes 控制平面的核心,kube-scheduler 的职责看似单一——决定每个 Pod 落在哪个节点上,但其调度策略的优劣直接影响着集群资源利用率、应用性能和高可用性。随着 Kubernetes 1.19 版本后全面转向 Scheduling Framework 插件化架构,我们有了前所未有的能力来干预和定制调度决策。本文将带你深入调度器内部,从内置的 Score 插件原理剖析,到动手开发一个自定义调度器。

一、概述

1.1 调度框架与流程

FilterScore,再到最终的 Bind,每个 Pod 的调度旅程都遵循一条清晰的插件链流水线:调度队列 → PreFilter → Filter → PostFilter → PreScore → Score → Reserve → Permit → PreBind → Bind → PostBind。各阶段分工明确,共同决定了 Pod 的最终归宿。

1.2 内置调度插件一览

调度器的能力由插件构成,主要分为两类:

  • Filter 插件(节点过滤):负责执行硬性检查,排除不满足条件的节点。
  • Score 插件(节点打分):负责对通过筛选的节点进行评分,分数越高,被选中的概率越大。

1.3 扩展方式对比

当默认调度策略无法满足需求时,我们有多种扩展路径,选择哪种取决于你的具体场景和可接受的复杂度。

方式 侵入性 灵活性 性能 适用场景
KubeSchedulerConfiguration 调整内置插件权重和参数
Scheduler Extender 低(HTTP 调用) 简单扩展,不想编译调度器
Scheduling Framework Plugin 高(进程内) 自定义调度逻辑,生产推荐
独立自定义调度器 最高 完全不同的调度策略

1.4 典型应用场景

深入理解调度机制,能帮助我们解决诸多实际问题:

  • 资源优化:通过调整 Score 插件权重,实现资源装箱(BinPacking)以提高利用率,或均衡分布以提高稳定性。
  • GPU等特殊硬件调度:基于 GPU 型号、显存利用率等指标进行自定义打分。
  • 拓扑感知调度:实现 NUMA 感知、跨可用区亲和等,优化应用性能与容灾能力。
  • 批处理与组调度:实现 Gang Scheduling,确保一组 Pod 同时成功调度或同时失败。
  • 多租户隔离:为不同团队或业务线配置不同的调度策略集(Profile)。

二、调度流程深度解析

2.1 Filter 阶段:节点过滤

Filter 阶段对每个候选节点执行一系列“一票否决”检查。核心判断逻辑包括:

  • 资源是否充足NodeResourcesFit 插件对比 Podrequests 与节点 Allocatable 减去已分配量。
  • 亲和性是否满足NodeAffinity 检查节点标签是否匹配 nodeSelectornodeAffinity 规则。
  • 污点是否容忍TaintToleration 检查 Podtolerations 是否覆盖节点所有 NoSchedule 污点。
  • 端口是否冲突NodePorts 检查 hostPort 在目标节点上是否已被占用。
  • 拓扑约束是否满足PodTopologySpread 检查调度后是否违反 maxSkew

当所有节点都被淘汰时,调度器会进入 PostFilter 阶段尝试抢占——驱逐部分低优先级 Pod 以腾出资源。

2.2 Score 阶段:节点打分

通过 Filter 的节点进入 Score 阶段。每个 Score 插件为节点打出一个 0-100 的分数,最终加权求和,得分最高者胜出。这是调度器做“选择题”的关键环节。

插件 算法 权重默认值
NodeResourcesFit (LeastAllocated) (Allocatable - Requested) / Allocatable * 100 1
NodeResourcesFit (MostAllocated) Requested / Allocatable * 100 1
NodeResourcesBalancedAllocation 100 - abs(cpuFraction - memFraction) * 100 1
ImageLocality 按已存在镜像大小占比打分 1
InterPodAffinity 满足的 preferredDuringScheduling 规则数加权 1
TaintToleration 未匹配的 PreferNoSchedule 污点越少分越高 3
PodTopologySpread 调度后 skew 越小分越高 2

2.3 调度器配置文件

调度行为可以通过 KubeSchedulerConfiguration 进行精细控制。以下是一个生产环境配置示例,展示了如何调整插件权重和策略。

# /etc/kubernetes/scheduler-config.yaml
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
parallelism: 16
leaderElection:
  leaderElect: true
  resourceNamespace: kube-system
  resourceName: kube-scheduler
profiles:
- schedulerName: default-scheduler
  plugins:
    score:
      enabled:
      # 启用资源均衡打分,权重设为 2
      - name: NodeResourcesBalancedAllocation
        weight: 2
      # 镜像本地性,减少拉取时间
      - name: ImageLocality
        weight: 1
      # 拓扑分散,确保跨故障域分布
      - name: PodTopologySpread
        weight: 3
      disabled:
      # 禁用不需要的插件减少调度延迟
      - name: NodeResourcesFit
    filter:
      enabled:
      - name: NodeResourcesFit
      - name: NodeAffinity
      - name: TaintToleration
  pluginConfig:
  - name: NodeResourcesFit
    args:
      # 评分策略:LeastAllocated 倾向空闲节点
      # MostAllocated 倾向装箱,适合缩容场景
      scoringStrategy:
        type: LeastAllocated
        resources:
        - name: cpu
          weight: 1
        - name: memory
          weight: 1

三、高级调度策略实战

3.1 亲和性与反亲和性

利用亲和性规则,你可以像“磁铁”一样将 Pod 吸引到特定节点或彼此靠近,也可以像“同极相斥”一样让它们分开。

节点亲和性示例:将数据库调度到带 SSD 的节点,并优先选择某个可用区。

apiVersion: v1
kind: Pod
metadata:
  name: postgres-primary
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: disk-type
            operator: In
            values: [ssd]
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 80
        preference:
          matchExpressions:
          - key: topology.kubernetes.io/zone
            operator: In
            values: [us-east-1a]
  containers:
  - name: postgres
    image: postgres:16
    resources:
      requests:
        cpu: "2"
        memory: 4Gi

Pod 间亲和与反亲和示例:Web 应用与 Redis 缓存保持同可用区,同时 Web 实例自身尽量分散在不同节点。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 6
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values: [redis]
            topologyKey: topology.kubernetes.io/zone
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 100
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values: [web]
              topologyKey: kubernetes.io/hostname
      containers:
      - name: web
        image: web-app:v2.1

3.2 污点与容忍

污点(Taint)和容忍(Toleration)为节点和 Pod 提供了一种排斥机制,常用于守护节点、专用硬件调度等场景。

GPU 节点专用调度配置

# 为 GPU 节点打污点
kubectl taint nodes gpu-node-01 nvidia.com/gpu=present:NoSchedule
# 为 GPU 节点添加标签
kubectl label nodes gpu-node-01 accelerator=nvidia-a100

对应的 GPU 训练任务 Pod

apiVersion: v1
kind: Pod
metadata:
  name: training-job
spec:
  tolerations:
  - key: nvidia.com/gpu
    operator: Equal
    value: present
    effect: NoSchedule
  nodeSelector:
    accelerator: nvidia-a100
  containers:
  - name: trainer
    image: pytorch-training:2.3
    resources:
      limits:
        nvidia.com/gpu: 2

3.3 拓扑分散约束

TopologySpreadConstraints 是控制 Pod 在拓扑域(如可用区、节点)间均匀分布的首选方案,比 podAntiAffinity 更灵活高效。

多维度拓扑分散示例

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 9
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      topologySpreadConstraints:
      # 可用区级别:最大偏差 1,硬约束
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule
        labelSelector:
          matchLabels:
            app: api
      # 节点级别:最大偏差 2,软约束
      - maxSkew: 2
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: api
      containers:
      - name: api
        image: api-server:v3.0

四、开发自定义调度插件

4.1 实现自定义 Score 插件

当内置插件无法满足需求时,例如需要根据 GPU 利用率进行调度,我们可以开发自定义插件。以下是一个根据节点 GPU 利用率打分的 Score 插件示例。

// gpu_score_plugin.go
// 基于节点 GPU 利用率的自定义打分插件
package gpuscore

import (
    "context"
    "fmt"
    "math"
    "strconv"

    v1 "k8s.io/api/core/v1"
    "k8s.io/apimachinery/pkg/runtime"
    "k8s.io/kubernetes/pkg/scheduler/framework"
)

const Name = "GPUUtilizationScore"

type GPUScorePlugin struct {
    handle framework.Handle
}

var _ framework.ScorePlugin = &GPUScorePlugin{}

func (pl *GPUScorePlugin) Name() string {
    return Name
}

func (pl *GPUScorePlugin) Score(ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string) (int64, *framework.Status) {
    nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
    if err != nil {
        return 0, framework.AsStatus(fmt.Errorf("获取节点信息失败: %v", err))
    }
    node := nodeInfo.Node()

    // 从节点注解读取 GPU 利用率(假设由监控组件定期更新)
    gpuUtil, ok := node.Annotations["gpu-monitor/utilization"]
    if !ok {
        return 50, nil // 没有信息,给中间分
    }

    utilization, err := strconv.ParseFloat(gpuUtil, 64)
    if err != nil {
        return 50, nil
    }

    // 利用率越低,分数越高(0-100 范围)
    score := int64(math.Round(100 - utilization))
    if score < 0 {
        score = 0
    }
    if score > 100 {
        score = 100
    }
    return score, nil
}

func (pl *GPUScorePlugin) ScoreExtensions() framework.ScoreExtensions {
    return nil
}

func New(_ context.Context, _ runtime.Object, h framework.Handle) (framework.Plugin, error) {
    return &GPUScorePlugin{handle: h}, nil
}

4.2 注册并构建自定义调度器

编写插件的 main 函数,将其注册到调度框架中。

// cmd/scheduler/main.go
package main

import (
    "os"

    "k8s.io/component-base/cli"
    "k8s.io/kubernetes/cmd/kube-scheduler/app"

    gpuscore "example.com/gpu-scheduler/pkg/gpuscore"
)

func main() {
    // 创建调度器命令,注册自定义插件
    command := app.NewSchedulerCommand(
        app.WithPlugin(gpuscore.Name, gpuscore.New),
    )

    code := cli.Run(command)
    os.Exit(code)
}

4.3 部署与使用

编译并部署你的自定义调度器,Pod 通过 spec.schedulerName 字段指定使用它。

#!/bin/bash
# build-scheduler.sh
set -euo pipefail

SCHEDULER_NAME="gpu-aware-scheduler"
IMAGE_TAG="v1.0.0"
REGISTRY="registry.internal.com/infra"

echo "=== 编译自定义调度器 ==="
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build \
  -ldflags="-s -w" \
  -o bin/${SCHEDULER_NAME} \
  ./cmd/scheduler/

echo "=== 构建容器镜像 ==="
docker build -t "${REGISTRY}/${SCHEDULER_NAME}:${IMAGE_TAG}" \
  --build-arg BINARY=${SCHEDULER_NAME} .

echo "=== 部署到集群 ==="
kubectl apply -f deploy/scheduler-deployment.yaml

五、生产环境最佳实践与避坑指南

5.1 资源请求与限制:调度的基石

务必牢记:调度器只看 requests,不看 limitsrequests 是调度和节点资源分配的凭据,limits 是运行时限制。

  • 设置合理的比例:对于在线服务,CPU requests:limits 可按 1:2 到 1:4 设置以允许突发,内存建议 1:1 到 1:1.5 以防止 OOM。
  • 使用 LimitRange 设置默认值:防止因未设置 requests 而创建大量 BestEffort Pod,导致资源竞争和不稳定。
  • 利用 ResourceQuota 进行总量控制:在命名空间级别限制资源总量,实现多租户间的资源隔离。

5.2 性能优化与大规模集群

当集群节点数量超过 500 时,默认配置可能成为瓶颈。

  • 调整 percentageOfNodesToScore:该参数控制打分阶段评估的节点比例。对于大规模集群,设置为 10-30 可显著降低调度延迟,同时基本不影响调度质量。
  • 善用 PriorityClass:建立清晰的优先级体系,确保系统组件和核心业务在资源紧张时优先被调度,甚至可以通过抢占低优先级任务获得资源。

5.3 常见调度失败与排查

Pod 处于 Pending 状态是常见问题,可通过 kubectl describe pod 查看事件快速定位。

  • Insufficient cpu/memory:检查 Podrequests 是否设置过高,或集群整体资源是否真的不足。
  • node(s) didn‘t match Pod’s node affinity:核对 PodnodeSelectornodeAffinity 规则与节点实际标签是否匹配。
  • node(s) had taint:确认 Pod 是否配置了对应的 tolerations
  • Too many pods:节点已达 kubelet--max-pods 限制,需调整该参数或将 Pod 分散到其他节点。

5.4 需要警惕的陷阱

  • podAntiAffinity 的性能代价requiredDuringScheduling 类型的反亲和规则在大规模集群中可能导致 O(n²) 的计算复杂度,尽量使用 preferredDuringSchedulingTopologySpreadConstraints
  • Request 设置过低:虽然能轻松调度,但实际运行时若资源使用远超 requests,在节点压力大时该 Pod 会优先被驱逐(kubelet 依据 (实际使用 - requests) 排序)。
  • 配置的版本兼容性:注意 KubeSchedulerConfiguration 的 API 版本(v1beta1, v1beta2, v1),不同 Kubernetes 版本支持情况不同,错误版本会导致调度器启动失败。

六、总结与展望

深入理解 Kubernetes Pod 调度,是从“会用”到“精通”容器化集群管理的关键一步。从内置的 Filter/Score 插件机制,到利用亲和性、污点、拓扑约束进行精细控制,再到通过 Scheduling Framework 开发自定义插件,我们拥有强大的工具来塑造集群的调度行为,使其更好地服务于多样的工作负载和业务目标。

在运维大规模、多租户的生产集群时,结合 ResourceQuotaLimitRangePriorityClass 以及监控告警,可以构建一个既高效又稳定的调度体系。当然,社区也在不断演进,像 Kueue(批处理作业队列)、Volcano(高性能计算调度)等高级调度器,以及 Descheduler(重新平衡集群)等工具,为我们解决更复杂的场景提供了方案。如果你在实践过程中有更多心得或遇到了有趣的调度难题,欢迎来到云栈社区与大家一起探讨。




上一篇:工业包装实测:基于Jetson与AI的仓库减废方案落地全解
下一篇:物联网系统架构深度解析:四层模型与通信技术详解,以智能农业大棚为例
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-3-7 05:31 , Processed in 0.497979 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表