作者:王庆璨 张凯

进击的Kubernetes调度零碎(一):Scheduling Framework
进击的Kubernetes调度零碎(二):反对批工作的Coscheduling/Gang scheduling

前言

首先咱们来理解一下什么是Coscheduling和Gang scheduling。Wikipedia对 Coscheduling的定义是“在并发零碎中将多个相关联的过程调度到不同处理器上同时运行的策略”。在Coscheduling的场景中,最次要的准则是保障所有相关联的过程可能同时启动。避免局部过程的异样,导致整个关联过程组的阻塞。这种导致阻塞的局部异样过程,称之为“碎片(fragement)”。
在Coscheduling的具体实现过程中,依据是否容许“碎片”存在,能够细分为Explicit Coscheduling,Local Coscheduling和Implicit Coscheduling。 其中Explicit Coscheduling就是大家常听到的Gang Scheduling。Gang Scheduling要求齐全不容许有“碎片”存在, 也就是“All or Nothing”。

咱们将上述定义的概念对应到Kubernetes中,就能够了解Kubernetes调度零碎反对批工作Coscheduling的含意了。 一个批工作(关联过程组)包含了N个Pod(过程),Kubernetes调度器负责将这N个Pod调度到M个节点(处理器)上同时运行。如果这个批工作须要局部Pod同时启动即可运行,咱们称需启动Pod的最小数量为min-available。特地地,当min-available=N时,批工作要求满足Gang Scheduling。

为什么Kubernetes调度零碎须要Coscheduling?

Kubernetes目前曾经宽泛的利用于在线服务编排,为了晋升集群的的利用率和运行效率,咱们心愿将Kubernetes作为一个对立的治理平台来治理在线服务和离线作业。默认的调度器是以Pod为调度单元进行顺次调度,不会思考Pod之间的互相关系。然而很多数据计算类的离线作业具备组合调度的特点,即要求所有的子工作都可能胜利创立后,整个作业能力失常运行。如果只有局部子工作启动的话,启动的子工作将继续期待残余的子工作被调度。这正是Gang Scheduling的场景。

如下图所示,JobA须要4个Pod同时启动,能力失常运行。Kube-scheduler顺次调度3个Pod并创立胜利。到第4个Pod时,集群资源有余,则JobA的3个Pod处于空等的状态。然而它们曾经占用了局部资源,如果第4个Pod不能及时启动的话,整个JobA无奈胜利运行,更蹩脚的是导致集群资源节约。

如果呈现更坏的状况的话,如下图所示,集群其余的资源刚好被JobB的3个Pod所占用,同时在期待JobB的第4个Pod创立,此时整个集群就呈现了死锁。

社区相干的计划

社区目前有Kube-batch以及基于Kube-batch衍生的Volcano 2个我的项目来解决上文中提到的痛点。实现的形式是通过开发新的调度器将Scheduler中的调度单元从Pod批改为PodGroup,以组的模式进行调度。应用形式是如果须要Coscheduling性能的Pod走新的调度器,其余的例如在线服务的Pod走Kube-scheduler进行调度。

这些计划尽管可能解决Coscheduling的问题,然而同样引入了新的问题。如大家所知,对于同一集群资源,调度器须要中心化。但如果同时存在两个调度器的话,有可能会呈现决策抵触,例如别离将同一块资源分配给两个不同的Pod,导致某个Pod调度到节点后因为资源有余,导致无奈创立的问题。解决的形式只能是通过标签的模式将节点强行的划分开来,或者部署多个集群。这种形式通过同一个Kubernetes集群来同时运行在线服务和离线作业,势必会导致整体集群资源的节约以及运维老本的减少。再者,Volcano运行须要启动定制的MutatingAdmissionWebhook和ValidatingAdmissionWebhook。这些Webhooks自身存在单点危险,一旦呈现故障,将影响集群内所有pod的创立。另外,多运行一套调度器,自身也会带来保护上的复杂性,以及与上游Kube-scheduler接口兼容上的不确定性。

基于Scheduling Framework的计划

本系列第一篇《进击的Kubernetes调度零碎 (一):Scheduling Framework》介绍了Kubernetes Scheduling Framework的架构原理和开发方法。在此基础上,咱们扩大实现了Coscheduling调度插件,帮忙Kubernetes原生调度器反对批作业调度,同时防止上述计划存在的问题。Scheduling framework的内容在前一篇文章具体介绍,欢送大家翻阅。

Kubernetes负责Kube-scheduler的小组sig-scheduler为了更好的治理调度相干的Plugin,新建了我的项目scheduler-plugins治理不同场景的Plugin。咱们基于scheduling framework实现的Coscheduling Plugin成为该项目标第一个官网插件,上面我将具体的介绍Coscheduling Plugin的实现和应用形式。

