新闻详情

新闻详情

首页 / 资讯中心 / 详情

Kubernetes调度器插件开发实战:从源码到集群的Filter与Score扩展

发布时间:2026/9/28 1:24:13来源:尧图网络
Kubernetes调度器插件开发实战:从源码到集群的Filter与Score扩展
简介本资源为基于K8s调度框架扩展Kubernetes调度器插件的示例源码与项目说明面向计算机相关专业的高校学生、教师及云原生方向从业者可用于课程设计、毕业设计或调度器二次开发的学习参考。压缩包共31个文件约87KB以yaml、yml配置清单和go源码为主辅以xml、txt说明文档、Dockerfile、Makefile及Helm Chart模板覆盖插件实现、部署配置与依赖管理各环节。项目代码完整、资料齐全含设计文档并经过测试可正常运行。已有72人学习下载。读者可从中获取调度框架扩展点的具体实现思路、插件注册与调度流程的代码组织方式以及Helm打包与容器化部署的配置范例适合在掌握基础后修改扩展实现自定义调度策略。1. 调度器插件到底改了什么从一次 Pod 卡在 Pending 说起线上一个 3 节点测试集群提交一个带nodeSelector的 Deployment副本数 6结果 4 个 Pod 卡在 Pending 十几分钟不动。kubectl describe pod翻到 Events 最后一行写着0/3 nodes are available: 3 node(s) didnt match Pods node affinity/selector。可节点标签明明打对了kubectl get nodes --show-labels里disktypessd清清楚楚。这种时候大多数人第一反应是去查标签、查污点、查资源但真正的问题往往出在调度器插件链上——某个扩展点在打分阶段把节点分数压到了负数或者 Filter 阶段被一个自定义插件提前拦截了。Kubernetes 调度器插件Scheduler Plugin就是干这个的它把原本写死在kube-scheduler里的 Predicates 和 Priorities 拆成一个个可插拔的扩展点让你在 PreFilter、Filter、PostFilter、PreScore、Score、Reserve、Permit、Bind 这些阶段插入自己的逻辑。标题里说的「基于 K8s 调度框架扩展 Kubernetes 调度器插件示例源码」本质就是一套能编译、能部署、能验证的最小插件工程。它解决的不是「调度器怎么装」而是「我想让调度结果按我的业务规则走代码该写在哪、怎么注册、怎么调参、怎么确认它真的生效了」。适合已经能跑起一个集群、写过 Operator 或 Webhook、想往调度层再深一层的后端和平台工程师。如果你还在k8s安装部署阶段这篇可以先收藏等集群稳了再回来照着做。2. 调度框架的扩展点与插件注册先搞清代码往哪写2.1 调度框架把一次调度拆成了哪些阶段要写插件先得知道kube-scheduler一次调度 Pod 的完整生命周期。调度框架Scheduling Framework把过程切成两段调度周期Scheduling Cycle和绑定周期Binding Cycle。调度周期是串行的同一时刻只处理一个 Pod绑定周期可以并发。调度周期里的扩展点按顺序是PreFilter预处理 Pod 信息检查集群级别的前置条件可以往 CycleState 里塞数据给后面用。Filter过滤节点返回的节点才能进入打分。等价于老的 Predicates。PostFilterFilter 全部失败后触发典型用途是抢占Preemption。PreScore打分前的预处理生成共享状态。Score给每个通过 Filter 的节点打分返回 0 到 100 的整数多个插件分数按权重加权。Reserve为 Pod 预留资源失败会触发 Unreserve。Permit允许、拒绝或等待 Pod等待用于 gang scheduling 这类场景。绑定周期里是PreBind、Bind、PostBind。插件通过实现对应接口来挂载到这些点。一个插件可以实现多个接口比如同时实现 Filter 和 Score。理解这张阶段图的价值在于你写代码前先想清楚「我的规则是硬性排除还是软性偏好」。硬性排除写 Filter软性偏好写 Score。写错阶段是新手最常见的翻车点——把本该 Filter 的规则塞进 Score结果节点分数再低也不会被排除Pod 照样调度上去。2.2 插件注册的三种方式和选哪种插件要生效必须注册到调度器的插件配置里。常见做法有三种第一种是编译进kube-scheduler二进制通过KubeSchedulerConfiguration的profiles[].plugins字段启用。这是最稳的方式适合生产。第二种是 out-of-tree 插件用--config指定配置插件以独立进程或共享库形式加载。Kubernetes 官方对 out-of-tree 的支持一直在演进早期靠调度器扩展器Scheduler Extender走 HTTP现在更推荐实现框架接口后重新编译。第三种是直接用 Scheduler Extender通过 webhook 方式在 Filter 和 Prioritize 阶段回调外部服务。它不用改调度器代码但性能差、扩展点少只适合轻量规则。我一般会推荐第一种把插件作为独立 Go module 写然后在调度器启动配置里注册。下面是一个最小插件骨架实现 Filter 和 Score 两个接口package myplugin import ( context fmt v1 k8s.io/api/core/v1 k8s.io/apimachinery/pkg/runtime k8s.io/kubernetes/pkg/scheduler/framework ) const Name MyPlugin // MyPlugin 实现 Filter 和 Score 两个扩展点 type MyPlugin struct { handle framework.Handle } var _ framework.FilterPlugin MyPlugin{} var _ framework.ScorePlugin MyPlugin{} // Name 返回插件名必须与配置里 plugins 字段一致 func (p *MyPlugin) Name() string { return Name } // Filter 阶段节点不满足条件直接排除 func (p *MyPlugin) Filter( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo, ) *framework.Status { node : nodeInfo.Node() if node nil { return framework.NewStatus(framework.Error, node not found) } // 示例规则只允许调度到带 envprod 标签的节点 if node.Labels[env] ! prod { return framework.NewStatus( framework.Unschedulable, fmt.Sprintf(node %s missing envprod label, node.Name), ) } return framework.NewStatus(framework.Success, ) } // Score 阶段返回 0-100 的分数越高越优先 func (p *MyPlugin) Score( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeName string, ) (int64, *framework.Status) { nodeInfo, err : p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName) if err ! nil { return 0, framework.AsStatus(err) } // 示例规则节点 CPU 空闲越多分越高 allocatable : nodeInfo.Allocatable.MilliCPU requested : nodeInfo.Requested.MilliCPU if allocatable 0 { return 0, framework.NewStatus(framework.Success, ) } freeRatio : float64(allocatable-requested) / float64(allocatable) return int64(freeRatio * 100), framework.NewStatus(framework.Success, ) } // ScoreExtensions 返回 nil 表示不需要 NormalizeScore func (p *MyPlugin) ScoreExtensions() framework.ScoreExtensions { return nil } // New 是插件工厂函数调度器通过它实例化插件 func New( _ runtime.Object, h framework.Handle, ) (framework.Plugin, error) { return MyPlugin{handle: h}, nil }这段代码的关键点Name()返回的字符串必须和配置文件里plugins下的名字完全一致大小写敏感。Filter返回framework.Unschedulable表示节点被排除返回framework.Error表示插件自身出错调度器会记录错误但通常不会直接判节点不可用。Score返回的分数会被框架归一化到 0-100 区间如果你有多个打分插件最终分数是各插件分数乘以权重后求和。ScoreExtensions()返回 nil 表示不做额外归一化如果多个节点分数分布很集中可以实现NormalizeScore把分数拉开。2.3 把插件注册进调度器配置插件写完后要在KubeSchedulerConfiguration里声明。下面是一个最小配置apiVersion: kubescheduler.config.k8s.io/v1 kind: KubeSchedulerConfiguration profiles: - schedulerName: my-scheduler plugins: filter: enabled: - name: MyPlugin score: enabled: - name: MyPlugin # 权重不写默认是 1多个插件时按需调整 pluginConfig: - name: MyPlugin args: # 自定义参数通过 args 传入插件工厂里解析 threshold: 0.3profiles[].schedulerName是调度器名字Pod 里spec.schedulerName要写这个值才会走你的调度器。plugins下按扩展点分组enabled是启用disabled是禁用。pluginConfig用来传自定义参数插件工厂函数的第一个参数runtime.Object就是这里反序列化出来的。参数说明threshold是我示例里预留的字段实际解析需要在New函数里用runtime.DecodeInto或类型断言处理。权重字段是weight写在插件名同级比如- name: MyPlugin下面加weight: 3。权重只对 Score 类插件有意义Filter 类插件没有权重概念。提示改完配置后调度器需要重启或重新加载。生产环境建议先用一个独立schedulerName做灰度别直接改默认调度器否则出问题影响面是整个集群。3. 从源码到集群编译、部署与验证的最小闭环3.1 编译调度器二进制的两种路径插件代码要生效必须和kube-scheduler一起编译。常见做法有两种。第一种是 forkkubernetes/kubernetes仓库把插件代码放到pkg/scheduler/framework/plugins/下然后在pkg/scheduler/framework/plugins/registry.go里注册。这种方式编译出来的就是完整调度器但仓库巨大编译慢升级 Kubernetes 版本时合并冲突多。第二种是独立仓库用k8s.io/kubernetes作为依赖自己写main.go调用调度器的Run函数。这种方式更干净推荐。核心代码大概长这样package main import ( os k8s.io/component-base/cli k8s.io/kubernetes/cmd/kube-scheduler/app k8s.io/kubernetes/pkg/scheduler/framework myproject/pkg/myplugin ) func main() { // 把自定义插件注册到调度器的插件注册表 command : app.NewSchedulerCommand( app.WithPlugin(myplugin.Name, myplugin.New), ) code : cli.Run(command) os.Exit(code) }app.WithPlugin是官方提供的注册入口第一个参数是插件名第二个是工厂函数。这样编译出来的二进制就是带自定义插件的调度器。编译命令# 依赖对齐go.mod 里 k8s.io/kubernetes 版本要和目标集群一致 go mod tidy # 编译输出到 bin 目录 CGO_ENABLED0 go build -o bin/my-scheduler ./cmd/my-scheduler参数说明CGO_ENABLED0是为了静态编译方便扔进 scratch 镜像。k8s.io/kubernetes的版本必须和集群版本对齐比如集群是 v1.26依赖也要用 v1.26.x否则 API 不兼容编译能过但运行时可能 panic。3.2 部署自定义调度器到集群编译出二进制后打成镜像用 Deployment 部署。关键点是调度器自己不能被自己调度要指定默认调度器或者用 nodeName 固定。apiVersion: apps/v1 kind: Deployment metadata: name: my-scheduler namespace: kube-system spec: replicas: 1 selector: matchLabels: app: my-scheduler template: metadata: labels: app: my-scheduler spec: serviceAccountName: my-scheduler # 关键调度器 Pod 用默认调度器避免自己调度自己 schedulerName: default-scheduler containers: - name: scheduler image: registry.example.com/my-scheduler:v1 command: - /my-scheduler args: - --config/etc/kubernetes/my-scheduler-config.yaml - --v4 volumeMounts: - name: config mountPath: /etc/kubernetes volumes: - name: config configMap: name: my-scheduler-config--v4是日志级别调试插件时开到 4 或 5 能看到每个扩展点的执行日志。--config指向配置文件配置文件通过 ConfigMap 挂进去。ServiceAccount 需要绑定能 list/watch Node、Pod、PV 等资源的 ClusterRole最省事的做法是直接绑定系统自带的system:kube-schedulerClusterRole。3.3 验证插件真的生效了部署完别急着上业务先用一个测试 Pod 验证。创建一个指定schedulerName: my-scheduler的 PodapiVersion: v1 kind: Pod metadata: name: test-myplugin spec: schedulerName: my-scheduler containers: - name: nginx image: nginx:1.25 resources: requests: cpu: 100m提交后看三处kubectl get pod test-myplugin -o wide看是否调度成功、落在哪个节点kubectl describe pod test-myplugin看 Events 里有没有你插件返回的 messagekubectl logs -n kube-system deploy/my-scheduler看插件日志。如果 Pod 一直 PendingEvents 里出现node(s) missing envprod label说明 Filter 生效了只是没有节点满足条件。这时候给某个节点打上envprod标签再试kubectl label node node-1 envprod再提交一次Pod 应该能调度上去。这一步验证的是 Filter。验证 Score 稍微麻烦点需要两个都满足 Filter 的节点然后观察 Pod 是否优先落在空闲 CPU 多的节点上。可以反复创建删除 Pod看分布是否符合预期。注意调试阶段建议把调度器日志级别开到--v5框架会打印每个插件在每个节点的打分结果。生产环境记得调回 2 或 3否则日志量爆炸。4. 避坑与排查插件不生效、Pod 卡 Pending、分数不按预期4.1 插件名大小写不一致导致静默失效现象配置里写了MyPlugin代码里Name()返回myplugin调度器启动不报错但插件完全不执行Pod 调度行为和默认调度器一模一样。原因调度框架按字符串精确匹配插件名大小写不一致时配置里的插件找不到对应实现框架会跳过而不是报错。解决把Name()返回值、配置文件里的name、app.WithPlugin的第一个参数三处统一。建议定义一个常量三处都引用它别手写字符串。4.2 Filter 返回 Error 而不是 Unschedulable现象插件里判断节点不满足条件时返回了framework.Error结果 Pod 卡 PendingEvents 里显示插件内部错误但节点其实只是不匹配规则。原因Error表示插件自身异常框架会把它当作调度失败处理但语义上不是「节点不合适」。大量 Error 会污染调度器指标排查时容易误判。解决业务规则不满足用framework.Unschedulable只有真正遇到无法处理的异常比如快照读取失败才用framework.Error。两者的区别在日志和指标里体现得很明显。4.3 Score 分数被其他插件淹没现象自定义 Score 插件逻辑没问题但 Pod 分布完全看不出偏好好像分数没起作用。原因默认调度器自带NodeResourcesBalancedAllocation、ImageLocality、InterPodAffinity等多个打分插件它们的分数加权后可能盖过你的插件。你的插件权重默认是 1别人也是 1但别人分数分布更分散。解决在配置里给你的插件加weight比如weight: 5或者实现NormalizeScore把分数拉开。调权重前先用--v5看各插件实际打分确认你的分数确实被计算了再决定加多少。4.4 调度器版本和集群版本不匹配现象自定义调度器能启动但调度 Pod 时报 API 版本错误或者某些字段解析失败。原因k8s.io/kubernetes依赖版本和集群 apiserver 版本不一致客户端和服务端 API 有差异。解决go.mod里k8s.io/kubernetes、k8s.io/api、k8s.io/apimachinery三个版本要和集群版本严格对齐。升级集群时调度器镜像也要同步重新编译。别用latest调度器这种核心组件版本漂移的代价很高。4.5 调度器 Pod 被自己调度导致死锁现象自定义调度器 Deployment 的 Pod 一直 Pending集群里其他 Pod 也调度不了。原因调度器 Pod 没有指定schedulerName被自己调度但它自己还没起来形成死锁。解决调度器 Deployment 的spec.template.spec.schedulerName显式写default-scheduler或者用nodeName直接固定节点。这是部署自定义调度器的铁律第一次部署时最容易忘。5. 进阶用 CycleState 做跨阶段数据传递与插件性能验证插件写顺了之后下一步是让它更聪明。调度框架提供了一个CycleState生命周期覆盖单个 Pod 的整个调度周期可以在PreFilter里算好数据在Filter和Score里直接读避免重复计算。这是提升插件性能最直接的手段。用法是定义一个 state key实现framework.StateData接口type precomputedData struct { allowedNodes map[string]bool } // Clone 必须实现框架在并发场景会调用 func (d *precomputedData) Clone() framework.StateData { cp : precomputedData{allowedNodes: make(map[string]bool, len(d.allowedNodes))} for k, v : range d.allowedNodes { cp.allowedNodes[k] v } return cp } const stateKey MyPluginPrecomputed // PreFilter 里写入 func (p *MyPlugin) PreFilter( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodes []*v1.Node, ) (*framework.PreFilterResult, *framework.Status) { data : precomputedData{allowedNodes: map[string]bool{}} for _, n : range nodes { if n.Labels[env] prod { data.allowedNodes[n.Name] true } } state.Write(stateKey, data) return nil, framework.NewStatus(framework.Success, ) } // Filter 里读取避免重复遍历 func (p *MyPlugin) Filter( ctx context.Context, state *framework.CycleState, pod *v1.Pod, nodeInfo *framework.NodeInfo, ) *framework.Status { raw, err : state.Read(stateKey) if err ! nil { return framework.AsStatus(err) } data : raw.(*precomputedData) if !data.allowedNodes[nodeInfo.Node().Name] { return framework.NewStatus(framework.Unschedulable, not in allowed set) } return framework.NewStatus(framework.Success, ) }Clone()必须实现因为框架在某些并发路径下会复制 state。state.Write和state.Read的 key 用常量别用裸字符串避免拼写错误。PreFilter返回的PreFilterResult可以用来提前缩小节点范围返回 nil 表示不缩小。性能验证方面调度器自带 Prometheus 指标重点看两个scheduler_framework_extension_point_duration_seconds按扩展点和插件名分桶能看出你的插件耗时scheduler_schedule_attempts_total看调度成功失败比例。部署时给调度器加--metrics-bind-address:10259然后 curl 拉指标kubectl port-forward -n kube-system deploy/my-scheduler 10259:10259 curl -s localhost:10259/metrics | grep MyPlugin如果Filter的 P99 超过几毫秒就要考虑把重逻辑挪到PreFilter或者加缓存。调度器是集群里对延迟最敏感的组件之一插件慢一点整个集群的调度吞吐就掉一截。我自己的习惯是任何插件上线前先在测试集群用 500 个 Pod 压一遍看scheduler_framework_extension_point_duration_seconds的 P99超过 5ms 就回去优化。这个数字不是官方标准是我踩过几次坑之后给自己定的线。调度器插件这东西功能对不对容易验证性能好不好往往要等集群规模上来才暴露提前压测能省很多后悔药。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Linux字符设备驱动实战:从beep蜂鸣器到多实例设备 2026/9/28 2:15:06

