Kueue 学习第三篇:安装、配额借用与优先级抢占

前言

在 Kubernetes 中,默认调度器主要负责将 Pod 调度到合适的节点上,但它并不负责从团队配额、任务优先级和批任务排队等角度决定一个 Job 是否应该立即启动。

Kueue 是 Kubernetes 原生的 Job 队列与准入控制系统,主要用于管理批处理、HPC、AI/ML 等类型的工作负载。它负责决定 Job 什么时候可以获得资源配额并开始运行,以及在资源竞争时哪些 Workload 应该等待或被抢占。Pod 最终运行在哪个节点上,仍然由 Kubernetes 调度器负责。

本文将通过三个实验验证 Kueue 的核心功能:

  1. 创建基础队列,验证配额充足和配额不足时 Job 的状态。

  2. 验证同一 Cohort 中不同 ClusterQueue 之间的配额借用。

  3. 验证高优先级 Job 抢占低优先级 Job。

本文实验环境如下:

复制代码
Kubernetes:v1.35.5
Kueue:v0.18.2
工作负载:batch/v1 Job
测试资源:CPU、内存

一、Kueue 核心对象与工作流程

在开始实验之前,需要先理解 Kueue 中几个核心对象之间的关系:

复制代码
Kubernetes Job
      │
      │ kueue.x-k8s.io/queue-name
      ▼
LocalQueue
      │
      ▼
ClusterQueue
      │
      ├── ResourceFlavor
      ├── CPU、内存、GPU 等资源配额
      ├── Cohort
      └── Preemption 抢占策略

1.1 ResourceFlavor

ResourceFlavor 用来表示一种资源变体,用于区分具有不同硬件特征、采购方式或调度属性的资源。例如,在实际生产环境中,不同节点可能存在以下差异:

  • CPU 架构不同,例如 x86 和 ARM。

  • GPU 型号不同,例如 NVIDIA A100、T4。

  • 节点类型不同,例如按量节点和抢占式节点。

  • 节点具有不同的标签、污点和容忍配置。

此时可以通过 ResourceFlavor 将不同资源类型与相应节点关联起来。

如果集群中的资源没有进行类型区分,也可以创建一个不包含任何 spec 配置的空 ResourceFlavor。本文的 CPU 测试不需要区分节点类型,因此会使用空 ResourceFlavor。

需要注意,ResourceFlavor 本身不保存 CPU、内存等资源配额。实际配额定义在 ClusterQueue 中。

1.2 ClusterQueue

ClusterQueue 是集群级别的资源对象,用于管理一组可供 Workload 使用的资源配额。

ClusterQueue 主要负责定义:

  • 哪些命名空间可以使用该 ClusterQueue。

  • 管理哪些资源,例如 CPU、内存和 GPU。

  • 每种 ResourceFlavor 对应多少资源配额。

  • Workload 使用什么排队策略。

  • 是否可以从 Cohort 中借用配额。

  • 是否允许 Workload 之间发生抢占。

ClusterQueue 中的配额按照 [ResourceFlavor, Resource] 组合分别管理,例如:

复制代码
[demo-flavor, cpu]
[demo-flavor, memory]

1.3 LocalQueue

LocalQueue 是命名空间级别的资源对象。用户通常不会直接将 Job 提交到 ClusterQueue,而是通过 Job 上的标签选择当前命名空间中的 LocalQueue:

复制代码
metadata:
  labels:
    kueue.x-k8s.io/queue-name: demo-queue

一个 LocalQueue 指向一个 ClusterQueue,ClusterQueue 再为 LocalQueue 中的 Workload 提供配额。一个 ClusterQueue 可以被多个 LocalQueue 引用。

1.4 Workload

Workload 是 Kueue 中真正的准入单元。创建一个受 Kueue 管理的 Kubernetes Job 后,Kueue 会自动为该 Job 创建对应的 Workload。Workload 中记录了:

  • Job 请求的资源总量。

  • Job 使用的 LocalQueue。

  • Workload 的优先级。

  • Kueue 为 Workload 选择的 ResourceFlavor。

  • 为 Workload 分配配额的 ClusterQueue。

  • Workload 当前是否已保留配额。

  • Workload 当前是否已经完成准入。

Kueue 会根据 Workload 中各个 PodSet 的 Pod 数量和单个 Pod 的资源请求计算总资源需求。