技术计划

总体架构

PodGroup

咱们通过label的模式来定义PodGroup的概念,领有同样label的Pod同属于一个PodGroup。min-available是用来标识该PodGroup的作业可能正式运行时所须要的最小正本数。

labels:     pod-group.scheduling.sigs.k8s.io/name: tf-smoke-gpu     pod-group.scheduling.sigs.k8s.io/min-available: "2"

备注: 要求属于同一个PodGroup的Pod必须放弃雷同的优先级

Permit

Framework的Permit插件提供了提早绑定的性能,即Pod进入到Permit阶段时,用户能够自定义条件来容许Pod通过、回绝Pod通过以及让Pod期待状态(可设置超时工夫)。Permit的提早绑定的性能,刚好能够让属于同一个PodGruop的Pod调度到这个节点时,进行期待,期待积攒的Pod数目满足足够的数目时,再对立运行同一个PodGruop的所有Pod进行绑定并创立。

举个理论的例子,当JobA调度时,须要4个Pod同时启动,能力失常运行。但此时集群仅能满足3个Pod创立,此时与Default Scheduler不同的是,并不是间接将3个Pod调度并创立。而是通过Framework的Permit机制进行期待。

此时当集群中有闲暇资源被开释后,JobA的中Pod所须要的资源均能够满足。

则JobA的4个Pod被一起调度创立进去,失常运行工作。

QueueSort

因为Default Scheduler的队列并不能感知PodGroup的信息,所以Pod在出队时处于无序性(针对PodGroup而言)。如下图所示,a和b示意两个不同的PodGroup,两个PodGroup的Pod在进入队列时,因为创立的工夫交织导致在队列中以交织的顺序排列。

当一个新的Pod创立后,入队后,无奈跟与其雷同的PodGroup的Pod排列在一起,只能持续以凌乱的模式交织排列。

这种无序性就会导致如果PodGroupA在Permit阶段处于期待状态,此时PodGroupB的Pod调度实现后也处于期待状态,互相占有资源使得PodGroupA和PodGroupB均无奈失常调度。这种状况即是把死锁景象呈现的地位从Node节点挪动到Permit阶段,无奈解决前文提到的问题。

针对如上所示的问题,咱们通过实现QueueSort插件, 保障在队列中属于同一个PodGroup的Pod可能排列在一起。咱们通过定义QueueSort所用的Less办法,作用于Pod在入队后排队的程序:

func  Less(podA *PodInfo, podB *PodInfo) bool

首先,继承了默认的基于优先级的比拟形式,高优先级的Pod会排在低优先级的Pod之前。
而后,如果两个Pod的优先级雷同,咱们定义了新的排队逻辑来反对PodGroup的排序。

  1. 如果两个Pod都是regularPod(一般的Pod),则谁先创立谁在队列里边排在前边。
  2. 如果两个Pod一个是regularPod,另一个是pgPod(属于某个PodGroup的Pod), 咱们比拟的是regularPod的创立工夫和pgPod所属PodGroup的创立工夫,则谁先创立谁在队列里边排在前边。
  3. 如果两个Pod都是pgPod,咱们比拟两个PodGroup的创立工夫,则谁先创立谁在队列里边排在前边。同时有可能两个PodGroup的创立工夫雷同,咱们引入了自增Id,使得PodGroup的Id谁小谁排在前边(此处的目标是为了辨别不同的PodGroup)。

通过如上的排队策略,咱们实现属于同一个PodGroup的Pod可能同一个PodGroup的Pod可能排列在一起。

当一个新的Pod创立后,入队后,会跟与其雷同的PodGroup的Pod排列在一起。

Prefilter

为了缩小有效的调度操作,晋升调度的性能,咱们在Prefilter阶段减少一个过滤条件,当一个Pod调度时,会计算该Pod所属PodGroup的Pod的Sum(包含Running状态的),如果Sum小于min-available时,则必定无奈满足min-available的要求,则间接在Prefilter阶段回绝掉,不再进入调度的主流程。

UnReserve

如果某个Pod在Permit阶段期待超时了,则会进入到UnReserve阶段,咱们会间接回绝掉所有跟Pod属于同一个PodGroup的Pod,防止残余的Pod进行长时间的有效期待。

Coscheduling试用

装置部署

用户既能够在本人搭建的Kubernetes集群中,也能够在任一个私有云提供的规范Kubernetes服务中来试用Coscheduling。须要留神的是集群版本1.16+, 以及领有更新集群master的权限。
本文将应用 阿里云容器服务 ACK 提供的Kubernetes集群来进行测试。

前提条件

  1. 反对Kubernetes 1.16以上版本
  2. 抉择创立ACK提供的规范专有集群(Dedicated cluster)
  3. 保障集群节点能够拜访公网
  4. helm v3:ACK在master节点上默认装置helm,请确认版本是否为helm v3,如果是helm v2请降级值helm v3,装置helm v3请参考helm v3 装置。

