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 Job | batch/v1 | job | extension.batchjob.enable | spec.suspend | spec.suspend: true 且 status.startTime 为空 |
| TFJob | kubeflow.org/v1 | tfjob | extension.tf.enable | spec.runPolicy.suspend 与注解 scheduling.x-k8s.io/suspend | 上述任一挂起形式,且 status.startTime 为空;存在 Queuing 条件时同样接管 |
| PyTorchJob | kubeflow.org/v1 | pytorchjob | extension.pytorch.enable | spec.runPolicy.suspend 与注解 scheduling.x-k8s.io/suspend | 上述任一挂起形式,且 status.startTime 为空;存在 Queuing 条件时同样接管 |
| RayJob | ray.io/v1 与 ray.io/v1alpha1 | rayjob、rayjobv1alpha1 | extension.ray.enable | spec.suspend | spec.suspend: true 且 status.startTime 为空 |
| RayCluster | ray.io/v1 | raycluster | 仅有 RBAC,未启用 | spec.suspend | spec.suspend: true;由 RayJob 拥有、或带有标签 ray.io/originated-from-crd: RayCluster 的集群永不被接管 |
| SparkApplication | sparkoperator.k8s.io/v1beta2 | sparkapp | extension.spark.enable | 注解 scheduling.x-k8s.io/suspend | 该注解取值为 "true" 且 status.appState.state 为空 |
| Argo Workflow | argoproj.io/v1alpha1 | workflow | extension.argo.enable | 名为 koord-queue-suspend 的模板,或 spec.suspend | spec.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 的特殊暂停模板。工作流必须满足以下条件才能被队列管理:
- 包含名为
koord-queue-suspend的模板,且有suspend字段 - 工作流有处于 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 时:
- Job extension 检测到带有
spec.runPolicy.suspend: true的新作业 - 自动创建对应的
QueueUnit - 作业在队列中等待直到资源可用
- 出队时,Extension Server 设置
spec.runPolicy.suspend: false,允许作业创建 Pod 并开始训练
使用 Queue
Queue Spec
| 字段 | 类型 | 描述 |
|---|---|---|
queuePolicy | string | 排队策略:Priority、Block 或 Intelligent。 |
priority | *int32 | 用于多队列排序的队列优先级。 |
priorityClassName | string | Kubernetes 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
策略对比
| 特性 | Priority | Block | Intelligent |
|---|---|---|---|
| 排序规则 | 优先级 + 时间戳 | 优先级 + 时间戳 | 双队列:高优先级(FIFO)+ 低优先级(Round-Robin) |
| 重试行为 | 重试失败作业 | 重试失败作业 | 高:重试同一作业;低:移动到下一作业 |
| 资源阻塞 | 乐观 | 严格/保守 | 平衡 |
| 抢占支持 | 是 | 是 | 否 |
| 适用场景 | 优先级调度 | 严格资源隔离 | 混合关键 + 批量工作负载 |
配置队列策略
可以通过两种方式设置队列策略:
- 通过 ElasticQuota 标签(推荐):
metadata:
labels:
koord-queue/queue-policy: Priority # 选项:Priority、Block、Intelligent
- 通过 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
| 字段 | 类型 | 描述 |
|---|---|---|
consumerRef | ObjectReference | 原始作业 CR 的引用。 |
priority | *int32 | 队列内的优先级。 |
queue | string | 目标队列名称。 |
resource | ResourceList | 总资源需求。 |
podSet | []PodSet | Pod 组定义(最多 8 个)。 |
priorityClassName | string | Kubernetes PriorityClass 名称。 |
request | ResourceList | 从作业解析的实际资源请求。 |
active | *bool | 该单元是否可被准入,未设置时按 true 处理。需要 QueueUnitActive 特性开关,以及高于 v1.8.0 Chart 的 QueueUnit CRD。 |
maximumExecutionTimeSeconds | *int32 | 作业的执行时长预算,自其 Pod 运行时开始计时。需要 MaximumExecutionTime 特性开关与更高的 CRD。 |
上述两个字段的详细说明与可用性约束见工作负载生命周期控制。
QueueUnit Status
| 字段 | 类型 | 描述 |
|---|---|---|
phase | QueueUnitPhase | 当前生命周期阶段。 |
attempts | int64 | 调度尝试次数。 |
message | string | 人类可读的状态消息。 |
lastUpdateTime | Time | 上次状态更新时间戳。 |
admissionChecks | []AdmissionCheckState | 每个准入检查的状态。 |
podState | PodState | Running/Pending Pod 计数。 |
admissions | []Admission | 每个 PodSet 准入的资源分配与状态:replicas、running、resources,以及该单元被选为抢占受害者时的 reclaimState。 |
lastAllocateTime | Time | 最近一次配额分配的时间戳,是回收保护期的起点。 |
conditions | []metav1.Condition | phase 的标准 Condition 表示。需要高于 v1.8.0 Chart 的 QueueUnit CRD。 |
requeueState | RequeueState | 退避记账: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 可观测性。
相关文档
- ElasticQuota 与 Queue 的映射关系:作业如何关联到配额与队列。
- 排队策略与调优:策略行为、调优注解与不生效的配置项。
- 队列级抢占:从低优先级作业回收配额。
- 工作负载生命周期控制:暂停作业、限定执行时长、Condition 与退避。
- Koord-Queue 可观测性:指标、大盘、Visibility API 与事件。
- Koord-Queue 设计:架构与核心概念。