k8s调度器扩展机制

在kube-scheduler有四种扩展机制:

一、Multiple Scheduler

若要部署第二调度器,可以直接修改kubernetes的源码
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
make
使用如下Dockerfile构建成镜像:
FROM busybox
ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kibe-scheduler
创建ServiceAccount并将其绑定到ClusterRole,使其与kube-scheduler具有相同的权限:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-scheduler
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-scheduler-as-kube-scheduler
subjects:
- kind: ServiceAccount
  name: my-scheduler
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:kube-scheduler
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-scheduler-as-volume-scheduler
subjects:
- kind: ServiceAccount
  name: my-scheduler
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:volume-scheduler
  apiGroup: rbac.authorization.k8s.io
部署该调度器:
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    component: scheduler
    tier: control-plane
  name: my-scheduler
  namespace: kube-system
spec:
  selector:
    matchLabels:
      component: scheduler
      tier: control-plane
  replicas: 1
  template:
    metadata:
      labels:
        component: scheduler
        tier: control-plane
        version: second
    spec:
      serviceAccountName: my-scheduler
      containers:
      - command:
        - /usr/local/bin/kube-scheduler
        - --address=0.0.0.0
        - --leader-elect=false
        - --scheduler-name=my-scheduler
        image: xxxxxx
        livenessProbe:
          httpGet:
            path: /healthz
            port: 10251
          initialDelaySeconds: 15
        name: kube-second-scheduler
        readinessProbe:
          httpGet:
            path: /healthz
            port: 10251
        resources:
          requests:
            cpu: '0.1'
        securityContext:
          privileged: false
        volumeMounts: []
      hostNetwork: false
      hostPID: false
      volumes: []
启动时通过参数scheduler-name指定了第二调度器的名字
Pod通过spec.schedulerName指定使用的调度器(默认调度器是default-scheduler)
 

二、Multiple profiles

无需部署第二调度器,1.18+的kube-scheduler默认支持多profile:
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
profiles:  
  - schedulerName: default-scheduler  
  - schedulerName: no-scoring-scheduler    
    plugins:      
      preScore:        
        disabled:        
        - name: '*'      
      score:        
        disabled:        
        - name: '*'

三、Schduler Extender

在启动官方调度器之后,可以再启动一个扩展调度器Schedule Extender。
Scheduler Extender实际上是一个额外的调度进程,用户需要实现自己的Filter方法、Prioritize方法、Bind方法
策略文件的配置内容:
apiVersion: v1
kind: Policy
alwaysCheckALLPredicates: false  # 不管每个prediacate的结果,所有的predicate都要走一遍
predicates:['GeneralPredicates','...']  # 不配的话使用默认的predicates;为空列表则跳过所有配置
hardPodAffinitySymmetricWeight:
# 配置打分算法插件;不配的话使用默认的打分插件;为空列表则跳过所有配置
priorities:
- name: LeastRequestedPriority
  weight: 1
extenders:
- urlPrxfix: "http://xxx:xxx/scheduler-gpu-extender"
  filterVerb: filter
  exableHttps: false
  nodeCacheCapable: true  # 服务是否已经有NodeCache,为true则调度器传递nodeName列表而不是整个nodeinfo完整结构
  ignorable: false   # 调用扩展调度器不成功时(如报错或网络问题),是否可忽略扩展调度器
  managedResources:
  - name: "example/gpu-mem"    # 官方调度器在遇到这个Resource时会用扩展调度器
    ignoredByScheduler: false  # resourceFit阶段是否忽略这个资源的校验
这里可以看到配置的过滤器predicates、配置的打分器 priorities、配置的扩展调度器。
 
以Filter阶段举例,执行过程会经过2个阶段:
(1)scheduler会先执行内置的Filter策略。如果执行失败,会直接标识Pod调度失败;
(2)如果内置的Filter策略执行成功,scheduler通过http调用Extender注册的webhook,将调度所需要的Pod和Node的信息发送到Extender,根据返回Filter结果作为最终结果。
Schedule Extender存在以下问题:
(1)调用Extender的接口是HTTP请求,性能远低于本地的函数调用。同时每次调用都需要将Pod和Node的信息进行marshaling和unmarshalling的操作,会进一步降低性能;
(2)用户可以扩展的点比较有限,位置比较固定,无法支持灵活的扩展,例如只能在执行完默认的Filter策略后才能调用。
因此,Extender在集群规模较小,调度效率要求不高的情况下,是一个灵活可用的扩展方案。但是在正常生产环境的大型集群中,Extender无法支持高吞吐量,性能较差。
 

