跳到主要内容
版本:v1.9 🚧

Koord-Queue

介绍​

Koord-Queue 是 Koordinator 生态系统的原生 Kubernetes 作业队列管理系统。它管理多个队列间的作业准入和排序,与 Koordinator 的 ElasticQuota 深度集成,实现资源公平性和多租户隔离。主要功能包括:

  • 多队列管理,支持 Priority、Block 和 Intelligent 排队策略。
  • 深度 ElasticQuota 集成,避免重复配额配置,实现弹性资源共享。
  • 预调度,通过在作业创建 Pod 之前排队来减少调度器压力。
  • 多框架支持,包括 TFJob、PyTorchJob、Spark、Argo Workflow、Ray 和原生 Kubernetes Job。
  • 准入检查框架:队列可以要求作业在出队之前通过外部检查。

配置​

前置要求​

  • Kubernetes >= 1.22
  • Koordinator >= 1.5(用于 ElasticQuota 集成)

安装​

使用 Helm 安装 Koord-Queue:

# 方式一:从 Helm 仓库安装
helm repo add koordinator-sh https://koordinator-sh.github.io/charts/
helm install koord-queue koordinator-sh/koord-queue --version 1.8.0 \
--namespace koord-queue \
--create-namespace


验证安装:

$ kubectl get deployment -n koord-queue
NAME READY UP-TO-DATE AVAILABLE AGE
koord-queue-controllers 1/1 1 1 30s
koord-queue 1/1 1 1 30s

$ kubectl get crd | grep scheduling.x-k8s.io
queues.scheduling.x-k8s.io 2024-01-01T00:00:00Z
queueunits.scheduling.x-k8s.io 2024-01-01T00:00:00Z

配置项​

Koord-Queue 默认使用 ElasticQuotaV2 模式。

默认配置​

# 镜像仓库(默认:阿里云北京)
global:
imagePrefix: registry.cn-beijing.aliyuncs.com

controller:
image:
repository: koordinator-sh/koord-queue
tag: v1.8.0

extension:
koord-queue-controllers:
repository: koordinator-sh/koord-queue-controllers
tag: v1.8.0
batchjob:
enable: true # 原生 Kubernetes Job 支持
tf:
enable: false
pytorch:
enable: false
argo:
enable: false
spark:
enable: false
ray:
enable: false
mpi:
enable: false

pluginConfigs:
apiVersion: scheduling.k8s.io/v1
kind: KoordQueueConfiguration
plugins:
- name: Priority
- name: ElasticQuotaV2

ElasticQuotaV2 模式(默认)​

使用独立的 ElasticQuota CR(scheduling.sigs.k8s.io/v1alpha1)。这是 Koordinator 用户的推荐模式。

使用 Koord-Queue​

使用 ElasticQuota 快速开始​

此示例使用 Koordinator 的 ElasticQuota 进行弹性资源管理。

创建 ElasticQuota​

apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: ElasticQuota
metadata:
name: team-a
namespace: default
labels:
quota.scheduling.koordinator.sh/parent: ""
quota.scheduling.koordinator.sh/is-parent: "false"
spec:
max:
cpu: "4"
memory: 8Gi
min:
cpu: "4"
memory: 8Gi
$ kubectl apply -f elastic-quota.yaml
Queue 自动创建​

使用 ElasticQuotaV2 时,插件会为每个 ElasticQuota 资源自动创建 Queue CR,位于 koord-queue 命名空间中。自动创建的 Queue 与 ElasticQuota 同名(例如 team-a),默认 priority: 1000、queuePolicy: Priority。无需为每个 ElasticQuota 手动创建 Queue。

如需自定义 Queue 策略,可在 ElasticQuota 上设置 koord-queue/queue-policy 标签:

metadata:
labels:
koord-queue/queue-policy: Priority # 选项:Priority、Block、Intelligent
提交作业并验证排队行为​

Koord-Queue 的 Job Extensions 会自动为提交的作业创建 QueueUnit 资源。要提交一个由 Koord-Queue 管理的 Kubernetes Job,需设置 spec.suspend: true 并添加配额标签。将以下双文档 YAML 保存为 jobs.yaml 并一次性提交:

apiVersion: batch/v1
kind: Job
metadata:
name: my-job
namespace: default
labels:
quota.scheduling.koordinator.sh/name: team-a
spec:
suspend: true
template:
spec:
containers:
- name: test
image: busybox:stable
command: ["/bin/sh", "-c", "sleep 30"]
resources:
requests:
cpu: "4"
memory: 8Gi
limits:
cpu: "4"
memory: 8Gi
restartPolicy: Never
---
apiVersion: batch/v1
kind: Job
metadata:
name: my-job-blocked
namespace: default
labels:
quota.scheduling.koordinator.sh/name: team-a
spec:
suspend: true
template:
spec:
containers:
- name: test
image: busybox:stable
command: ["/bin/sh", "-c", "sleep 30"]
resources:
requests:
cpu: "4"
memory: 8Gi
limits:
cpu: "4"
memory: 8Gi
restartPolicy: Never
$ kubectl apply -f jobs.yaml

team-a 配额的 max.cpu 为 "4"、max.memory 为 "8Gi",正好供一个作业使用。ElasticQuotaV2 插件基于 Pod 实际运行时的资源消耗来统计配额用量。当 my-job 的 Pod 进入 Running 状态并占满配额后,my-job-blocked 将被阻塞在队列中:

# 先等待 my-job 的 Pod 进入 Running 状态
$ kubectl wait --for=condition=Ready pod -l job-name=my-job -n default --timeout=120s

$ kubectl get queueunit my-job-blocked -n default
NAME PHASE PRIORITY ADMISSIONS JOBTYPE
my-job-blocked Job

QueueUnit 保持 Enqueued 状态,因为 team-a 已达到 max 配额上限。当 my-job 完成并释放资源后,my-job-blocked 将自动出队执行。

不同作业类型的暂停方式​

不同类型的作业使用不同的字段进行暂停:

每种作业框架由运行在 koord-queue-controllers 中的一个作业扩展负责。只有当扩展名被传入该 Deployment 的 --enabled-extensions 参数时,对应扩展才会生效;Chart 会依据 extension.*.enable 取值生成该参数。

作业类型API 版本扩展名Helm 取值作业的挂起方式被接管的条件
Kubernetes Jobbatch/v1jobextension.batchjob.enablespec.suspendspec.suspend: true 且 status.startTime 为空
TFJobkubeflow.org/v1tfjobextension.tf.enablespec.runPolicy.suspend 与注解 scheduling.x-k8s.io/suspend上述任一挂起形式,且 status.startTime 为空;存在 Queuing 条件时同样接管
PyTorchJobkubeflow.org/v1pytorchjobextension.pytorch.enablespec.runPolicy.suspend 与注解 scheduling.x-k8s.io/suspend上述任一挂起形式,且 status.startTime 为空;存在 Queuing 条件时同样接管
RayJobray.io/v1 与 ray.io/v1alpha1rayjob、rayjobv1alpha1extension.ray.enablespec.suspendspec.suspend: true 且 status.startTime 为空
RayClusterray.io/v1raycluster仅有 RBAC,未启用spec.suspendspec.suspend: true;由 RayJob 拥有、或带有标签 ray.io/originated-from-crd: RayCluster 的集群永不被接管
SparkApplicationsparkoperator.k8s.io/v1beta2sparkappextension.spark.enable注解 scheduling.x-k8s.io/suspend该注解取值为 "true" 且 status.appState.state 为空
Argo Workflowargoproj.io/v1alpha1workflowextension.argo.enable名为 koord-queue-suspend 的模板,或 spec.suspendspec.templates 或 status.storedTemplates 中存在带 suspend 字段的 koord-queue-suspend 模板

另有两点需要说明:

  • values.yaml 中存在 extension.mpi.enable,但没有任何模板引用它,代码中也未注册 MPI 扩展,因此发布的 Chart 不会对 MPIJob 排队;XGBoostJob 与 PaddleJob 同样不受支持。
  • raycluster 扩展已在代码中注册,Chart 也授予了访问 RayCluster 所需的 RBAC,但不会将 raycluster 加入 --enabled-extensions,因此对 RayCluster 排队需要自行定制 koord-queue-controllers 的部署。

koord-queue-controllers 的参数 --manage-all-jobs 会使扩展接管已启用类型的全部作业,包括未以挂起状态提交的 作业。该参数未由 Chart 暴露。

Argo Workflow 示例:

对于 Argo Workflow,Koord-Queue 使用名为 koord-queue-suspend 的特殊暂停模板。工作流必须满足以下条件才能被队列管理:

  1. 包含名为 koord-queue-suspend 的模板,且有 suspend 字段
  2. 工作流有处于 Running 状态的 suspend 节点,或者 spec.suspend 设置为 true
apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
name: my-workflow
annotations:
koord-queue/min-resources: |
cpu: 5
memory: 5Gi
spec:
suspend: true
templates:
# 添加此暂停模板用于队列管理
- name: koord-queue-suspend
suspend: {}
# 你的实际工作流模板
- name: main
container:
image: python:3.9
command: [python, -c, "print('Hello from workflow')"]
entrypoint: main

工作原理:

当提交工作流时,Koord-Queue 通过以下方式检查是否应该管理它:

  • 扫描所有模板,查找带有 suspend 字段的 koord-queue-suspend 模板
  • 检查是否有任何工作流节点类型为 Suspend 且状态为 Running
  • 或者检查 spec.suspend 是否设置为 true

当 QueueUnit 出队时,Extension Server 将移除暂停条件,允许工作流继续执行。

TFJob 示例:

对于 TFJob,设置 spec.runPolicy.suspend: true 启用队列管理:

apiVersion: kubeflow.org/v1
kind: TFJob
metadata:
labels:
quota.scheduling.koordinator.sh/name: team-a-queue
spec:
runPolicy:
suspend: true

PyTorchJob 示例:

对于 PyTorchJob,设置 spec.runPolicy.suspend: true 启用队列管理:

apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
labels:
quota.scheduling.koordinator.sh/name: team-a-queue
spec:
runPolicy:
suspend: true

Kubeflow Jobs 的工作原理:

当提交 TFJob 或 PyTorchJob 时:

  1. Job extension 检测到带有 spec.runPolicy.suspend: true 的新作业
  2. 自动创建对应的 QueueUnit
  3. 作业在队列中等待直到资源可用
  4. 出队时,Extension Server 设置 spec.runPolicy.suspend: false,允许作业创建 Pod 并开始训练

使用 Queue​

Queue Spec​

字段类型描述
queuePolicystring排队策略:Priority、Block 或 Intelligent。
priority*int32用于多队列排序的队列优先级。
priorityClassNamestringKubernetes PriorityClass 名称。
admissionChecks[]AdmissionCheckWithSelector所需的准入检查列表。

queuePolicy 可取 Priority、Block 与 Intelligent。代码中还存在常量 FIFO 与 Round,但它们未在队列 工厂中注册,请求这两种策略的队列会构造失败。各策略的行为、调优注解以及策略变更时的运行时行为,详见 排队策略与调优。

Queue 优先级​

默认情况下,Koord-Queue 的 Job Extensions 会自动从作业的 Pod 模板中派生 QueueUnit 的优先级:它读取 spec.template.spec.priorityClassName 和 spec.template.spec.priority。如果找到 PriorityClass 对象,则使用其 .value 作为 QueueUnit 的优先级;否则使用 spec.template.spec.priority 中的原始整数值。

你也可以在 QueueUnit 创建后手动修改其 spec.priority 来覆盖默认值,从而影响出队顺序。

Priority Queue​

优先级值更高的作业优先出队。优先级相同的作业中,较早创建的作业优先出队:

设置 QueueUnit 的优先级:

apiVersion: scheduling.x-k8s.io/v1alpha1
kind: QueueUnit
metadata:
name: high-priority-job
namespace: default
spec:
queue: priority-queue
priority: 200
consumerRef:
apiVersion: batch/v1
kind: Job
name: important-job
namespace: default
resource:
cpu: "2"
memory: 4Gi

队列策略​

Koord-Queue 支持三种队列策略来控制作业的出队和调度。

Priority 策略(优先级策略)​

排序规则:队列单元按优先级值(降序)排序,然后按创建时间戳(升序)排序。优先级高的作业总是优先出队。相同优先级的作业按 FIFO 顺序处理。

行为特点:

  • 优先级值更高的作业优先出队
  • 相同优先级时,较早创建的作业先调度
  • 失败的作业会被重新加入队列并重试
  • 支持抢占:低优先级作业可以被抢占,为高优先级作业腾出空间

调度行为: Priority 策略不是严格的优先级调度。当高优先级作业被阻塞(如配额耗尽)时,调度器会跳过它们继续扫描。可调度的低优先级作业可以在被阻塞的高优先级作业之前出队。这提高了吞吐量并防止调度器停滞。

与 Block 策略的关键区别:

  • Priority:乐观调度 - 配额接近限制时仍继续调度,阻塞的作业会被跳过
  • Block:保守调度 - 配额达到限制时严格阻塞作业

适用场景:

  • 多租户环境,不同优先级级别
  • 生产作业应该抢占开发作业

配置示例:

apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: ElasticQuota
metadata:
name: priority-queue
labels:
koord-queue/queue-policy: Priority
spec:
max:
cpu: "10"
memory: 20Gi

