不能直接修改 default-scheduler 源码,因其为独立二进制,修改即维护 fork 分支,导致升级困难、安全滞后、无法享受调度框架演进;应通过编写外部调度器实现可维护扩展。

为什么不能直接修改 default-scheduler 的源码
Kubernetes 官方 default-scheduler 是一个独立的、编译好的二进制,它和 kube-apiserver 通过 watch + REST 交互。你改它的 Go 源码再编译,等于在维护一个 fork 分支——升级困难、安全补丁滞后、无法享受社区调度框架(如 scheduler framework)的演进红利。
真正可维护的做法是:用 Go 写一个**外部调度器(custom scheduler)**,监听未调度 Pod(Pod.Spec.NodeName == ""),调用调度逻辑,再 PATCH 或 UPDATE Pod 绑定到目标节点。
- 必须监听
Pod资源的Unscheduledphase(即Pod.Status.Phase == "Pending"且Pod.Spec.NodeName == "") - 不能监听
Node变化来触发调度——那是调度器的下游行为,不是起点 - 绑定操作必须用
schedulerclientset.SchedulingV1().Pods(pod.Namespace).Bind(context, bind, metav1.CreateOptions{}),而不是 PATCH;否则可能被 admission controller 拦截或绕过调度策略校验
如何用 client-go 正确监听 Pending Pod 并过滤未调度状态
很多人直接 list/watch 所有 Pod,结果发现大量已调度 Pod 也被拉下来,CPU 和内存白白浪费。关键在于:要组合两个条件过滤,不是只看 Phase == Pending。
-
Pod.Status.Phase == "Pending"是必要但不充分条件——有些 Pending Pod 已经被其他调度器绑定了,只是 status 还没同步完 - 必须同时检查
Pod.Spec.NodeName == "",这才是“真正待调度”的唯一可靠标志 - 推荐用 field selector:
fieldSelector=spec.nodeName==,status.phase=Pending,比在内存里遍历过滤更省资源 - 注意 client-go 的
Informer默认不缓存 status 字段的全部内容,确保你用的是cache.NewSharedIndexInformer+podInformer.Lister().Pods(ns).Get(name)获取最新状态
示例片段:
立即学习“go语言免费学习笔记(深入)”;
listOptions := metav1.ListOptions{
FieldSelector: "spec.nodeName==,status.phase=Pending",
}
podInformer := cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
return clientset.CoreV1().Pods(metav1.NamespaceAll).List(context.TODO(), listOptions)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
return clientset.CoreV1().Pods(metav1.NamespaceAll).Watch(context.TODO(), listOptions)
},
},
&corev1.Pod{},
0,
cache.Indexers{},
)
调度决策后如何安全完成 Bind 操作
写完调度算法选出 nodeName,下一步不是 PATCH Pod,也不是直接改 Spec.NodeName——那会绕过调度框架的 binding cycle,导致 Pod.Status.Conditions 缺失 Scheduled=True,后续 controller(如 daemonset controller)可能反复干扰。
- 必须使用
scheme.Scheme.Convert()将v1.Binding对象转成schedulingv1.Binding(K8s 1.22+ 强制要求) - Binding 对象的
Target.Name必须是真实存在的 Node 名(大小写敏感),且该 Node 的Status.Conditions中Type=="Ready"的Status=="True" - 如果用
clientset.CoreV1().Pods(pod.Namespace).Bind(),需传入metav1.CreateOptions{},不能传空 struct;否则某些版本 API server 会返回405 Method Not Allowed - Bind 失败常见错误:
Binding not allowed for namespaced object—— 表明你用了corev1.Binding而不是schedulingv1.Binding
为什么你的 custom scheduler 启动后没反应
最常被忽略的点:RBAC 权限不全,或者没开 --authentication-token-webhook 和 --authorization-mode=Node,RBAC(尤其在 minikube 或 kind 集群中默认关了 webhook)。
- ServiceAccount 至少需要:
get/list/watchonpods,get/watchonnodes,createonbindings(注意是scheduling.k8s.io/v1组) - ClusterRole 示例中漏掉
resourceNames不影响,但漏掉verbs: ["create"]onbindings就完全 bind 不了 - 本地调试时,用
kubectl auth can-i create bindings --namespace=default --as=system:serviceaccount:default:my-scheduler-sa快速验证权限 - 如果日志里只有 “no events received”,八成是 Informer 没 start,检查是否忘了调
informer.Run(stopCh)
调度器不是写完就能跑的服务,它高度依赖集群侧的配置对齐。哪怕算法逻辑 100% 正确,缺一个 create bindings 权限,它就永远卡在 pending 状态——而且不会报错,只会静默跳过。