部署Coscheduling

咱们曾经将Coscheduling插件和原生调度器代码对立构建成新的容器镜像。并提供了一个helm chart包 ack-coscheduling来主动装置。它会启动一个工作,主动用Coscheduling scheduler替换集群默认装置的原生scheduler,并且会批改scheduler的相干Config文件,使scheduling framework正确地加载Coscheduling插件。实现试用后,用户可通过下文提醒的卸载性能复原集群默认scheduler及相干配置。

下载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-coschedulingNAME: ack-coschedulingLAST DEPLOYED: Mon Apr 13 16:03:57 2020NAMESPACE: kube-systemSTATUS: deployedREVISION: 1TEST SUITE: None

验证Coscheduling

在Master节点上,应用helm命令验证是否装置胜利。

$ helm get manifest ack-coscheduling -n kube-system | kubectl get -n kube-system -f -NAME                           COMPLETIONS   DURATION   AGEscheduler-update-clusterrole   1/1           8s         35sscheduler-update               3/1 of 3      8s         35s

### 卸载Coscheduling
通过helm卸载,将kube-scheduler的版本及配置回滚到集群默认的状态。

$ helm uninstall ack-coscheduling -n kube-system

应用形式

应用Coscheduling时,只须要在创立工作的yaml形容中配置pod-group.scheduling.sigs.k8s.io/name和pod-group.scheduling.sigs.k8s.io/min-available这两个label即可。

labels:    pod-group.scheduling.sigs.k8s.io/name: tf-smoke-gpu    pod-group.scheduling.sigs.k8s.io/min-available: "3"

pod-group.scheduling.sigs.k8s.io/name:用于示意PodGroup的Name

pod-group.scheduling.sigs.k8s.io/min-available: 用于示意以后集群资源至多满足min-available个pod启动时,能力整体调度该工作

备注: 属于同一个PodGroup的Pod必须放弃雷同的优先级

Demo展现

接下来咱们通过运行Tensorflow的分布式训练作业来演示Coscheduling的成果。以后测试集群有4个GPU卡

  1. 通过Kubeflow的Arena工具在已有Kubernetes集群中部署Tensorflow作业运行环境。
Arena是基于Kubernetes的机器学习零碎开源社区Kubeflow中的子项目之一。Arena用命令行和SDK的模式反对了机器学习工作的次要生命周期治理(包含环境装置,数据筹备,到模型开发,模型训练,模型预测等),无效晋升了数据科学家工作效率。
git clone https://github.com/kubeflow/arena.gitkubectl create ns arena-systemkubectl create -f arena/kubernetes-artifacts/jobmon/jobmon-role.yamlkubectl create -f arena/kubernetes-artifacts/tf-operator/tf-crd.yamlkubectl create -f arena/kubernetes-artifacts/tf-operator/tf-operator.yaml

查看是否部署胜利

$ kubectl  get pods -n arena-systemNAME                                READY   STATUS    RESTARTS   AGEtf-job-dashboard-56cf48874f-gwlhv   1/1     Running   0          54stf-job-operator-66494d88fd-snm9m    1/1     Running   0          54s
  1. 用户向集群中提交Tensorflow作业(TFJob),如下示例,蕴含1个Parameter Server pod和4个Worker pod,每个Worker须要2个GPU。配置了pod group,须要至多5个pod启动后能力运行。
apiVersion: "kubeflow.org/v1"kind: "TFJob"metadata:  name: "tf-smoke-gpu"spec:  tfReplicaSpecs:    PS:      replicas: 1      template:        metadata:          creationTimestamp: null          labels:            pod-group.scheduling.sigs.k8s.io/name: tf-smoke-gpu            pod-group.scheduling.sigs.k8s.io/min-available: "5"         spec:          containers:          - args:            - python            - tf_cnn_benchmarks.py            - --batch_size=32            - --model=resnet50            - --variable_update=parameter_server            - --flush_stdout=true            - --num_gpus=1            - --local_parameter_device=cpu            - --device=cpu            - --data_format=NHWC            image: registry.cn-hangzhou.aliyuncs.com/kubeflow-images-public/tf-benchmarks-cpu:v20171202-bdab599-dirty-284af3            name: tensorflow            ports:            - containerPort: 2222              name: tfjob-port            resources:              limits:                cpu: '1'            workingDir: /opt/tf-benchmarks/scripts/tf_cnn_benchmarks          restartPolicy: OnFailure    Worker:      replicas: 4      template:        metadata:          creationTimestamp: null          labels:            pod-group.scheduling.sigs.k8s.io/name: tf-smoke-gpu            pod-group.scheduling.sigs.k8s.io/min-available: "5"        spec:          containers:          - args:            - python            - tf_cnn_benchmarks.py            - --batch_size=32            - --model=resnet50            - --variable_update=parameter_server            - --flush_stdout=true            - --num_gpus=1            - --local_parameter_device=cpu            - --device=gpu            - --data_format=NHWC            image: registry.cn-hangzhou.aliyuncs.com/kubeflow-images-public/tf-benchmarks-gpu:v20171202-bdab599-dirty-284af3            name: tensorflow            ports:            - containerPort: 2222              name: tfjob-port            resources:              limits:                nvidia.com/gpu: 2            workingDir: /opt/tf-benchmarks/scripts/tf_cnn_benchmarks          restartPolicy: OnFailure
  1. 当用户不应用Coscheduling性能时

