Kubernetes 的开始起源于谷歌,是一个谷歌花了很多力气来为你的工作和服务创建的新管理工具。它在谷歌系统中有自己的起源: Borg 和 Omega 。无论是公有云还是私有云甚至混合云,Kubernetes将作为一个为任何应用,任何环境的容器管理框架无处不在。正因为如此, 受到各大巨头及初创公司的青睐,如IBM,Microsoft、VMWare、Red Hat、CoreOS、Mesos等,纷纷加入给Kubernetes贡献代码。Kubernetes毫无疑问坐拥容器编排的霸主地位。
关于Kubernetes的起源,架构,原理,组件的介绍网上太多了,感兴趣的可以自行google,但是我发现关于源码的解读比较少,即使有,也比较泛泛。大多是从某个函数入口讲起,然后讲到包,结构体,属性,方法结束,关于每个方法的作用要么是略过,要么是一句话解释,对Kubernetes的一个组件的解读尽然一篇文章就写完了,大师的思维太快,跟不上~~~
架构是大师们的事情,我们凡夫俗子能做到清楚就很不容易了!我打算在接下来的文章里,每一篇文章之读一个关键方法。细细品味大神们的杰作。
我想好多用过Kubernetes的都有一个疑问,Pod是如何通过层层筛选被绑定到一个节点的。接下来就分析这个方法实现:scheduleOne()。
关于scheduler的predicate
和prioirity
设计请参考devel/scheduler.md,关于它的算法请参考devel/scheduler_algorithm.md,读完之后,你就会对Kubernetes Master三大组件之一的scheduler有了一个基本的印象,甚至感觉好简单啊~~~ 那么,恭喜你,你与大神的差距又缩短了一厘米~~~
好,现在我们从 kubernetes/plugin/pkg/scheduler/scheduler.go
中摘出scheduleOne
方法,我会把解读放到每一行代码的前面。
// scheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting.
func (sched *Scheduler) scheduleOne() {
// 获取一个待调度的Pod, NextPod()方法是阻塞的
pod := sched.config.NextPod()
//首先,判断这个Pod是否正在被删除中,如果是,则返回,跳过调度。
if pod.DeletionTimestamp != nil {
//记录发生的事件,可以通过kubectl describe pods命令看到此处的事件打印信息
sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name)
glog.V(3).Infof("Skip schedule deleting pod: %v/%v", pod.Namespace, pod.Name)
return
}
// 开始尝试调度pod,以命名空间和pod名称区分
glog.V(3).Infof("Attempting to schedule pod: %v/%v", pod.Namespace, pod.Name)
// Synchronously attempt to find a fit for the pod.
start := time.Now()
// 此处的schedule()方法中,会调用注册的初选和优选的算法,最终返回一个节点的名称存放到suggestedHost变量中
suggestedHost, err := sched.schedule(pod)
// 此处,会调用go的一个客户端,原子地的更新观测总和。关于它的具体功能,我们下次再细读。
metrics.SchedulingAlgorithmLatency.Observe(metrics.SinceInMicroseconds(start))
if err != nil {
return
}
// Tell the cache to assume that a pod now is running on a given node, even though it hasn't been bound yet.
// This allows us to keep scheduling without waiting on binding to occur.
//即使该pod还未真正绑定到节点上,我们先假设这个pod已经在指定的节点上运行了。这是为了更新shcedulerCache, 与绑定操作(需要花费一些时间)以异步方式进行。
err = sched.assume(pod, suggestedHost)
if err != nil {
return
}
// bind the pod to its host asynchronously (we can do this b/c of the assumption step above).
// 创建一个协程goruntime,异步地绑定pod。(注意,该shceduleOne方法也是在一个协程中)
go func() {
// 执行绑定方法,把该pod的命名空间,名称和UID绑定到指定的节点(suggestedHost)上,到此,一个pod绑定到一个节点的过程就完成了。
err := sched.bind(pod, &v1.Binding{
ObjectMeta: metav1.ObjectMeta{Namespace: pod.Namespace, Name: pod.Name, UID: pod.UID},
Target: v1.ObjectReference{
Kind: "Node",
Name: suggestedHost, // 分配的节点
},
})
// 与上面类似,会在后面单独分析,它们的实现在vendor/github.com/prometheus/client_golang/prometheus/histogram.go,可以自行看一下
metrics.E2eSchedulingLatency.Observe(metrics.SinceInMicroseconds(start))
if err != nil {
glog.Errorf("Internal error binding pod: (%v)", err)
}
}()
}
是不是感觉很简单?确实,大神的代码让你感觉逻辑清晰,代码简洁。但是,还有许多问题未解决,比如,如果绑定失败,retrying 是如何触发的呢?retrying的筛选逻辑,使用的算法是否和之前一致呢?如果调度过程中节点挂了怎么办?scheduler是如何拿到各个节点的资源信息的?绑定之后,Kubelet是如何接收Pod的?如何添加自己定制的算法?等等。下一篇文章会分析retrying机制,敬请期待。