暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

kube-scheduler组件调度实现

DevOps视角 2021-11-29
97
  • kube-scheduler架构设计

    • 最优解

    • 调度阶段

  • kube-scheduler启动流程

    • 内置算法调度注册

    • 命令行参数解析

    • 实例化Scheduler对象

    • 运行事件管理器

    • 运行HTTP或HTTPS服务

    • 运行informer同步资源

    • 领导者选举实例化

    • 运行sched.Run调度器

  • 优先级和抢占机制

  • 亲和性调度

  • 领导者选举


kube-scheduler架构设计

kube-scheduler是Kubernetes的默认调度器,其架构设计本身并不复杂,但后期引入了优先级和抢占机制及亲和性调度等功能,使得kube-scheduler调度器的整体设计略微复杂。

最优解

最优解是数学中的基本概念之一。

实际生产中,kubernetes集群中用于运行Pod资源对象的node节点并不是1台、2台,对于中大型公司的kubernetes集群可能有几百台、几千台,kubernetes也会很快选择出一个node节点用于运行Pod资源对象,这就是因为kube-scheduler使用两种最优解中的局部最优解实现的。

在具体说两种最优解前,先说下调度周期。一个调度周期就是kube-scheduler为一个 Pod资源对象寻找合适的节点的过程。

  • 全局最优解
    • 每个调度周期都会遍历Kubernetes集群中的所有节点,以便找出全局最优的节点
  • 局部最优解
    • 每个调度周期只会遍历部分Kubernetes集群中的节点,找出局部最优的节点

                                               图片来自于网络

  调度阶段

调度可分为三个阶段:预选、优选、选定。

预选阶段是使用内置预选算法对节点进行筛选,比如CPU、内存、GPU等资源是否符合要求,节点上的Pod是否达到上限,等等。该阶段默认使用16个goroutine来执行。

优选阶段主要是对预选阶段筛选出来的节点进行打分,得分最高的节点就会成为这次调度最终最佳节点。如果有多个,会使用round-robin轮询方式选择一个。该阶段默认使用16个goroutine来执行。

选定阶段也可以称为bind阶段,是将Pod运行到最佳节点上。该步是一个异步的过程,先修改本地缓存,然后异步通过api server修改etcd。

kube-scheduler启动流程

内置算法调度注册

启动后第一件事是将kubernetes内置的调度算法注册到注册表中。内置算法有预选算法和优选算法两种。算法的注册是通过Go语言的导入和初始化机制触发。

命令行参数解析

在Kubernetes系统中,Cobra是作为统一的命令行参数解析库使用的,kube-scheduler组件通过Cobra解析配置参数。首先是初始化各个模块,比如HTTP或HTTPS服务等,然后验证配置参数的合法性和可用性,最后将配置参数传递给kube-scheduler组件启动的逻辑。

实例化Scheduler对象

Scheduler对象实例化可分为3个步骤:实例化所有的informer、实例化调度算法函数、为所有的informer对象添加对资源事件的监控。

  • 实例化所有的informer

kube-scheduler组件依赖于资源的informer对象,用于监控资源对象的事件,比如PodInformer监控Pod资源的Add、Update、Delete事件。

  • 实例化调度算法函数

前面只是注册了调度算法,在这一步是将已经注册的调度算法实例化成对应的调度算法函数。

  • 为所有的informer添加对资源事件的监控

主要是为所有的informer对象添加对资源事件的监控并设置回调函数。比如PodInformer对象监控Pod资源对象,当该资源对象触发Add、Update、Delete事件时,触发对应的回调函数。比如触发Add事件后,PodInformer将其放入调度队列中,等待为该Pod资源对象分配节点

运行事件管理器

在kubernetes系统中,Event是一种资源对象,用于展示集群内发生的情况。kube-scheduler会向apiserver上报这些事件。比如为什么Pod资源对象调度不成功、为什么从节点驱逐某个或某些Pod资源对象等。

运行HTTP或HTTPS服务

kubernetes是直接使用Go语言提供的HTTP标准库,并没有过多的封装。HTTPS只是增加了TSLConfig配置。

kube-scheduler提供三个重要的接口:

  • /healthz:用于健康检查
  • /metrics:用于监控指标,一般用于Prometheus指标采集
  • /debug/pprof:用于pprof性能分析

运行informer同步资源

在正式启调度器之前,需要等待所有运行中的Informer的数据同步,使本地缓存数据与Etcd集群中的数据保持一致。

领导者选举实例化

领导者选举机制的目的是实现kube-scheduler组件的高可用。

主要是实例化了两个回调函数:OnStartedLeading函数和OnStoppedLeading。

OnStartedLeading是当前节点领导者选举成功的回调函数,该函数定义了kube-scheduler组件的主逻辑。OnStoppedLeading当前节点领导者被抢占后,会退出当前kube-scheduler组件的主逻辑。

leaderelection.NewLeaderElector函数会一直尝试使节点为领导者。

运行sched.Run调度器

在运行之前,会再次确认所有运行中的informer的数据是否已同步到本地。

主逻辑是一个定时器,对定时调用组件的主逻辑。

优先级和抢占机制

