深入分析kube-batch(3)——Plugins

深入分析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)
      },
   })
}
  1. 统计集群中所有node可分配资源总量
  2. 统计Job资源申请,计算资源占比(资源申请/资源总量)
  3. 注册Job排序函数,根据资源占比进行排序,主要资源占比越低job优先级越高
  4. 注册事件处理函数,包括分配函数以及驱逐函数,函数实现比较简单,就是当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
}
  1. 检查Job的task(Pod)数量,是否满足要求。这里就是kube-batch的一个核心逻辑,如果创建了足够数量的Pod(PodGroup),就调度这些Pod。
  2. 如果不满足数量要求,就Discard丢弃,不调度Job。
  3. 注册抢占函数,如上
  4. 注册Job排序函数,根据Job是否ready排序,ready的大
  5. 注册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)
}

  1. 注册task排序函数,根据pod优先级排序
  2. 注册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)
          }
      }
   }

函数比较长,分开介绍。

  1. 统计集群中所有node可分配资源总量,这个与之前的drf重复了,后期可以考虑优化
  2. 资源总量减去其他调度器分配的资源
  3. 根据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
      }
   }
  1. 统计所有queue的资源权重
  2. 根据剩余资源*权重/总权重计算每个queue应得到的资源数量,
  3. 如果应得资源大于申请,就让应得资源等于资源申请
  4. 计算queue分配/应得,分配数量就是资源申请数量
  5. 统计应得资源分配总数
  6. 计算剩余资源,如果不剩资源就跳出循环。
   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)
      },
   })
}
  1. 注册queue排序函数,根据比重分配/应得排序
  2. 注册回收函数
  3. 注册overused超用函数,比较应得和分配的资源数量
  4. 注册事件处理函数,包括分配函数以及驱逐函数,当task发生变化时,增加(分配)/减少(驱逐)Job资源分配总量,并且更新资源占比。

你可能感兴趣的:(深入分析kube-batch(3)——Plugins)