Kubernetes 学习(九)Kubernetes 源码阅读之正式篇------核心组件之 Scheduler
0. 前言
- 继续上一篇博客阅读 Kubernetes 源码,参照《k8s 源码阅读》首先学习 Kubernetes 的一些核心组件,首先是 kube-scheduler
- 本文严重参考原文:《k8s 源码阅读》之 2.2 章节:scheduler,加入部分自己阅读的体会作为自己的阅读笔记
- 感谢《k8s 源码阅读》的作者们辛苦编写教材,在此郑重表示感谢,望大家多多支持!~
1. 整体设计
1.1 概述
- 官网描述:
- The Kubernetes scheduler runs as a process alongside the other master components such as the API server.
- Its interface to the API server is to watch for Pods with an empty PodSpec.NodeName, and for each Pod,
- it posts a binding indicating where the Pod should be scheduled.
- Scheduler 是相对独立的一个组件,主动访问 API server,寻找等待调度的 Pod(PodSpec.NodeName 为空)
- 然后通过一系列调度算法寻找哪个 Node 适合跑这个 Pod
- 然后将这个 Pod 和 Node 的绑定关系发给 API server,即整个调度流程
1.2 源码层次
- cmd/kube-scheduler/scheduler.go:main() 函数入口位置,在 Scheduler 过程开始前的一系列初始化工作
- pkg/scheduler/scheduler.go:调度框架整体逻辑,抽象调用各个算法的 Interface
- pkg/scheduler/core/generic_scheduler.go:计算哪些 Node适合跑哪些 Pod 的具体算法
1.3 调度流程
- 通过一系列的 Predicates(预选) 过滤掉不能运行 Pod 的 Node
- 比如一个 Pod 需要 500M 的内存,有些节点剩余内存只有 100M 了,就会被剔除
- 通过一系列的 Priorities(优选)给剩下的 Node 排一个分数,寻找能够运行 Pod 的 Node 中最合适的一个
- 得分最高的一个 Node 胜出,获得了运行 Pod 的资格
- 若是上述预选过程中没有找到任何一个合适的 Node,则进入抢占模式,移除部分低优先级的 Pod,再选择 Node
1.4 Predicates 和 Priorities 策略
- Predicates 是用于过滤不合适 Node的策略
- Priorities 是用于计算 Node 分数的策略(作用在通过 Predicates 过滤的 Node上)
- Kubernetes 默认内建了一些 Predicates 和 Priorities 策略,代码分别在:
- pkg/scheduler/algorithm/predicates/predicates.go
- pkg/scheduler/algorithm/priorities/
1.5 调度策略的修改
- 默认调度策略是通过 defaultPredicates() 和 defaultPriorities() 函数定义的
- 代码在 pkg/scheduler/algorithmprovider/defaults/defaults.go
- 可以通过命令行 flag --policy-config-file 来覆盖默认行为
- 可以修改 pkg/scheduler/algorithm/predicates/predicates.go 和 /pkg/scheduler/algorithm/priorities/,然后注册到 defaultPredicates() 和 defaultPriorities() 来实现
- 或者通过配置文件:
{
"kind" : "Policy",
"apiVersion" : "v1",
"predicates" : [
{"name" : "PodFitsHostPorts"},
{"name" : "PodFitsResources"},
{"name" : "NoDiskConflict"},
{"name" : "NoVolumeZoneConflict"},
{"name" : "MatchNodeSelector"},
{"name" : "HostName"}
],
"priorities" : [
{"name" : "LeastRequestedPriority", "weight" : },
{"name" : "BalancedResourceAllocation", "weight" : },
{"name" : "ServiceSpreadingPriority", "weight" : },
{"name" : "EqualPriority", "weight" : }
],
"hardPodAffinitySymmetricWeight" : ,
"alwaysCheckAllPredicates" : false
}
2. 启动前逻辑
- Scheduler 可以分为三层,第一层是调度器启动前的逻辑,包括:命令行参数解析、参数校验、调度器初始化等一系列逻辑
2.1 Cobra
2.1.1 简介
- Cobra 既是一个创建强大的现代化命令行程序的库,又是一个用于生成应用和命令行文件的程序。有很多流行的 Golang 项目用了 Cobra(Kubernetes 和 Docker)
2.1.2 具体使用
- Go 1.11 版本以上先暂时关闭 go modules,安装 Cobra:go get -u github.com/spf13/cobra/cobra
- 若是网卡,可能导致安装不成功,可以先下载报错的依赖包,再重新 go get 安装
- 安装成功后会看到 $GOPATH/
- 进入 $GOPATH/src 目录,初始化项目:cobra init myapp --pkg-name myapp
$ cobra init myapp --pkg-name myapp
Your Cobra applicaton is ready at
/home/ao/go/src/myapp
$ ls myapp
cmd LICENSE main.go
$ pwd
/home/ao/go/src
- 本地可以看到一个 main.go 和一个 cmd 目录
- 查看 cmd/root.go:
package cmd import (
"fmt"
"os"
"github.com/spf13/cobra" homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/viper" ) var cfgFile string // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "myapp",
Short: "A brief description of your application",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
// Uncomment the following line if your bare application
// has an action associated with it:
// Run: func(cmd *cobra.Command, args []string) { },
}
- 查看 main.go:
package main import "myapp/cmd" func main() {
cmd.Execute()
}
- main.go 里 import了 myapp/cmd(也就是 root.go 文件)
- 在 Execute 里面调用了 rootCmd.Execute() 方法
- rootCmd 是*cobra.Command 类型
- 添加新的命令:cobra add version
$ cobra add version
version created at /home/ao/go/src/myapp%
- 查看 cmd/version.go:
- init() 函数里面调用 rootCmd.AddCommand(versionCmd),子命令就是 version
package cmd import (
"fmt" "github.com/spf13/cobra"
) // versionCmd represents the version command
var versionCmd = &cobra.Command{
Use: "version",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example: Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("version called")
},
} func init() {
rootCmd.AddCommand(versionCmd)
......
}
添加多级子命令:
$ cobra add server
server created at /home/ao/go/src/myapp%
$ cobra add create -p serverCmd
create created at /home/ao/go/src/myapp%
- 查看 cmd/create.go 文件:
- init() 函数调用 serverCmd.AddCommand(createCmd),表示 create 为子命令
package cmd import (
"fmt" "github.com/spf13/cobra"
) // createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "A brief description of your command",
Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example: Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("create called")
},
} func init() {
serverCmd.AddCommand(createCmd)
......
}
2.2 Scheduler 的 main 函数
- 下面正式进入 Kubernetes 的 Scheduler 源码阅读
- 查看 cmd/kube-scheduler/scheduler.go,删除非主干代码:
func main() {
command := app.NewSchedulerCommand()
if err := command.Execute(); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit()
}
}
- 查看定义 NewSchedulerCommand 的 cmd/kube-scheduler/app/server.go,删除非主干代码:
/ NewSchedulerCommand creates a *cobra.Command object with default parameters
func NewSchedulerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "kube-scheduler",
Long: `The Kubernetes scheduler is a policy-rich, topology-aware,
workload-specific function that significantly impacts availability, performance,
and capacity. The scheduler needs to take into account individual and collective
resource requirements, quality of service requirements, hardware/software/policy
constraints, affinity and anti-affinity specifications, data locality, inter-workload
interference, deadlines, and so on. Workload-specific requirements will be exposed
through the API as necessary.`,
Run: func(cmd *cobra.Command, args []string) {
if err := runCommand(cmd, args, opts); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit()
}
},
}
return cmd
}
- Schduler 启动时调用了 runCommand(cmd, args, opts),查看 cmd/kube-scheduler/app/server.go 关于 runCommand 定义,删除非主干代码:
// runCommand runs the scheduler.
func runCommand(cmd *cobra.Command, args []string, opts *options.Options) error {
c, err := opts.Config()
stopCh := make(chan struct{})
// Get the completed config
cc := c.Complete()
return Run(cc, stopCh)
}
- 处理配置问题后调用了一个 Run() 函数,Run() 的作用是基于给定的配置启动 Scheduler,它只会在出错时或者 channel stopCh 被关闭时才退出,查看 cmd/kube-scheduler/app/server.go 关于 Run 定义,删除非主干代码:
// Run executes the scheduler based on the given configuration. It only return on error or when stopCh is closed.
func Run(cc schedulerserverconfig.CompletedConfig, stopCh <-chan struct{}) error {
// Create the scheduler.
sched, err := scheduler.New(cc.Client,
cc.InformerFactory.Core().V1().Nodes(),
stopCh,
scheduler.WithName(cc.ComponentConfig.SchedulerName)) // Prepare a reusable runCommand function.
run := func(ctx context.Context) {
sched.Run()
<-ctx.Done()
} ctx, cancel := context.WithCancel(context.TODO())
defer cancel() go func() {
select {
case <-stopCh:
cancel()
case <-ctx.Done():
}
}() // Leader election is disabled, so runCommand inline until done.
run(ctx)
return fmt.Errorf("finished without leader elect")
}
- 最终是要跑 sched.Run() 这个方法来启动 Scheduler,sched.Run() 方法就是 Scheduler 框架真正运行的逻辑
- 上述有一个 sched 变量,linux 里经常会看到一些软件叫 ***d,d 也就是 daemon,守护进程的意思,也就是一直跑在后台的一个程序
- 这里的 sched 也就是指 “Scheduler Daemon“
- sched 的其实是 *Scheduler 类型,定义在 pkg/scheduler/scheduler.go:
- Scheduler 监控新创建的未被调度的 Pods,然后尝试寻找合适的 Node,回写一个绑定关系到 API Server
- 这里也可以体会到 Daemon 的感觉,我们平时搭建的 k8s 集群中运行着一个 Daemon 进程叫做 kube-scheduler,在程序里面也就对应这样一个对象:Scheduler
// Scheduler watches for new unscheduled pods. It attempts to find
// nodes that they fit on and writes bindings back to the api server.
type Scheduler struct {
config *factory.Config
}
Scheduler 结构体中的 Config 属性对象定义在 pkg/scheduler/factory/factory.go,主要属性如下:
// Config is an implementation of the Scheduler's configured input data.
type Config struct {
// It is expected that changes made via SchedulerCache will be observed
// by NodeLister and Algorithm.
SchedulerCache schedulerinternalcache.Cache
// Ecache is used for optimistically invalid affected cache items after
// successfully binding a pod
Ecache *equivalence.Cache
NodeLister algorithm.NodeLister
Algorithm algorithm.ScheduleAlgorithm
GetBinder func(pod *v1.Pod) Binder
// PodConditionUpdater is used only in case of scheduling errors. If we succeed
// with scheduling, PodScheduled condition will be updated in apiserver in /bind
// handler so that binding and setting PodCondition it is atomic.
PodConditionUpdater PodConditionUpdater
// PodPreemptor is used to evict pods and update pod annotations.
PodPreemptor PodPreemptor // NextPod should be a function that blocks until the next pod
// is available. We don't use a channel for this, because scheduling
// a pod may take some amount of time and we don't want pods to get
// stale while they sit in a channel.
NextPod func() *v1.Pod // SchedulingQueue holds pods to be scheduled
SchedulingQueue internalqueue.SchedulingQueue
}
3. 整体调度框架
3.1 启动
- 调度的第二层:负责 Scheduler 除了具体 Node 过滤算法外的工作逻辑
- Scheduler 对象绑定了一个 Run() 方法,定义如下:
// Run begins watching and scheduling. It waits for cache to be synced, then starts a goroutine and returns immediately.
func (sched *Scheduler) Run() {
if !sched.config.WaitForCacheSync() {
return
}
go wait.Until(sched.scheduleOne, , sched.config.StopEverything)
}
- Run 函数在 cmd/kube-scheduler/app/server.go 调用:
// Prepare a reusable runCommand function.
run := func(ctx context.Context) {
sched.Run()
<-ctx.Done()
}
- 调用了 sched.Run() 之后就等待 ctx.Done()
- wait.Until 实现:每隔 n 时间调用 f 一次,除非 channel c 被关闭
- 这里的 n 就是 0,也就是一直调用,前一次调用返回下一次调用就开始了
- f 指 sched.scheduleOne,c 指 sched.config.StopEverything
3.2 一个 Pod 的基本调度流程
- scheduleOne 实现 1 个 Pod 的完整调度工作流
- 这个过程是顺序执行的,也就是非并发的
- 即前一个 Pod 的 scheduleOne 一完成,下一个 Pod 的 ScheduleOne 立马接着执行
- scheduleOne 的主要逻辑:
func (sched *Scheduler) scheduleOne() {
pod := sched.config.NextPod()
suggestedHost, err := sched.schedule(pod)
if err != nil {
if fitError, ok := err.(*core.FitError); ok {
preemptionStartTime := time.Now()
sched.preempt(pod, fitError)
}
return
}
assumedPod := pod.DeepCopy()
allBound, err := sched.assumeVolumes(assumedPod, suggestedHost)
err = sched.assume(assumedPod, suggestedHost)
go func() {
err := sched.bind(assumedPod, &v1.Binding{
ObjectMeta: metav1.ObjectMeta{Namespace: assumedPod.Namespace, Name: assumedPod.Name, UID: assumedPod.UID},
Target: v1.ObjectReference{
Kind: "Node",
Name: suggestedHost,
},
})
}()
}
参考流程如下:
3.3 Schedule 算法计算合适 Node
- 主流程核心步骤是 suggestedHost, err := sched.schedule(pod)
- 这里完成了非抢占模式下 Node 的计算,包括预选过程、优选过程等
- sched.schedule(pod) 方法定义见 pkg/scheduler/scheduler.go:
// schedule implements the scheduling algorithm and returns the suggested host.
func (sched *Scheduler) schedule(pod *v1.Pod) (string, error) {
host, err := sched.config.Algorithm.Schedule(pod, sched.config.NodeLister)
if err != nil {
pod = pod.DeepCopy()
sched.config.Error(pod, err)
sched.config.Recorder.Eventf(pod, v1.EventTypeWarning, "FailedScheduling", "%v", err)
sched.config.PodConditionUpdater.Update(pod, &v1.PodCondition{
Type: v1.PodScheduled,
Status: v1.ConditionFalse,
LastProbeTime: metav1.Now(),
Reason: v1.PodReasonUnschedulable,
Message: err.Error(),
})
return "", err
}
return host, err
}
- 里面调用了 sched.config.Algorithm.Schedule() 方法(本身为接口),定义见 pkg/scheduler/algorithm/scheduler_interface.go:
type ScheduleAlgorithm interface {
Schedule(*v1.Pod, NodeLister) (selectedMachine string, err error)
Preempt(*v1.Pod, NodeLister, error) (selectedNode *v1.Node, preemptedPods []*v1.Pod, cleanupNominatedPods []*v1.Pod, err error)
Predicates() map[string]FitPredicate
Prioritizers() []PriorityConfig
}
- 这个接口有 4 个方法,实现 ScheduleAlgorithm 接口的对象实现如何调度 Pods 到 Nodes 上
- 默认的实现是 pkg/scheduler/core/generic_scheduler.go 的 genericScheduler 对象
- Schedule():给定 Pod 和 Nodes,计算出一个适合跑 Pod 的 Node 并返回
- Preempt():抢占模式
- Predicates():预选过程
- Prioritizers():优选过程
4. 一般调度过程
6. 参考文献
Kubernetes 学习(九)Kubernetes 源码阅读之正式篇------核心组件之 Scheduler的更多相关文章
- Kubernetes 学习(八)Kubernetes 源码阅读之初级篇------源码及依赖下载
0. 前言 阅读了一段时间 Golang 开源代码,准备正式阅读 Kubernetes 项目代码(工作机 Golang 版本为 Go 1.12) 参照 <k8s 源码阅读> 选择 1.13 ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(一)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(二)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(source-code-study)进行参考交流,有详细的源码注释,以及知识总结,同时 ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(三)浅谈 REST 和 CRUD
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
- 【 js 基础 】【 源码学习 】backbone 源码阅读(三)
最近看完了 backbone.js 的源码,这里对于源码的细节就不再赘述了,大家可以 star 我的源码阅读项目(https://github.com/JiayiLi/source-code-stud ...
- SDWebImage源码阅读-第三篇
这一篇讲讲不常用的一些方法. 1 sd_setImageWithPreviousCachedImageWithURL: placeholderImage: options: progress: com ...
- 15.DRF学习以及相关源码阅读
1.http请求协议 代码很枯燥,结果和奇妙. 1.cbv django.vuews import View classs LoginView(View): def get(self,requset) ...
- [源码阅读]RocketMQ-策略篇
一:为什么要阅读rocketmq的源码? 1 可以了解mq的底层实现逻辑. 二:打算怎么读,行动路径是哪儿些? 1: 本地启动 2 分步调试 3 fork项目,添加中文注释,提交到自己的代码库.并改 ...
- Flask源码阅读-第四篇(flask\app.py)
flask.app该模块2000多行代码,主要完成应用的配置.初始化.蓝图注册.请求装饰器定义.应用的启动和监听,其中以下方法可以重点品读和关注 def setupmethod(f): @setupm ...
随机推荐
- 微软官方 Github 上的 EF 示例项目 EntityFramework.Docs
项目地址:https://github.com/aspnet/EntityFramework.Docs/tree/master/samples/core 谢谢浏览!
- 从零到一手写基于Redis的分布式锁框架
1.分布式锁缘由 学习编程初期,我们做的诸如教务系统.成绩管理系统大多是单机架构,单机架构在处理并发的问题上一般是依赖于JDK内置的并发编程类库,如synchronize关键字.Lock类等.随着业务 ...
- Zabbix邮件预警-这个坑我跳了不止一次
文章 Github 地址:点我 每每碰到 Zabbix,我发现配置邮件预警这个坑,我必须要跳进去,跟它是有八辈子的仇哦,哎,接下来数数这些坑.看看你遇到过类似的吗? Zabbix 预警配置流程 监控项 ...
- 基于kafka_2.11-2.1.0实现的生产者和消费者代码样例
1.搭建部署好zookeeper集群和kafka集群,这里省略. 启动zk: bin/zkServer.sh start conf/zoo.cfg. 验证zk是否启动成功: bin/zkServer. ...
- C#: 解决Fody is only supported on MSBuild 16 and above
背景信息: 使用Costura.Fody插件将自己写的程序打包成一个可以独立运行的EXE文件我们在开发程序的时候会引用很多DLL文件,在程序完成编写后,如果不把这些引用的DLL打包,在拷贝给到别人使用 ...
- Java开发设计——UML类图
Java开发设计——UML类图 摘要:本文主要介绍了UML类图的相关知识. 简介 在UML中,类使用包含类名.属性和操作且带有分隔线的长方形来表示,类图分为三层. 第一层是类的名称,如果是抽象类或接口 ...
- Ajax异步后台加载Html绑定不上事件
因项目需要,需要实时从后台动态加载html,开发过程中,遇到事件绑定不上,后来百度一番,大概意思:ajax是异步加载的,页面一开始绑定事件的时候,后台数据还没有传过来,就绑定事件,这个时候找不到这个d ...
- Asp.Net MVC控制器获取视图传值几种方式
前台表单(V:视图) @{ Layout = null; } <!DOCTYPE html> <html> <head> <meta name="v ...
- .NET同一个页面父容器与子容器通信方案
主界面: 关键主页面代码: <div id="EditDiv"> <iframe src="javascript:void(0)" id=&q ...
- Java中级—JSP九大内置对象和动作
一.内置对象 在JSP中,内置对象又称为隐含对象,是指在不声明和创建的情况下就可以被使用的一些成员变量.JSP一共提供有9个内置对象,分别是request(响应对象).pageContext(页面上下 ...