kube-batch 从代码中找出gang scheduler这个过程

https://www.jianshu.com/p/7286d895dcc0

从上面这个过程中,已经知道了kube-batch的启动过程。kube-batch总共有4个过程。这里我们从Allocate开始。

目录:
一: 流程解释
二:代码说明

一: 流程解释

在allocate.go中:找到Execute函数。首先用文字解释一下整个的过程:
流程:
(1)将kube-batch的job放入对应的队列。这是一个具有优先级的队列。
(2)依次遍历这些队列,如果为空就跳过
(3)如果不为空,依次从队列中pop出一个job.即接下来要调度这个job
(4)取出这个job对应的所有Tasks(即要绑定的pod),对每个task进行假绑定,这里的假绑定意思是 只是更新task的状态,先记录pod绑定在哪个节点上。当达到JobReady时,进行真正的绑定。这样就实现了一次性绑定了好几个Pod.
(5)更新job的信息,将pod重新加入队列。跳出循环,再次进行调度。

对上面流程有两个地方需要再解释一下:
(1) jobReady 作用是什么,gang scheduler和这个有什么关系?
allocate每次都是对task进行假绑定。jobReady是一个信号。表示现在可以进行真正的绑定了。

在gang.go的75行,实现了这个接口:

func jobReady(obj interface{}) bool {
    job := obj.(*api.JobInfo)

    occupied := readyTaskNum(job)

    return occupied >= job.MinAvailable
}

可以看出来,gang scheduler中 就是通过数量上的判断来进行限制的。job.MinAvailable这个是podgroup的minNumber数量。这样就使得每次调度的时候,只有当MinAvailable个task准备好了之后。才会进行调度,从而达到gang scheduler的效果。

(2)为什么job还要重新加入队列。这个job不是已经调度了吗?
因为有可能job的Tasks数量会多于 job.MinAvailable。例如,一个job有8个task,但是它指定的podgroup的 minNumber=4。这样调度时会首先调度4个task.当真正绑定之后。剩余没绑定的4个task是一个新的job.所有需要重新加入队列。

二:代码说明