因此,在排查 Kueue 问题时,不能只查看 Job 和 Pod,还需要重点查看对应的 Workload。


二、实验注意事项

Kueue 中配置的配额与 Kubernetes 节点的真实资源不是同一个概念。

例如,在 ClusterQueue 中配置:

复制代码
nominalQuota: "4"

表示 Kueue 最多允许符合条件的 Workload 占用 4 CPU 配额,但这并不表示 Kubernetes 集群一定存在 4 个可用 CPU。完整的启动过程可以理解为:

复制代码
Job 提交
    ↓
Kueue 创建 Workload
    ↓
Kueue 检查 ClusterQueue 配额
    ↓
Workload 获得准入
    ↓
Job 解除挂起
    ↓
Kubernetes 调度器调度 Pod
    ↓
Pod 在节点上运行

因此:

  • Workload 没有获得准入时,应优先检查 Kueue 配额和队列配置。

  • Workload 已经获得准入,但 Pod 仍然 Pending 时,应继续检查节点资源、污点、容忍、节点选择器和存储等 Kubernetes 调度条件。


三、安装 Kueue

3.1 安装 Kueue

Kueue v0.18.2 可以直接使用官方发布的 manifests.yaml 安装:

复制代码
kubectl apply --server-side \
  -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.18.2/manifests.yaml

该命令会在集群中安装 Kueue 控制器、CRD、Webhook 和相应的 RBAC 资源。Kueue v0.18.2 官方安装文档要求 Kubernetes 版本不低于 1.29,本文使用的 Kubernetes v1.35.5 满足要求。

3.2 检查 Kueue 控制器

安装完成后,查看 kueue-system 命名空间中的 Pod:

复制代码
kubectl -n kueue-system get pod

输出如下:

复制代码
NAME                                        READY   STATUS    RESTARTS   AGE
kueue-controller-manager-7869bdd57b-7md6g   1/1     Running   0          18s

这表示 Kueue Controller Manager 已正常运行。

3.3 检查 Kueue CRD

执行:

复制代码
kubectl get crd | grep -i kueue

输出如下:

复制代码
admissionchecks.kueue.x-k8s.io
clusterqueues.kueue.x-k8s.io
cohorts.kueue.x-k8s.io
localqueues.kueue.x-k8s.io
multikueueclusters.kueue.x-k8s.io
multikueueconfigs.kueue.x-k8s.io
provisioningrequestconfigs.kueue.x-k8s.io
resourceflavors.kueue.x-k8s.io
topologies.kueue.x-k8s.io
workloadpriorityclasses.kueue.x-k8s.io
workloads.kueue.x-k8s.io

本文主要会使用以下 CRD:

CRD 作用
resourceflavors.kueue.x-k8s.io 定义资源类型
clusterqueues.kueue.x-k8s.io 定义集群级资源配额
localqueues.kueue.x-k8s.io 为命名空间提供队列入口
workloads.kueue.x-k8s.io 表示 Kueue 的准入单元
cohorts.kueue.x-k8s.io 组织可以相互共享配额的 ClusterQueue
workloadpriorityclasses.kueue.x-k8s.io 定义 Workload 优先级

这些对象均属于 Kueue v1beta2 API。


四、基础实验:验证 Kueue 配额与排队功能

本章主要验证两个场景:

  1. ClusterQueue 配额充足时,Job 能够正常获得准入并运行。

  2. ClusterQueue 配额不足时,Job 会保持等待状态。

4.1 创建实验命名空间

创建 kueue-demo 命名空间:

复制代码
kubectl create namespace kueue-demo

4.2 创建 ResourceFlavor

创建 demo-resourceflavor.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
  name: demo-flavor

这段配置中:

  • apiVersion:使用 Kueue v1beta2 API。

  • kind:资源类型为 ResourceFlavor

  • metadata.name:ResourceFlavor 名称为 demo-flavor

  • 没有配置 spec.nodeLabels,表示该 Flavor 不限定特定的节点类型。

对于资源类型相同、不需要区分节点硬件的环境,可以使用这种空 ResourceFlavor。

检查 ResourceFlavor:

复制代码
kubectl get resourceflavor

输出如下:

复制代码
NAME          AGE
demo-flavor   6m20s

4.3 创建 ClusterQueue

创建 demo-clusterqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
  name: demo-cluster-queue
