如何内置的 Filter 策略执行成功的话,scheduler 通过 Http 调用 Extender 注册的 webhook, 将调度所需要的 Pod 和 Node 的信息发送到 Extender,根据返回 filter 结果,作为最终结果。
我们可以发现 Extender 存在以下问题:
调用 Extender 的接口是 HTTP 请求,受到网络环境的影响,性能远低于本地的函数调用。同时每次调用都需要将 Pod 和 Node 的信息进行 marshaling 和 unmarshalling 的操作,会进一步降低性能;
用户可以扩展的点比较有限,位置比较固定,无法支持灵活的扩展,例如只能在执行完默认的 Filter 策略后才能调用。
基于以上介绍,Extender 的方式在集群规模较小,调度效率要求不高的情况下,是一个灵活可用的扩展方案,但是在正常生产环境的大型集群中,Extender 无法支持高吞吐量,性能较差。
2)Multiple schedulers
Scheduler 在 Kubernetes 集群中其实类似于一个特殊的 Controller,通过监听 Pod 和 Node 的信息,给 Pod 挑选最佳的节点,更新 Pod 的 spec.NodeName 的信息来将调度结果同步到节点。所以对于部分有特殊的调度需求的用户,有些开发者通过自研 Custom Scheduler 来完成以上的流程,然后通过和 default scheduler 同时部署的方式,来支持自己特殊的调度需求。
Custom Scheduler 会存在一下问题:
如果与 default scheduler 同时部署,因为每个调度器所看到的资源视图都是全局的,所以在调度决策中可能会在同一时刻在同一个节点资源上调度不同的 Pod,导致节点资源冲突的问题;
有些用户将调度器所能调度的资源通过 Label 划分不同的池子,可以避免资源冲突的现象出现。但是这样又会导致整体集群资源利用率的下降;
有些用户选择通过完全自研的方式来替换 default scheduler,这种会带来比较高的研发成本,以及 Kubernetes 版本升级后可能存在的兼容性问题。
Scheduler Extender 的性能较差可是维护成本较小,Custom Scheduler 的研发和维护的成本特别高但是性能较好,这种情况是开发者面临这种两难处境。这时候 Kubernetes Scheduling Framework V2 横空出世,给我们带来鱼和熊掌可以兼得的方案。
新一代调度框架 Scheduling Framework 之解析
社区也逐渐的发现开发者所面临的困境,为了解决如上问题,使 Kube-scheduler 扩展性更好、代码更简洁,社区从 Kubernetes 1.16 版本开始, 构建了一种新的调度框架 Kubernetes Scheduling Framework 的机制。
Scheduling Framework 在原有的调度流程中, 定义了丰富扩展点接口,开发者可以通过实现扩展点所定义的接口来实现插件,将插件注册到扩展点。Scheduling Framework 在执行调度流程时,运行到相应的扩展点时,会调用用户注册的插件,影响调度决策的结果。通过这种方式来将用户的调度逻辑集成到 Scheduling Framework 中。
Framework 的调度流程是分为两个阶段 scheduling cycle 和 binding cycle。
scheduling cycle 是同步执行的,同一个时间只有一个 scheduling cycle,是线程安全的;
binding cycle 是异步执行的,同一个时间中可能会有多个 binding cycle在运行,是线程不安全的。
1. scheduling cycle
scheduling cycle 是调度的核心流程,主要的工作是进行调度决策,挑选出唯一的节点。
1)Queue sort
// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
Plugin
// Less are used to sort pods in the scheduling queue.
Less(*PodInfo, *PodInfo) bool
}
// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodQueueInfo.timestamp.
func (pl *PrioritySort) Less(pInfo1, pInfo2 *framework.QueuedPodInfo) bool {
p1 := pod.GetPodPriority(pInfo1.Pod)
p2 := pod.GetPodPriority(pInfo2.Pod)
return (p1 > p2) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))
}
2)PreFilter
PreFilter 在 scheduling cycle 开始时就被调用,只有当所有的 PreFilter 插件都返回 success 时,才能进入下一个阶段,否则 Pod 将会被拒绝掉,标识此次调度流程失败。PreFilter 类似于调度流程启动之前的预处理,可以对 Pod 的信息进行加工。同时 PreFilter 也可以进行一些预置条件的检查,去检查一些集群维度的条件,判断否满足 pod 的要求。
3)Filter
Filter 插件是 scheduler v1 版本中的 Predicate 的逻辑,用来过滤掉不满足 Pod 调度要求的节点。为了提升效率,Filter 的执行顺序可以被配置,这样用户就可以将可以过滤掉大量节点的 Filter 策略放到前边执行,从而减少后边 Filter 策略执行的次数,例如我们可以把 NodeSelector 的 Filter 放到第一个,从而过滤掉大量的节点。Node 节点执行 Filter 策略是并发执行的,所以在同一调度周期中多次调用过滤器。
4)PostFilter
新的 PostFilter 的接口定义在 1.19 的版本会发布,主要是用于处理当 Pod 在 Filter 阶段失败后的操作,例如抢占,Autoscale 触发等行为。
5)PreScore
PreScore 在之前版本称为 PostFilter,现在修改为 PreScore,主要用于在 Score 之前进行一些信息生成。此处会获取到通过 Filter 阶段的节点列表,我们也可以在此处进行一些信息预处理或者生成一些日志或者监控信息。
6)Scoring
Scoring 扩展点是 scheduler v1 版本中 Priority 的逻辑,目的是为了基于 Filter 过滤后的剩余节点,根据 Scoring 扩展点定义的策略挑选出最优的节点。
Scoring 扩展点分为两个阶段:
打分:打分阶段会对 Filter 后的节点进行打分,scheduler 会调用所配置的打分策略
归一化: 对打分之后的结构在 0-100 之间进行归一化处理
7)Reserve
Reserve 扩展点是 scheduler v1 版本的 assume 的操作,此处会对调度结果进行缓存,如果在后边的阶段发生了错误或者失败的情况,会直接进入 Unreserve 阶段,进行数据回滚。
8)Permit
Permit 扩展点是 framework v2 版本引入的新功能,当 Pod 在 Reserve 阶段完成资源预留之后,Bind 操作之前,开发者可以定义自己的策略在 Permit 节点进行拦截,根据条件对经过此阶段的 Pod 进行 allow、reject 和 wait 的 3 种操作。allow 表示 pod 允许通过 Permit 阶段。reject 表示 pod 被 Permit 阶段拒绝,则 Pod 调度失败。wait 表示将 Pod 处于等待状态,开发者可以设置超时时间。
2. binding cycle
binding cycle 需要调用 apiserver 的接口,耗时较长,为了提高调度的效率,需要异步执行,所以此阶段线程不安全。
1)Bind
Bind 扩展点是 scheduler v1 版本中的 Bind 操作,会调用 apiserver 提供的接口,将 pod 绑定到对应的节点上。
2)PreBind 和 PostBind
开发者可以在 PreBind 和 PostBind 分别在 Bind 操作前后执行,这两个阶段可以进行一些数据信息的获取和更新。
3)UnReserve
UnReserve 扩展点的功能是用于清理到 Reserve 阶段的的缓存,回滚到初始的状态。当前版本 UnReserve 与 Reserve 是分开定义的,未来会将 UnReserve 与 Reserve 统一到一起,即要求开发者在实现 Reserve 同时需要定义 UnReserve,保证数据能够有效的清理,避免留下脏数据。
// QoSSort is a plugin that implements QoS class based sorting.
type Sort struct{}
// New initializes a new plugin and returns it.
func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error) {
return &Sort{}, nil
}
// QueueSortPlugin is an interface that must be implemented by "QueueSort" plugins.
// These plugins are used to sort pods in the scheduling queue. Only one queue sort
// plugin may be enabled at a time.
type QueueSortPlugin interface {
Plugin
// Less are used to sort pods in the scheduling queue.
Less(*PodInfo, *PodInfo) bool
}
// Less is the function used by the activeQ heap algorithm to sort pods.
// It sorts pods based on their priority. When priorities are equal, it uses
// PodInfo.timestamp.
func (*Sort) Less(pInfo1, pInfo2 *framework.PodInfo) bool {
p1 := pod.GetPodPriority(pInfo1.Pod)
p2 := pod.GetPodPriority(pInfo2.Pod)
return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod))
}
func compQOS(p1, p2 *v1.Pod) bool {
p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)
if p1QOS == v1.PodQOSGuaranteed {
return true
} else if p1QOS == v1.PodQOSBurstable {
return p2QOS != v1.PodQOSGuaranteed
} else {
return p2QOS == v1.PodQOSBestEffort
}
}
// cmd/main.go
func main() {
rand.Seed(time.Now().UnixNano())
command := app.NewSchedulerCommand(
app.WithPlugin(qos.Name, qos.New),
)
if err := command.Execute(); err != nil {
os.Exit(1)
}
}
$ make
$ bin/kube-scheduler --kubeconfig=scheduler.conf --config=./manifests/qos/scheduler-config.yaml