调度器如何选择节点
CubeMaster 的调度器负责从集群中选择一个合适的节点来运行沙箱。它采用 5 阶段选择器链,类似 Kubernetes 的调度框架设计。
1. 调度器全景
2. 初始化:InitScheduler
CubeMaster/pkg/scheduler/init.go 的 InitScheduler() 注册所有选择器:
func InitScheduler(ctx context.Context) {
scheduler.preSelector = prefilter.NewPreFilter()
scheduler.backoffSelector = backofffilter.NewBackoffFilter()
scheduler.filter = filter.NewSelector()
scheduler.score = score.NewSelector(ctx)
scheduler.postScore = postscore.NewSelector()
initTask(ctx)
}选择器结构
3. Select():调度入口
CubeMaster/pkg/scheduler/schedule.go 的 Select() 是调度入口:
func Select(selCtx *selctx.SelectorCtx) (nodes *node.Node, err error) {
// 1. PreFilter
if err := runPreFilter(selCtx); err != nil {
// 失败时尝试 BackoffFilter
if err = runBackoffFilter(selCtx); err != nil {
return nil, err
}
}
// 2. Filter(并行执行)
if err := runFilter(selCtx, scheduler.filter); err != nil {
return BackoffSelect(selCtx)
}
// 3. Score
if err := runScoreFilter(selCtx, scheduler.score); err != nil {
return nil, err
}
// 4. 最终选择
return selCtx.LeastRandomSelect(config.GetConfig().Scheduler.PrioritySelectNum), nil
}4. 五个阶段详解
4.1 PreFilter:预过滤
职责:从所有节点中快速筛选出可能满足条件的节点子集。
实现位置:CubeMaster/pkg/selector/prefilter/
典型过滤条件:
- 节点状态是否 Ready
- 节点是否有足够的总资源(CPU、内存)
- 节点是否在可用区列表中
4.2 Filter:并行过滤
职责:对 PreFilter 输出的候选节点进行细粒度过滤。
实现位置:CubeMaster/pkg/selector/filter/
关键特性:所有 Filter 并行执行,取交集。
func parallelRunFilters(selCtx *selctx.SelectorCtx, filters []filter.Selector) (node.NodeList, error) {
eg, _ := errgroup.WithContext(selCtx.Ctx)
for _, f := range filters {
f := f
eg.Go(func() error {
result := f.Filter(selCtx)
tmpStat.Add(result)
return nil
})
}
eg.Wait()
return tmpStat.Intersect(), nil // 所有 Filter 结果取交集
}典型 Filter:
- 资源充足性检查(CPU、内存、rootfs)
- 节点亲和性 / 反亲和性
- 模板本地性(template_locality)
- NUMA 拓扑感知
4.3 Score:打分
职责:对通过 Filter 的节点进行打分排序。
实现位置:CubeMaster/pkg/selector/score/
打分维度:
- 资源利用率(倾向选择资源充裕的节点)
- 负载均衡(倾向选择负载低的节点)
- 亲和性加分
4.4 PostScore:后处理
职责:对打分结果进行最后调整。
实现位置:CubeMaster/pkg/selector/postscore/
典型操作:
- 去重
- 最终裁决
4.5 BackoffFilter:兜底过滤
职责:当正常调度路径失败时,提供兜底选择。
触发条件:
- PreFilter 失败
- Filter 失败(无节点通过)
实现位置:CubeMaster/pkg/selector/backofffilter/
5. SelectorCtx:调度上下文
CubeMaster/pkg/scheduler/selctx/SelectorCtx.go 定义了调度上下文,在整个调度过程中传递信息:
type SelectorCtx struct {
Ctx context.Context
ReqRes *RequestResource // 请求的资源量
Nodes node.NodeList // 候选节点列表
LastBadNode node.NodeList // 上次失败的节点(熔断)
// ...
}关键方法
| 方法 | 作用 |
|---|---|
SetNodes(nodes) | 更新候选节点列表 |
Nodes() | 获取当前候选节点 |
AddLastBadNode(node) | 记录失败节点,下次调度避开 |
LeastRandomSelect(n) | 从 top-n 节点中随机选一个 |
LeastRandomSelect
最终选择不是简单取最高分,而是从 top-n 中随机选一个,避免所有沙箱都集中在同一个节点:
func (s *SelectorCtx) LeastRandomSelect(n int) *node.Node {
nodes := s.Nodes()
if nodes.Len() <= n {
return nodes[rand.Intn(nodes.Len())]
}
// 取前 n 个最高分节点,随机选一个
topN := nodes[:n]
return topN[rand.Intn(len(topN))]
}6. 模板本地性特殊处理
如果模板配置了 template_locality(模板必须在特定节点上运行),调度器会跳过 BackoffFilter:
func shouldSkipBackoffForTemplate(selCtx *selctx.SelectorCtx) bool {
templateLocalitySelectorID := constants.SelectorFilterID + "/" + "template_locality"
for _, selector := range scheduler.filter {
if selector.ID() == templateLocalitySelectorID {
return true // 有 template_locality,跳过 Backoff
}
}
return false
}这是因为如果模板只存在于特定节点,BackoffFilter 选择其他节点也没有意义。
7. 完整调度流程
8. 调度器与 Kubernetes 调度器的对比
| 特性 | CubeMaster 调度器 | Kubernetes 调度器 |
|---|---|---|
| 阶段数 | 5 | 3 (Filter → Score → Reserve) |
| Filter 并行 | 是 | 否(串行) |
| 兜底机制 | BackoffFilter | 无(pending) |
| 最终选择 | top-n 随机 | 最高分 |
| 调度队列 | buffer 队列 | 优先级队列 |
| 熔断 | 支持(lastBadNode) | 不支持 |
延伸阅读
- 创建沙箱时 CubeMaster 做了哪些事 — 调度器的调用方
- 进程启动与服务注册 — 调度器初始化
- Cubelet 节点面 — 被选中节点上的执行逻辑