图解kubernetes调度器预选设计实现学习
Scheduler中在进行node选举的时候会首先进行一轮预选流程,即从当前集群中选择一批node节点,本文主要分析k8s在预选流程上一些优秀的筛选设计思想,欢迎大佬们指正
1. 基础设计
1.1 预选场景
预选顾名思义就是从当前集群中的所有的node中,选择出满足当前pod资源和亲和性等需求的node节点,如何在集群中快速选择这样的节点,是个复杂的问题
1.2 平均分布
平均分布主要是通过让一个分配索引来进行即只有当所有的node都在本轮分配周期内分配一次后,才开始从头进行分配,从而保证集群的平均分布
1.3 预选中断
预选终端即在预选的过程中如果发现node已经不能满足当前pod资源需求的时候,就进行中断预选流程,尝试下一个节点
1.4 并行筛选
在当前k8s版本中,默认会启动16个goroutine来进行并行的预选,从而提高性能,从而提高预选的性能
1.5 局部最优解
预选流程需要从当前集群中选择一台符合要求的node随着集群规模的增长,如果每次遍历所有集群node则会必然导致性能的下降,于是通过局部最优解的方式,缩小筛选节点的数量
2. 源码分析
预选的核心流程是通过findNodesThatFit来完成,其返回预选结果供优选流程使用
2.1 取样逻辑
取样是通过当前集群中的node数量和默认的最小值来决定本次预选阶段需要获取的node节点数量
// 获取所有的节点数量,并通过计算百分比,获取本次选举选择的节点数量
allNodes := int32(g.cache.NodeTree().NumNodes())
// 确定要查找node数量
numNodesToFind := g.numFeasibleNodesToFind(allNodes)
2.2 取样算法
取样算法很简单从集群中获取指定百分比的节点默认是50%,如果50%的节点数量小于minFeasibleNodesToFind则按照minFeasibleNodesToFind(最小取样节点数量)来取样,
func (g *genericScheduler) numFeasibleNodesToFind(numAllNodes int32) (numNodes int32) {
// 如果当前节点数量小于minFeasibleNodesToFind即小于100台node
// 同理百分比如果大于100就是全量取样
// 这两种情况都直接遍历整个集群中所有节点
if numAllNodes < minFeasibleNodesToFind || g.percentageOfNodesToScore >= 100 {
return numAllNodes
}
adaptivePercentage := g.percentageOfNodesToScore
if adaptivePercentage <= 0 {
adaptivePercentage = schedulerapi.DefaultPercentageOfNodesToScore - numAllNodes/125
if adaptivePercentage < minFeasibleNodesPercentageToFind {
adaptivePercentage = minFeasibleNodesPercentageToFind
}
}
// 正常取样计算:比如numAllNodes为5000,而adaptivePercentage为50%
// 则numNodes=50000*0.5/100=250
numNodes = numAllNodes * adaptivePercentage / 100
if numNodes < minFeasibleNodesToFind { // 如果小于最少取样则按照最少取样进行取样
return minFeasibleNodesToFind
}
return numNodes
}
2.3 取样元数据准备
通过filtered来进行预选结果的存储,通过filteredLen来进行原子保护协作多个取样goroutine, 并通过predicateMetaProducer和当前的snapshot来进行元数据构建
filtered = make([]*v1.Node, numNodesToFind)
errs := errors.MessageCountMap{}
var (
predicateResultLock sync.Mutex
filteredLen int32
)
ctx, cancel := context.WithCancel(context.Background())
// We can use the same metadata producer for all nodes.
meta := g.predicateMetaProducer(pod, g.nodeInfoSnapshot.NodeInfoMap)
2.4 通过channel协作并行取样
并行取样主要通过调用下面的函数来启动16个goroutine来进行并行取样,并通过ctx来协调退出
workqueue.ParallelizeUntil(ctx, 16, int(allNodes), checkNode)