Block 策略(阻塞策略)​

排序规则:与 Priority 策略相同 - 按优先级(降序)然后时间戳(升序)排序。

行为特点:

  • 严格资源阻塞:当配额达到限制时,使用该配额的后续作业会被阻塞
  • 与 Priority 策略(会跳过阻塞的高优先级作业,让后面的可调度作业先出队)不同,Block 策略严格执行优先级顺序
  • 防止资源过度分配
  • 被阻塞的队列单元在调度时会被跳过,直到资源可用

关键区别:

  • Priority 策略:不是严格的优先级调度 - 允许低优先级作业在被阻塞的高优先级作业之前出队
  • Block 策略:严格的优先级调度 - 被阻塞的高优先级作业必须等待,防止低优先级作业绕过它们

适用场景:

  • 资源受限的环境
  • 需要保证资源可用性的生产工作负载
  • 需要严格执行资源限制的多租户隔离场景

配置示例:

apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: ElasticQuota
metadata:
name: block-queue
labels:
koord-queue/queue-policy: Block
spec:
max:
cpu: "10"
memory: 20Gi

Intelligent 策略(智能策略)​

排序规则:使用 双队列机制,配置优先级阈值(默认:4):

  • 高优先级队列:优先级 >= 阈值的作业
    • 按优先级(降序)然后时间戳(升序)排序
    • 重试行为:失败时,重试同一作业(FIFO 模式)
  • 低优先级队列:优先级 < 阈值的作业
    • 按优先级(降序)然后时间戳(升序)排序
    • 重试行为:失败时,移动到下一作业(Round-Robin 模式)

行为特点:

  • 优先处理高优先级作业:高优先级队列总是先被检查
  • 高优先级作业获得重试保证:失败的高优先级作业立即重试
  • 低优先级作业使用轮询:失败的低优先级作业让出给下一个作业
  • 可通过 annotation 配置阈值:koord-queue/priority-threshold

适用场景:

  • 混合负载,既有关键作业又有批量作业
  • 需要既执行优先级又保证公平调度的环境

配置示例:

apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: ElasticQuota
metadata:
name: intelligent-queue
labels:
koord-queue/queue-policy: Intelligent
annotations:
koord-queue/priority-threshold: "5"
spec:
max:
cpu: "10"
memory: 20Gi

策略对比​

特性PriorityBlockIntelligent
排序规则优先级 + 时间戳优先级 + 时间戳双队列:高优先级(FIFO)+ 低优先级(Round-Robin)
重试行为重试失败作业重试失败作业高:重试同一作业;低:移动到下一作业
资源阻塞乐观严格/保守平衡
抢占支持是是否
适用场景优先级调度严格资源隔离混合关键 + 批量工作负载

配置队列策略​

可以通过两种方式设置队列策略:

  1. 通过 ElasticQuota 标签(推荐):
metadata:
labels:
koord-queue/queue-policy: Priority # 选项:Priority、Block、Intelligent
  1. 通过 Queue CR(高级配置):
apiVersion: scheduling.x-k8s.io/v1alpha1
kind: Queue
metadata:
name: my-queue
namespace: koord-queue
annotations:
koord-queue/priority-threshold: "5"
spec:
queuePolicy: Intelligent
priority: 1000

高级调优 Annotations:完整列表以及各策略不予评估的配置项见排队策略与调优:

  • koord-queue/priority-threshold:设置 Intelligent 策略的阈值(默认:4)
  • koord-queue/max-depth:限制调度时考虑的作业数量(仅 Priority 与 Block)
  • koord-queue/wait-for-pods-running:等待已准入作业的 Pod 进入 Running 状态后再出队下一个作业(仅 Priority 与 Block)

使用 QueueUnit​

QueueUnit Spec​

字段类型描述
consumerRefObjectReference原始作业 CR 的引用。
priority*int32队列内的优先级。
queuestring目标队列名称。
resourceResourceList总资源需求。
podSet[]PodSetPod 组定义(最多 8 个)。
priorityClassNamestringKubernetes PriorityClass 名称。
requestResourceList从作业解析的实际资源请求。
active*bool该单元是否可被准入,未设置时按 true 处理。需要 QueueUnitActive 特性开关,以及高于 v1.8.0 Chart 的 QueueUnit CRD。
maximumExecutionTimeSeconds*int32作业的执行时长预算,自其 Pod 运行时开始计时。需要 MaximumExecutionTime 特性开关与更高的 CRD。

