以下笔记是在工作中使用
capacity插件配置volcano的hierarchy队列,通过分析volcano源码,并且实践得到的经验。volcano源码版本为commit: 80eea1df4b922773c47ac4f8e483b48a3ccc7090,最新提交时间:Wed Aug 13 10:17:58 2025 +0800。
默认root队列问题
volcano默认情况下会给创建的一级队列设置默认的父级队列为root队列。volcano资源队列默认的root队列资源配额是集群的所有资源的总和(nodes allocatable),并且会随着节点变化或节点资源的变化而自动改变。- 当队列已分配的额度超过
root队列时,volcano调度器将不在继续工作,出现系统性故障。 - 具体请参考问题排查:Volcano层级队列配置引发的调度器系统性故障问题
层级队列的插件配置
层级队列的特性需要启用capacity插件,并且设置enableHierarchy: true。
apiVersion: v1
data:
volcano-scheduler.conf: |
actions: "enqueue, allocate, backfill"
tiers:
- plugins:
- name: priority
- name: gang
enablePreemptable: false
- name: conformance
- plugins:
- name: overcommit
- name: drf
enablePreemptable: false
- name: predicates
- name: capacity
enableHierarchy: true
- name: nodeorder
- name: binpack
kind: ConfigMap
metadata:
name: volcano-scheduler-configmap
namespace: volcano-system
capability、deserved与guarantee介绍
- 大部分使用场景下,不需要对队列设置
deserved,只需要设置guarantee和capability即可。 guarantee是给队列的预留资源量,不管队列用不用,volcano调度器都会为该队列预留guarantee的资源量。capability是队列的资源配额,当队列的资源使用量超过capability时,volcano调度器会拒绝该队列的资源申请。- 队列可以通过
guarantee预留某一种资源,并且通过capability限制对该资源的最大使用量。在队列的capability和guarantee中不设置其他资源的配额时,表示该队列对集群中的其他资源使用集群中空余的资源。test-queue-card.yamlapiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: test-queue-card
spec:
capability:
nvidia.com/gpu: 2
guarantee:
resource:
nvidia.com/gpu: 2
层级队列的guarantee与capability配置
-
Volcano Job或者Pod只能创建到队列的叶子节点上,否则任务无法创建成功,并且会引发调度器系统性无法工作。调度器日志中会出现类似如下的报错:E0826 09:01:29.031005 1 capacity.go:551] The Queue <test-queue> of Job <volcano-system/podgroup-b82f796a-6767-46b9-94ad-b94ae7f7b695> is not leaf queue -
当子级队列设置有
guarantee配额时,父级队列必须设置guarantee配额(root队列除外),并且父级队列的guarantee配额必须大于等于子级队列的guarantee配额。 -
当子级队列设置有
capability配额时,父级队列可以不设置capability配额,但如果父级队列设置了capability配额,那么父级队列的capability配额必须大于等于子级队列的capability配额。使用示例:
test-queue.yamlapiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: test-queue
spec:
guarantee:
resource:
cpu: 2
memory: 2Gitest-queue2.yamlapiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: test-queue2
spec:
parent: test-queue
capability:
cpu: 2
memory: 2Gi
guarantee:
resource:
cpu: 2
memory: 2Gi -
子级可以设置
capability和guarantee,表示对该队列预留了资源并且设置了资源上限。父级可以只设置guarantee,不需要设置capability。并且父级队列的guarantee配额必须大于等于所有子级队列的guarantee配额。使用示例:
层级关系:
test-queue
├── test-queue-card
└── test-queue-cputest-queue.yamlapiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: test-queue
spec:
guarantee:
resource:
cpu: 2
memory: 2Gi
nvidia.com/gpu: 2test-queue-card.yamlapiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: test-queue-card
spec:
capability:
nvidia.com/gpu: 2
guarantee:
resource:
nvidia.com/gpu: 2test-queue-cpu.yamlapiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: test-queue-cpu
spec:
capability:
cpu: 2
memory: 2Gi
guarantee:
resource:
cpu: 2
memory: 2Gi
Pod使用volcano队列
- 如果
Pod想要使用volcano队列:- 需要通过注解的形式注入队列名称,并且设置
Pod的schedulerName为volcano。 - 需要注意注解的键名为
scheduling.volcano.sh/queue-name而不是volcano.sh/queue-name。 - 队列的识别是由
volcano controller实现的,volcano controller只识别通过注解方式注入的队列名称,不支持标签注入队列名称。
- 需要通过注解的形式注入队列名称,并且设置
volcano controller会通过Informer机制监听所有Pod的创建,并判断schedulerName为volcano时才会纳入自身管理。volcano controller会为每个Pod创建对应的PodGroup,随后通过PodGroup的逻辑使用到volcano队列机制。- 通过
volcano job创建的Pod中,volcano会将队列名称注入到Pod的标签和注解中,键名为volcano.sh/queue-name。注意该键名和手动为Pod注入volcano队列注解的键名不同。
哪些状态的Pod会被计入队列使用量
-
volcano将Pod包装为了Task,同时扩展了Pod的状态,叫做TaskStatus。TaskStatus的枚举值如下:const (
// Pending means the task is pending in the apiserver.
Pending TaskStatus = 1 << iota
// Allocated means the scheduler assigns a host to it.
Allocated
// Pipelined means the scheduler assigns a host to wait for releasing resource.
Pipelined
// Binding means the scheduler send Bind request to apiserver.
Binding
// Bound means the task/Pod bounds to a host.
Bound
// Running means a task is running on the host.
Running
// Releasing means a task/pod is deleted.
Releasing
// Succeeded means that all containers in the pod have voluntarily terminated
// with a container exit code of 0, and the system is not going to restart any of these containers.
Succeeded
// Failed means that all containers in the pod have terminated, and at least one container has
// terminated in a failure (exited with a non-zero exit code or was stopped by the system).
Failed
// Unknown means the status of task/pod is unknown to the scheduler.
Unknown
) -
只有
Bound, Binding, Running, Allocated四种TaskStatus的Pod才会被计入队列使用量。关键的源码如下:// 根据Pod生成TaskStatus
func getTaskStatus(pod *v1.Pod) TaskStatus {
switch pod.Status.Phase {
case v1.PodRunning:
if pod.DeletionTimestamp != nil {
return Releasing
}
return Running
case v1.PodPending:
if pod.DeletionTimestamp != nil {
return Releasing
}
if len(pod.Spec.NodeName) == 0 {
return Pending
}
return Bound
case v1.PodUnknown:
return Unknown
case v1.PodSucceeded:
return Succeeded
case v1.PodFailed:
return Failed
}
return Unknown
}
// AllocatedStatus判断Pod是否计入队列使用量
func AllocatedStatus(status TaskStatus) bool {
switch status {
case Bound, Binding, Running, Allocated:
return true
default:
return false
}
} -
其中
Binding和Allocated的状态计算较复杂,如果需要粗略计算,可以只关注Bound和Running状态即可。
常见问题
多副本部署时,注意默认选主参数是false
scheduler和controller多副本部署时,注意默认选主参数是false,需要设置为true。
以下是默认的显式指定的选主启动参数:
--leader-elect=false
需要修改为:
--leader-elect=true
队列配额设置不合理
如:
- 父级队列有设置
guarantee配额,并且该配额值小于所有子级队列的guarantee配额值之和 - 父级队列有设置
capability配额,并且该配额值小于所有子级队列的capability配额值之和 - 这个时候可以查看
volcano调度器日志,并通过grep关键字than来查看是否出现该问题,以及出现配额设置不正确的队列名称
调度器系统性不工作
这个时候查看volcano调度器日志即可,默认每隔1秒钟就会执行Session的创建和关闭,会有大量的错误日志输出。
队列资源确实不够用,Pod无法调度
如:
- 其他队列使用了大量的
guarantee配置,占用了集群资源,导致其他队列没有配置guarantee时,无法调度Pod。 - 队列配置的
capability资源确实不够用了,导致无法调度Pod。- 这种场景的问题不是很好排查,
volcano调度器的日志不是很友好,只有自己编写工具排查并确定资源问题。
- 这种场景的问题不是很好排查,
连续创建层级队列WebHook报错,子级队列创建时找不到父级
先创建父级队列xxx,再创建子级队列yyy,有可能会触发volcano的webhook的以下报错:
admission webhook "validatequeue.volcano.sh" denied the request: failed to get parent queue of queue yyy: queue.scheduling.volcano.sh "xxx" not found
既然父级队列创建接口调用成功,那么表示数据肯定是已经落到了etcd中。
通过查看volcano的webhook的源码,位置:https://github.com/volcano-sh/volcano/blob/ad7386527362627d2817d0867c8fb5c2e20a787c/pkg/webhooks/admission/queues/validate/validate_queue.go#L278 发现是由于webhook是通过informer机制来查找队列信息的,在连续创建队列时,webhook中的indexer内存数据还没有更新,导致找不到数据。
这里在业务程序中使用retry机制来保证接口调用成功,参考源码:
var (
unstructuredQueue = &unstructured.Unstructured{Object: unstructuredObj}
resource = schedulingv1beta1.SchemeGroupVersion.WithResource("queues")
)
// 连续创建队列可能会引发volcano webhook报错,例如:
// admission webhook "validatequeue.volcano.sh" denied the request:
// failed to get parent queue of queue yyy: queue.scheduling.volcano.sh "xxx" not found
// 原因是webhook那边的校验使用的是informer机制查看的队列信息,webhook可能还未来得及更新本地内存数据。
// 因此这里直接重试机制来保障创建成功
for i := 0; i <= 3; i++ {
retryMark := ""
if i > 0 {
retryMark = fmt.Sprintf(`, retry %d`, i)
}
logger.Infof(ctx, `create queue "%s"%s`, req.Name, retryMark)
_, err = q.client.Resource(resource).Create(ctx, unstructuredQueue, metav1.CreateOptions{})
if err == nil {
break
}
// 由于webhook的报错可能不会带有NotFound的错误码,因此这里同时通过错误字符串关键字匹配
if k8serr.IsNotFound(err) || strings.Contains(err.Error(), "not found") {
time.Sleep(time.Millisecond * 500 * time.Duration(i+1))
} else {
break
}
}