深入分析kube-batch(4)——actions

weixin_34292287發表於2018-10-18

深入分析kube-batch(4)——actions

action是真正的排程過程,順序是reclaim -> allocate -> backfill -> preempt

interface

// Action is the interface of scheduler action.
type Action interface {
   // The unique name of Action.
   Name() string

   // Initialize initializes the allocator plugins.
   Initialize()

   // Execute allocates the cluster's resources into each queue.
   Execute(ssn *Session)

   // UnIntialize un-initializes the allocator plugins.
   UnInitialize()
}

重點關注Execute實現

reclaim

kube-batch\pkg\scheduler\actions\reclaim\reclaim.go

func (alloc *reclaimAction) Execute(ssn *framework.Session) {
   queues := util.NewPriorityQueue(ssn.QueueOrderFn)

   preemptorsMap := map[api.QueueID]*util.PriorityQueue{}
   preemptorTasks := map[api.JobID]*util.PriorityQueue{}

   for _, job := range ssn.Jobs {
      queues.Push(queue)

      if len(job.TaskStatusIndex[api.Pending]) != 0 {
         preemptorsMap[job.Queue].Push(job)
         
         for _, task := range job.TaskStatusIndex[api.Pending] {
            preemptorTasks[job.UID].Push(task)
         }
      }
   }

  1. 根據優先順序排序queue
  2. 將待排程的task儲存為搶佔者
for {
   if queues.Empty() {
      break
   }

   queue := queues.Pop().(*api.QueueInfo)
   jobs, found := preemptorsMap[queue.UID]
   tasks, found := preemptorTasks[job.UID]

   resreq := task.Resreq.Clone()
   reclaimed := api.EmptyResource()

   assigned := false

   for _, n := range ssn.Nodes {
      if err := ssn.PredicateFn(task, n); err != nil {
         continue
      }

      var reclaimees []*api.TaskInfo
      for _, task := range n.Tasks {
         if task.Status != api.Running {
            continue
         }

         reclaimees = append(reclaimees, task.Clone())
      }
      victims := ssn.Reclaimable(task, reclaimees)

      if len(victims) == 0 {
         continue
      }

      // If not enough resource, continue
      allRes := api.EmptyResource()
      for _, v := range victims {
         allRes.Add(v.Resreq)
      }
      if allRes.Less(resreq) {
         continue
      }

      // Reclaim victims for tasks.
      for _, reclaimee := range victims {
         ssn.Evict(reclaimee, "reclaim")
         
         reclaimed.Add(reclaimee.Resreq)
         if resreq.LessEqual(reclaimee.Resreq) {
            break
         }
         resreq.Sub(reclaimee.Resreq)
      }       
      break
   }

}
  1. 找到優先順序最高的queue,job,task
  2. 遍歷node,首先過預選函式,很奇怪,沒有PodFitsResources,應該是kube-batch自己管理資源
  3. 找到node上正在執行的pod
  4. 找到受害者
  5. 如果受害者資源總量小於pod申請資源總量,就跳過
  6. 驅逐受害者,呼叫刪除介面
  7. 如果釋放足夠的資源,就跳出驅逐

reclaim過程目前還沒遇到過,回收函式也不是很理解。我覺得回收不是很必要,驅逐邏輯不應該在這裡做,kubelet已經有了驅逐邏輯,不是很明白reclaim的必要性。而且不是每次都需要回收,應該判斷node是否自願不足。我會在配置中移除reclaim的action,還能提高效能。

allocate

        for !tasks.Empty() {
            task := tasks.Pop().(*api.TaskInfo)

            for _, node := range ssn.Nodes {
                if err := ssn.PredicateFn(task, node); err != nil {
                    continue
                }

                // Allocate idle resource to the task.
                if task.Resreq.LessEqual(node.Idle) {
                    ssn.Allocate(task, node.Name)
                    break
                }
            }
        }

分配過程只看最核心的部分,

  1. 過一遍預選函式,
  2. 比較pod資源申請和node空閒資源
  3. bind

這裡解決了上面的疑問,預選函式中沒有PodFitsResources,是因為在這裡實現了類似功能;不過又多了一個疑問,這裡如果node滿足pod要求,那麼就直接bind了?沒有優選過程嗎?那soft親和性怎麼辦?

backfill

func (alloc *backfillAction) Execute(ssn *framework.Session) {

   for _, job := range ssn.Jobs {
      for _, task := range job.TaskStatusIndex[api.Pending] {
         
         if task.Resreq.IsEmpty() {
            for _, node := range ssn.Nodes {
               ssn.PredicateFn(task, node);

               ssn.Allocate(task, node.Name)
               break
            }
         }
      }
   }
}

backfill是為了處理BestEffort後加的action,相關issue

preempt

搶佔邏輯一直沒有很理解,這裡先暫時不分析了,會另外開一篇文章專門介紹搶佔,不過kube-batch的搶佔跟K8S的不太一樣。

總結

學習了馬達老師的kube-batch為我開啟了一個新思路,不過對於我的專案可能有點重,不是很需要回收和搶佔邏輯,還有貌似也不支援優選邏輯,沒有優選就沒有soft親和性,這個是個非常致命的問題,所以後期應該準備參考default-scheduler和kube-batch自己寫一個排程器了。

相關文章