四、Scheduling Framework

基于scheduler framework进行out-of-tree的scheduler plugins开发示例:
kubernetes-sigs团队实现了两个插件示例

1、Qos的插件

调度过程中如果Pod的优先级相同,根据Pod的Qos来决定调度顺序
(1)插件构造
定义插件的对象和构造函数:
type Sort struct {}
// 新初始化一个插件,返回它
func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error){
    return &Sort{}, nil
}
然后,实现QueueSort接口的Less函数,改为在优先级相同的情况下,通过比较Qos来决定优先级。
func (*Sort) Less(pInfo1, pInfo2 *framework.PodInfo) bool{
    p1 := pod.GetPodPriority(pInfo1.Pod)
    p2 := pod.GetPodPriority(pInfo2.Pod)
    return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod))
}
func compQOS(p1, p2 *v1.Pod) bool{
    p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)
    if p1QOS == v1.PodQOSGuaranteed {
        return true
    } else if p1QOS == v1.PodQOSBurstable {
        return p2QOS != v1.PodQOSGuaranteed
    } else{
        return p2QOS == v1.PodQOSBestEffort
    }
}
(2)插件注册在main函数中注册自定义插件和相应的构造函数:
func main() {    
    rand.Seed(time.Now().UnixNano())    // 向scheduler framework注册用户自定义插件    
    command := app.NewSchedulerCommand(        
        app.WithPlugin(qos.Name, qos.New),        
        app.WithPlugin(qos.Name, qos.New),    
    )    
    if err := command.Execute(); 
    err != nil {        
        os.Exit(1)    
    }
}
(3)通过make命令进行代码编译
(4)配置scheduler-config.yaml
apiVersion: kubescheduler.config.k8s.io/v1alpha2
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: false
clientConnection:
  kubeconfig: "REPLACE_ME_WITH_KUBE_CONFIG_PATH"
profiles:
- schedulerName: default-scheduler
  plugins:
    queueSort:
      enabled:
      - name: QOSSort
      disabled:
      - name: "*"
启动kube-scheduler时传入集群的kubeconfig文件以及插件的配置文件即可
$ kube-scheduler --kubeconfig=scheduler.conf --config=scheduler-config.yaml

2、Coscheduling插件

在并发系统中将多个相关联的进程调度到不同处理器上同时运行,需要保证所有相关联的进程能够同时启动。
部分进程的异常,可能导致整个关联进程组的阻塞。这种导致阻塞的部分异常进程,称之为碎片(fragement)。
Coscheduling的具体实现过程中,根据是否允许碎片存在,可以细分为完全不允许有碎片存在的Explicit Coscheduling(Gang Scheduling)、Local Coscheduling和Implicit Coscheduling。
对应到Kubernetes中,Coscheduling的含义就是:一个批任务(关联进程组)包括了N个Pod(进程),Kubernetes调度器负责将这N个Pod组成的PodGroup调度到M个节点(处理器)上同时运行。
如果这个批任务需要部分Pod同时启动即可运行,称需启动Pod的最小数量为min-available。
特别地,当min-available=N时,批任务要求满足Gang Scheduling。
 
插件通过给Pod打label的形式来定义PodGroup:
    pod-group.scheduling.sigs.k8s.io/name:xxx用于表示PodGroup的Name;
    pod-group.scheduling.sigs.k8s.io/min-available:"2" 表示PodGroup能够运行所需要的最小副本数
备注:要求属于同一个PodGroup的Pod必须保持相同的优先级
 
