最近在支持CPU/GPU混布资源任务的时候,在我们自己开发的训练框架上,遇到volcano无法成功调度的情况,为此给volcano提了个issue。
最终发现是因为我们的kubeflow/common版本太低,导致没有给PodTemplate添加annotation volcano.sh/task-spec=replecaType
。
背景
我们的crd类似于pytorch/tensorflow,需要创建一个master和多个worker,其中master只用到CPU资源,worker需要GPU资源。
为了保证集群中的GPU资源能够最大化使用,我们往集群里添加了CPU机器。将master节点调度到CPU机器上,worker调度到GPU机器上。
避免因master调度到GPU机器,导致GPU机器的卡分不完(极端情况下,如果有多个任务,master可能占用某个GPU机器的大量CPU资源,导致GPU卡分不完)。
这里有两种方式实现: nodeSelector或者nodeAffinity。
nodeSelector
nodeSelector可以定义要匹配拥有哪些标签的node。
这里可以为CPU的node增加对应的标签,比如resType=CPU,并在master的PodSpec增加nodeSelector。
worker可以不需要nodeSelector,因为CPU机器没有GPU卡,肯定不满足资源需要。
下面借用tensorflow的crd作为例子:
apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
name: tf-test
spec:
tfReplicaSpecs:
Master:
replicas: 1
template:
spec:
containers:
- name: tensorflow
image: docker.io/kubeflowkatib/tf-mnist-with-summaries:latest
command:
- sh
- -c
- sleep 10
nodeSelector:
resType: CPU -- 此处增加nodeSelector
Worker:
replicas: 1
template:
spec:
containers:
- name: tensorflow
image: docker.io/kubeflowkatib/tf-mnist-with-summaries:latest
command:
- sh
- -c
- sleep 10
resources:
limits:
nvidia.com/gpu: "1"
requests:
nvidia.com/gpu: "1"
nodeAffinity
nodeAffinity的表述能力更强,可以对label做In/NotIn等操作。
对于GPU机器,因为用的是nvidia的卡,为保证nvidia-device-plugin/dcgm-exporter这些组件正常工作,会给GPU机器打上label: nvidia-device-enable=enable
。
所以可以通过nodeAffinity的NotIn来实现,也无需单独为CPU机器增加label。
apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
name: tf-test
spec:
tfReplicaSpecs:
Master:
replicas: 1
template:
spec:
affinity:
nodeAffinity: -- 此处增加nodeAffnitiy排除某些label的机器
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: nvidia-device-enable
operator: NotIn
values:
- enable
containers:
- name: tensorflow
image: docker.io/kubeflowkatib/tf-mnist-with-summaries:latest
command:
- sh
- -c
- sleep 10
Worker:
replicas: 1
template:
spec:
containers:
- name: tensorflow
image: docker.io/kubeflowkatib/tf-mnist-with-summaries:latest
command:
- sh
- -c
- sleep 10
resources:
limits:
nvidia.com/gpu: "1"
requests:
nvidia.com/gpu: "1"
问题表现
在为master增加nodeSelector/nodeAffinity之后,发现volcano一直无法选择出合适的node。具体的日志可以参考文章开头的issue。
分析volcano-scheduler的日志发现,master和worker会依次尝试分配node。顺序可能是master先分配,也可能worker先分配。分配过程中,可选的node集合会被不断缩小。
比如master先分配,那么worker可选的node就只能是满足master条件的那些node,而worker又有自己的一些条件,所以worker可选的node集合就变成master条件和worker条件的交集,在这里的场景下交集为空(master需要cpu机器,worker需要GPU机器),就无法调度起来。
源码阅读
volcano调度任务的时候,会参考worker和master对node节点的条件。这里的条件有几部分,分别对应predicates plugin的一些参数。
/********************************************************
* pkg/scheduler/plugins/predicates/predicates.go 155行 *
********************************************************/
predicate := predicateEnable{
nodeAffinityEnable: true, -- nodeAffinity/nodeSelector
nodePortEnable: true,
taintTolerationEnable: true,
podAffini