通过channel来构建取样索引的管道,每个worker会负责从channel获取的指定索引取样node的填充
func ParallelizeUntil(ctx context.Context, workers, pieces int, doWorkPiece DoWorkPieceFunc) {
var stop <-chan struct{}
if ctx != nil {
stop = ctx.Done()
}
// 生成指定数量索引,worker通过索引来进行预选成功节点的存储
toProcess := make(chan int, pieces)
for i := 0; i < pieces; i++ {
toProcess <- i
}
close(toProcess)
if pieces < workers {
workers = pieces
}
wg := sync.WaitGroup{}
wg.Add(workers)
for i := 0; i < workers; i++ {
// 启动多个goroutine
go func() {
defer utilruntime.HandleCrash()
defer wg.Done()
for piece := range toProcess {
select {
case <-stop:
return
default:
//获取索引,后续会通过该索引来进行结果的存储
doWorkPiece(piece)
}
}
}()
}
// 等待退出
wg.Wait()
}
2.5 取样并行函数
checkNode := func(i int) {
// 获取一个节点
nodeName := g.cache.NodeTree().Next()
// 取样核心流程是通过podFitsOnNode来确定
fits, failedPredicates, status, err := g.podFitsOnNode(
pluginContext,
pod,
meta,
g.nodeInfoSnapshot.NodeInfoMap[nodeName],
g.predicates, // 传递预选算法
g.schedulingQueue,
g.alwaysCheckAllPredicates,
)
if err != nil {
predicateResultLock.Lock()
errs[err.Error()]++
predicateResultLock.Unlock()
return
}
if fits {
// 如果当前以及查找到的数量大于预选的数量,就退出
length := atomic.AddInt32(&filteredLen, 1)
if length > numNodesToFind {
cancel()
atomic.AddInt32(&filteredLen, -1)
} else {
filtered[length-1] = g.nodeInfoSnapshot.NodeInfoMap[nodeName].Node()
}
} else {
// 进行错误状态的保存
predicateResultLock.Lock()
if !status.IsSuccess() {
filteredNodesStatuses[nodeName] = status
}
if len(failedPredicates) != 0 {
failedPredicateMap[nodeName] = failedPredicates
}
predicateResultLock.Unlock()
}
}
2.6 面向未来的筛选

在kubernetes中经过调度器调度后的pod结果会放入到SchedulingQueue中进行暂存,这些pod未来可能会经过后续调度流程运行在提议的node上,也可能因为某些原因导致最终没有运行,而预选流程为了减少后续因为调度冲突(比如pod之间的亲和性等问题,并且当前pod不能抢占这些pod),则会在进行预选的时候,将这部分pod考虑进去
如果在这些pod存在的情况下,node可以满足当前pod的筛选条件,则可以去除被提议的pod再进行筛选(如果这些提议的pod最终没有调度到node,则当前node也需要满足各种亲和性的需求)
2.6 取样核心设计