spec:
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: kueue-demo

  queueingStrategy: BestEffortFIFO

  resourceGroups:
    - coveredResources:
        - cpu
        - memory

      flavors:
        - name: demo-flavor
          resources:
            - name: cpu
              nominalQuota: "3"

            - name: memory
              nominalQuota: 2Gi

下面解释其中的主要字段。

namespaceSelector

复制代码
namespaceSelector:
  matchLabels:
    kubernetes.io/metadata.name: kueue-demo

namespaceSelector 用于限制哪些命名空间中的 Workload 可以通过 LocalQueue 使用该 ClusterQueue 的资源配额。kubernetes.io/metadata.name 是 Kubernetes 自动添加到 Namespace 上的不可变标签,其值就是命名空间名称。

因此,这段配置表示只有 kueue-demo 命名空间中的 Workload 可以使用 demo-cluster-queue

queueingStrategy

复制代码
queueingStrategy: BestEffortFIFO

queueingStrategy 用于控制 Workload 在 ClusterQueue 中的排队方式。

Kueue 支持:

  • StrictFIFO

  • BestEffortFIFO

BestEffortFIFO 会按照优先级和创建时间排列 Workload,但较早进入队列且暂时无法满足配额的 Workload,不会阻塞后面能够使用当前剩余配额的 Workload。

BestEffortFIFO 也是默认排队策略。

coveredResources

复制代码
coveredResources:
  - cpu
  - memory

表示这个 ResourceGroup 同时管理 CPU 和内存。CPU 和内存位于同一个 ResourceGroup 中,因此 Kueue 在为 Workload 分配 Flavor 时,会为这两个资源选择同一个 ResourceFlavor。

flavors

复制代码
flavors:
  - name: demo-flavor

这里引用前面创建的 demo-flavor

如果引用的 ResourceFlavor 不存在,ClusterQueue 将无法进入正常的 Active 状态。

nominalQuota

复制代码
resources:
  - name: cpu
    nominalQuota: "3"

  - name: memory
    nominalQuota: 2Gi

表示 demo-cluster-queuedemo-flavor 下拥有:

复制代码
CPU 名义配额:3
内存名义配额:2Gi

nominalQuota 表示 ClusterQueue 在对应 [ResourceFlavor, Resource] 组合下拥有的名义配额。

检查 ClusterQueue:

复制代码
kubectl get clusterqueue

输出如下:

复制代码
NAME                 COHORT   PENDING WORKLOADS
demo-cluster-queue            0

这里 COHORT 为空,表示该 ClusterQueue 当前没有加入任何 Cohort。

4.4 创建 LocalQueue

创建 demo-localqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
  name: demo-queue
  namespace: kueue-demo
spec:
  clusterQueue: demo-cluster-queue

主要字段说明如下:

复制代码
metadata:
  name: demo-queue
  namespace: kueue-demo

表示创建一个名为 demo-queue 的 LocalQueue,该 LocalQueue 属于 kueue-demo 命名空间。

复制代码
spec:
  clusterQueue: demo-cluster-queue

表示 demo-queue 使用 demo-cluster-queue 中的资源配额。

LocalQueue 是命名空间级对象,并且一个 LocalQueue 指向一个 ClusterQueue。

检查 LocalQueue:

复制代码
kubectl -n kueue-demo get localqueue

输出如下:

复制代码
NAME         CLUSTERQUEUE         PENDING WORKLOADS   ADMITTED WORKLOADS
demo-queue   demo-cluster-queue   0                   0

此时三个对象之间的关系如下:

复制代码
ResourceFlavor:demo-flavor
          ↑
ClusterQueue:demo-cluster-queue
          ↑
LocalQueue:kueue-demo/demo-queue

4.5 创建第一个 Job

创建 job-1.yaml

复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: job-1
  namespace: kueue-demo
  labels:
    kueue.x-k8s.io/queue-name: demo-queue
spec:
  parallelism: 3
  completions: 3

  template:
    spec:
      containers:
        - name: worker
          image: ubuntu:22.04
          command:
            - /bin/sh
          args:
            - -c
            - sleep 300

          resources:
            requests:
              cpu: "1"
              memory: 200Mi

      restartPolicy: Never

下面解释几个关键字段。

queue-name

复制代码
labels:
  kueue.x-k8s.io/queue-name: demo-queue

表示将这个 Job 提交到 kueue-demo 命名空间中的 demo-queue

