欢迎关注本专栏,本专栏主要从 K8s 源码出发,深入理解 K8s 一些组件底层的代码逻辑,同时借助 debug Minikube 来进一步了解 K8s 底层的代码运行逻辑细节,帮助我们更好的了解不为人知的运行机制,让自己学会如何调试源码,玩转 K8s。
本专栏适合于运维、开发以及希望精进 K8s 细节的同学。同时本人水平有限,尽量将本人理解的内容最大程度的展现给大家~
前情提要:
《K8s 源码剖析及debug实战(一):Minikube 安装及源码准备》
《K8s 源码剖析及debug实战(二):debug K8s 源码》
《K8s 源码剖析及debug实战之 Kube-Scheduler(一):启动流程详解》
文中采用的 K8s 版本是 v1.16。紧接上篇,本文主要介绍 K8s 的 Kube-Scheduler 源码的 Run
函数的主体流程。
这里的代码看起来比较多,而且每个不起眼的方法,点进去又拓展出更多的方法,如果你被这些细节绊住的话,陷入细节则看起来会非常费劲。我这里把一些主要逻辑展示出来,如下:
func Run(cc schedulerserverconfig.CompletedConfig, stopCh <-chan struct{}, registryOptions ...Option) error {
...
// 1. 创建 scheduler
sched, err := scheduler.New(cc.Client, ...)
// 2. 启动健康检查server、metrics server、连接apiserver
if cc.InsecureServing != nil {...}
if cc.InsecureMetricsServing != nil {...}
if cc.SecureServing != nil {...}
// 3. 启动informers
go cc.PodInformer.Informer().Run(stopCh)
cc.InformerFactory.Start(stopCh)
...
// 4. 运行scheduler
run := func(ctx context.Context) {
sched.Run()
<-ctx.Done()
}
run(ctx)
}
这里最主要的方法就是 sched.Run()
,因为末尾的 run(ctx)
最终调用的就是 sched.Run()
,抓住重点之后,后面看核心代码才有的放矢。
这个方法,整了一个很简单的封装,起了一个 go 协程,不断去执行 sched.scheduleOne
,所以又弯弯绕绕转到了 sched.scheduleOne
func (sched *Scheduler) Run() {
if !sched.WaitForCacheSync() {
return
}
// 0,表示按照系统的事件循环频率(通常是由操作系统的调度决定的)重复执行 sched.scheduleOne
go wait.Until(sched.scheduleOne, 0, sched.StopEverything)
}
这个方法内容很长,我把这里的内容精炼来看:
// scheduleOne does the entire scheduling workflow for a single pod. It is serialized on the scheduling algorithm's host fitting.
func (sched *Scheduler) scheduleOne() {
...
// 1. 执行调度
pod := sched.NextPod()
scheduleResult, err := sched.schedule(pod, pluginContext)
// 2. 绑定pv,更新缓存
assumedPod := pod.DeepCopy()
allBound, err := sched.assumeVolumes(assumedPod, scheduleResult.SuggestedHost)
// 3. 执行预留的插件
if sts := fwk.RunReservePlugins(pluginContext, assumedPod, scheduleResult.SuggestedHost); !sts.IsSuccess() {...}
// 4. 假设pod调度成功,更新缓存
err = sched.assume(assumedPod, scheduleResult.SuggestedHost)
// 5. 起一个协程,异步绑定pod到host上
go func() {...}()
}
我们接下来先来看 sched.schedule
,这也是其中最核心的方法,包含了 predicate(预选)、priority(优选)。
sched.schedule
如下,里面又绕了一层,可以看到这里关键的方法是 sched.Algorithm.Schedule
,好吧,那我们就看 sched.Algorithm.Schedule
这里的内容。
func (sched *Scheduler) schedule(pod *v1.Pod, pluginContext *framework.PluginContext) (core.ScheduleResult, error) {
result, err := sched.Algorithm.Schedule(pod, pluginContext)
if err != nil {
pod = pod.DeepCopy()
sched.recordSchedulingFailure(pod, err, v1.PodReasonUnschedulable, err.Error())
return core.ScheduleResult{}, err
}
return result, err
}
sched.Algorithm.Schedule
是一个接口,具体实现在 generic_scheduler.go
的 Schedule
方法里
generic_scheduler.go
的 Schedule
方法先看下整体吧,代码不长,我们等下一篇再来详细讲解吧!
func (g *genericScheduler) Schedule(pod *v1.Pod, pluginContext *framework.PluginContext) (result ScheduleResult, err error) {
trace := utiltrace.New("Scheduling", utiltrace.Field{Key: "namespace", Value: pod.Namespace}, utiltrace.Field{Key: "name", Value: pod.Name})
defer trace.LogIfLong(100 * time.Millisecond)
if err := podPassesBasicChecks(pod, g.pvcLister); err != nil {
return result, err
}
// Run "prefilter" plugins.
preFilterStatus := g.framework.RunPreFilterPlugins(pluginContext, pod)
if !preFilterStatus.IsSuccess() {
return result, preFilterStatus.AsError()
}
numNodes := g.cache.NodeTree().NumNodes()
if numNodes == 0 {
return result, ErrNoNodesAvailable
}
if err := g.snapshot(); err != nil {
return result, err
}
trace.Step("Basic checks done")
startPredicateEvalTime := time.Now()
filteredNodes, failedPredicateMap, filteredNodesStatuses, err := g.findNodesThatFit(pluginContext, pod)
if err != nil {
return result, err
}
// Run "postfilter" plugins.
postfilterStatus := g.framework.RunPostFilterPlugins(pluginContext, pod, filteredNodes, filteredNodesStatuses)
if !postfilterStatus.IsSuccess() {
return result, postfilterStatus.AsError()
}
if len(filteredNodes) == 0 {
return result, &FitError{
Pod: pod,
NumAllNodes: numNodes,
FailedPredicates: failedPredicateMap,
FilteredNodesStatuses: filteredNodesStatuses,
}
}
trace.Step("Computing predicates done")
metrics.SchedulingAlgorithmPredicateEvaluationDuration.Observe(metrics.SinceInSeconds(startPredicateEvalTime))
metrics.DeprecatedSchedulingAlgorithmPredicateEvaluationDuration.Observe(metrics.SinceInMicroseconds(startPredicateEvalTime))
metrics.SchedulingLatency.WithLabelValues(metrics.PredicateEvaluation).Observe(metrics.SinceInSeconds(startPredicateEvalTime))
metrics.DeprecatedSchedulingLatency.WithLabelValues(metrics.PredicateEvaluation).Observe(metrics.SinceInSeconds(startPredicateEvalTime))
startPriorityEvalTime := time.Now()
// When only one node after predicate, just use it.
if len(filteredNodes) == 1 {
metrics.SchedulingAlgorithmPriorityEvaluationDuration.Observe(metrics.SinceInSeconds(startPriorityEvalTime))
metrics.DeprecatedSchedulingAlgorithmPriorityEvaluationDuration.Observe(metrics.SinceInMicroseconds(startPriorityEvalTime))
return ScheduleResult{
SuggestedHost: filteredNodes[0].Name,
EvaluatedNodes: 1 + len(failedPredicateMap),
FeasibleNodes: 1,
}, nil
}
metaPrioritiesInterface := g.priorityMetaProducer(pod, g.nodeInfoSnapshot.NodeInfoMap)
priorityList, err := PrioritizeNodes(pod, g.nodeInfoSnapshot.NodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders, g.framework, pluginContext)
if err != nil {
return result, err
}
trace.Step("Prioritizing done")
metrics.SchedulingAlgorithmPriorityEvaluationDuration.Observe(metrics.SinceInSeconds(startPriorityEvalTime))
metrics.DeprecatedSchedulingAlgorithmPriorityEvaluationDuration.Observe(metrics.SinceInMicroseconds(startPriorityEvalTime))
metrics.SchedulingLatency.WithLabelValues(metrics.PriorityEvaluation).Observe(metrics.SinceInSeconds(startPriorityEvalTime))
metrics.DeprecatedSchedulingLatency.WithLabelValues(metrics.PriorityEvaluation).Observe(metrics.SinceInSeconds(startPriorityEvalTime))
host, err := g.selectHost(priorityList)
trace.Step("Selecting host done")
return ScheduleResult{
SuggestedHost: host,
EvaluatedNodes: len(filteredNodes) + len(failedPredicateMap),
FeasibleNodes: len(filteredNodes),
}, err
}
好吧,到这里大概知道 Kube-Scheduler 的核心逻辑在哪里了,后续会详细介绍调度算法,我这里先用一张图来总结下目前的流程:
目前我们走到了执行调度算法的流程,后续针对这里的内容详细展开讲解~
《K8s 源码剖析及debug实战(一):Minikube 安装及源码准备》
《K8s 源码剖析及debug实战(二):debug K8s 源码》
《K8s 源码剖析及debug实战之 Kube-Scheduler(一):启动流程详解》
欢迎关注本人,我是喜欢搞事的程序猿; 一起进步,一起学习;