1.scheduler简介
kube-scheduler 会对 pod, node 进行 Watch,当 kube-scheduler 监测到未被调度的pod(spec.nodeName 为空),它会取出这个 pod,然后根据内部设定的调度算法选择合适的 node,通过 api-server 写回到 etcd,完成状态持久化。这时该 pod 就绑定到了该 node 上,之后 kubelet 会读取到这一信息,在相应的 node 上运行 pod。
2.调度器基本流程
1.客户端通过 api-server 创建 pod,相关数据存储到 etcd。
2.kube-scheduler 通过 NodeLister 获取所有节点信息。
3.过滤掉不合适的节点(Predicates 预选)。
4.给剩下的节点依次打分(Priorities 优选)。
5.若分数相同,在节点中随机选择一个节点,否则选择分数最高的节点调用 api 进行 pod 和 node 的绑定。结果存储到 etcd 里。
1.预选(Predicates)
NoDiskConflict:pod所需的卷是否和节点已存在的卷冲突。如果节点已经挂载了某个卷,其它同样使用这个卷的pod不能再调度到这个主机上。
NoVolumeZoneConflict:检查给定的zone限制前提下,检查如果在此主机上部署Pod是否存在卷冲突。假定一些volumes可能有zone调度约束, VolumeZonePredicate根据volumes自身需求来评估pod是否满足条件。必要条件就是任何volumes的zone-labels必须与节点上的zone-labels完全匹配。节点上可以有多个zone-labels的约束(比如一个假设的复制卷可能会允许进行区域范围内的访问)。目前,这个只对PersistentVolumeClaims支持,而且只在PersistentVolume的范围内查找标签。处理在Pod的属性中定义的volumes(即不使用PersistentVolume)有可能会变得更加困难,因为要在调度的过程中确定volume的zone,这很有可能会需要调用云提供商。
PodFitsResources:检查节点是否有足够资源(例如 CPU、内存与GPU等)满足一个Pod的运行需求。调度器首先会确认节点是否有足够的资源运行Pod,如果资源不能满足Pod需求,会返回失败原因(例如,CPU/内存 不足等)。这里需要注意的是:根据实际已经分配的资源量做调度,而不是使用已实际使用的资源量做调度。
PodFitsHostPorts:检查Pod容器所需的HostPort是否已被节点上其它容器或服务占用。如果所需的HostPort不满足需求,那么Pod不能调度到这个主机上。
HostName:检查节点是否满足PodSpec的NodeName字段中指定节点主机名,不满足节点的全部会被过滤掉。
MatchNodeSelector:检查节点标签(label)是否匹配Pod的nodeSelector属性要求。
MaxEBSVolumeCount:确保已挂载的EBS存储卷不超过设置的最大值。
MaxGCEPDVolumeCount:确保已挂载的GCE存储卷不超过预设的最大值。
MaxAzureDiskVolumeCount : 确保已挂载的Azure存储卷不超过设置的最大值。
CheckNodeMemoryPressure : 判断节点是否已经进入到内存压力状态,如果是则只允许调度内存为0标记的Pod。检查Pod能否调度到内存有压力的节点上。如有节点存在内存压力, Guaranteed类型的Pod(例如,requests与limit均指定且值相等) 不能调度到节点上。
CheckNodeDiskPressure : 判断节点是否已经进入到磁盘压力状态,如果是,则不调度新的Pod。
PodToleratesNodeTaints : 根据 taints 和 toleration 的关系判断Pod是否可以调度到节点上Pod是否满足节点容忍的一些条件。
MatchInterPodAffinity : 节点亲和性筛选。
2.优选(Priorites)
LeastRequestedPriority: 按 node计算资源(CPU/MEM)剩余量排序,挑选最空闲的 node。
BalancedResourceAllocation: 补充 LeastRequestedPriority,在 CPU 和 MEM 的剩余量中取平衡。
SelectorSpreadPriority: 同一个 Service/RC 下的 Pod 应该尽可能地分散在集群里。Node 上运行的同个 Service/RC 下的 Pod 数目越少,分数越高。
NodeAffinityPriority: 按soft(preferred) NodeAffinity 规则匹配情况排序,规则命中越高,分数越高。
TaintTolerationPriority: 按Pod tolerations 与 node taints 的匹配情况排序,越多 taints 不匹配,分数越低。
InterPodAffinityPriority: 按soft(preferred) Pod Affinity/Anti-Affinity 规则匹配情况排序,规则命中越多,分数越高/低。
最终主机的得分由以下公式计算得到:
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + … + (weightn * priorityFuncn)
3.具体源码实现
代码入口 pkg/scheduler/scheduler.go
4.自定义调度
方式一:定制预选(Predicates) 和优选(Priority)策略
kube-scheduler在启动的时候可以通过 --policy-config-file参数可以指定调度策略文件,用户可以根据需要组装Predicates和Priority函数。选择不同的过滤函数和优先级函数、控制优先级函数的权重、调整过滤函数的顺序都会影响调度过程。
方式二:自定义Priority和Predicate
上面的方式一是对已有的调度模块进行组合,Kubernetes还允许用户编写自己的Priority 和 Predicate函数。
过滤函数的接口:
// FitPredicate is a function that indicates if a pod fits into an existing node.
// The failure information is given by the error.
type FitPredicate func(pod *v1.Pod, meta PredicateMetadata, nodeInfo *schedulercache.NodeInfo) (bool, []PredicateFailureReason, error)
自定义Predicates函数步骤如下:
在plugin/pkg/scheduler/algorithm/predicates/predicates.go文件中编写对象实现上面接口。
编写完过滤函数之后进行注册,让 kube-scheduler 启动的时候知道它的存在,注册部分可以在 plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go 完成,可以参考其他过滤函数(例如PodFitsHostPorts)的注册代码:kubernetes/plugin/pkg/scheduler/algorithmprovider/defaults/defaults.go factory.RegisterFitPredicate("PodFitsPorts", predicates.PodFitsHostPorts)。
在 --policy-config-file把自定义过滤函数写进去,kube-scheduler运行时可以执行自定义调度逻辑了。
自定义优先级函数,实现过程和过滤函数类似。
方式三:编写自己的调度器
除了上面2种方式外,Kubernetes也允许用户编写自己的调度器组件,并在创建资源的时候引用它。多个调度器可以同时运行和工作,只要名字不冲突。
使用某个调度器就是在Pod的spec.schedulername字段中填写上调度器的名字。Kubernetes提供的调度器名字是default,如果自定义的调度器名字是my-scheduler,那么只有当spec.schedulername字段是my-scheduler才会被调度。
调度器最核心的逻辑并不复杂。Scheduler首先监听apiserver ,获取没有被调度的Pod和全部节点列表,而后根据一定的算法和策略从节点中选择一个作为调度结果,最后向apiserver中写入binding 。
引用与参考:
Kubernetes 源码笔记(kube-scheduler)
DockOne微信分享(一四九):Kubernetes调度详解