使用 Kueue 管理 Job 时,需要通过该标签指定 LocalQueue。Kueue 会自动管理 Job 的挂起和恢复状态,因此这里不需要手动配置 spec.suspend: true

parallelism 和 completions

复制代码
parallelism: 3
completions: 3

表示:

  • Job 总共需要完成 3 个 Pod。

  • 最多可以同时运行 3 个 Pod。

每个 Pod 请求:

复制代码
CPU:1
内存:200Mi

因此该 Job 的总资源请求为:

复制代码
CPU:3 × 1 = 3
内存:3 × 200Mi = 600Mi

Kueue 会根据 PodSet 中的 Pod 数量乘以单个 Pod 的资源请求,计算 Workload 的总资源需求。

4.6 验证 Job 是否正常运行

查看 Job:

复制代码
kubectl -n kueue-demo get job

输出如下:

复制代码
NAME    STATUS    COMPLETIONS   DURATION   AGE
job-1   Running   0/3           8s         9s

查看对应的 Workload:

复制代码
kubectl -n kueue-demo get workloads

输出如下:

复制代码
NAME              QUEUE        RESERVED IN          ADMITTED   FINISHED   AGE
job-job-1-4d593   demo-queue   demo-cluster-queue   True                  33s

这里可以看到:

复制代码
QUEUE:demo-queue
RESERVED IN:demo-cluster-queue
ADMITTED:True

表示:

  • Job 通过 demo-queue 进入 Kueue。

  • Workload 的资源配额由 demo-cluster-queue 提供。

  • Workload 已经完成准入。

4.7 查看 Workload 详细信息

执行:

复制代码
kubectl -n kueue-demo describe workload job-job-1-4d593

在输出中可以看到:

复制代码
Status:
  Admission:
    Cluster Queue:  demo-cluster-queue
    Pod Set Assignments:
      Count:  3
      Flavors:
        Cpu:     demo-flavor
        Memory:  demo-flavor
      Name:      main
      Resource Usage:
        Cpu:     3
        Memory:  600Mi

这表示:

  • Workload 被 demo-cluster-queue 准入。

  • CPU 和内存都使用 demo-flavor

  • PodSet 中包含 3 个 Pod。

  • CPU 总请求为 3。

  • 内存总请求为 600Mi。

继续查看 Conditions:

复制代码
Conditions:
  Message:  Quota reserved in ClusterQueue demo-cluster-queue
  Reason:   QuotaReserved
  Status:   True
  Type:     QuotaReserved

  Message:  The workload is admitted
  Reason:   Admitted
  Status:   True
  Type:     Admitted

其中:

QuotaReserved=True

表示 Kueue 已经在 demo-cluster-queue 中为该 Workload 保留了资源配额。

Admitted=True

表示 Workload 已完成准入,对应 Job 可以解除挂起并开始运行。

官方排查文档也建议通过 Workload 中的 QuotaReservedAdmitted Condition 判断 Job 是否已经获得准入。

4.8 查看 ClusterQueue 配额使用情况

执行:

复制代码
kubectl describe clusterqueue demo-cluster-queue

可以看到:

复制代码
Status:
  Admitted Workloads:  1

  Flavors Reservation:
    Name:  demo-flavor
    Resources:
      Borrowed:  0
      Name:      cpu
      Total:     3
      Borrowed:  0
      Name:      memory
      Total:     600Mi

  Flavors Usage:
    Name:  demo-flavor
    Resources:
      Borrowed:  0
      Name:      cpu
      Total:     3
      Borrowed:  0
      Name:      memory
      Total:     600Mi

  Pending Workloads:    0
  Reserving Workloads:  1

重点观察:

复制代码
CPU Total:3
Memory Total:600Mi
Borrowed:0

第一个 Job 已经使用了全部 3 CPU 配额。由于当前 ClusterQueue 没有加入 Cohort,因此不会从其他 ClusterQueue 借用资源,Borrowed 为 0。

Flavors Reservation 表示已经被 Workload 保留的配额。 Flavors Usage 表示已经被完成准入的 Workload 实际占用的配额。 本文没有配置额外的 AdmissionCheck,因此 Workload 保留配额后很快完成准入,两组数值相同。

4.9 创建第二个 Job

创建 job-2.yaml

复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: job-2
  namespace: kueue-demo
  labels:
    kueue.x-k8s.io/queue-name: demo-queue
