kube-scheduler

时间:July 26, 2020 分类:

目录:

kube-scheduler的介绍

kube-scheduler功能比较单一,为pod选择node节点,流程也简单

  1. 获取未调度的Podlist
  2. 通过算法为pod选择node
  3. 数据提交到apiserver

kube-scheduler的启动

启动主要使用的Run方法

  1. 初始化Scheduler,scheduler.New
  2. 启动事件广播
  3. 启动kube-scheduler
  4. 启动http服务
  5. 启动informer
  6. 执行sched.Run()调度逻辑

scheduler.New

  1. 加载scheduler配置文件
  2. 加载默认算法
  3. 创建scheduler对象

informer只监听status.phase不为succeeded和failed的pod,不为terminating的pod

informer的cache创建完,会启动sched.scheduleOne方法,流程是

  1. 从调度队列获取pod,如果pod不符合就跳过
  2. 执行sched.schedule()返回最佳的node
  3. 过滤算法没合适的就返回core.FitError
  4. core.FitError的pod是否启动抢占策略,如果启动则执行抢占策略
  5. 调用sched.assumeVolumes判断了是否启动VolumeScheduling策略
  6. 调用fwk.RunReservePlugins执行reserve plugin
  7. pod的spec.NodeName设置为选择的node,更新scheduler cache
  8. 异步写入apiserver
  9. 调用fwk.RunPermitPlugins执行permit plugin
  10. 调用fwk.RunPreBindPlugins执行prebind plugin
  11. 调用fwk.RunPostBindPlugins执行postbind plugin

sched.schedule()调用的sched.Algorithm.Schedule()

  1. 调用podPassesBasicChecks检查pod的pvc
  2. 调用g.framework.RunPreFilterPlugins执行prefilter plugins
  3. 调用g.cache.NodeTree().NumNodes()和g.snapshot获取scheduler cache
  4. 调用g.findNodesThatFit()预选算法
  5. 调用g.framework.RunPostFilterPlugins执行postfilter plugin
  6. 如果node为0直接返回失败的error,如果node数为1直接返回该node
  7. 执行g.priorityMetaProducer()计算pod的metadata,检查该node上是否有相同meta的pod
  8. 执行PrioritizeNodes()算法
  9. 执行g.selectHost()通过得分选择一个最佳的node

predicates和priorities调度算法

predicates用于过滤node

func defaultPredicates() sets.String {
    return sets.NewString(
        predicates.NoVolumeZoneConflictPred,    // 
        predicates.MaxEBSVolumeCountPred,
        predicates.MaxGCEPDVolumeCountPred,
        predicates.MaxAzureDiskVolumeCountPred,
        predicates.MaxCSIVolumeCountPred,
        predicates.MatchInterPodAffinityPred,
        predicates.NoDiskConflictPred,
        predicates.GeneralPred,
        predicates.CheckNodeMemoryPressurePred,
        predicates.CheckNodeDiskPressurePred,
        predicates.CheckNodePIDPressurePred,
        predicates.CheckNodeConditionPred,
        predicates.PodToleratesNodeTaintsPred,
        predicates.CheckVolumeBindingPred,
    )
}

算法主要分五类

第一类GeneralPredicates类型,这个类型会在kubelet启动pod的时候使用Admit操作再次确认

  1. PodFitsHost检测node是否匹配Pod的spec.nodeName
  2. PodFitsHostPorts检测node是否匹配Pod的spec.nodePort是否冲突
  3. PodMatchNodeSelector检测node是否匹配nodeSelector和nodeAffinity
  4. PodFitsResources检测node是否满足pod的request需求

第二类与Volume相关,包括NoDiskConflictPred、MaxGCEPDVolumeCountPred、MaxAzureDiskVolumeCountPred、MaxCSIVolumeCountPred、MaxEBSVolumeCountPred、NoVolumeZoneConflictPred、CheckVolumeBindingPred

第三类是宿主机相关的过滤规则,主要是PodToleratesNodeTaintsPred

第四类是Pod相关的过滤规则,主要是MatchInterPodAffinityPred

第五类是新增的过滤规则,与宿主机运行情况有关,包括CheckNodeCondition、 CheckNodeMemoryPressure、CheckNodePIDPressure、CheckNodeDiskPressure,如果启动了TaintNodesByCondition,则不进行以上四种,TaintNodesByCondition会为node打上pressure的taint,v1.13默认启用

预选算法会设定最多需要检查的节点数,提高效率

  1. 如果node节点数小于minFeasibleNodesToFind(默认100)直接返回了所有node
  2. 如果node节点数大于minFeasibleNodesToFind,根据percentageOfNodesToScore(默认50)百分比的节点数
  3. 如果百分比数目后仍然大于minFeasibleNodesToFind,则返回百分比,如果小于,则返回minFeasibleNodesToFind

最后并发执行podFitsOnNode

priorities用于给node打分

func defaultPriorities() sets.String {
    return sets.NewString(
        priorities.SelectorSpreadPriority,
        priorities.InterPodAffinityPriority,
        priorities.LeastRequestedPriority,
        priorities.BalancedResourceAllocation,
        priorities.NodePreferAvoidPodsPriority,
        priorities.NodeAffinityPriority,
        priorities.TaintTolerationPriority,
        priorities.ImageLocalityPriority,
    )
}

每个函数有打分0~10,并且每个函数有权重,最后返回一个加权评分

优先级和抢占机制

调度器维护了两个调度队列

  • activeQ 用于新的pod调度
  • unschedulableQ 用于存放调度失败,pod被更新之后就会移动到activeQ

pod调度失败后会直接执行抢占的逻辑,sched.preempt用于执行抢占的逻辑

  1. 获取pod的info
  2. 调用sched.Algorithm.Preempt()执行抢占,成功返回node和被抢占的pod
  3. 更新scheduler缓存,为抢占pod绑定nodeName,设定pod.Status.NominatedNodeName
  4. 将pod的info提交到apiserve
  5. 删除被抢占的pods和其上的NominatedNodeName

抢占的过程并不会直接调度到对应的node,而是设置status.nominatedNodeName,让抢占的pod进入下一个调度周期,调度不保证一定会运行在被抢占的node上

因为删除了被抢占的pod会退出,在这过程其他的node可以被调度,或者有更高优先级被pod需要进行抢占

sched.Algorithm.Preempt()调用了

  • nodesWherePreemptionMightHelp 过滤predicates算法执行失败的node
  • selectNodesForPreemption 过滤出可以抢占的node列表
  • pickOneNodeForPreemption 选出最佳的node
  • getLowerPriorityNominatedPods 移除低优先级pod的Nominated,更新pod的状态移入activeQ队列

selectNodesForPreemption获取node上可以被抢占的pod,模拟移除低优先级的pod是否能调度成功,如果可以pod将PDB(中断预算保证pod高可用)分成violatingVictims和nonViolatingVictims,将pod根据优先级,将高优先级的模拟加回node中,直到node不能调度,返回victims和需要删除的pod数量

pickOneNodeForPreemption进行排序的原则

  1. node的numViolatingVictim最低
  2. 挑选具有高优先级较少的node
  3. node上所有victims的优先级进项累加,选取最小的
  4. 选择victims pod数最少的Node
  5. 具有高优先级且pod运行时间最短的
  6. random

kubectl get pod -o wide -w可以看到NOMINATED NODE