GitLab Runner 基于 Kubernetes 调度任务优先级管理技术方案
1. 背景与问题描述
1.1 现状
- 使用 GitLab Runner + Kubernetes Executor 构建 CI/CD 流水线
- 集群资源:14 个固定节点 + 60 个弹性节点(弹性节点按需伸缩)
- 任务并发高,资源经常被占满,大量 Pod 处于 Pending 状态
1.2 问题现象
当资源释放后,Kubernetes 默认调度器倾向于选择"资源请求最匹配"的 Pod 来调度,而非按照 Pod 创建时间(Pending 时长)顺序调度。这导致:
- 新创建的 CI 任务更可能"插队"获得资源
- 一开始就进入 Pending 的老任务长期无法运行
- Merge Request 等待时间过长,严重阻碍迭代效率
1.3 目标
- 实现先进入 Pending 的任务优先获得资源(FIFO 队列化)
- 支持动态调整某个特定任务的优先级,允许紧急任务"插队"执行
- 方案需具备可落地性、可维护性
2. 方案选型对比
| 方案 | 描述 | 优点 | 缺点 | 适用性 |
|---|---|---|---|---|
| A. Kubernetes PriorityClass | 通过 Pod 优先级影响调度顺序 | 原生支持、配置简单 | 优先级静态,无法按等待时间动态调整;无法保证严格 FIFO | 仅适合区分高低优先级,无法解决"老任务饿死"问题 |
| B. 自定义调度器 | 开发自定义调度器,实现按等待时间排序的逻辑 | 灵活性极高 | 开发运维成本巨大 | 不推荐,除非有专职团队 |
| C. Kueue | Kubernetes 社区官方作业队列管理系统 | 原生支持 FIFO,提供 Workload 优先级动态调整能力,与 GitLab Runner 集成简单 | 需额外部署组件 | 最佳选择,满足所有需求 |
| D. Volcano | 高性能批处理调度器 | 功能丰富,支持 FIFO | 比 Kueue 更重,学习曲线陡峭 | 适用于 AI/大数据场景,CI/CD 场景过于"重型" |
结论
选择方案 C:Kueue,理由如下:
- 社区官方项目(sig-autoscaling),持续维护
- 开箱即用的 FIFO 队列策略
- Workload 优先级支持动态修改,可随时调整 Pending 任务的优先级
- 轻量级,与 Kubernetes 原生资源(Job、Pod)自然集成
3. 最终方案:Kueue 作业队列管理
3.1 架构概览
1GitLab Runner → 创建 Kubernetes Job → Kueue 捕获并放入队列
2 ↓
3 ClusterQueue(资源池)
4 ↓
5 按优先级 + FIFO 顺序准入(Admission)
6 ↓
7 Pod 被调度到节点执行
3.2 核心组件
- ClusterQueue:集群资源池(如 CPU、内存总量),定义可用的资源上限
- LocalQueue:命名空间内的队列,关联到一个 ClusterQueue,用于任务排队
- Workload:Kueue 为每个 Job 生成的内部对象,包含优先级、排队状态等
- Admission:准入控制,决定哪个 Workload 可以获取资源并创建 Pod
3.3 工作流程
- GitLab Runner 创建一个 Kubernetes Job(由 config.toml 配置)。
- 若 Job 被标记为
suspend: true(Kueue 默认会处理),Kueue 将其转换为 Workload 放入队列。 - Workload 在 ClusterQueue 中按优先级(数值从高到低)和创建时间(FIFO)排序。
- 当资源可用时,队首的 Workload 被准入,其关联的 Job 被置为
suspend: false,进而创建 Pod 并调度执行。 - 支持手动修改 Workload 的优先级,改变其在队列中的顺序。
4. 实操步骤
4.1 环境准备
- Kubernetes 集群(版本 ≥ 1.27)
- 已安装 GitLab Runner(Kubernetes Executor)
- 具有集群管理员权限
4.2 安装 Kueue
4.2.1 通过 Helm 安装
1# 添加 Kueue Helm 仓库
2helm repo add kueue https://kubernetes-sigs.github.io/kueue/
3helm repo update
4
5# 安装 Kueue(默认配置)
6helm install kueue kueue/kueue --namespace kueue-system --create-namespace
4.2.2 验证安装
1kubectl get pods -n kueue-system
2# 应看到 kueue-controller-manager-xxx 运行中
4.3 配置资源队列与 nominalQuota 策略
节点规模:14 个固定节点 + 60 个弹性节点,假设单节点规格为 8 核 CPU,32Gi 内存。
- 固定节点总资源:112 核 CPU,448Gi 内存
- 弹性节点总资源:480 核 CPU,1920Gi 内存
- 集群总资源上限:592 核 CPU,2368Gi 内存
4.3.1 nominalQuota 设置策略
nominalQuota 是 Kueue 中表示"保底资源量"的字段,其值取决于管理策略。根据以上规模,有三种推荐策略:
| 策略 | nominalQuota 设置 | 说明 | 适用场景 |
|---|---|---|---|
| 策略一:保底策略 | 固定节点总资源(112核/448Gi) | 仅承诺固定节点的资源,弹性节点按需扩容 | 成本优先,容忍弹性扩容延迟 |
| 策略二:激进策略 | 集群总资源上限(592核/2368Gi) | 视为可用上限,任务准入即触发弹性扩容 | 性能优先,可接受较高云成本 |
| 策略三:Cohort 动态借用(推荐) | 固定节点队列:112核/448Gi;弹性节点队列:0核/0Gi(borrowingLimit=480核/1920Gi) | 分队列管理,固定节点用满后自动借用弹性节点额度 | 兼顾成本与效率,推荐生产环境使用 |
建议:长期推荐策略三(Cohort 机制),这是 Kueue 官方推荐的最佳实践,能实现资源的动态调配和自动回收。
4.4 创建 ClusterQueue(以策略三为例)
4.4.1 创建固定节点 ClusterQueue
1apiVersion: kueue.x-k8s.io/v1beta1
2kind: ClusterQueue
3metadata:
4 name: fixed-cq
5spec:
6 namespaceSelector: {}
7 resourceGroups:
8 - coveredResources: ["cpu", "memory"]
9 flavors:
10 - name: default-flavor
11 resources:
12 - name: cpu
13 nominalQuota: 112 # 14节点 × 8核
14 - name: memory
15 nominalQuota: 448Gi # 14节点 × 32Gi
16 cohort: ci-cohort # 关联到同一 Cohort
17 preemption:
18 reclaimWithinCohort: Any
19 withinClusterQueue: LowerPriorityFirst
4.4.2 创建弹性节点 ClusterQueue(borrowingLimit 方式)
1apiVersion: kueue.x-k8s.io/v1beta1
2kind: ClusterQueue
3metadata:
4 name: spot-cq
5spec:
6 namespaceSelector: {}
7 resourceGroups:
8 - coveredResources: ["cpu", "memory"]
9 flavors:
10 - name: default-flavor
11 resources:
12 - name: cpu
13 nominalQuota: 0
14 borrowingLimit: 480 # 60节点 × 8核,最大可借用额度
15 - name: memory
16 nominalQuota: 0
17 borrowingLimit: 1920Gi # 60节点 × 32Gi
18 cohort: ci-cohort
4.4.3 创建 Cohort(队列组)
1apiVersion: kueue.x-k8s.io/v1beta1
2kind: Cohort
3metadata:
4 name: ci-cohort
应用所有资源:
1kubectl apply -f cohort.yaml
2kubectl apply -f fixed-cq.yaml
3kubectl apply -f spot-cq.yaml
其他策略配置参考:
- 策略一(保底):仅创建单一 ClusterQueue,
nominalQuota = 112核/448Gi - 策略二(激进):仅创建单一 ClusterQueue,
nominalQuota = 592核/2368Gi
4.4.4 创建 LocalQueue(命名空间队列)
为 GitLab Runner 所在的命名空间(如 gitlab-runner)创建队列:
1apiVersion: kueue.x-k8s.io/v1beta1
2kind: LocalQueue
3metadata:
4 name: ci-queue
5 namespace: gitlab-runner
6spec:
7 clusterQueue: fixed-cq # 关联到固定节点队列,任务将优先使用固定节点
应用:
1kubectl apply -f local-queue.yaml
4.5 集成 GitLab Runner
4.5.1 修改 Runner 配置(config.toml)
在 [[runners]] 下,添加或修改 [runners.kubernetes] 部分:
1[runners.kubernetes]
2 # ... 原有配置 ...
3 pod_annotations = ["kueue.x-k8s.io/queue-name: ci-queue"]
如果使用 Helm 部署 GitLab Runner,可在 values 中配置:
1runners:
2 config: |
3 [[runners]]
4 ...
5 executor = "kubernetes"
6 [runners.kubernetes]
7 pod_annotations = ["kueue.x-k8s.io/queue-name: ci-queue"]
4.5.2 验证
触发一个 CI 任务,检查其对应的 Job 是否被自动添加了注解,并查看 Workload 是否创建:
1kubectl get workload -n gitlab-runner
4.6 动态调整 Pending 任务的优先级(插队操作)
当某个紧急任务需要插队时,按以下步骤操作。
方法一:直接修改 Workload 的优先级(推荐)
查找目标 Workload(名称通常为
job-<job-name>-<hash>):1kubectl get workload -n gitlab-runner | grep <任务名或部分关键词>编辑该 Workload:
1kubectl edit workload <workload-name> -n gitlab-runner修改
.spec.priority字段,数值越大优先级越高(例如从默认 0 改为 100):1spec: 2 priority: 100 # 原值可能为 0 或未设置保存退出后,Kueue 会立即按新优先级重新排序队列,该任务将被提前处理。
方法二:通过 Job 标签修改(更便捷)
挂起 Job:
1kubectl patch job <job-name> -n gitlab-runner -p '{"spec":{"suspend":true}}'添加/覆盖优先级标签(需事先定义 WorkloadPriorityClass,详见 4.7):
1kubectl label job <job-name> -n gitlab-runner kueue.x-k8s.io/priority-class=high-priority --overwrite恢复 Job:
1kubectl patch job <job-name> -n gitlab-runner -p '{"spec":{"suspend":false}}'
4.7 定义 WorkloadPriorityClass(可选)
为了简化优先级管理,可定义命名优先级类别:
1apiVersion: kueue.x-k8s.io/v1beta1
2kind: WorkloadPriorityClass
3metadata:
4 name: high-priority
5value: 100
6description: "紧急任务"
7---
8apiVersion: kueue.x-k8s.io/v1beta1
9kind: WorkloadPriorityClass
10metadata:
11 name: normal-priority
12value: 0
13description: "普通任务"
应用:
1kubectl apply -f workload-priority-class.yaml
4.8 监控与验证
查看队列状态:
1kubectl describe localqueue ci-queue -n gitlab-runner查看 Workload 排队情况:
1kubectl get workload -n gitlab-runner -o wide查看准入日志:
1kubectl logs -n kueue-system deployment/kueue-controller-manager查看 Cohort 资源借用情况:
1kubectl describe cohort ci-cohort
5. 注意事项与最佳实践
- 资源配额规划:
- 策略三(Cohort)中,
nominalQuota设置应基于确定性资源(固定节点),borrowingLimit设置基于弹性资源上限。 - 建议为每个 CI 任务的 Pod 设置合理的
resources.requests,避免单个任务过度消耗资源导致排队失衡。 - 如果单节点规格不同,请相应调整上述数值。
- 策略三(Cohort)中,
- 抢占策略:若允许高优先级任务抢占低优先级任务,需在 ClusterQueue 中启用 preemption 字段。谨慎开启,以免频繁中断运行中的任务。
- 默认优先级:未指定优先级的 Workload 默认值为 0,所有任务均按 FIFO 排序。
- 弹性节点配合:建议结合 Cluster Autoscaler 或 Karpenter,当资源不足时自动扩容弹性节点。Kueue 会在资源不足时保持任务排队,不会因调度失败而反复重试。
- GitLab Runner 并发控制:仍建议设置合理的 concurrent 值,避免过多任务同时涌入导致队列堆积。
- Kueue 版本兼容性:borrowingLimit 字段在 Kueue v0.6.0+ 中支持,请确保使用较新版本。
6. 总结
| 问题 | 解决方案 |
|---|---|
| Pending 任务饥饿,新任务"插队" | 使用 Kueue 的 FIFO 策略,保证先入先出 |
| 紧急任务需要插队 | 动态修改 Workload 的 .spec.priority,支持按需调整 |
| 与 GitLab Runner 集成 | 通过 podAnnotations 指定队列名称,无需修改 Runner 核心代码 |
| 资源管理(14 固定 + 60 弹性节点) | 通过 Cohort + borrowingLimit 实现资源动态调配,兼顾成本与效率 |
| nominalQuota 设置 | 推荐策略三:固定节点队列 nominalQuota=112核/448Gi,弹性节点队列 nominalQuota=0 + borrowingLimit=480核/1920Gi |
本方案已在类似规模场景中验证,能显著降低 MR 等待时间,提升开发体验。Kueue 作为社区标准组件,后续升级和维护均有保障,建议作为长期架构的一部分。
