深入分析kube-batch(3)——Plugins
在之前启动过程一文中分析过,kube-batch
会先执行plugins,分别是drf/gang/predicates/priority/proportion
,本文将分别分析这四个组件。
ps. plugins一般用于初始化工作,比如数据预处理,校验,注册处理函数等等。
interface
kube-batch\pkg\scheduler\framework\interface.go
type Plugin interface {
OnSessionOpen(ssn *Session)
OnSessionClose(ssn *Session)
}
这里着重分析每个plugin的OnSessionOpen
实现
drf
drf
Dominant Resource Fairness
kube-batch\pkg\scheduler\plugins\drf\drf.go
func (drf *drfPlugin) OnSessionOpen(ssn *framework.Session) {
for _, n := range ssn.Nodes {
drf.totalResource.Add(n.Allocatable)
}
for _, job := range ssn.Jobs {
for status, tasks := range job.TaskStatusIndex {
for _, t := range tasks {
attr.allocated.Add(t.Resreq)
}
}
drf.updateShare(attr)
drf.jobOpts[job.UID] = attr
}
ssn.AddJobOrderFn(jobOrderFn)
ssn.AddEventHandler(&framework.EventHandler{
AllocateFunc: func(event *framework.Event) {
attr := drf.jobOpts[event.Task.Job]
attr.allocated.Add(event.Task.Resreq)
drf.updateShare(attr)
},
EvictFunc: func(event *framework.Event) {
attr := drf.jobOpts[event.Task.Job]
attr.allocated.Sub(event.Task.Resreq)
drf.updateShare(attr)
},
})
}
- 统计集群中所有node可分配资源总量
- 统计Job资源申请,计算资源占比(资源申请/资源总量)
- 注册Job排序函数,根据资源占比进行排序,主要资源占比越低job优先级越高
- 注册事件处理函数,包括分配函数以及驱逐函数,函数实现比较简单,就是当task发生变化时,增加(分配)/减少(驱逐)Job资源申请总量,并且更新资源占比。
可以不使用
gang
kube-batch\pkg\scheduler\plugins\gang\gang.go
func (gp *gangPlugin) OnSessionOpen(ssn *framework.Session) {
for _, job := range ssn.Jobs {
if validTaskNum(job) < job.MinAvailable {
ssn.Discard(job, arbcorev1.UnschedulableEvent, "not enough valid tasks for gang-scheduling")
}
}
ssn.AddPreemptableFn(preemptableFn)
ssn.AddJobOrderFn(jobOrderFn)
ssn.AddJobReadyFn(jobReady)
}
func jobReady(obj interface{}) bool {
job := obj.(*api.JobInfo)
occupid := readyTaskNum(job)
return occupid >= job.MinAvailable
}
- 检查Job的task(Pod)数量,是否满足要求。这里就是
kube-batch
的一个核心逻辑,如果创建了足够数量的Pod(PodGroup),就调度这些Pod。 - 如果不满足数量要求,就
Discard
丢弃,不调度Job。 - 注册抢占函数,如上
- 注册Job排序函数,根据Job是否ready排序,ready的大
- 注册JobReady函数,非pending,即已经调度的task数量大于等于要求数量
总的来说,gang plugin就如同名字一样,实现了batch调度的一个核心逻辑,只有满足数量要求的PodGroup,才可以调度,否则丢弃。
kube-batch\pkg\scheduler\framework\session.go
func (ssn *Session) Discard(job *api.JobInfo, event arbcorev1.Event, reason string) error {
ssn.cache.Backoff(job, event, reason)
delete(ssn.JobIndex, job.UID)
for i, j := range ssn.Jobs {
if j.UID == job.UID {
ssn.Jobs[i] = ssn.Jobs[len(ssn.Jobs)-1]
ssn.Jobs = ssn.Jobs[:len(ssn.Jobs)-1]
break
}
}
return nil
}
发送相关记录事件,并从session中将Job删掉。
predicates
kube-batch\pkg\scheduler\plugins\predicates\predicates.go
func (pp *nodeAffinityPlugin) OnSessionOpen(ssn *framework.Session) {
ssn.AddPredicateFn(func(task *api.TaskInfo, node *api.NodeInfo) error {
nodeInfo := cache.NewNodeInfo(node.Pods()...)
nodeInfo.SetNode(node.Node)
if node.Allocatable.MaxTaskNum <= len(nodeInfo.Pods()) {
return fmt.Errorf("Node <%s> can not allow more task running on it.", node.Name)
}
// NodeSeletor Predicate
fit, _, err := predicates.PodMatchNodeSelector(task.Pod, nil, nodeInfo)
// HostPorts Predicate
fit, _, err = predicates.PodFitsHostPorts(task.Pod, nil, nodeInfo)
// Toleration/Taint Predicate
fit, _, err = predicates.PodToleratesNodeTaints(task.Pod, nil, nodeInfo)
// Pod Affinity/Anti-Affinity Predicate
podAffinityPredicate := predicates.NewPodAffinityPredicate(ni, pl)
fit, _, err = podAffinityPredicate(task.Pod, nil, nodeInfo)
})
}
predicates唯一负责的事情就是注册预选函数,函数来自default scheduler。这里看到预选函数都是硬编码的,后期应该可以跟default-scheduler一样,通过配置文件解耦。顺序也值得关注,一般我们都会将最影响性能的函数放最后,比如pod亲和性。
priority
kube-batch\pkg\scheduler\plugins\priority\priority.go
func (pp *priorityPlugin) OnSessionOpen(ssn *framework.Session) {
ssn.AddTaskOrderFn(taskOrderFn)
ssn.AddJobOrderFn(jobOrderFn)
}
- 注册task排序函数,根据pod优先级排序
- 注册job排序函数,根据job优先级排序
可以不使用
proportion
kube-batch\pkg\scheduler\plugins\proportion\proportion.go
func (pp *proportionPlugin) OnSessionOpen(ssn *framework.Session) {
for _, n := range ssn.Nodes {
pp.totalResource.Add(n.Allocatable)
}
for _, task := range ssn.Others {
pp.totalResource.Sub(task.Resreq)
}
for _, job := range ssn.Jobs {
if _, found := pp.queueOpts[job.Queue]; !found {
queue := ssn.QueueIndex[job.Queue]
pp.queueOpts[job.Queue] = &queueAttr{...}
}
for status, tasks := range job.TaskStatusIndex {
for _, t := range tasks {
attr := pp.queueOpts[job.Queue]
attr.allocated.Add(t.Resreq)
attr.request.Add(t.Resreq)
}
}
}
函数比较长,分开介绍。
- 统计集群中所有node可分配资源总量,这个与之前的drf重复了,后期可以考虑优化
- 资源总量减去其他调度器分配的资源
- 根据Job的资源申请,初始化queue,这里queue就是namespace。
remaining := pp.totalResource.Clone()
meet := map[api.QueueID]struct{}{}
for {
totalWeight := int32(0)
for _, attr := range pp.queueOpts {
if _, found := meet[attr.queueID]; found {
continue
}
totalWeight += attr.weight
}
// Calculates the deserved of each Queue.
deserved := api.EmptyResource()
for _, attr := range pp.queueOpts {
if _, found := meet[attr.queueID]; found {
continue
}
attr.deserved.Add(remaining.Clone().Multi(float64(attr.weight) / float64(totalWeight)))
if !attr.deserved.LessEqual(attr.request) {
attr.deserved = helpers.Min(attr.deserved, attr.request)
meet[attr.queueID] = struct{}{}
}
pp.updateShare(attr)
deserved.Add(attr.deserved)
}
remaining.Sub(deserved)
if remaining.IsEmpty() {
break
}
}
- 统计所有queue的资源权重
- 根据
剩余资源*权重/总权重
计算每个queue应得到的资源数量, - 如果应得资源大于申请,就让应得资源等于资源申请
- 计算queue
分配/应得
,分配数量就是资源申请数量 - 统计应得资源分配总数
- 计算剩余资源,如果不剩资源就跳出循环。
ssn.AddQueueOrderFn(fun(){...})
ssn.AddReclaimableFn(func(reclaimer *api.TaskInfo, reclaimees []*api.TaskInfo) []*api.TaskInfo {
var victims []*api.TaskInfo
allocations := map[api.QueueID]*api.Resource{}
for _, reclaimee := range reclaimees {
job := ssn.JobIndex[reclaimee.Job]
attr := pp.queueOpts[job.Queue]
if _, found := allocations[job.Queue]; !found {
allocations[job.Queue] = attr.allocated.Clone()
}
allocated := allocations[job.Queue]
if allocated.Less(reclaimee.Resreq) {
continue
}
allocated.Sub(reclaimee.Resreq)
if attr.deserved.LessEqual(allocated) {
victims = append(victims, reclaimee)
}
}
return victims
})
ssn.AddOverusedFn(func(obj interface{}) bool {
queue := obj.(*api.QueueInfo)
attr := pp.queueOpts[queue.UID]
return attr.deserved.LessEqual(attr.allocated)
})
// Register event handlers.
ssn.AddEventHandler(&framework.EventHandler{
AllocateFunc: func(event *framework.Event) {
job := ssn.JobIndex[event.Task.Job]
attr := pp.queueOpts[job.Queue]
attr.allocated.Add(event.Task.Resreq)
pp.updateShare(attr)
},
EvictFunc: func(event *framework.Event) {
job := ssn.JobIndex[event.Task.Job]
attr := pp.queueOpts[job.Queue]
attr.allocated.Sub(event.Task.Resreq)
pp.updateShare(attr)
},
})
}
- 注册queue排序函数,根据比重
分配/应得
排序 - 注册回收函数
- 注册overused超用函数,比较应得和分配的资源数量
- 注册事件处理函数,包括分配函数以及驱逐函数,当task发生变化时,增加(分配)/减少(驱逐)Job资源分配总量,并且更新资源占比。