spec:
  template:
    spec:
      containers:
        - name: worker
          image: ubuntu:22.04
          command:
            - /bin/sh
          args:
            - -c
            - sleep 300

          resources:
            requests:
              cpu: "2"
              memory: 200Mi

      restartPolicy: Never

该 Job 请求:

复制代码
CPU:2
内存:200Mi

但是第一个 Job 已经使用了 ClusterQueue 中全部 3 CPU 配额,因此第二个 Job 无法获得配额。

4.10 验证配额不足时的状态

查看第二个 Job 对应的 Workload:

复制代码
kubectl -n kueue-demo get workload

然后查看详细信息:

复制代码
kubectl -n kueue-demo describe workload job-job-2-40da7

关键输出如下:

复制代码
Conditions:
  Message:  couldn't assign flavors to pod set main:
            insufficient unused quota for cpu in flavor demo-flavor,
            2 more needed
  Reason:   Pending
  Status:   False
  Type:     QuotaReserved

Resource Requests:
  Name:  main
  Resources:
    Cpu:     2
    Memory:  200Mi

这表示:

复制代码
QuotaReserved=False
Reason=Pending

Kueue 已经尝试为该 Workload 分配配额,但是 demo-flavor 中没有可用的 CPU 配额,还缺少 2 CPU,因此 Workload 保持 Pending。此时 Job 没有运行,并不是 Kubernetes 调度器无法找到合适节点,而是 Kueue 在准入阶段就没有为 Workload 分配到足够配额。

当 Job 被 Kueue 挂起时,Kubernetes 不会为该 Job 创建新的 Pod;如果 Job 已经存在运行中的 Pod,挂起 Job 时这些 Pod 会被终止和删除。


五、Cohort 配额借用实验

在实际环境中,不同团队通常会拥有独立的 ClusterQueue。

如果每个团队只能使用自己的固定配额,就可能出现以下情况:

复制代码
team-a:有大量空闲配额
team-b:自身配额不足,Job 无法运行

Cohort 可以将多个 ClusterQueue 组织在一起,使它们在满足限制条件的情况下共享未使用的配额。

5.1 创建命名空间

创建两个团队的命名空间:

复制代码
kubectl create namespace team-a
kubectl create namespace team-b

5.2 创建 ResourceFlavor

创建 cohort-resourceflavor.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
  name: cohort-flavor

由于本实验只测试 CPU 配额借用,不需要区分节点类型,因此同样使用空 ResourceFlavor。

检查创建结果:

复制代码
kubectl get resourceflavor cohort-flavor

5.3 创建 Cohort

创建 team-ab-cohort.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: Cohort
metadata:
  name: team-ab

检查 Cohort:

复制代码
kubectl get cohort

Cohort 是集群级资源对象。多个 ClusterQueue 可以通过 spec.cohortName 加入同一个 Cohort,并在相同的资源和 ResourceFlavor 维度上共享未使用配额。

5.4 创建 team-a ClusterQueue

创建 team-a-clusterqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
  name: team-a-cq
spec:
  cohortName: team-ab

  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: team-a

  resourceGroups:
    - coveredResources:
        - cpu

      flavors:
        - name: cohort-flavor
          resources:
            - name: cpu
              nominalQuota: "4"
              borrowingLimit: "2"
              lendingLimit: "2"

其中:

复制代码
cohortName: team-ab

表示 team-a-cq 加入 team-ab Cohort。

复制代码
namespaceSelector:
  matchLabels:
    kubernetes.io/metadata.name: team-a

表示只允许 team-a 命名空间中的 Workload 使用该 ClusterQueue。

配额配置如下:

复制代码
nominalQuota:4 CPU
borrowingLimit:2 CPU
lendingLimit:2 CPU

字段含义如下:

  • nominalQuota:team-a-cq 自己拥有的名义配额。

  • borrowingLimit:team-a-cq 最多可以从 Cohort 中借入的配额。

  • lendingLimit:team-a-cq 最多允许其他 ClusterQueue 借走的空闲配额。

检查 ClusterQueue:

复制代码
kubectl get clusterqueue team-a-cq

5.5 创建 team-b ClusterQueue

创建 team-b-clusterqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
  name: team-b-cq
spec:
  cohortName: team-ab

  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: team-b

  resourceGroups:
    - coveredResources:
        - cpu

      flavors:
        - name: cohort-flavor
          resources:
            - name: cpu
              nominalQuota: "2"
              borrowingLimit: "2"
              lendingLimit: "2"

