根据特定的调度算法和调度策略将 Pod 调度到合适的 Node 节点上去。
启动之后会一直监听 API Server,获取到 PodSpec.NodeName
为空的 Pod,对每个 Pod 都会创建一个 binding。
------------- 原始的扩展调度器的方法 ------
1.魔改kube-schduler
clone 官方 kube-schedule,然后对其进行代码级更改。
2.自行实现调度器
实现一个新的调度器,配置 pod.spec.schedulerName 来选择使用哪一个调度器。
--------------------------------------------------------
1.插件注册
包括树内插件和树外插件。
每个插件都实现了一个或多个扩展点。
在生成调度器profile时,针对每个扩展点,指定要使用的插件。
1.1 树内插件
1.2 树外插件
在通过新的调度框架进行扩展时,需要自己实现一个调度器。
我们可以使用kube-schduler的代码,将我们实现的插件作为树外插件传递进去。
2.运行机制
2.1 老的调度框架:两阶段
Kubernetes 调度器介绍-阳明的博客|Kubernetes|Istio|Prometheus|Python|Golang|云原生 (qikqiak.com)
整个调度过程分两步:预选(Predicates)和优选(Policies)。
2)kube-schderl中的extender接口
提供了HTTPExtender实现。
作用是利用http访问用户的extender服务。
2.2 新的调度框架
1)扩展点
2)新的调度框架与extender的融合
PreEnqueue | 在将 Pod 被添加到内部活动队列之前被调用。 在此队列中 Pod 被标记为准备好进行调度。 注意: 只有当所有 PreEnqueue 插件返回 否则,它将被放置在内部无法调度的 Pod 列表中,并且不会获得 |
sort | 用于对调度队列中的 Pod 进行排序。 队列排序插件本质上提供 一次只能启动一个队列插件。 |
PreFilter | |
Filter | 用于过滤出不能运行该 Pod 的节点。 对于每个节点, 调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行, 则不会为该节点调用剩下的过滤插件。 |
extender.Filter | |
PreScore | 生成一个可共享状态供 Score 插件使用。 |
Score | 对通过过滤阶段的节点进行排序。 调度器将为每个节点调用每个评分插件。 调度器将根据配置的插件权重 合并所有插件的节点分数。 |
extenders.Prioritize | |
PostFilter | 仅在该 Pod 没有可行的节点时调用。 如果任何 PostFilter 插件标记节点为“Schedulable”, 则其余的插件不会调用。 作用: 1)抢占,试图通过抢占其他 Pod 的资源使该 Pod 可以调度。 -- DefaultPreemption插件 1) 确保当前pod有权抢占其他POD 2)找到一些可选方案 可选方案:[节点1,需要驱逐的pod]/[节点2,需要驱逐的pod] 3)调用注册的extender.ProcessPreemption来过滤调一部分可选方案 4) 找到最佳方案 5) - 驱逐受害者pod - Reject the victim pods if they are in waitingPod map - 清理low-priority pods的nominatedNodeName状态 |
Reserve | 发生在调度器实际将一个 Pod 绑定到其指定节点之前。 两个方法: 1)
|
Permit | 用于防止或延迟 Pod 的绑定。 插件可以做以下三件事之一: |
waitOnPermit | |
PreBind | 用于执行 Pod 绑定前所需的所有工作。 作用: 一个 PreBind 插件可能需要制备网络卷并且在允许 Pod 运行在该节点之前 将其挂载到目标节点上 |
extender.Bind | |
Bind | 各 Bind 插件按照配置顺序被调用。 Bind 插件可以选择是否处理指定的 Pod。 如果某 Bind 插件选择处理某 Pod,剩余的 Bind 插件将被跳过。 |
PostBind | 可用于清理相关的资源。 |
3. 启动参数
通过 --config
参数指定调度器将使用哪些参数。(同时配置老/新框架)
algorithmSource设置了默认的调度器配置。
Profiles在此基础上,开启/关闭插件,设置插件的参数,最终生成新的调度器。
Extender在此基础上,为每个调度器新增功能。
例子:
3.1 algorithmSource调度算法源
如果为空,使用默认provider。
1) policy
也可以通过-policy-config-file指定文件地址。
主要是针对老框架。
例子:
老框架整合到新调度框架:
将老的“预选”和“优选”的插件,分配到调度框架的各个阶段。
(一个插件可能涉及多个阶段)
以前的框架,没有以下阶段,添加默认插件。
extender配置信息:
2)provider
针对新框架。
目前,kube-schduler有两个。
对应的是调度框架。
DefaultProvider:
列举了,在各个阶段使用的插件。
schedulerapi.Plugins{
QueueSort: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: queuesort.Name},
},
},
PreFilter: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: noderesources.FitName},
{Name: nodeports.Name},
{Name: podtopologyspread.Name},
{Name: interpodaffinity.Name},
{Name: volumebinding.Name},
{Name: computeresource.Name},
},
},
Filter: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: nodeunschedulable.Name},
{Name: nodename.Name},
{Name: tainttoleration.Name},
{Name: nodeaffinity.Name},
{Name: nodeports.Name},
{Name: noderesources.FitName},
{Name: volumerestrictions.Name},
{Name: nodevolumelimits.EBSName},
{Name: nodevolumelimits.GCEPDName},
{Name: nodevolumelimits.CSIName},
{Name: nodevolumelimits.AzureDiskName},
{Name: volumebinding.Name},
{Name: volumezone.Name},
{Name: podtopologyspread.Name},
{Name: interpodaffinity.Name},
{Name: computeresource.Name},
{Name: localreplicas.Name},
{Name: fitipresource.Name},
},
},
PostFilter: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: defaultpreemption.Name},
},
},
PreScore: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: interpodaffinity.Name},
{Name: podtopologyspread.Name},
{Name: tainttoleration.Name},
},
},
Score: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: noderesources.BalancedAllocationName, Weight: 1},
{Name: imagelocality.Name, Weight: 1},
{Name: interpodaffinity.Name, Weight: 1},
{Name: noderesources.LeastAllocatedName, Weight: 1},
{Name: nodeaffinity.Name, Weight: 1},
{Name: nodepreferavoidpods.Name, Weight: 10000},
// Weight is doubled because:
// - This is a score coming from user preference.
// - It makes its signal comparable to NodeResourcesLeastAllocated.
{Name: podtopologyspread.Name, Weight: 2},
{Name: tainttoleration.Name, Weight: 1},
{Name: computeresource.Name, Weight: 1},
},
},
Reserve: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: volumebinding.Name},
},
},
PreBind: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: volumebinding.Name},
},
},
Bind: &schedulerapi.PluginSet{
Enabled: []schedulerapi.Plugin{
{Name: defaultbinder.Name},
},
},
}
}
ClusterAutoscalerProvider
3.2 Profiles
一个Profiles对应一个调度器。kube-schduler进程中可以定义多个调度器。
如果schedulerName=Pod的spec.schedulerName,则Pod使用此方案调度。
3.3 extender
支持 Predicate、Priority、Bind、preemption扩展点。
4. 使用extender进行扩展
直接在默认调度器上,进行扩展。
如果kube-schduler进程,设置了多个调度器。那么会扩展所有调度器。
缺点:
- 通信成本:数据在默认调度程序和调度器扩展程序之间以
http(s)
传输,在执行序列化和反序列化的时候有一定成本 - 有限的扩展点:扩展程序只能在某些阶段的末尾参与,例如
“ Filter”
和“ Prioritize”
,它们不能在任何阶段的开始或中间被调用 - 减法优于加法:与默认调度程序传递的节点候选列表相比,我们可能有一些需求需要添加新的候选节点列表,但这是比较冒险的操作,因为不能保证新节点可以通过其他要求,所以,调度器扩展程序最好执行
“减法”
(进一步过滤),而不是“加法”
(添加节点) - 缓存共享:上面只是一个简单的测试示例,但在真实的项目中,我们是需要通过查看整个集群的状态来做出调度决策的,默认调度程序可以很好地调度决策,但是无法共享其缓存,这意味着我们必须构建和维护自己的缓存
4.1 创建extender服务器
1) 创建一个http server,针对以下路径提供对应的handler
2)针对predicates(过滤)
入参:
出参:
3)针对priorities
入参:
同2
出参:
实现:
使用算法,为每个节点打分。
4)针对bind
入参:
出参:
5)编译并部署
4.2 修改kube-schduler的配置文件
--config=/etc/kubernetes/scheduler-extender.yaml
在extendr处进行注册。
5.使用新的调度框架进行扩展
缺点:需要在pod上指定要使用的调度器。
5.1 实现对应的扩展点
5.2 将插件注册到调度器中
相当于在kube-schduler注册了树外插件。
自己独立起一个仓库,调用kube-schduler的代码。
5.3 部署
实现完成后,编译打包成镜像即可,然后我们就可以当成普通的应用用一个 Deployment
控制器来部署。
通过 --config
参数来配置我们的调度器。新的调度器名字为sample-scheduler。在默认调度器的基础上,在preFilter/filter/preBind阶段新增自己的插件。
5.4 使用
指定要使用的调度器名字。