动手写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
的过程:
从step 6,7可以看到scheduler主要任务就是:
- 通过API Server来watch未标记nodeName的pod;
- 经过计算将pod调度到合适的node。
Scheduler初始版本实现
OK,根据上面的分析,可以看到Scheduler的任务还是比较简单的,这里我们根据sheduler的原理,先实现一个最简易的Scheduler。这里的代码主要还是fork的BanzaiCloud的code,简单修改了一下。
首先,我们需要通过APIServer来watchspec.schedulerName
为custom-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 <none> <none>
sleep-5d4cc94556-zhrb5 1/1 Running 0 6m8s 10.244.0.214 master <none> <none>
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)。不过没关系,先迈开脚步开干,这些功能我们慢慢增量地完善,这样更有动力继续学习。