删除上述TFJob yaml中的pod-group.scheduling.sigs.k8s.io/name和pod-group.scheduling.sigs.k8s.io/min-available标签,示意该工作不应用Coscheduling。创立工作后,集群资源只能满足2个Worker启动,残余两个处于Pending状态。

$ kubectl get podsNAME                    READY   STATUS    RESTARTS   AGEtf-smoke-gpu-ps-0       1/1     Running   0          6m43stf-smoke-gpu-worker-0   1/1     Running   0          6m43stf-smoke-gpu-worker-1   1/1     Running   0          6m43stf-smoke-gpu-worker-2   0/1     Pending   0          6m43stf-smoke-gpu-worker-3   0/1     Pending   0          6m43s

查看其中正在运行的Worker的日志,都处于期待残余那两个Worker启动的状态。此时,4个GPU都被占用却没有理论执行工作。

$ kubectl logs -f tf-smoke-gpu-worker-0INFO|2020-05-19T07:02:18|/opt/launcher.py|27| 2020-05-19 07:02:18.199696: I tensorflow/core/distributed_runtime/master.cc:221] CreateSession still waiting for response from worker: /job:worker/replica:0/task:3INFO|2020-05-19T07:02:28|/opt/launcher.py|27| 2020-05-19 07:02:28.199798: I tensorflow/core/distributed_runtime/master.cc:221] CreateSession still waiting for response from worker: /job:worker/replica:0/task:2
  1. 当用户应用Coscheduling性能时

增加pod-group相干标签后创立工作,因为集群的资源无奈满足用户设定的min-available要求,则PodGroup无奈失常调度,所有的Pod始终处于Pending状态。

$ kubectl get podsNAME                    READY   STATUS    RESTARTS   AGEtf-smoke-gpu-ps-0       0/1     Pending   0          43stf-smoke-gpu-worker-0   0/1     Pending   0          43stf-smoke-gpu-worker-1   0/1     Pending   0          43stf-smoke-gpu-worker-2   0/1     Pending   0          43stf-smoke-gpu-worker-3   0/1     Pending   0          43s

此时,如果通过集群扩容,新增4个GPU卡,资源能满足用户设定的min-available要求,则PodGroup失常调度,4个Worker开始运行

$ kubectl get podsNAME                    READY   STATUS    RESTARTS   AGEtf-smoke-gpu-ps-0       1/1     Running   0          3m16stf-smoke-gpu-worker-0   1/1     Running   0          3m16stf-smoke-gpu-worker-1   1/1     Running   0          3m16stf-smoke-gpu-worker-2   1/1     Running   0          3m16stf-smoke-gpu-worker-3   1/1     Running   0          3m16s

查看其中一个Worker的日志,显示训练任务曾经开始

$ kubectl logs -f tf-smoke-gpu-worker-0INFO|2020-05-19T07:15:24|/opt/launcher.py|27| Running warm upINFO|2020-05-19T07:21:04|/opt/launcher.py|27| Done warm upINFO|2020-05-19T07:21:04|/opt/launcher.py|27| Step    Img/sec    lossINFO|2020-05-19T07:21:05|/opt/launcher.py|27| 1    images/sec: 31.6 +/- 0.0 (jitter = 0.0)    8.318INFO|2020-05-19T07:21:15|/opt/launcher.py|27| 10    images/sec: 31.1 +/- 0.4 (jitter = 0.7)    8.343INFO|2020-05-19T07:21:25|/opt/launcher.py|27| 20    images/sec: 31.5 +/- 0.3 (jitter = 0.7)    8.142

后续工作

利用Kubernetes Scheduling Framework的机制实现了Coscheduling,解决了AI、数据计算类的批工作须要组合调度,同时缩小资源节约的问题。从而晋升集群整体资源利用率。
咱们将在本系列接下来的文章中具体介绍更多针对批工作的调度策略,如Capacity Scheduling,多队列治理等个性,以及在Scheduling Framework中的设计与实现。敬请期待。