在kube-scheduler组件中,会有一个调度队列:activeQ,是一个优先级队列。可以看如下三种场景。

                                       图片来自于《Kubernetes原码剖析》

场景一中,在资源足够的情况下,同时创建三个不同优先级的pod,pod会按照优先级依次调度到3个节点上。

场景二中,在资源不足的情况下,同时创建三个不同优先级的pod,高优先级和中优先级的Pod会依次调度到节点上。

场景三,在资源不足的情况下,中优先级和低优先级的Pod先被创建,调度到节点上,再创建高优先级的Pod时,高优先级的Pod会抢占低优先级的Pod,低优先级的Pod会重新被调度。

可以知道,抢占是发生在调度失败的时候,也就是说,优先级和抢占机制解决的是Pod调度失败时该怎么办的问题。

要想使用优先级,必须创建一个PriorityClass对象。定义如下:

apiVersion: scheduling.k8s.io.v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
globalDefault: false
description: "This should be used for web service pod only"

这里需要注意的有两个点,一个点是value的值。在kubernetes中,优先级的值是一个32bit的整数,最大值不能超过10亿(超过10亿的是为系统Pod预留的),数字越大优先级越高。

另外一个点是 globalDefault,是设置这个PriorityClass是否要被全局使用。在kubernetes集群中,默认只能有一个用于全局的PriorityClass资源。

定义好PriorityClass后,就可以用于Pod了。示例如下:

apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: web
spec:
  containers:
  - name: nginx
    image: nginx
    imagePullPolicy: AllWays
  priorityClassName: high-priority

亲和性调度

在生产中,一般都会干预、控制Pod资源对象的调度。比如有些业务对于网络方面要求高,让Pod调度到网卡是万兆万卡的节点上,两个业务Pod之间有比较紧密的联系,希望部署到同一个节点上,等等需求。

上面的需求,只需要在这些节点上打上响应的标签,即label,在调度的时候可以使用标签来对Pod进行调度。这种调度机制被称为亲和性和反亲和性调度,或者亲密性和反亲密性调度。

亲和性分为NodeAffinity和PodAffinity,反亲和性只有PodAntiAffinity。

NodeAffinity是将Pod资源对象调度到特定的节点上。比如调度到有万兆网卡的节点上。

NodeAffinity支持两种调度策略:

  • RequiredDuringSchedulingIgnoredDuringExecution:Pod资源对象必须调度到满足条件的节点上,否则Pod资源对象会调度失败并不断尝试。该策略被称为硬策略。
  • PreferredDuringSchedulingIgnoredDuringExecution:Pod资源优先被部署到满足条件的节点上,否则从其它节点中选择较优的节点。该策略被称为软策略。

PreferredDuringSchedulingIgnoredDuringExecution该策略在1.14版本中被官方注释了,不建议使用

PodAffinity将Pod资源对象调度到和另外一个Pod资源对象相近的位置,比如调度到同一节点上或同一机房。PodAffinity也支持和NodeAffinity一样的两种策略,不再赘述。

PodAntiAffinity多用于容灾,比如某一业务的Pod不部署到同一节点上。PodAntiAffinity也支持和NodeAffinity一样的两种策略,不再赘述。

领导者选举

领导者选举是分布式锁实现的一种,kube-scheduler组件通过etcd来实现分布式锁。当多个节点中的一个节点成功在etcd创建key并将自身节点信息写入到etcd中后,该节点就会成为领导者,并定时更新该key的信息。成为领导者的节点会执行kube-scheduler调度器的核心逻辑。剩余的节点会处于阻塞状态但并定时尝试获取锁。

如下图,A、B、C三个部署kube-scheduler组件的节点,当A成为领导者后,由A执行调度器核心逻辑,B、C成为候选者,处于阻塞状态不会执行调度器核心逻辑。

                                    图片来自于Kubernetes原码剖析

kube-scheduler组件会创建两个回调函数:OnStartedLeading和OnStopedLeading。kube-scheduler的acquire函数会尝试获取锁,获取到资源锁后,会执行kube-scheduler的核心逻辑即OnStartedLeading回调函数,并通过renew函数默认每隔2秒进行续约。候选者并不会退出,默认每隔2秒定时尝试获取锁。当由于某种原因,领导者被抢占,会执行OnStopedLeading回调函数退出当前主逻辑。

 图片来自于Kubernetes原码剖析

kube-scheduler实现领导选举的分布式锁(即资源锁),基于etcd集群的key依赖于kubernetes的某种资源,在早期的版本(比如从1.14<=version<=1.19)支持三种锁:Endpoints、ConfigMaps、Leases。默认是使用Endpoints作为资源锁。从1.18.10新增了endpointsleases和configmapsleases两种锁,但是从1.20版本才开始将默认的资源锁修改为Leases。如果不想使用默认的资源锁,可通过--leader-elect-resource-lock参数指定想要使用的资源锁。

由于kube-scheduler组件内容比较多,未展开太多,更多内容大家阅读参考部分的内容,如有问题,请斧正。此外是个人维护公众号,产出比较慢,还望大家多包涵。

参考:

《Kubernetes原码剖析》

Kubernetes Documentation


文章转载自DevOps视角,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论