动手写kube-scheduler(1)

不知道各位小伙伴是不是和我一样都有看过蛮多关于kubernetes的文档和各种源码解读(说实话我是看不下去的),所以还是觉得自己飘在外面。

我觉得基本的k8s的使用,如果有业务驱动花上几个月时间是能够熟悉主要的功能,其他比较偏的知识可以在用到的时候查看。网上关于k8s的原理和源码解读的文章有一大堆,但是研究k8s自身的源码,说实话如果没有业务驱动或者debug的需求,真的蛮难啃下去的。所以我以另外的一条学习之路,就是通过增量的实现和完善一个kube-scheduler来完成对kube-scheduler的学习。

为什么首先选择scheduler呢?一是scheduler是k8s的核心组件,可以了解k8s组件之间的交互;二是scheduler比较简单,项目上也有潜在的定制scheduler的需求,我先花点业余时间写一个试试水。另外在网上我也搜到一篇英文的blog可以参考,不至于完全没头绪。

scheduler调度过程

写scheduler之前,还是需要简单回顾和介绍k8s创建一个pod的过程。下图是从Kubernetes in Action截取的k8s部署一个deployment的过程:

动手写kube-scheduler(1)_第1张图片
Creating a deployemnt

从step 6,7可以看到scheduler主要任务就是:

  1. 通过API Server来watch未标记nodeName的pod;
  2. 经过计算将pod调度到合适的node。

Scheduler初始版本实现

OK,根据上面的分析,可以看到Scheduler的任务还是比较简单的,这里我们根据sheduler的原理,先实现一个最简易的Scheduler。这里的代码主要还是fork的BanzaiCloud的code,简单修改了一下。

首先,我们需要通过APIServer来watchspec.schedulerNamecustom-scheduler(我们创建的scheduler name)且nodeName为空字符串的pod。(这里注意一下,如果没有指定schedulerName,所有scheduler都会尝试去为该pod绑定node)。
然后,我们可以开始监听符合条件的pod的ADDED事件(意味着新创建的pod)。

    // start to watch pod
    watch, err := s.clientSet.CoreV1().Pods("").Watch(metav1.ListOptions{
        FieldSelector: fmt.Sprintf("spec.schedulerName=%s,spec.nodeName=", name),
    })
...
    // handle add pod event
    for event := range watch.ResultChan() {
        if event.Type != "ADDED" {
            continue
        }
        p, ok := event.Object.(*v1.Pod)
        if !ok {
            fmt.Println("unexpected type")
            continue
        }

对新创建的pod,我们需要绑定到合适的node上,这里我们list了集群所有的node,每次选择第一个node进行绑定(实际k8s的scheduler会对node的资源进行过滤并且对node打分,这里只是为了验证功能所以每次都选择第一个node):

    nodes, err := s.clientSet.CoreV1().Nodes().List(metav1.ListOptions{})
...
    //always use the first node
    log.Println("Available node list:")
    for _, node := range nodes.Items {
        log.Println("available node name:", node.GetName())
    }
    return &nodes.Items[0], nil
...
    // binding the pod to selected node
    return s.clientSet.CoreV1().Pods(p.Namespace).Bind(&v1.Binding{
        ObjectMeta: metav1.ObjectMeta{
            Name:      p.Name,
            Namespace: p.Namespace,
        },
        Target: v1.ObjectReference{
            APIVersion: "v1",
            Kind:       "Node",
            Name:       node.Name,
        },
    })

完成了pod和node的绑定,再触发一个Scheduled的event:

    _, err := s.clientSet.CoreV1().Events(p.Namespace).Create(&v1.Event{
        Count:          1,
        Message:        message,
        Reason:         "Scheduled",
        LastTimestamp:  metav1.NewTime(timestamp),
        FirstTimestamp: metav1.NewTime(timestamp),
        Type:           "Normal",
        Source: v1.EventSource{
            Component: name,
        },
        InvolvedObject: v1.ObjectReference{
            Kind:      "Pod",
            Name:      p.Name,
            Namespace: p.Namespace,
            UID:       p.UID,
        },
        ObjectMeta: metav1.ObjectMeta{
            GenerateName: p.Name + "-",
        },
    })

这样调度完之后可以看到“Scheduled”的event:

 kubectl get pod -o wide |grep sleep
sleep-5d4cc94556-g4ggq              1/1     Running   0          46s    10.244.0.215   master              
sleep-5d4cc94556-zhrb5              1/1     Running   0          6m8s   10.244.0.214   master              

kubectl get event |grep sleep
4m41s Normal   Scheduled  Pod  Placed pod [default/sleep-5d4cc94556-zhrb5] on master
0s    Normal   Scheduled  Pod  Placed pod [default/sleep-5d4cc94556-g4ggq] on master

本文中的代码在这里,下载下来,执行步骤(我是在mac下编译的,所以可以根据自己的环境修改一下Makefile):

make
kubectl create -f deployment

小结

本文根据banzaicloud的blog总结和实现了一个简易的scheduler,总的代码也只有一百多行,比较容易理解和练习。当然这个scheduler还有许多问题,比如没leader-election,并发效率不高,没有处理delete pod等功能(必须使用--force删除pod)。不过没关系,先迈开脚步开干,这些功能我们慢慢增量地完善,这样更有动力继续学习。

你可能感兴趣的:(动手写kube-scheduler(1))