Linux字符设备驱动实战:从beep蜂鸣器到多实例设备

简介:这份资源面向嵌入式Linux驱动开发初学者与IMX6uLL开发板使用者,聚焦蜂鸣器驱动从内核模块到用户态调用的完整实现,帮助读者理解GPIO控制、驱动加载与应用程序交互的基本流程。压缩包共5个文件,约8KB,包含2个C源文…

阅读更多 →
【KivyMD】KivyMD 1.1.1 Icons在应用设计中的魅力 2026/9/28 2:14:59

【KivyMD】KivyMD 1.1.1 Icons在应用设计中的魅力

Material Design Icons作为Google在界面设计领域的重要革新,为开发者和设计师提供了一套覆盖广泛且极具辨识度的图标集。这些图标不仅风格统一,且易于用户理解,自其推出以来,便迅速成为众多应用和网站的首选。 在移动互联网迅速发展的背景下,拥有这样一套实用且视觉效果出…

阅读更多 →
【KivyMD】KivyMD 1.1.1 MDAnchorLayout 锚点布局 2026/9/28 2:14:59

【KivyMD】KivyMD 1.1.1 MDAnchorLayout 锚点布局

MDAnchorLayout 是 Kivy 框架中 AnchorLayout 的一次重要进化,旨在结合 Material Design 风格,为开发者提供更具现代感的布局方案。在传统的 Kivy 布局中,AnchorLayout 以其简洁高效的锚点布局方式备受开发者青睐,允许通过固定锚点轻松实现小部件的布局和定位。而 MDAnchor…

