进击的-Kubernetes-调度系统二支持批任务的-CoschedulingGang-scheduling

34次阅读

共计 10325 个字符,预计需要花费 26 分钟才能阅读完成。

作者 | 王庆璨(阿里云技术专家)、张凯(阿里云高级技术专家)

导读: 阿里云容器服务团队联合多年 Kubernetes 产品与客户反对教训,对 Kube-scheduler 进行了大量优化和扩大,逐渐使其在不同场景下仍然能稳固、高效地调度各种类型的简单工作负载。《进击的 Kubernetes 调度零碎》系列文章将把咱们的教训、技术思考和实现细节全面地展示给 Kubernetes 用户和开发者,冀望帮忙大家更好地理解 Kubernetes 调度零碎的弱小能力和将来倒退方向。本文为该系列文章的第二篇。

前言

什么是 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 的排序。

  • 如果两个 Pod 都是 regularPod(一般的 Pod),则谁先创立谁在队列里边排在前边;
  • 如果两个 Pod 一个是 regularPod,另一个是 pgPod(属于某个 PodGroup 的 Pod), 咱们比拟的是 regularPod 的创立工夫和 pgPod 所属 PodGroup 的创立工夫,则谁先创立谁在队列里边排在前边;
  • 如果两个 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 集群来进行测试。

前提条件

  • 反对 Kubernetes 1.16 以上版本
  • 抉择创立 ACK 提供的规范专有集群(Dedicated cluster)
  • 保障集群节点能够拜访公网
  • 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-coscheduling
NAME: ack-coscheduling
LAST DEPLOYED: Mon Apr 13 16:03:57 2020
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None

验证 Coscheduling

在 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

通过 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 卡

  • 通过 Kubeflow 的 Arena 工具在已有 Kubernetes 集群中部署 Tensorflow 作业运行环境。

Arena 是基于 Kubernetes 的机器学习零碎开源社区 Kubeflow 中的子项目之一。Arena 用命令行和 SDK 的模式反对了机器学习工作的次要生命周期治理(包含环境装置,数据筹备,到模型开发,模型训练,模型预测等),无效晋升了数据科学家工作效率。

git clone https://github.com/kubeflow/arena.git
kubectl create ns arena-system
kubectl create -f arena/kubernetes-artifacts/jobmon/jobmon-role.yaml
kubectl create -f arena/kubernetes-artifacts/tf-operator/tf-crd.yaml
kubectl create -f arena/kubernetes-artifacts/tf-operator/tf-operator.yaml

查看是否部署胜利:

$ kubectl  get pods -n arena-system
NAME                                READY   STATUS    RESTARTS   AGE
tf-job-dashboard-56cf48874f-gwlhv   1/1     Running   0          54s
tf-job-operator-66494d88fd-snm9m    1/1     Running   0          54s
  • 用户向集群中提交 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
  • 当用户不应用 Coscheduling 性能时

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

$ kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
tf-smoke-gpu-ps-0       1/1     Running   0          6m43s
tf-smoke-gpu-worker-0   1/1     Running   0          6m43s
tf-smoke-gpu-worker-1   1/1     Running   0          6m43s
tf-smoke-gpu-worker-2   0/1     Pending   0          6m43s
tf-smoke-gpu-worker-3   0/1     Pending   0          6m43s

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

$ kubectl logs -f tf-smoke-gpu-worker-0
INFO|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:3
INFO|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
  • 当用户应用 Coscheduling 性能时

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

$ kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
tf-smoke-gpu-ps-0       0/1     Pending   0          43s
tf-smoke-gpu-worker-0   0/1     Pending   0          43s
tf-smoke-gpu-worker-1   0/1     Pending   0          43s
tf-smoke-gpu-worker-2   0/1     Pending   0          43s
tf-smoke-gpu-worker-3   0/1     Pending   0          43s

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

$ kubectl get pods
NAME                    READY   STATUS    RESTARTS   AGE
tf-smoke-gpu-ps-0       1/1     Running   0          3m16s
tf-smoke-gpu-worker-0   1/1     Running   0          3m16s
tf-smoke-gpu-worker-1   1/1     Running   0          3m16s
tf-smoke-gpu-worker-2   1/1     Running   0          3m16s
tf-smoke-gpu-worker-3   1/1     Running   0          3m16s

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

$ kubectl logs -f tf-smoke-gpu-worker-0
INFO|2020-05-19T07:15:24|/opt/launcher.py|27| Running warm up
INFO|2020-05-19T07:21:04|/opt/launcher.py|27| Done warm up
INFO|2020-05-19T07:21:04|/opt/launcher.py|27| Step    Img/sec    loss
INFO|2020-05-19T07:21:05|/opt/launcher.py|27| 1    images/sec: 31.6 +/- 0.0 (jitter = 0.0)    8.318
INFO|2020-05-19T07:21:15|/opt/launcher.py|27| 10    images/sec: 31.1 +/- 0.4 (jitter = 0.7)    8.343
INFO|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、数据计算类的批工作须要组合调度,同时缩小资源节约的问题。从而晋升集群整体资源利用率。

作者介绍:
王庆璨,阿里云技术专家,专一于大规模集群资源管理和调度。Kubernetes 社区成员,次要参加 Kube-scheduler 社区开发。目前负责阿里云容器服务 ACK 资源调度和云原生 AI 相干工作。

张凯,阿里云高级技术专家,从事容器服务 ACK 和云原生 AI 解决方案的研发和客户反对,以及相干畛域的开源建设。领有 10 余年大规模深度学习平台,云计算,SOA 等畛域教训。

“阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术畛域、聚焦云原生风行技术趋势、云原生大规模的落地实际,做最懂云原生开发者的公众号。”

正文完
 0