上述两个字段的详细说明与可用性约束见工作负载生命周期控制。

QueueUnit Status​

字段类型描述
phaseQueueUnitPhase当前生命周期阶段。
attemptsint64调度尝试次数。
messagestring人类可读的状态消息。
lastUpdateTimeTime上次状态更新时间戳。
admissionChecks[]AdmissionCheckState每个准入检查的状态。
podStatePodStateRunning/Pending Pod 计数。
admissions[]Admission每个 PodSet 准入的资源分配与状态:replicas、running、resources,以及该单元被选为抢占受害者时的 reclaimState。
lastAllocateTimeTime最近一次配额分配的时间戳,是回收保护期的起点。
conditions[]metav1.Conditionphase 的标准 Condition 表示。需要高于 v1.8.0 Chart 的 QueueUnit CRD。
requeueStateRequeueState退避记账:count 与 requeueAt。需要 QueueUnitRequeueState 特性开关与更高的 CRD。
reclaimablePods[]ReclaimablePod各 PodSet 中不再需要预留配额的 Pod 数量。需要更高的 CRD。
accumulatedPastExecutionTimeSeconds*int32此前若干次“准入—驱逐”周期中已消耗的执行时长。需要更高的 CRD。

使用 AdmissionCheck​

准入检查​

Queue 可以要求在 QueueUnit 释放之前必须通过准入检查。这对于与外部资源供应系统集成非常有用。

Koord-Queue 提供该机制的队列侧能力:Queue API 的 admissionChecks 字段、QueueUnit 的 status.admissionChecks 状态机、AdmissionCheck 与 ProvisioningRequestConfig 两个 CRD, 以及读写它们所需的 RBAC。而决定检查结果的组件(例如 ProvisioningRequest 控制器)不属于 Koord-Queue,需要单独部署,它是 status.admissionChecks 的写入方。

apiVersion: scheduling.x-k8s.io/v1alpha1
kind: Queue
metadata:
name: checked-queue
namespace: koord-queue
spec:
queuePolicy: Priority
admissionChecks:
- name: prov-req-check
labelSelector:
matchLabels:
requires-provisioning: "true"

当 QueueUnit 被预留时,其所属队列要求的检查会被复制到 status.admissionChecks,单元停留在 Reserved 阶段等待;只有当全部检查报告 Ready 时,它才会转换为 Dequeued。若某项检查报告 Retry 或 Rejected, 或检查超时,单元会进入 TimeoutBackoff 阶段,其预留被释放并重新入队。驱动该状态机的协程数量由参数 --admissionCheckControllerWorker 设置,默认为 2。

可观测性​

Koord-Queue 在 koord-queue Deployment 的 10259 端口暴露 Prometheus 指标,提供用于查询队列内容的 Visibility 聚合 API,并可选提供调试 HTTP API。Chart 不会为指标端口创建 Service,因此采集需直接指向 Pod:

# 转发控制器的指标端口
$ kubectl -n koord-queue port-forward deployment/koord-queue 10259:10259

# 获取指标
$ curl -s http://127.0.0.1:10259/metrics | head

当 controller.enableVisibilityServer=true 时,Chart 会创建 Service koord-queue-visibility-server 与 APIService v1alpha1.visibility.koord-queue.x-k8s.io,此时可通过 APIServer 查询队列或配额的内容:

$ kubectl get --raw "/apis/visibility.koord-queue.x-k8s.io/v1alpha1/queues/team-a/queueunits" | jq .

调试​

通过作业的 QueueUnit 查看调度决策,并查看其上记录的事件:

$ kubectl get queueunit <name> -n <namespace> -o yaml

$ kubectl get events -n <namespace> --field-selector involvedObject.name=<name> \
-o custom-columns='LAST:.lastTimestamp,TYPE:.type,REASON:.reason,MESSAGE:.message'

其中与调度结果相关的事件是 Scheduled 与 FailedScheduling,与抢占相关的是 Preempted 与 Reclaimed; 当由单元标签推导出的队列名没有对应的 Queue 时记录 QueueNotFound。完全未带配额标签的作业不会产生事件, 详见 ElasticQuota 与 Queue 的映射关系。控制器日志通过如下命令查看:

$ kubectl -n koord-queue logs deployment/koord-queue -c controller --tail=200 -f
$ kubectl -n koord-queue logs deployment/koord-queue-controllers -c manager --tail=200 -f

指标清单、项目自带的 Grafana 大盘、Visibility 与调试 API 的接口以及日志级别,详见 Koord-Queue 可观测性。

相关文档​