func (alloc *allocateAction) Execute(ssn *framework.Session) {
    glog.V(3).Infof("Enter Allocate ...")
    defer glog.V(3).Infof("Leaving Allocate ...")

    // 这是优先级队列,即队列里面的内容是有优先级的
    queues := util.NewPriorityQueue(ssn.QueueOrderFn)
    jobsMap := map[api.QueueID]*util.PriorityQueue{}

    //首先将所有的kube-batch job放入
    for _, job := range ssn.Jobs {
        if queue, found := ssn.Queues[job.Queue]; found {
            queues.Push(queue)
        } else {
            glog.Warningf("Skip adding Job <%s/%s> because its queue %s is not found",
                job.Namespace, job.Name, job.Queue)
            continue
        }

        if _, found := jobsMap[job.Queue]; !found {
            jobsMap[job.Queue] = util.NewPriorityQueue(ssn.JobOrderFn)
        }

        glog.V(4).Infof("Added Job <%s/%s> into Queue <%s>", job.Namespace, job.Name, job.Queue)
        jobsMap[job.Queue].Push(job)
    }

    glog.V(3).Infof("Try to allocate resource to %d Queues", len(jobsMap))

    pendingTasks := map[api.JobID]*util.PriorityQueue{}

    for {
        if queues.Empty() {
            break
        }
        // 从第一个队列开始寻找是否有job需要调度
        queue := queues.Pop().(*api.QueueInfo)
        if ssn.Overused(queue) {
            glog.V(3).Infof("Queue <%s> is overused, ignore it.", queue.Name)
            continue
        }

        jobs, found := jobsMap[queue.UID]

        glog.V(3).Infof("Try to allocate resource to Jobs in Queue <%v>", queue.Name)

        if !found || jobs.Empty() {
            glog.V(4).Infof("Can not find jobs for queue %s.", queue.Name)
            continue
        }
        
        // 从队列中依次弹出job进行调度
        job := jobs.Pop().(*api.JobInfo)
        if _, found := pendingTasks[job.UID]; !found {
            tasks := util.NewPriorityQueue(ssn.TaskOrderFn)
            for _, task := range job.TaskStatusIndex[api.Pending] {
                // Skip BestEffort task in 'allocate' action.
                if task.Resreq.IsEmpty() {
                    glog.V(4).Infof("Task <%v/%v> is BestEffort task, skip it.",
                        task.Namespace, task.Name)
                    continue
                }

                tasks.Push(task)
            }
            pendingTasks[job.UID] = tasks
        }
        tasks := pendingTasks[job.UID]

        glog.V(3).Infof("Try to allocate resource to %d tasks of Job <%v/%v>",
            tasks.Len(), job.Namespace, job.Name)
        
        // 具体调度Task的循环,每次都假绑定一个Task,表示这个task已经完成
        for !tasks.Empty() {
            predicateNodes := []*api.NodeInfo{}
            nodeScores := map[int][]*api.NodeInfo{}

            task := tasks.Pop().(*api.TaskInfo)
            assigned := false

            glog.V(3).Infof("There are <%d> nodes for Job <%v/%v>",
                len(ssn.Nodes), job.Namespace, job.Name)

            //any task that doesn't fit will be the last processed
            //within this loop context so any existing contents of
            //NodesFitDelta are for tasks that eventually did fit on a
            //node
            
            // 后面的很长一般分,就是为task选择一个合适的node。
            //主要内容是先过滤,然后选择一个满足task的最优节点,然后更新job中该task的信息
            if len(job.NodesFitDelta) > 0 {
                job.NodesFitDelta = make(api.NodeResourceMap)
            }
            for _, node := range ssn.Nodes {
                glog.V(3).Infof("Considering Task <%v/%v> on node <%v>: <%v> vs. <%v>",
                    task.Namespace, task.Name, node.Name, task.Resreq, node.Idle)

                // TODO (k82cn): Enable eCache for performance improvement.
                if err := ssn.PredicateFn(task, node); err != nil {
                    glog.V(3).Infof("Predicates failed for task <%s/%s> on node <%s>: %v",
                        task.Namespace, task.Name, node.Name, err)
                    continue
                } else {
                    predicateNodes = append(predicateNodes, node)
                }
            }
            for _, node := range predicateNodes {
                score, err := ssn.NodeOrderFn(task, node)
                if err != nil {
                    glog.V(3).Infof("Error in Calculating Priority for the node:%v", err)
                } else {
                    nodeScores[score] = append(nodeScores[score], node)
                }
            }
            selectedNodes := util.SelectBestNode(nodeScores)
            for _, node := range selectedNodes {
                // Allocate idle resource to the task.
                if task.InitResreq.LessEqual(node.Idle) {
                    glog.V(3).Infof("Binding Task <%v/%v> to node <%v>",
                        task.Namespace, task.Name, node.Name)
                    // !!!这里需要重点注意,这里调用了session.go中Allocate函数。 下面会将这个的作用
                    if err := ssn.Allocate(task, node.Name); err != nil {         
                        glog.Errorf("Failed to bind Task %v on %v in Session %v, err: %v",
                            task.UID, node.Name, ssn.UID, err)
                        continue
                    }
                    assigned = true
                    break
                } else {
                    //store information about missing resources
                    job.NodesFitDelta[node.Name] = node.Idle.Clone()
                    job.NodesFitDelta[node.Name].FitDelta(task.Resreq)
                    glog.V(3).Infof("Predicates failed for task <%s/%s> on node <%s> with limited resources",
                        task.Namespace, task.Name, node.Name)
                }

                // Allocate releasing resource to the task if any.
                if task.InitResreq.LessEqual(node.Releasing) {
                    glog.V(3).Infof("Pipelining Task <%v/%v> to node <%v> for <%v> on <%v>",
                        task.Namespace, task.Name, node.Name, task.InitResreq, node.Releasing)
                    if err := ssn.Pipeline(task, node.Name); err != nil {
                        glog.Errorf("Failed to pipeline Task %v on %v in Session %v",
                            task.UID, node.Name, ssn.UID)
                        continue
                    }

                    assigned = true
                    break
                }
            }
            
            
            //如果绑定某个task过程中失败,比如资源不足。那么就会跳出这个循环。
            if !assigned {
                break
            }
            // 将job重新加入队列,然后进行下一个job的调度。
            if ssn.JobReady(job) {
                jobs.Push(job)
                break
            }
        }
        // Added Queue back until no job in Queue.  
        queues.Push(queue)
    }

session.go中Allocate函数

func (ssn *Session) Allocate(task *api.TaskInfo, hostname string) error {
    if err := ssn.cache.AllocateVolumes(task, hostname); err != nil {
        return err
    }

    // 这里这是更新task的状态。
    // Only update status in session
    job, found := ssn.Jobs[task.Job]
    if found {
        if err := job.UpdateTaskStatus(task, api.Allocated); err != nil {
            glog.Errorf("Failed to update task <%v/%v> status to %v in Session <%v>: %v",
                task.Namespace, task.Name, api.Allocated, ssn.UID, err)
            return err
        }
    } else {
        glog.Errorf("Failed to found Job <%s> in Session <%s> index when binding.",
            task.Job, ssn.UID)
        return fmt.Errorf("failed to find job %s", task.Job)
    }

    task.NodeName = hostname

    if node, found := ssn.Nodes[hostname]; found {
        if err := node.AddTask(task); err != nil {
            glog.Errorf("Failed to add task <%v/%v> to node <%v> in Session <%v>: %v",
                task.Namespace, task.Name, hostname, ssn.UID, err)
            return err
        }
        glog.V(3).Infof("After allocated Task <%v/%v> to Node <%v>: idle <%v>, used <%v>, releasing <%v>",
            task.Namespace, task.Name, node.Name, node.Idle, node.Used, node.Releasing)
    } else {
        glog.Errorf("Failed to found Node <%s> in Session <%s> index when binding.",
            hostname, ssn.UID)
        return fmt.Errorf("failed to find node %s", hostname)
    }

    //gang.go中有,这里是真正的绑定了,当jobReady时,调用dispatch函数对所有的Allocated的task进行绑定。
    // dispatch就在该函数的下面。内容也很直观,就是调用k8s的接口,真正的绑定pod
    if ssn.JobReady(job) {
        for _, task := range job.TaskStatusIndex[api.Allocated] {
            if err := ssn.dispatch(task); err != nil {                    // 如果job准备好了,就直接真正绑定所有准备好的任务??
                glog.Errorf("Failed to dispatch task <%v/%v>: %v",
                    task.Namespace, task.Name, err)
                return err
            }
        }
    }

    return nil
}

总结:
感觉自己的文字表达能力还是不行,还需要更多的锻炼。
结合代码的注释和上面的流程说明一起看会更容易理解。

在session.go中可以看到,每次为task分配资源时,首先都是更新状态,只有达到jobReady时,才真正的绑定到具体的某个结点上。

当然如果当前要调度的job1,它需要的资源不足,那么当前这个job1就会跳出循环,找下一个要进行调度的job。不用担心,job1中已经绑定的task所占的资源。backfill操作会将Job1清空。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,706评论 4 366
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,002评论 1 301
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,462评论 0 250
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,375评论 0 216
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,763评论 3 294
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,849评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,033评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,768评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,490评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,734评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,204评论 1 264
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,566评论 3 260
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,227评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,137评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,934评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,926评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,774评论 2 274

推荐阅读更多精彩内容