阅读更多 →
【KivyMD】KivyMD 1.1.1 Theming 主体化 2026/9/28 2:14:53

【KivyMD】KivyMD 1.1.1 Theming 主体化

当今时代移动应用已成为日常生活中不可或缺的一部分,而一个引人入胜且用户友好的界面设计无疑是吸引用户的重要因素之一。界面设计不仅需要满足功能性的要求,还需要在视觉上给用户留下深刻印象,这就需要开发者在设计时考虑如何将功能性与美观性完美融合。Google的Material D…

阅读更多 →
【KivyMD】KivyMD 2.0.1 Theming 定主题色彩方案 2026/9/28 2:14:53

【KivyMD】KivyMD 2.0.1 Theming 定主题色彩方案

在现代应用开发中,视觉一致性和品牌识别对用户体验至关重要。尤其是在基于 KivyMD 框架的开发环境中,通过选择合适的主题色彩方案,能够提升应用的整体美感,并增强用户与应用的互动体验。 本文将深入探讨 primary_palette 属性的使用,分析它如何影响应用的主题风格,并通过…

阅读更多 →
【KivyMD】KivyMD 2.0.1 Theming 自定义字体样式 2026/9/28 2:14:53

【KivyMD】KivyMD 2.0.1 Theming 自定义字体样式

在 Kivy 框架中,LabelBase 提供了一种灵活的方式来实现自定义字体的管理,通过注册外部字体文件,开发者可以在应用中呈现个性化的文本效果。这种机制对于应用的品牌塑造、视觉设计和多语言支持有着重要的意义。通过 LabelBase.register 方法,开发者可以轻松地引入和管理自定…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