team-b-cq 的配置为:

复制代码
nominalQuota:2 CPU
borrowingLimit:2 CPU
lendingLimit:2 CPU

两个 ClusterQueue 的关系如下:

复制代码
Cohort:team-ab
│
├── team-a-cq
│   ├── nominalQuota:4 CPU
│   ├── borrowingLimit:2 CPU
│   └── lendingLimit:2 CPU
│
└── team-b-cq
    ├── nominalQuota:2 CPU
    ├── borrowingLimit:2 CPU
    └── lendingLimit:2 CPU

同一个 ResourceFlavor 可以被多个 ClusterQueue 引用。

这里的 cohort-flavor 并不拥有 6 CPU 配额,而是:

复制代码
team-a-cq 在 [cohort-flavor, cpu] 下拥有 4 CPU
team-b-cq 在 [cohort-flavor, cpu] 下拥有 2 CPU

配额始终属于具体的 ClusterQueue。检查两个 ClusterQueue:

复制代码
kubectl get clusterqueue team-a-cq team-b-cq

5.6 创建 team-a LocalQueue

创建 team-a-localqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
  name: team-a-queue
  namespace: team-a
spec:
  clusterQueue: team-a-cq

检查:

复制代码
kubectl -n team-a get localqueue

team-a-queue 属于 team-a 命名空间,并使用 team-a-cq 中的资源配额。

5.7 创建 team-b LocalQueue

创建 team-b-localqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
  name: team-b-queue
  namespace: team-b
spec:
  clusterQueue: team-b-cq

检查:

复制代码
kubectl -n team-b get localqueue

team-b-queue 属于 team-b 命名空间,并使用 team-b-cq 中的资源配额。

5.8 创建需要借用配额的 Job

创建 team-b-job.yaml

复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: team-b-job
  namespace: team-b
  labels:
    kueue.x-k8s.io/queue-name: team-b-queue
spec:
  template:
    spec:
      containers:
        - name: worker
          image: ubuntu:22.04
          command:
            - /bin/sh
          args:
            - -c
            - sleep 300

          resources:
            requests:
              cpu: "3"

      restartPolicy: Never

该 Job 请求 3 CPU,但 team-b-cq 自己只有 2 CPU 名义配额:

复制代码
Job 请求:3 CPU
team-b-cq nominalQuota:2 CPU
缺少配额:1 CPU

因为:

  • team-a-cqteam-b-cq 属于同一个 Cohort。

  • team-a-cq 当前有空闲配额。

  • team-a-cq 允许最多借出 2 CPU。

  • team-b-cq 允许最多借入 2 CPU。

因此 team-b-cq 可以从 Cohort 中借用 1 CPU。

ClusterQueue 能够使用的最大配额可以理解为:

复制代码
nominalQuota + borrowingLimit

但实际能否借到,还取决于 Cohort 中其他 ClusterQueue 是否存在可借出的空闲配额。

5.9 验证 Cohort 借用结果

查看 Pod:

复制代码
kubectl -n team-b get pod

输出如下:

复制代码
NAME               READY   STATUS    RESTARTS   AGE
team-b-job-cnzcm   1/1     Running   0          14s

Job 成功运行,说明 Workload 已经获得 3 CPU 配额。

查看 team-b-cq

复制代码
kubectl describe clusterqueue team-b-cq

关键输出如下:

复制代码
Status:
  Admitted Workloads:  1

  Flavors Reservation:
    Name:  cohort-flavor
    Resources:
      Borrowed:  1
      Name:      cpu
      Total:     3

  Flavors Usage:
    Name:  cohort-flavor
    Resources:
      Borrowed:  1
      Name:      cpu
      Total:     3

  Pending Workloads:    0
  Reserving Workloads:  1

其中:

复制代码
Total:3
Borrowed:1

表示:

复制代码
team-b-cq 当前使用总量:3 CPU
team-b-cq 自身名义配额:2 CPU
从 Cohort 借用的配额:1 CPU

Kueue v1beta2 API 中,total 表示当前使用的总配额,包括从 Cohort 借用的部分;borrowed 表示超过自身 nominalQuota 的使用量。


六、高优先级 Job 抢占低优先级 Job

前面的实验解决的是多个 ClusterQueue 之间如何共享空闲配额。本章继续验证另一个场景:

