diff --git a/.DS_Store b/.DS_Store index 8ecdc13..131b54c 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/README.md b/README.md index 233dc92..1f939d1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ ## 前言 在熟悉kubernetes及常用组件、插件的管理使用后,总还觉得差了些什么,不够通透,是时候来读一读源码了,结合代码与实际使用场景来互相印证,有助于对kubernetes的理解更为透彻。这里将会分多篇介绍kubernetes各核心组件的工作模式、调度管理算法等。 -注:本系列文章,全部基于最近部署的v1.14.3版本的源码 + + +## 版本 + +Kubernetes v1.14.3 ,最新部署的一套环境是此版本,代码版本保持一致,方便后续测试调试 ## 核心组件 - [Scheduler](https://github.com/yinwenqin/kubeSourceCodeNote/tree/master/scheduler) @@ -44,5 +48,5 @@ Kubernetes这一整个项目颇为庞大,一般情况下,如果熟悉kuberne 1.官方开发者向导md文档: https://github.com/kubernetes/community/tree/master/contributors/devel 2.http://hutao.tech/k8s-source-code-analysis/ - + diff --git a/scheduler/P2-调度器框架.md b/scheduler/P2-调度器框架.md index fd32ce2..5efc44f 100644 --- a/scheduler/P2-调度器框架.md +++ b/scheduler/P2-调度器框架.md @@ -1,4 +1,4 @@ -调度器框架 +# 调度器框架 ## 前言 @@ -10,7 +10,7 @@ ## 框架流程 -让回到我们上一篇篇末找到的调度逻辑入口位置,`pkg/scheduler/scheduler.go:435`, `scheduleOne()`函数内部,定位在`pkg/scheduler/scheduler.go:457`位置,是通过这个`sched.schedule(pod)`方法来获取与pod匹配的node的,我们直接跳转2次,来到了这里`pkg/scheduler/core/generic_scheduler.go:107` +回顾上一篇篇末,我们找到了调度框架的实际调度工作逻辑的入口位置,`pkg/scheduler/scheduler.go:435`, `scheduleOne()`函数内部,定位在`pkg/scheduler/scheduler.go:457`位置,是通过这个`sched.schedule(pod)`方法来获取与pod匹配的node的,我们直接跳转2次,来到了这里`pkg/scheduler/core/generic_scheduler.go:107` ![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/schedule.jpg) @@ -22,7 +22,29 @@ ![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/genericSchedule.jpg) -本篇我们不看Schedule方法内的具体调度算法细节,先来逆向回溯代码结构,找到哪里创建了scheduler,调度器的默认初始化配置,默认的调度算法来源等等框架相关的东西。`Schedule()`方法属于`genericScheduler`结构体,先查看`genericScheduler`结构体,再选中结构体名称,crtl + b组合键查看它在哪些地方被引用,找出创建结构体的位置: +这个函数里面有4个重要的步骤: + +```go +// 调度前预先检查pvc是否创建 +pkg/scheduler/core/generic_scheduler.go:166 +err := podPassesBasicChecks(pod, g.pvcLister) + +// 根据Predicate筛选node +pkg/scheduler/core/generic_scheduler.go:184 +filteredNodes, failedPredicateMap, err := g.findNodesThatFit(pod, nodes) + +// 给筛选出的node排出优先级 +pkg/scheduler/core/generic_scheduler.go:215 + PrioritizeNodes(pod, g.nodeInfoSnapshot.NodeInfoMap, metaPrioritiesInterface, g.prioritizers, filteredNodes, g.extenders) + +// 选出优先级最高的node作为fit node +pkg/scheduler/core/generic_scheduler.go:226 +g.selectHost(priorityList) +``` + +本篇我们不看Schedule方法内的具体调度算法细节,在这里标记一下,下一篇我们将从这里开始. + +先来逆向回溯代码结构,找到哪里创建了scheduler,调度器的默认初始化配置,默认的调度算法来源等等框架相关的东西。`Schedule()`方法属于`genericScheduler`结构体,先查看`genericScheduler`结构体,再选中结构体名称,crtl + b组合键查看它在哪些地方被引用,找出创建结构体的位置: ![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/createGenSche.jpg) @@ -60,4 +82,202 @@ ![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/memPressure.jpg) -`CheckNodeMemoryPressure`这个词相应熟悉kubernetes pod的朋友一定不会陌生,在node内存压力大无法调度的pod时,`kubectl describe pod xxx`就会在状态信息里面看到这个关键词。同时,该变量的定义位于`pkg/scheduler/algorithm/predicates/predicates.go:102`,`pkg/scheduler/algorithm/`这个目录在调度器的总览中已经讲过是调度算法相关的目录,`defaultPredicates(), defaultPriorities()`里面的内容都是位于这个目录中,具体调度器的断言过滤、优先级排序算法详情将在下篇中详解,本篇调度器框架结构梳理到此结束! \ No newline at end of file +`CheckNodeMemoryPressure`这个词相应熟悉kubernetes 应用的朋友一定不会陌生,例如在node内存压力大无法调度的pod时,`kubectl describe pod xxx`就会在状态信息里面看到这个关键词。 + +让我们回到`pkg/scheduler/algorithmprovider/defaults/defaults.go:102`这个位置,查看`factory.RegisterAlgorithmProvider(factory.DefaultProvider, predSet, priSet)`方法的详情,可以看到`参数factory.DefaultProvider`值为字符串格式的`DefaultProvider`,**先记住这个关键值**,进入方法内部: + +`pkg/scheduler/factory/plugins.go:387`: + +```go +func RegisterAlgorithmProvider(name string, predicateKeys, priorityKeys sets.String) string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + validateAlgorithmNameOrDie(name) + algorithmProviderMap[name] = AlgorithmProviderConfig{ + FitPredicateKeys: predicateKeys, + PriorityFunctionKeys: priorityKeys, + } + return name +} +``` + +可以看到,这个方法为DefaultProvider绑定了配置:筛选算法和优先级排序算法的key集合,这些key只是字符串,那么是怎么具体落实到计算的方法过程上去的呢?让我们看看`pkg/scheduler/algorithmprovider/defaults/`目录下的`register_predicates.go,register_priorities.go`这两个文件: + +![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/preinit.jpg) + +它们同样也在init()函数中初始化时使用`factory.RegisterFitPredicate()`方法做了一些注册操作,这个方法的两个参数,前一个是筛选/计算优先级 的关键key名,后一个是具体计算的功能实现方法,点击`factory.RegisterFitPredicate()`方法,深入一级,查看内部代码, + +```go +// RegisterFitPredicateFactory registers a fit predicate factory with the +// algorithm registry. Returns the name with which the predicate was registered. +func RegisterFitPredicateFactory(name string, predicateFactory FitPredicateFactory) string { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + validateAlgorithmNameOrDie(name) + fitPredicateMap[name] = predicateFactory + return name +} +``` + +可以看出,两者使用map[string]func()的方式关联在了一起,那么在后面实际调用的时候,必定是在map中基于key找出方法并执行。优先级先关的`factory.RegisterPriorityFunction2()`方法亦是同理。 + + + +### 生成默认配置 + +还记得刚刚重点圈出的`DefaultProvider`关键值吗?通过上面我们知道了,所有默认Predicate/priority算法的实现都是绑定在这个默认的`AlgorithmProvider`身上的,那么,启动scheduler的时候,究竟是如何将`DefaultProvider`作为默认`AlgorithmProvider`呢?让我们回到最初的调度器启动命令入口位置`cmd/kube-scheduler/app/server.go:62`: + +```go +opts, err := options.NewOptions() +// 点击NewOptions跳转进入内部,来到了这个位置:cmd/kube-scheduler/app/options/options.go:75 + +func NewOptions() (*Options, error) { + cfg, err := newDefaultComponentConfig() + if err != nil { + return nil, err + } + ... // 省略 +} +// 这个newDefaultComponentConfig方法特别有意思,从字面看它是用来为组件填充默认配置的 +// 来看看它的内容,点击来到了cmd/kube-scheduler/app/options/options.go:132 + +func newDefaultComponentConfig() (*kubeschedulerconfig.KubeSchedulerConfiguration, error) { + cfgv1alpha1 := kubeschedulerconfigv1alpha1.KubeSchedulerConfiguration{} + kubeschedulerscheme.Scheme.Default(&cfgv1alpha1) +... // 省略 +} + +// 点击kubeschedulerscheme.Scheme.Default(&cfgv1alpha1)中的Default跳转进入 +// 来到了这里:vendor/k8s.io/apimachinery/pkg/runtime/scheme.go:389 +func (s *Scheme) AddTypeDefaultingFunc(srcType Object, fn func(interface{})) { + s.defaulterFuncs[reflect.TypeOf(srcType)] = fn +} + +// Default sets defaults on the provided Object. +func (s *Scheme) Default(src Object) { + if fn, ok := s.defaulterFuncs[reflect.TypeOf(src)]; ok { + fn(src) + } +} + +// 看看defaulterFuncs的数据类型: + // defaulterFuncs is an array of interfaces to be called with an object to provide defaulting + // the provided object must be a pointer. + defaulterFuncs map[reflect.Type]func(interface{}) + +// 不难看出,这个Default()方法是通过反射器,获取对象的类型,以类型作为map的key,从而获取该类型 +// 对应的defaulterFuncs,也即是该结构体填充默认配置的方法,最后执行该方法 +// 那么这个defaulterFuncs map[reflect.Type]func(interface{}),里面的元素时怎么填充的呢? +// 作者很贴心地将添加map元素的方法写在了Default()方法的正上方: +func (s *Scheme) AddTypeDefaultingFunc(srcType Object, fn func(interface{})) { + s.defaulterFuncs[reflect.TypeOf(srcType)] = fn +} + +``` + +我们选中然后ctrl+b,查找AddTypeDefaultingFunc()的引用,弹窗中你可以看到有非常非常多的对象都引用了该方法,这些不同类型的对象相信无一例外都是通过Default()方法来生成默认配置的,我们找到其中的包含scheduler的方法: + +![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/addDefaultFunc.jpg) + +跳转进去,来到了这个位置`pkg/scheduler/apis/config/v1alpha1/zz_generated.defaults.go:31`: + +![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/registerDefaults.jpg) + +进入`SetDefaults_KubeSchedulerConfiguration()`,来到`pkg/scheduler/apis/config/v1alpha1/defaults.go:42`: + +![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/SetDefaults_KubeSchedulerConfiguration.jpg) + +看到了`DefaultProvider`吗?是不是觉得瞬间豁然开朗,原来是在这里调用指定了scheduler配置的`AlgorithmSource.Provider`。 + + + +### 调度功能实现的回溯 + +让我们捋一捋调度器框架运行调度功能相关的流程: + +``` +// 1.获取AlgorithmSource.Provider(默认"DefaultProvider"),作为key从map中获取到pkg/scheduler/algorithmprovider包内为其初始化的两种算法key集合 +algorithmProviderMap[name] = AlgorithmProviderConfig{ + FitPredicateKeys: predicateKeys, + PriorityFunctionKeys: priorityKeys, + } + +// 2.填充genericScheduler对象的predicates元素: +// pkg/scheduler/factory/plugins.go:411 + +func getFitPredicateFunctions(names sets.String, args PluginFactoryArgs) (map[string]predicates.FitPredicate, error) { + schedulerFactoryMutex.Lock() + defer schedulerFactoryMutex.Unlock() + + fitPredicates := map[string]predicates.FitPredicate{} + for _, name := range names.List() { + factory, ok := fitPredicateMap[name] + if !ok { + return nil, fmt.Errorf("invalid predicate name %q specified - no corresponding function found", name) + } + fitPredicates[name] = factory(args) + } + + // Always include mandatory fit predicates. + for name := range mandatoryFitPredicates { + if factory, found := fitPredicateMap[name]; found { + fitPredicates[name] = factory(args) + } + } + + return fitPredicates, nil +} + +// 3.对predicates内的每一个key,找到对应的检查方法,执行每一项检查,返回检查结果 +// pkg/scheduler/core/generic_scheduler.go:608 + +func podFitsOnNode( + pod *v1.Pod, + meta predicates.PredicateMetadata, + info *schedulernodeinfo.NodeInfo, + predicateFuncs map[string]predicates.FitPredicate, + queue internalqueue.SchedulingQueue, + alwaysCheckAllPredicates bool, +) (bool, []predicates.PredicateFailureReason, error) { + var failedPredicates []predicates.PredicateFailureReason + + podsAdded := false + for i := 0; i < 2; i++ { + metaToUse := meta + nodeInfoToUse := info + if i == 0 { + 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 + ) + if predicate, exist := predicateFuncs[predicateKey]; exist { + fit, reasons, err = predicate(pod, metaToUse, nodeInfoToUse) + if err != nil { + return false, []predicates.PredicateFailureReason{}, err + } + ... // 省略 + } + } + } + } + } + + return len(failedPredicates) == 0, failedPredicates, nil +} +``` + +### 目录结构总结 + +最后,对`pkg/scheduler`路径下的各子目录的功能来一个图文总结吧: + +![](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/image/p2/dir.jpg) + + + +如果有沉下心来阅读代码,结合上面的图文讲解,相信你对调度器框架包内的代码结构会有一个较为清晰的整体掌握,本篇框架篇到此结束,下一篇来谈谈详细的调度算法的细节 \ No newline at end of file diff --git a/scheduler/P3-调度器算法.md b/scheduler/P3-调度器算法.md new file mode 100644 index 0000000..a33ff39 --- /dev/null +++ b/scheduler/P3-调度器算法.md @@ -0,0 +1,12 @@ +# 调度器算法 + +## 前言 + +在上一篇文档中,我们找到调度器筛选node的`pkg/scheduler/core/generic_scheduler.Scheduler()` function,然后简单回溯了一遍调度器框架的代码结构: + +[**P2-调度器框架篇**]([https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/P2-%E8%B0%83%E5%BA%A6%E5%99%A8%E6%A1%86%E6%9E%B6.md](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/P2-调度器框架.md)) + +那么在本篇,从此function展开,看一看调度算法的逻辑 + +## 正文 + diff --git a/scheduler/README.md b/scheduler/README.md index a9a29a4..e0eaeb1 100644 --- a/scheduler/README.md +++ b/scheduler/README.md @@ -3,6 +3,8 @@ ## 调度器源码分段阅读目录 - [调度器入口](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/P1-%E8%B0%83%E5%BA%A6%E5%99%A8%E5%85%A5%E5%8F%A3%E7%AF%87.md) - [调度器框架](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/P1-%E8%B0%83%E5%BA%A6%E5%99%A8%E5%85%A5%E5%8F%A3%E7%AF%87.md) +- [调度器算法](https://github.com/yinwenqin/kubeSourceCodeNote/blob/master/scheduler/P1-%E8%B0%83%E5%BA%A6%E5%99%A8%E5%85%A5%E5%8F%A3%E7%AF%87.md) +- 待补充 ## 概览 首先列出官方md链接,讲解颇为生动: @@ -43,7 +45,7 @@ Kubernetes scheduler独立运作与其他主要组件之外(例如API Server), | v +-------------------+-------------------------+ - | 剩余可选 nodes: | + | 剩余可选nodes: | | +--------+ +--------+ | | | node 1 | | node 2 | | | +--------+ +--------+ | @@ -54,8 +56,8 @@ Kubernetes scheduler独立运作与其他主要组件之外(例如API Server), v +-------------------+-------------------------+ - 优先级判断: node 1: priority=2 - node 2: priority=5 + 优先级判断: node 1: priority=2 + node 2: priority=5 +-------------------+-------------------------+ | diff --git a/scheduler/image/p2/SetDefaults_KubeSchedulerConfiguration.jpg b/scheduler/image/p2/SetDefaults_KubeSchedulerConfiguration.jpg new file mode 100644 index 0000000..d04be70 Binary files /dev/null and b/scheduler/image/p2/SetDefaults_KubeSchedulerConfiguration.jpg differ diff --git a/scheduler/image/p2/addDefaultFunc.jpg b/scheduler/image/p2/addDefaultFunc.jpg new file mode 100644 index 0000000..6693a3b Binary files /dev/null and b/scheduler/image/p2/addDefaultFunc.jpg differ diff --git a/scheduler/image/p2/dir.jpg b/scheduler/image/p2/dir.jpg new file mode 100644 index 0000000..7b8f934 Binary files /dev/null and b/scheduler/image/p2/dir.jpg differ diff --git a/scheduler/image/p2/preinit.jpg b/scheduler/image/p2/preinit.jpg new file mode 100644 index 0000000..64057a7 Binary files /dev/null and b/scheduler/image/p2/preinit.jpg differ diff --git a/scheduler/image/p2/registerDefaults.jpg b/scheduler/image/p2/registerDefaults.jpg new file mode 100644 index 0000000..c9f4a5b Binary files /dev/null and b/scheduler/image/p2/registerDefaults.jpg differ