kubernetes集群中的调度程序 kube-scheduler
会 watch
未分配节点的新创建的Pod,并未该Pod找到可运行的最佳(特定)节点。那么这些动作或者说这些原理是怎么实现的呢,让我们往下剖析下。
对于新创建的 pod 或其他未调度的 pod来讲,kube-scheduler 选择一个最佳节点供它们运行。但是,Pod 中的每个容器对资源的要求都不同,每个 Pod 也有不同的要求。因此,需要根据具体的调度要求对现有节点进行过滤。
在Kubernetes集群中,满足 Pod 调度要求的节点称为可行节点 ( feasible nodes
FN ) 。如果没有合适的节点,则 pod 将保持未调度状态,直到调度程序能够放置它。也就是说,当我们创建Pod时,如果长期处于 Pending
状态,这个时候应该看你的集群调度器是否因为某些问题没有合适的节点了
调度器为 Pod 找到 FN 后,然后运行一组函数对 FN 进行评分,并在 FN 中找到得分最高的节点来运行 Pod。
调度策略在决策时需要考虑的因素包括个人和集体资源需求、硬件/软件/策略约束 ( constraints
)、亲和性 ( affinity
) 和反亲和性( anti-affinity
)规范、数据局部性、工作负载间干扰等。
如何为pod选择节点?
kube-scheduler
为pod选择节点会分位两部:
Filtering Scoring
过滤也被称为预选 ( Predicates
),该步骤会找到可调度的节点集,然后通过是否满足特定资源的请求,例如通过 PodFitsResources
过滤器检查候选节点是否有足够的资源来满足 Pod 资源的请求。这个步骤完成后会得到一个包含合适的节点的列表(通常为多个),如果列表为空,则Pod不可调度。
打分也被称为优选( Priorities
),在该步骤中,会对上一个步骤的输出进行打分,Scheduer 通过打分的规则为每个通过 Filtering
步骤的节点计算出一个分数。
完成上述两个步骤之后, kube-scheduler
会将Pod分配给分数最高的 Node,如果存在多个相同分数的节点,会随机选择一个。
kubernetes的调度策略
Kubernetes 1.21之前版本可以在代码 kubernetes\pkg\scheduler\algorithmprovider\registry.go 中看到对应的注册模式,在1.22 scheduler 更换了其路径,对于registry文件更换到了 kubernetes\pkg\scheduler\framework\plugins\registry.go ;对于kubernetes官方说法为, 调度策略是用于“预选” ( Predicates
)或 过滤( filtering
) 和 用于 优选( Priorities
)或 评分 ( scoring
)的
注:kubernetes官方没有找到预选和优选的概念,而Predicates和filtering 是处于预选阶段的动词,而Priorities和scoring是优选阶段的动词。后面用PF和PS代替这个两个词。
上面也提到了, filtering
的目的是为了排除(过滤)掉不满足 Pod 要求的节点。例如,某个节点上的闲置资源小于 Pod 所需资源,则该节点不会被考虑在内,即被过滤掉。在 “Predicates” 阶段实现的 filtering 策略,包括:
NoDiskConflict
:评估是否有合适Pod请求的卷NoVolumeZoneConflict
:在给定zone限制情况下,评估Pod请求所需的卷在Node上是否可用PodFitsResources
:检查空闲资源(CPU、内存)是否满足Pod请求PodFitsHostPorts
:检查Pod所需端口在Node上是否被占用HostName
: 过滤除去,PodSpec
中NodeName
字段中指定的Node之外的所有Node。MatchNodeSelector
:检查Node的 label 是否与 Pod 配置中nodeSelector
字段中指定的 label 匹配,并且从 Kubernetes v1.2 开始, 如果存在nodeAffinity
也会匹配。CheckNodeMemoryPressure
:检查是否可以在已出现内存压力情况节点上调度 Pod。CheckNodeDiskPressure
:检查是否可以在报告磁盘压力情况的节点上调度 Pod
具体对应得策略可以在 kubernetes\pkg\scheduler\framework\plugins\registry.go 看到
通过上面步骤过滤过得列表则是适合托管的Pod,这个结果通常来说是一个列表,如何选择最优Node进行调度,则是接下来打分的步骤步骤。
例如:Kubernetes对剩余节点进行优先级排序,优先级由一组函数计算;优先级函数将为剩余节点给出从 0~10
的分数,10 表示最优,0 表示最差。每个优先级函数由一个正数加权组成,每个Node的得分是通过将所有加权得分相加来计算的。设有两个优先级函数, priorityFunc1
和 priorityFunc2
加上权重因子 weight1
和 weight2
,那么这个Node的最终得分为: \(finalScore = (w1 \times priorityFunc1) + (w2 \times priorityFunc2)\) 。计算完分数后,选择最高分数的Node做为Pod的宿主机,存在多个相同分数Node情况下会随机选择一个Node。
目前kubernetes提供了一些在打分 Scoring 阶段算法:
LeastRequestedPriority
:Node的优先级基于Node的空闲部分 \(\frac{capacity\ -\ Node上所有存在的Pod\ -\ 正在调度的Pod请求}{capacity}\) ,通过计算具有最高分数的Node是FNBalancedResourceAllocation
:该算法会将 Pod 放在一个Node上,使得在Pod 部署后 CPU 和内存的使用率为平衡的SelectorSpreadPriority
:通过最小化资源方式,将属于同一种服务、控制器或同一Node上的Replica的 Pod的数量来分布Pod。如果节点上存在Zone,则会调整优先级,以便 pod可以分布在Zone之上。CalculateAntiAffinityPriority
:根据label来分布,按照相同service上相同label值的pod进行分配ImageLocalityPriority
:根据Node上镜像进行打分,Node上存在Pod请求所需的镜像优先级较高。
在代码中查看上述的代码
以 PodFitsHostPorts
算法为例,因为是Node类算法,在 kubernetes\pkg\scheduler\framework\plugins\nodeports
调度框架 ( scheduling framework
SF ) 是kubernetes为 scheduler设计的一个pluggable的架构。SF 将scheduler设计为 Plugin 式的 API,API将上一章中提到的一些列调度策略实现为 Plugin
。
在 SF 中,定义了一些扩展点 ( extension points
EP ),而被实现为Plugin的调度程序将被注册在一个或多个 EP 中,换句话来说,在这些 EP 的执行过程中如果注册在多个 EP 中,将会在多个 EP 被调用。
每次调度都分为两个阶段,调度周期( Scheduling Cycel
)与绑定周期( Binding Cycle
)。
- SC 表示为,为Pod选择一个节点; SC 是串行运行的。
- BC 表示为,将 SC 决策结果应用于集群中; BC 可以同时运行。
调度周期与绑定周期结合一起,被称为 调度上下文 ( Scheduling Context
),下图则是调度上下文的工作流
注:如果决策结果为Pod的调度结果无可用节点,或存在内部错误,则中止 SC 或 BC 。Pod将重入队列重试
图1:Pod的调度上下文
Source: https://kubernetes.io/docs/concepts/scheduling-eviction