Permit阶段使用了延迟绑定功能,当属于同一个PodGruop的Pod数量不够时,进行等待;积累的Pod数目足够时,才会将所有Pod全部绑定并创建。
如果某个Pod在Permit阶段等待超时了,则会进入到UnReserve阶段。
QueueSort阶段,由于默认的Scheduler队列并不能感知 PodGroup 的信息,同一个PodGroup中的Pod在队列中是混乱无序的。
一旦某个PodGroup在 Permit 阶段处于等待状态,其它的PodGroup也会因此处于等待状态,从而导致死锁。
此处自定义的Less方法继承了默认的基于优先级的比较方式,高优先级的Pod会排在低优先级的Pod之前。如果两个Pod的优先级相同,则采用新的排队逻辑,以保证队列中属于同一个PodGroup的Pod排列在一起:  
  • 如果两个Pod 都是普通的Pod,则谁先创建谁在队列里边排在前边;
  • 如果两个Pod 一个是普通的Pod,另一个是属于某个PodGroup的Pod,则比较的是前者的创建时间和后者所属PodGroup的创建时间;
  • 如果两个Pod 都是属于某个PodGroup的Pod,比较两个 PodGroup 的创建时间,则谁先创建谁在队列里排在前边;
  • 如果两个PodGroup 的创建时间相同,谁的PodGroup 的自增Id谁小谁在队列里排在前边
Prefilter 阶段增加一个过滤条件,当一个Pod调度时,会计算该Pod所属PodGroup的Pod的Sum(包括Running状态的),如果Sum小于min-available时,则肯定无法满足min-available要求,则直接在该阶段拒绝掉,不再进入调度的主流程。
UnReserve 阶段会直接拒绝掉所有跟Pod属于同一个PodGroup的Pod,避免剩余的Pod进行长时间的无效等待。
 
Coscheduling插件和原生调度器代码已经统一构建成一个容器镜像。可以通过helm chart包ack-coscheduling来自动安装。它会启动一个任务,自动用Coscheduling scheduler替换原生的kube-scheduler,并且会修改相关Config文件,使scheduling framework正确地加载Coscheduling 插件。
 
下载 helm chart包,执行命令安装:
$  wget http://kubeflow.oss-cn-beijing.aliyuncs.com/ack-coscheduling.tar.gz
$  tar zxvf ack-coscheduling.tar.gz
$  helm install ack-coscheduling -n kube-system ./ack-coscheduling
NAME: ack-coscheduling
LAST DEPLOYED: Mon Apr 13 16:03:57 2020
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
在 Master 节点上,使用helm命令验证是否安装成功。
$ helm get manifest ack-coscheduling -n kube-system | kubectl get -n kube-system -f -
NAME                           COMPLETIONS   DURATION   AGE
scheduler-update-clusterrole   1/1           8s         35s
scheduler-update               3/1 of 3      8s         35s
使用Coscheduling时,只需要在创建任务的yaml描述中配置两个描述任务状态的label即可。
通过 helm 卸载,将kube-scheduler的版本及配置回滚到集群默认的状态:
$ helm uninstall ack-coscheduling -n kube-system
 
 
阿里云实现的插件:

3、RequestedToCapacityRatio插件

用户自己定义的资源利用率与得分间的线性关系、根据哪些资源进行打分、每种资源的权重:
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: false
clientConnection:
  kubeconfig: "REPLACE_ME_WITH_KUBE_CONFIG_PATH"
plugins:
  score:
    enabled:
    - name: RequestedToCapacityRatio
      weight: 100
    disabled:
    - name: LeastRequestedPriority
pluginConfig:
- name: RequestedToCapacityRatio
  args:
    functionshape:
      - utilization: 0
        score: 0
      - utilization: 100
        score: 100
    resourcetoweightmap: # 定义具体根据哪种资源类型进行binpack操作,多种资源时可以设置weight来进行比重设置
      "cpu": 1
      "nvidia.com/gpu": 1
在打分过程中,会通过计算(pod.Request + node.Allocated)/node.Total的结果得到对应资源的利用率,并且将利用率带入上文中所述的打分函数中,得到相应的分数。
最后将所有的资源根据weight值,加权得到最终的分数。
 

参考资料:

[1] https://kubernetes.io/docs/home/

[2] https://edu.aliyun.com/roadmap/cloudnative

[3] 郑东旭《Kubernetes源码剖析》

posted @ 2021-01-10 12:39  扬羽流风  阅读(2300)  评论(0编辑  收藏  举报