复制代码
ClusterQueue 的配额已经全部被低优先级 Job 使用,
此时又提交了一个需要相同资源的高优先级 Job。

Kueue 的抢占机制可以驱逐一个或多个已经获得准入的 Workload,从而为另一个 Workload 释放配额。

6.1 创建实验命名空间

复制代码
kubectl create namespace preemption-demo

6.2 创建 ResourceFlavor

创建 preemption-resourceflavor.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: ResourceFlavor
metadata:
  name: preemption-flavor

检查:

复制代码
kubectl get resourceflavor preemption-flavor

6.3 创建 ClusterQueue

创建 preemption-clusterqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata:
  name: preemption-cq
spec:
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: preemption-demo

  preemption:
    withinClusterQueue: LowerPriority

  resourceGroups:
    - coveredResources:
        - cpu

      flavors:
        - name: preemption-flavor
          resources:
            - name: cpu
              nominalQuota: "2"

该 ClusterQueue 只管理 CPU,并配置了 2 CPU 名义配额:

复制代码
nominalQuota: "2"

本实验最关键的配置是:

复制代码
preemption:
  withinClusterQueue: LowerPriority

withinClusterQueue 用于控制同一个 ClusterQueue 内部的抢占。LowerPriority 表示:

当一个待准入 Workload 因配额不足无法运行时,允许它抢占同一个 ClusterQueue 中优先级更低的已准入 Workload。

需要注意,其作用范围是 ClusterQueue,而不是 LocalQueue。即使两个 Job 来自不同的 LocalQueue,只要这些 LocalQueue 指向同一个 ClusterQueue,也可能受到该策略影响。

检查 ClusterQueue:

复制代码
kubectl get clusterqueue preemption-cq

6.4 创建 LocalQueue

创建 preemption-localqueue.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: LocalQueue
metadata:
  name: preemption-queue
  namespace: preemption-demo
spec:
  clusterQueue: preemption-cq

检查:

复制代码
kubectl -n preemption-demo get localqueue

该 LocalQueue 使用 preemption-cq 提供的 2 CPU 配额。

6.5 创建 WorkloadPriorityClass

创建 workload-priority-class.yaml

复制代码
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
  name: low-priority
value: 100
description: "低优先级任务"
---
apiVersion: kueue.x-k8s.io/v1beta2
kind: WorkloadPriorityClass
metadata:
  name: high-priority
value: 1000
description: "高优先级任务"

检查:

复制代码
kubectl get workloadpriorityclasses

输出如下:

复制代码
NAME            VALUE
high-priority   1000
low-priority    100

WorkloadPriorityClass 是集群级对象,可以被不同命名空间中的 Job 使用。

其中:

  • value 表示 Workload 的优先级数值。

  • 数值越大,优先级越高。

  • description 用于说明该优先级的用途。

Workload 优先级主要用于:

  • ClusterQueue 内 Workload 的排序。

  • 判断一个 Workload 是否可以抢占另一个 Workload。

  • Cohort 中需要借用配额的 Workload 排序。

WorkloadPriorityClass 与 Kubernetes 原生的 PriorityClass 不是同一个对象。WorkloadPriorityClass 只影响 Kueue 的排队和抢占,不会改变 Pod 在 Kubernetes 调度器中的优先级。

6.6 创建低优先级 Job

创建 low-priority-job.yaml

复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: low-job
  namespace: preemption-demo
  labels:
    kueue.x-k8s.io/queue-name: preemption-queue
    kueue.x-k8s.io/priority-class: low-priority
spec:
  template:
    spec:
      containers:
        - name: worker
          image: ubuntu:22.04
          command:
            - /bin/sh
          args:
            - -c
            - sleep 600

          resources:
            requests:
              cpu: "2"

      restartPolicy: Never

其中:

复制代码
kueue.x-k8s.io/queue-name: preemption-queue

表示将 Job 提交到 preemption-queue

复制代码
kueue.x-k8s.io/priority-class: low-priority

表示该 Job 使用前面创建的 low-priority WorkloadPriorityClass。官方推荐通过这个标签为 Job 指定 WorkloadPriorityClass。

该 Job 请求 2 CPU,正好占满 preemption-cq 的全部配额。

检查 Job:

复制代码
kubectl -n preemption-demo get job

输出如下:

