K8s 源码剖析及debug实战之 Kube-Scheduler(二):终于找到了调度算法的代码入口

文章目录

  • 0. 引言
  • 1. Run
  • 2. sched.Run()
  • 3. sched.scheduleOne
  • 4. (g *genericScheduler) Schedule
  • 5. 阶段性总结
  • 6. 参考


0. 引言

欢迎关注本专栏,本专栏主要从 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 函数的主体流程。

1. 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() ,抓住重点之后,后面看核心代码才有的放矢。

2. 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)
}

3. sched.scheduleOne

这个方法内容很长,我把这里的内容精炼来看:

// 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.goSchedule 方法里
K8s 源码剖析及debug实战之 Kube-Scheduler(二):终于找到了调度算法的代码入口_第1张图片

4. (g *genericScheduler) Schedule

generic_scheduler.goSchedule 方法先看下整体吧,代码不长,我们等下一篇再来详细讲解吧!

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
}

5. 阶段性总结

好吧,到这里大概知道 Kube-Scheduler 的核心逻辑在哪里了,后续会详细介绍调度算法,我这里先用一张图来总结下目前的流程:

K8s 源码剖析及debug实战之 Kube-Scheduler(二):终于找到了调度算法的代码入口_第2张图片
目前我们走到了执行调度算法的流程,后续针对这里的内容详细展开讲解~

6. 参考

《K8s 源码剖析及debug实战(一):Minikube 安装及源码准备》
《K8s 源码剖析及debug实战(二):debug K8s 源码》
《K8s 源码剖析及debug实战之 Kube-Scheduler(一):启动流程详解》

欢迎关注本人,我是喜欢搞事的程序猿; 一起进步,一起学习;

也欢迎关注我的wx公众号:一个比特定乾坤
K8s 源码剖析及debug实战之 Kube-Scheduler(二):终于找到了调度算法的代码入口_第3张图片

你可能感兴趣的:(K8s源码剖析及debug实战,kubernetes,算法,容器,云原生,运维,k8s)