1.背景
kubernetes 社区十分火热,版本迭代非常快,本分析基于社区Kubernetes master版本。
2.scheduler模块介绍
调度器是kubernetes中独特而又重要的一个模块。独特是因为在kubernetes体系中,scheduler是唯一一个以plugin形式存在的模块,重要是因为kubernetes中最重要的基础单元pod的部署是通过scheduler完成的。
正常情形下scheduler 通过算法为pod筛选合适的node,然后绑定node和pod的关系,相应node上的kubele watch 到绑定信息才会拉起pod。在这个过程中scheduler的职责很明确,就是为需要部署的pod筛选合适的node,如果找到了就绑定node信息,如果没有找到,pod则会一直处于pending状态,一旦一个合适的Node接入到kubernetes cluster,调度器会将pod调度到该Node上。
在kubernetes中有一种非常重要的对象-ReplicationController,翻译过来是副本控制器,简称RC。在部署pod的时候,通常通过RC来做的,在RC里面定义pod的模版及副本数量,而不是部署单个pod。为什么我们需要通过RC来做呢?当然是有原因的,首先就是快速方便,只需指定副本数量即可,RC可以部署指定数量的副本pod,另外就是一旦某个pod fail掉,RC可以通过调度器再次拉起与失败个数相同的副本以确保running的副本个数与指定的个数一致。
这里我们可能有个疑问,既然调度器的目的是为pod筛选合适的node,那么在pod中能否指定node呢? 答案是可以的,如果在pod中指定node,则不会经过调度器,node上的kubelet会拉起pod。这时候问题又来了,没有经过调度器的算法筛选,这个由用户指定的node如果不满足Pod的要求,比如资源要求,那又会出现什么样的情况呢? 答案其实很简单,这个pod会被kubelet设置成fail状态,那么这么说来,kubelet会像scheduler一样对node做一些判断,以确保pod适合当前的node。下面我们会分析这个判断的过程。
3.scheduler模块分析
调度的过程分两步,首先是做筛选(predict),其次是排序(priorities)。筛选的过程是决定是否,排序的过程是区分优劣。算法是调度的依据,这个算法其中的一部分-GeneralPredicates也会被kubelet用到,这个在第二节中提到过。算法加载的过程比较巧妙,是通过包的引用来作为算法加载的入口的:
package algorithmprovider
import (
_ "k8s.io/kubernetes/plugin/pkg/scheduler/algorithmprovider/defaults"
)
这个文件存在与plugin/pkg/scheduler/algorithmprovider/plugins.go,在import package时,会执行defaults中的init方法。这里不得不提出这里的一个考虑,目前是加载default,意味着我们可以加载自己的定制化的算法。下面展示了加载的方式。
在predicates算法中非常值得一提的是PodFitsResources,这个算法是确定pod的resource request 能否被node满足。
在排序算法中非常值得一提的算法是ImageLocalityPriority,这个算法是根据Image是否在Node上及Image的大小,来为每个node打分。
这里面另两个比较重要的点是亲和性及Toleration&Taint,都是关于亲和性方面的检测。
调度过程中是如何使用这些加载的算法的呢?下面展示了使用这些predict算法的场景。
相信大家已经看明白了,使用predict作为过滤器,逐一进行筛选,一气呵成,这里将算法传递给数据的思想,将Golang的思维展示得淋漓尽致!
调度的predict和priorities执行完之后,会得到最终的node。接下来就是将node和pod绑定的过程了。下面创建了binding对象,这个对象的创建如下:
b := &api.Binding{
ObjectMeta: api.ObjectMeta{Namespace: pod.Namespace, Name: pod.Name},
Target: api.ObjectReference{
Kind: "Node",
Name: dest,
},
}
我们可以看到,binding包含了node和pod,一旦绑定,kubelet认为pod找到了合适的Node,然后node上的kubelet会拉起pod。下面是Bind方法的实现!
// Bind just does a POST binding RPC.
func (b *binder) Bind(binding *api.Binding) error {
glog.V(3).Infof("Attempting to bind %v to %v", binding.Name, binding.Target.Name)
ctx := api.WithNamespace(api.NewContext(), binding.Namespace)
return b.Post().Namespace(api.NamespaceValue(ctx)).Resource("bindings").Body(binding).Do().Error()
// TODO: use Pods interface for binding once clusters are upgraded
// return b.Pods(binding.Namespace).Bind(binding)
}
我们看到其实是给apiserver发起请求,创建binding对象,不过这里非常需要指出的是,这个请求在apiserver端并没有真正在etcd中创建binding对象,而是为pod指定了node name,并且更新pod到etcd。
kubernetes 的调度器模块并不是最复杂的模块,这篇介绍也并未剖析所有的真像,希望今后有机会进一步分析。