复制代码
NAME      STATUS    COMPLETIONS   DURATION   AGE
low-job   Running   0/1           43s        43s

此时:

复制代码
ClusterQueue 配额:2 CPU
low-job 使用:2 CPU
剩余配额:0 CPU

6.7 创建高优先级 Job

创建 high-priority-job.yaml

复制代码
apiVersion: batch/v1
kind: Job
metadata:
  name: high-job
  namespace: preemption-demo
  labels:
    kueue.x-k8s.io/queue-name: preemption-queue
    kueue.x-k8s.io/priority-class: high-priority
spec:
  template:
    spec:
      containers:
        - name: worker
          image: ubuntu:22.04
          command:
            - /bin/sh
          args:
            - -c
            - sleep 300

          resources:
            requests:
              cpu: "2"

      restartPolicy: Never

高优先级 Job 同样请求 2 CPU,但此时全部配额已经被 low-job 使用。

两个 Job 的优先级为:

复制代码
low-job:100
high-job:1000

由于 high-job 的优先级更高,并且 ClusterQueue 配置了:

复制代码
withinClusterQueue: LowerPriority

因此,Kueue 可以抢占低优先级的 low-job,释放 2 CPU 配额,再将配额分配给 high-job

6.8 验证抢占结果

查看两个 Job:

复制代码
kubectl -n preemption-demo get job

输出如下:

复制代码
NAME       STATUS      COMPLETIONS   DURATION   AGE
high-job   Running     0/1           4s         4s
low-job    Suspended   0/1                      56s

可以看到:

复制代码
high-job:Running
low-job:Suspended

这表示:

  1. low-job 被 Kueue 抢占。low-job 的 Workload 释放了资源配额。

  2. low-job 被重新设置为挂起状态。high-job 获得 2 CPU 配额并开始运行。

6.9 查看两个 Workload 的状态

执行:

复制代码
kubectl -n preemption-demo get workload

输出如下:

复制代码
NAME                 QUEUE              RESERVED IN     ADMITTED   FINISHED   AGE
job-high-job-27ca1   preemption-queue   preemption-cq   True                  43s
job-low-job-3b11d    preemption-queue                   False                 95s

高优先级 Workload:

复制代码
RESERVED IN:preemption-cq
ADMITTED:True

表示 high-job 已经获得 ClusterQueue 配额并完成准入。低优先级 Workload:

复制代码
RESERVED IN:空
ADMITTED:False

表示 low-job 已失去原来的配额,不再处于已准入状态。


七、总结

通过本文的三个实验,可以得到以下结论。

1. Kueue 负责 Job 级别的配额准入

Kueue 不替代 Kubernetes 调度器。Kueue 负责决定:

复制代码
Job 是否应该等待
Job 是否可以获得配额
Job 是否可以解除挂起
Workload 是否需要被抢占

Kubernetes 调度器继续负责决定 Pod 最终运行在哪个节点上。

2. Job 通过 LocalQueue 使用 ClusterQueue 配额

基本关系为:

复制代码
Job
  ↓
LocalQueue
  ↓
ClusterQueue
  ↓
ResourceFlavor 和资源配额

Job 通过:

复制代码
kueue.x-k8s.io/queue-name

选择 LocalQueue。

3. Workload 是 Kueue 的准入单元

Kueue 会为 Job 创建对应的 Workload。

排查 Job 为什么没有运行时,应重点查看 Workload 中的:

复制代码
QuotaReserved
Admitted
Evicted
Preempted

4. Cohort 可以实现 ClusterQueue 之间的配额共享

同一 Cohort 中的 ClusterQueue 可以在限制范围内借用其他 ClusterQueue 的空闲名义配额。

本次实验中:

复制代码
team-b-cq 自身配额:2 CPU
team-b-job 请求:3 CPU
从 Cohort 借用:1 CPU
最终使用:3 CPU

5. 高优先级 Workload 可以抢占低优先级 Workload

配置:

复制代码
preemption:
  withinClusterQueue: LowerPriority

后,高优先级 Workload 可以抢占同一 ClusterQueue 中优先级更低的已准入 Workload。

被抢占的 Job 会重新进入挂起状态,其运行中的 Pod 会被删除,高优先级 Job 则获得释放出来的配额并开始运行。

相关推荐
探索云原生2 天前
Kueue 如何管理 DRA 模式下的 GPU 配额
ai·云原生·kubernetes·kueue