Kubernetes组件
- kube-apiserver:是Kubernetes控制平面的组件,负责对外开放Kubernetes API,接受处理http请求,是Kubernetes控制平面的前端。
- etcd:是一款分布式键值存储存储中间件,用于Kubernetes的所有集群数据的后台数据库。
- kube-scheduler:调度器是Kubernetes控制平面的组件,负责监听新创建、未指定运行节点的Pods,并选择最合适的节点让Pod运行。
kube-scheduler调度策略因素包含单个Pod及Pods集合的资源需求、软硬件约束、亲和性规则等
- kube-controller-manager:控制器管理器是Kubernetes控制平面的组件, 负责运行控制器。
- cloud-controller-manager:云控制器管理器是Kubernetes控制平面的组件,负责与云平台交互。(本地环境运行k8s不需要启动该组件)
- node:节点组件,负责维护Pod的运行环境。
- kubelet:在集群的每个node上运行,从kube-apiserver组件接收新的或修改的PodSpecs,并确保Pod及其容器在期望规范下运行,同时向kube-apiserver汇报主机的运行状况。
- kube-proxy:是集群中每个节点上运行的网络代理,维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与Pod进行网络通信。
- 容器运行时(Container Runtime):用于支持许多容器运行环境。
kube-scheduler
启动调度器
启动入口在cmd/kube-scheduler/scheduler.go
的main()函数,保留主干代码如下:
|
|
核心函数就两行:
command := app.NewSchedulerCommand()
创建一个*cobra.Command
对象command.Execute()
会调用*cobra.Command
对象的那个Run字段
NewSchedulerCommand()
函数保留主干代码如下:
|
|
核心方法大概也是两行:
options.NewOptions()
创建了*Options
对象,是apiserver的连接配置信息和调度的一些策略配置- cobra最终调用的是
runCommand(cmd, args, opts, registryOptions...)
函数,传入了*Options
对象
runCommand()
函数保留主干代码如下:
|
|
opts.Config()
获取的配置有client、PodInformer等, 获取的配置通过c.Complete()
封装到CompletedConfig结构体的成员变量中runCommand()
函数最终调用了Run()
函数
Run()
函数保留主干代码如下:
|
|
Run()
函数为启动调度器的核心函数
scheduler.New()
函数创建了一个调度器sched,最开始传入的配置在该函数得到使用,如cc.Client
、cc.InformerFactory
、cc.PodInformer
- 下面是
scheduler.New()
函数的部分逻辑,提供了两种设置预选和优选算法并初始化Scheduler的方式,默认情况下:当source.Provider != nil
调用createFromProvider()
函数生成Scheduler, 自定义情况下:source.Policy != nil
调用initPolicyFromXXX()
,有两种方式加载配置,一种是File,另一种是ConfigMap。如果Provider和Policy都不为空的情况,按switch的逻辑自定义策略会覆盖默认策略
|
|
newHealthzHandler
和newHealthzHandler
分别提供了/healthz
和/metrics
结构的具体处理逻辑,用于健康检测和指标监控- 启动所有informer,
cc.PodInformer.Informer().Run(ctx.Done())
、cc.InformerFactory.Start(ctx.Done())
cc.LeaderElection != nil
表示调度器配置了高可用,用选举模式启动调度器,OnStartedLeading
的值设置为sched.Run
,leaderElector.Run(ctx)
中会调用该函数- 如果调度器是非集群模式,会跳过if中的逻辑,直接执行调度器的
Run()
函数,即sched.Run(ctx)
,调度器的Run()
函数中是具体的调度逻辑,只有异常情况才会退出,该函数位于pkg/scheduler/scheduler.go
中
调度框架
我们来复习一下,k8s调度器的流程是从apiserver获取未被调度(nodeName字段为空)的pods,通过预选(过滤掉不合条件的node,比如内存、CPU资源)、优选(通过算法为node打分)为pod寻找一个最合适的node后,向apiserver发送一个post请求设置nodeName的值
上一节中已经调试到cobra最终执行到sched.Run(ctx)
,我们先学习一下Scheduler
结构体和它的Run()
函数
Scheduler
结构体,位于pkg/scheduler/scheduler.go
|
|
- 调度器Scheduler的
Run()
函数
|
|
- 核心逻辑是
wait.UntilWithContext(ctx, sched.scheduleOne, 0)
,入参有三个:ctx
、sched.scheduleOne
、0
,进入方法内部具体逻辑如下:
|
|
该方法的具体逻辑
BackoffUntil(f func(), backoff BackoffManager, sliding bool, stopCh <-chan struct{})
函数有4个入参:
- f: 具体的执行逻辑,这里为
sched.scheduleOne
- backoff: 定时任务的结构体对象,duration传入的为0,jitter传入的也为0.0
|
|
- sliding:如果为false,间隔时间包含f的执行时间,如果为true,不包含f的执行时间,也就是f执行完后才开始计时,这里外部传入的true
case <-stopCh:
这个分支读取channel的值,在没有写入或关闭channel时不会执行该分支,stopCh即为接受停止信号的channel
总结:
调度器Scheduler的
Run()
函数中的wait.UntilWithContext(ctx, sched.scheduleOne, 0)
的逻辑为循环调用sched.scheduleOne
,两次调用之间的间隔时间为0
scheduleOne
scheduleOne
为单个pod执行整个调度工作流,根据上面定时任务的设置情况,该函数是顺序执行的,执行完成后立即再次执行,没有并发情况
注意:
scheduleOne
执行完成是指通过post请求写一个binding到apiserver,而不会等kubelet创建pod