Kubernetes Scheduler
Kubernetes Scheduler 基本使用概念
Scheduler Workflow
Scheduler WorkFlow
定义了Scheduler Cycle
和Binding Cycle
,在Scheduler Cycle
中有串行Filter
和Score
两个阶段,Bind
是异步并行阶段。
Scheduler FrameWork
Scheduler FrameWork
是通过在Scheduler Cycle
中通过预留扩展点来进行扩展调度流程,FrameWork
预留了多个扩展点:
QueueSortFunc() LessFunc
:Pod
从队列中获取的优先级PreFilter
: 在过滤之前执行Filter
: 执行过滤函数PostFilter
: 执行抢占PreScore
:执行`Score之前执行Score
: 执行Node
打分PreBind
: 执行Binding
之前Bind
: 执行Binding
动作PostBind
:执行Binding
之后
自定义Schedlue
可以通过直接实现Scheduler FrameWork
中暴露的接口,然后在启动过程中进行Plugin
注册即可。
func main() {
rand.Seed(time.Now().UTC().UnixNano())
logs.InitLogs()
defer logs.FlushLogs()
cmd := app.NewSchedulerCommand(
app.WithPlugin(sample.Name, sample.New),
)
if err := cmd.Execute(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
Scheduler Plugin
如下的Scheduler Plugin
是kube-scheduler
里面内嵌的插件,涵盖了WorkFlow
里面的所有流程。
Sort(排序)
1.PrioritySort
PrioritySort
优先队列是进行Pod
在入队时的排序函数,按找Pod
的优先级进行排序,通过读取pod.Spec.Priority
字段值进行排序,如果未设置Pod
的优先级则按照添加到队列的时间先后进行排序。
p1 := pod1.Spec.Priority != nil ? *pod1.Spec.Priority : 0
p2 := pod2.Spec.Priority != nil ? *pod2.Spec.Priority : 0
return (p1 > p2) || (p1 == p2 && pInfo1.Timestamp.Before(pInfo2.Timestamp))
Filter(预选)
1. InterPodAffinity
InterPodAffinity
检查Pod
的亲和性是否满足,实现了PreFilterPlugin
和FilterPlugin
两个插件;
2. NodeAffinity
NodeAffinity
是Node
亲和性的过滤,通过pod.Spec.Affinity.NodeAffinity
是否配置Node
亲和性和Node
进行Match
动作。
3.NodeName
NodeName
用于在对pod.Spec.NodeName
字段值的判断,判断是否是当前的Node
,如果指定了pod.Spec.NodeName
并且不是当前的节点,则返回UnschedulableAndUnresolvable
状态。
4.NodePorts
NodePorts
用于检查要启用的端口是否已经在Node
上使用过,如果使用过则返回Unschedulable
状态。
5.NodeResourcesFit
NodeResourcesFit
检查节点是否有足够的资源,如:cpu
、memory
、gpu
等,通过那取max_resource(sum_pod, any_init_container)
和node
可分配的资源来判断节点是否资源足够。如果有任一资源不够,此节点则会返回资源不足被过滤。
6.NodeUnschedulable
NodeUnschedulable
插件过滤设置node.Spec.Unschedulable=true
的节点,但是如果Pod
设置了容忍Unschedulable
的污点则不过滤。
7.NodeVolumeLimits
NodeVolumeLimits
是一个节点检查volume
容量的插件; 校验PVC指定的Provision在CSI plugin或非CSI Plugin(后三个)上报的单机最大挂盘数(存储插件提供方一般对每个节点的单机最大挂载磁盘数是有限制的)
8.PodTopologySpread
PodTopologySpread
是用于检查Pod
的拓扑逻辑是否满足条件,
9.TaintToleration
TaintToleration
是一个污点容忍度的插件,用于检查Pod
是否能容忍次污点。Taint
污点是Node
上的标签,Toleration
容忍度是Pod
上的功能,一个容忍度和一个污点相“匹配”是指它们有一样的key
和value
。
10.VolumeBinding
VolumeBinding
检查Pod
挂载的PVC
,如果其对应SC(StorageClass)
的VolumeBindingMode
是Immediate
模式,该PVC
必须已经是bound
,否则需要返回UnschedulableAndUnresolvable
。
11.VolumeRestrictions
VolumeRestrictions
检查挂载该Node上的卷是否满足存储提供者的要求
Score(优选)
1.ImageLocality
ImageLocality
是一个打分插件,对于调度Pod
的镜像已经存在Node
上给评分。已经存在的镜像并且镜像越大的分越高;(逻辑就是越大的镜像如果被重新拉取需要花费更多的时间,但是小的镜像拉起来就快,所以镜像存在并且越大的分越高就是这个逻辑);如果一个节点上的镜像总大小< 23Mb
则node
会被打0
分;如果一个节点上的镜像总大于1G * 容器数
则会被打1
分。
2.InterPodAffinity
InterPodAffinity
在Pod
亲和性之间计算亲和性的的分值。
3.NodeAffinity
NodeAffinity
是通过节点的亲和性来进行节点打分的,PreScore
主要逻辑是从pod.Spec.Affinity.NodeAffinity.PreferredDuringSchedulingIgnoredDuringExecution
字段中获取首选节点亲和配置,主要的是配置的weight
字段值,在通过Label
匹配过程中,发现Node.Meta.Label
是匹配matchExpressions
中的字段则score += weight
,这样这个节点的优先级就提高了。
4.NodeResourcesBalancedAllocation
NodeResourcesBalancedAllocation
节点资源均衡插件,是一个通过cpu
和memory
容量分数之间的差异,根据他们的优先级进行排名;对于资源使用率均衡的节点分值更高。
# ResourceSpec[{"name":"cpu","Weight":1},{"name":"memory","Weight":1}]
std = Σ((fraction(i)-mean)^2)/len(resources)
# std计算取决于需要均衡几种资源,如果只有cpu和memory
std=Abs( (cpuRequest1/cpuAllocable1 - memoryRequest1/memoryAllocable1) / 2 )
# node打分则由如下计算
score = (1 - std) * MaxNodeScore(100)
5.NodeResourcesFit
NodeResourcesFit
打分已经配置的策略进行,有如下三种:
LeastAllocated
: 优先分配资源最少的节点;就是资源堆叠MostAllocated
: 优先分配资源最多的节点;就是资源打散RequestedToCapacityRatio
: 可以自定义函数,对资源容量进行打分。
LeastAllocated
计算节点分值逻辑:(cpu((capacity-requested)*MaxNodeScore*cpuWeight/capacity) + memory((capacity-requested)*MaxNodeScore*memoryWeight/capacity) + ...)/weightSum
MostAllocated 计算节点分值逻辑:(cpu(MaxNodeScore * sum(requested) / capacity) + memory(MaxNodeScore * sum(requested) / capacity) + ...) / weightSum
capacity
是allocable
6.PodTopologySpread
PodTopologySpread
权重为2
7.SelectorSpread
SelectorSpread
是一个计算选择器传播优先级的打分插件;
8.TaintToleration
TaintToleration计算无法容忍污点的一个插件,用于给
Pod 无法容忍污点打分,每一个无法容忍则+1
,最后得到的就是Node
的分。
Binding(绑定)
1. DefaultBind
DefaultBind
是通过使用k8s client
绑定Pod
到Node
。绑定流程是先创建一个Binding
类型对象,通过设置Target
字段值,来记录绑定对象的目标对象。
# Binding是将一个对象与另一个对象联系起来
type Binding struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
Target ObjectReference `json:"target" protobuf:"bytes,2,opt,name=target"`
}
binding := &v1.Binding{
ObjectMeta: metav1.ObjectMeta{Namespace: p.Namespace, Name: p.Name, UID: p.UID},
Target: v1.ObjectReference{Kind: "Node", Name: nodeName},
}
创建Binding
对象之后,通过Pods
的Bind
方法就可以进行结果绑定。最终是创建Pod
的绑定。
POST /api/v1/namespaces/{namespace}/pods/{name}/binding
namespace: pod namespace
name: binding 名字
body: Binding对象结构体
选中的节点在和待调度Pod进行Bind的时候,有可能会Bind失败,此时需要做回退,把Pod的Assumed状态退回Initial,从Node里面把Pod数据账本擦除掉,会把Pod重新丢回到unschedulableQ队列里面。在unschedulableQ里,如果一个Pod一分钟没调度过,就会重新回到activeQ。它的轮询周期是30s。
调度失败的Pod
会放到backoffQ
,在backoffQ
里等待的时间会比在unschedulableQ
里更短,backoffQ
里的降级策略是2
的指数次幂降级。假设重试第一次为1s
,那第二次就是2s
,第三次就是4s
,但最大到10s
。
2.VolumeBinding
VolumeBinding
是PreBinding
作为Binding
之前检查的Volume
是否绑定到Node
上面,
Preemption(抢占)
1.DefaultPreemption
DefaultPreemption
是一个PostFilter
插件,实现了默认抢占逻辑;当高优先级的Pod
没有找到合适的Node
时,会执行Preempt
抢占算法,抢占的流程。
- 一个
Pod
进入抢占的时候,首先会判断Pod
是否拥有抢占的资格,有可能上次已经抢占过一次。 - 如果符合抢占资格,会先对所有的节点进行一次过滤,过滤出符合这次抢占要求的节点。然后
- 模拟一次调度,把优先级低的
Pod
先移除出去,再尝试能否把待抢占的Pod
放置到此节点上。然后通过这个过程从过滤剩下的节点中选出一批节点进行抢占。 ProcessPreemptionWithExtenders
是一个扩展的钩子,用户可以在这里加一些自己抢占节点的策略。如果没有扩展的钩子,这里面不做任何动作。PickOneNodeForPreemption
,从上面选出的节点里挑选出最合适的一个节点,策略包括:- 优先选择打破
PDB
最少的节点; - 其次选择待抢占
Pods
中最大优先级最小的节点; - 再次选择待抢占
Pods
优先级加和最小的节点; - 接下来选择待抢占
Pods
数目最小的节点; - 最后选择拥有最晚启动
Pod
的节点;
- 优先选择打破
通过过滤之后,会选出一个最合适的节点。对这个节点上待抢占的Pod
进行delete
,完成抢占过程。