结合上面说的面向未来的筛选,通过两轮筛选在无论那些优先级高的pod是否被调度到当前node上,都可以满足pod的调度需求,在调度的流程中只需要获取之前注册的调度算法,完成预选检测,如果发现有条件不通过则不会进行第二轮筛选,继续选择下一个节点
func (g *genericScheduler) podFitsOnNode(
pluginContext *framework.PluginContext,
pod *v1.Pod,
meta predicates.PredicateMetadata,
info *schedulernodeinfo.NodeInfo,
predicateFuncs map[string]predicates.FitPredicate,
queue internalqueue.SchedulingQueue,
alwaysCheckAllPredicates bool,
) (bool, []predicates.PredicateFailureReason, *framework.Status, error) {
var failedPredicates []predicates.PredicateFailureReason
var status *framework.Status
// podsAdded主要用于标识当前是否有提议的pod如果没有提议的pod则就不需要再进行一轮筛选了
podsAdded := false
for i := 0; i < 2; i++ {
metaToUse := meta
nodeInfoToUse := info
if i == 0 {
// 首先获取那些提议的pod进行第一轮筛选, 如果第一轮筛选出错,则不会进行第二轮筛选
podsAdded, metaToUse, nodeInfoToUse = addNominatedPods(pod, meta, info, queue)
} else if !podsAdded || len(failedPredicates) != 0 {
// 如果
break
}
for _, predicateKey := range predicates.Ordering() {
var (
fit bool
reasons []predicates.PredicateFailureReason
err error
)
//TODO (yastij) : compute average predicate restrictiveness to export it as Prometheus metric
if predicate, exist := predicateFuncs[predicateKey]; exist {
// 预选算法计算
fit, reasons, err = predicate(pod, metaToUse, nodeInfoToUse)
if err != nil {
return false, []predicates.PredicateFailureReason{}, nil, err
}
if !fit {
// eCache is available and valid, and predicates result is unfit, record the fail reasons
failedPredicates = append(failedPredicates, reasons...)
// if alwaysCheckAllPredicates is false, short circuit all predicates when one predicate fails.
if !alwaysCheckAllPredicates {
klog.V(5).Infoln("since alwaysCheckAllPredicates has not been set, the predicate " +
"evaluation is short circuited and there are chances " +
"of other predicates failing as well.")
break
}
}
}
}
status = g.framework.RunFilterPlugins(pluginContext, pod, info.Node().Name)
if !status.IsSuccess() && !status.IsUnschedulable() {
return false, failedPredicates, status, status.AsError()
}
}
return len(failedPredicates) == 0 && status.IsSuccess(), failedPredicates, status, nil
}
微信号:baxiaoshi2020
关注公告号阅读更多源码分析文章
更多文章关注 www.sreguide.com
本文由博客一文多发平台 OpenWrite 发布
图解kubernetes调度器预选设计实现学习的更多相关文章
- 图解kubernetes调度器ScheduleAlgorithm核心实现学习框架设计
ScheduleAlgorithm是一个接口负责为pod选择一个合适的node节点,本节主要解析如何实现一个可扩展.可配置的通用算法框架来实现通用调度,如何进行算法的统一注册和构建,如何进行metad ...
- 图解kubernetes调度器抢占流程与算法设计
抢占调度是分布式调度中一种常见的设计,其核心目标是当不能为高优先级的任务分配资源的时候,会通过抢占低优先级的任务来进行高优先级的调度,本文主要学习k8s的抢占调度以及里面的一些有趣的算法 1. 抢占调 ...
- 图解kubernetes调度器SchedulerExtender扩展
在kubernetes的scheduler调度器的设计中为用户预留了两种扩展机制SchdulerExtender与Framework,本文主要浅谈一下SchdulerExtender的实现, 因为还有 ...
- 图解kubernetes调度器SchedulingQueue核心源码实现
SchedulingQueue是kubernetes scheduler中负责进行等待调度pod存储的对,Scheduler通过SchedulingQueue来获取当前系统中等待调度的Pod,本文主要 ...
- 图解kubernetes调度器SchedulerCache核心源码实现
SchedulerCache是kubernetes scheduler中负责本地数据缓存的核心数据结构, 其实现了Cache接口,负责存储从apiserver获取的数据,提供给Scheduler调度器 ...
- Kubernetes 调度器实现初探
Kubernetes 调度器 Kubernetes 是一个基于容器的分布式调度器,实现了自己的调度模块.在Kubernetes集群中,调度器作为一个独立模块通过pod运行.从几个方面介绍Kuberne ...
- kubernetes 调度器
调度器 kube-scheduler 是 kubernetes 的核心组件之一,主要负责整个集群资源的调度功能,根据特定的调度算法和策略,将 Pod 调度到最优的工作节点上面去,从而更加合理.更加充分 ...
- 第十五章 Kubernetes调度器
一.简介 Scheduler 是 kubernetes 的调度器,主要的任务是把定义的 pod 分配到集群的节点上.听起来非常简单,但有很多要考虑的问题: ① 公平:如何保证每个节点都能被分配资源 ② ...
- 巧用Prometheus来扩展kubernetes调度器
Overview 本文将深入讲解 如何扩展 Kubernetes scheduler 中各个扩展点如何使用,与扩展scheduler的原理,这些是作为扩展 scheduler 的所需的知识点.最后会完 ...
随机推荐
- uva 624 CD (01背包)
CD You have a long drive by car ahead. You have a tape recorder, but unfortunately your best musi ...
- hibernate中因双向依赖而造成的json怪相--springmvc项目
简单说一下Jackson 如果想要详细了解一下Jackson,可以去其github上的项目主页查看其版本情况以及各项功能.除此以外,需要格外提一下Jackson的版本问题.Jackson目前主流版本有 ...
- H3C NAT ALG
- java 文件过滤器 java.io.FilenameFilter
File 类里有方法: String[] list(FilenameFilter filter) 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中满足指定过滤器的文件和目录. File ...
- Can you find it?——[二分查找]
Description Give you three sequences of numbers A, B, C, then we give you a number X. Now you need t ...
- ASP.NET MVC 实现页落网资源分享网站+充值管理+后台管理(15)之前台网站页面
源码下载地址:http://www.yealuo.com/Sccnn/Detail?KeyValue=c891ffae-7441-4afb-9a75-c5fe000e3d1c 本项目主要是一个素材的分 ...
- springboot+nginx+https+linux实现负载均衡加域名访问简单测试
把springboot项目打包成三个jar包,并指定端口为 14341,14342,14343 下载腾讯云免费ssl证书,解压后会出现如下图文件夹 把nginx文件夹下的 .crt 和 .key文件复 ...
- yum安装gcc和gcc-c++
本次总结参考 博客:http://blog.csdn.net/robertkun/article/details/8466700 ,非常 感谢他的博客,帮我解决了问题. 今天安装gcc-c++时出现 ...
- 博客同步到CSDN客户端
同步本人博客到CSDN客户端 http://blog.csdn.net/johnnyz1234
- DynamoDB的基本操作(一)
一.创建表 1 var AWS = require("aws-sdk"); 2 AWS.config.update({ 3 region: "us-west-2" ...

