集群队列(ClusterQueue)

一个集群范围的资源对象,用于管理一组资源池,定义使用上限和公平共享规则。

ClusterQueue 是一个集群范围的对象,用于管理一组资源池,如 Pod、CPU、内存和硬件加速器。ClusterQueue 定义了:

  • ClusterQueue 管理的资源规格的配额,包括使用上限和消耗顺序。
  • 集群中多个 ClusterQueue 之间的公平共享规则。

只有批处理管理员才应创建 ClusterQueue 对象。

一个示例 ClusterQueue 如下所示:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "cluster-queue"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  resourceGroups:
  - coveredResources: ["cpu", "memory", "pods"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 9
      - name: "memory"
        nominalQuota: 36Gi
      - name: "pods"
        nominalQuota: 5

只有在以下条件全部满足时,该 ClusterQueue 才会接纳工作负载

  • CPU 请求总和小于等于 9。
  • 内存请求总和小于等于 36Gi。
  • Pod 总数小于等于 5。

您可以将配额指定为数量

Cohort

资源规格与资源

在 ClusterQueue 中,你可以为多种规格定义配额,这些规格提供特定的计算资源 (如 CPU、内存、GPU、Pods 等)。

规格代表某种资源的不同变体(例如,不同型号的 GPU)。您可以通过 ResourceFlavor 对象定义规格与节点组的映射关系。 在 ClusterQueue 中,您可以为每个规格所提供的资源分别设置配额。

在为 ClusterQueue 定义配额时,您可以设置以下值:

  • nominalQuota:该资源在某一时刻可供 ClusterQueue 使用的数量。
  • borrowingLimit:该 ClusterQueue 允许从同一队列组中其他 ClusterQueue 未用名义配额中借用的最大配额数量。
  • lendingLimit:当本 ClusterQueue 未使用其名义配额时,允许队列组内其他 ClusterQueue 借用的最大配额数量。

在称为准入的流程中,Kueue 会为工作负载 Pod 集合分配每个所需资源的规格。 Kueue 会优先分配 ClusterQueue .spec.resourceGroups[*].flavors 列表中第一个拥有足够未用 nominalQuota 的规格,无论是在本 ClusterQueue 还是其队列组中。

资源组

当一个 ResourceFlavor 绑定到节点组、机器系列或虚拟机可用性策略时,通常要求所有与这些节点相关的资源(如 cpumemory 和 GPU)在准入时分配到同一个规格。 要将两个或多个资源绑定到同一组规格,请将它们列在同一个资源组中。

如果希望为不同资源分配不同的规格,请将它们分别列在不同的资源组中。 这在某些资源不是直接与节点关联、可以通过网络动态挂载,或你希望独立追踪其配额时非常有用。

一个包含多个资源组的 ClusterQueue 示例如下:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "cluster-queue"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  resourceGroups:
  - coveredResources: ["cpu", "memory", "foo.com/gpu"]
    flavors:
    - name: "spot"
      resources:
      - name: "cpu"
        nominalQuota: 9
      - name: "memory"
        nominalQuota: 36Gi
      - name: "foo.com/gpu"
        nominalQuota: 50
    - name: "on-demand"
      resources:
      - name: "cpu"
        nominalQuota: 18
      - name: "memory"
        nominalQuota: 72Gi
      - name: "foo.com/gpu"
        nominalQuota: 100
  - coveredResources: ["bar.com/license"]
    flavors:
    - name: "pool1"
      resources:
      - name: "bar.com/license"
        nominalQuota: 10
    - name: "pool2"
      resources:
      - name: "bar.com/license"
        nominalQuota: 10

在上述示例中,cpumemoryfoo.com/gpu 属于同一个 resourceGroup,而 bar.com/license 属于另一个。

一个资源规格最多只能属于一个资源组。

命名空间选择器

您可以在 ClusterQueue 中限制哪些命名空间可以有工作负载被接纳,通过设置 label selector.spec.namespaceSelector 字段中。

要允许所有命名空间的工作负载,请将空选择器 {} 设置为 spec.namespaceSelector 字段。

有多种方法可以允许特定命名空间访问 Cluster Queue。使用 matchLabels 匹配工作负载到命名空间 team-a 的示例 namespaceSelector 如下:

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

这里 kubernetes.io/metadata.name: team-a 指的是 Kubernetes 控制平面在所有命名空间上设置的不可变标签 kubernetes.io/metadata.name。标签的值是命名空间名称,在这种情况下是 team-a

然而,matchLabels 可以采用任何与命名空间对象中存在的标签匹配的键。 例如,假设 team-ateam-b 在 cohort team-a-b 中, 并且两个命名空间上都存在用户定义的标签 research-cohort: team-a-b,如下所示:

apiVersion: v1
kind: Namespace
metadata:
  name: team-a
  labels:
    research-cohort: team-a-b
apiVersion: v1
kind: Namespace
metadata:
  name: team-b
  labels:
    research-cohort: team-a-b

允许两个命名空间向此 ClusterQueue 提交 Job 的 namespaceSelector 配置如下:

namespaceSelector:
  matchLabels:
    research-cohort: team-a-b

另一种配置 namespaceSelector 的方法是使用 matchExpressions。请参阅 Kubernetes 文档 了解更多详细信息。

排队策略

您可以在 ClusterQueue 中使用 .spec.queueingStrategy 字段设置不同的排队策略。排队策略决定了工作负载在 ClusterQueue 中的顺序以及在失败的 admission 尝试后如何重新排队。

以下是支持的排队策略:

  • StrictFIFO:工作负载首先按 优先级排序, 然后按创建时间 .metadata.creationTimestamp 排序。无法被接纳的旧工作负载会阻止新工作负载,即使新工作负载适合可用配额。
  • BestEffortFIFO:工作负载按与 StrictFIFO 相同的方式排序。然而, 无法被接纳的旧工作负载不会阻止新工作负载,只要新工作负载适合可用配额。

默认排队策略是 BestEffortFIFO

队列组(Cohort)

ClusterQueues 可以分组为 队列组。属于 同一队列组的 ClusterQueues 可以从每个其他 ClusterQueue 借用未使用的配额。

要向队列组添加 ClusterQueue,请在 .spec.cohort 字段中指定 cohort 的名称。所有具有匹配 spec.cohort 的 ClusterQueues 都是 cohort 的一部分。如果 spec.cohort 字段为空,则 ClusterQueue 不属于任何 cohort,因此它不能从任何其他 ClusterQueue 借用配额。

规格与借用语义

当 ClusterQueue 是 cohort 的一部分时,Kueue 满足以下准入语义:

  • 当分配规格时,Kueue 通过 ClusterQueue 的 (.spec.resourceGroups[*].flavors). 对于每个规格,Kueue 尝试 根据 ClusterQueue 为规格定义的配额和 cohort 中的未用配额来适应 Workload 的 pod set。 如果 Workload 不适合,Kueue 评估列表中的下一个规格。
  • Workload 的 pod set 资源适合在 ClusterQueue 中定义的规格,如果 Workload 的请求资源总和:
    1. 小于或等于规格在 ClusterQueue 中的未用 nominalQuota;或
    2. 小于或等于规格在 cohort 中的所有 ClusterQueues 的未用 nominalQuota 之和,并且
    3. 小于或等于规格在 ClusterQueue 中的未用 nominalQuota + borrowingLimit。 在 Kueue 中,当 (2) 和 (3) 满足但未满足 (1) 时,这称为 borrowing quota
  • ClusterQueue 只能借用其定义的规格配额。
  • 对于 Workload 中的每个 pod set 资源,ClusterQueue 只能借用一个规格配额。

你可以设置一个 flavorFungibility 来影响一些规格选择和借用的语义。

借用示例

假设你创建了以下两个 ClusterQueues:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-a-cq"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  cohort: "team-ab"
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 9
      - name: "memory"
        nominalQuota: 36Gi
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-b-cq"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  cohort: "team-ab"
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 12
      - name: "memory"
        nominalQuota: 48Gi

ClusterQueue team-a-cq 可以根据以下情况接纳工作负载:

  • 如果 ClusterQueue team-b-cq 没有被接纳的工作负载,那么 ClusterQueue team-a-cq 可以接纳资源总和为 12+9=21 CPUs 和 48+36=84Gi 内存的工作负载。
  • 如果 ClusterQueue team-b-cq 有待处理的工作负载,并且 ClusterQueue team-a-cq 的所有 nominalQuota 配额已用完,Kueue 将首先在 ClusterQueue team-b-cq 中接纳工作负载,然后再接纳任何新工作负载 team-a-cq。 因此,Kueue 确保 nominalQuota 配额用于 team-b-cq

借用限制

要限制 ClusterQueue 可以从其他地方借用的资源量,你可以设置 .spec.resourcesGroup[*].flavors[*].resource[*].borrowingLimit quantity 字段。

例如,假设你创建了以下两个 ClusterQueues:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-a-cq"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  cohort: "team-ab"
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 9
        borrowingLimit: 1
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-b-cq"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  cohort: "team-ab"
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 12

在这种情况下,因为我们在 ClusterQueue team-a-cq 中设置了 borrowingLimit,如果 ClusterQueue team-b-cq 没有被接纳的工作负载,那么 ClusterQueue team-a-cq 可以接纳资源总和为 9+1=10 CPUs 的工作负载。

如果,对于给定的规格/资源,borrowingLimit 字段为空或 null, ClusterQueue 可以借用所有名义配额从 cohort 中的所有 ClusterQueues。因此,对于上面列出的 yamls,team-b-cq 可以使用 最多 12+9 CPUs。

LendingLimit

要限制 ClusterQueue 可以在 cohort 中借出的资源量,你可以设置 .spec.resourcesGroup[*].flavors[*].resource[*].lendingLimit quantity 字段。

Feature state beta since Kueue v0.9

例如,假设你创建了以下两个 ClusterQueues:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-a-cq"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  cohort: "team-ab"
  resourceGroups:
  - coveredResources: ["cpu"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 9
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-b-cq"
spec:
  namespaceSelector: {} # 匹配所有命名空间。
  cohort: "team-ab"
  resourceGroups:
  - coveredResources: ["cpu"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 12
        lendingLimit: 1

在这里,你在 ClusterQueue team-b-cq 中设置了 lendingLimit=1。这意味着 如果 ClusterQueue team-b-cq 中所有被接纳的工作负载的总 配额使用量低于 nominalQuota(小于或等于 12-1=11 CPUs), 然后 ClusterQueue team-a-cq 可以接纳资源 总和为 9+1=10 CPUs 的工作负载。

如果 lendingLimit 字段未指定,ClusterQueue 可以借出 所有资源。在这种情况下,team-b-cq 可以使用最多 9+12 CPUs。

抢占

当 ClusterQueue 或其 cohort 中没有足够的配额时,新进入的工作负载可以触发以前被接纳的工作负载的预留,基于 ClusterQueue 的策略。

一个配置 ClusterQueue 以启用预留的示例如下:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-a-cq"
spec:
  preemption:
    reclaimWithinCohort: Any
    borrowWithinCohort:
      policy: LowerPriority
      maxPriorityThreshold: 100
    withinClusterQueue: LowerPriority

上述字段如下:

  • reclaimWithinCohort 确定是否可以预留 cohort 中使用更多配额的 Workloads。可能的值是:

    • Never(默认):不要预留 cohort 中的 Workloads。
    • LowerPriority:如果待处理的工作负载适合其 ClusterQueue 的配额,则仅预留 cohort 中优先级较低的 Workloads。
    • Any:如果待处理的工作负载适合其 ClusterQueue 的配额,则预留 cohort 中的任何 Workloads,无论优先级如何。
  • borrowWithinCohort 确定是否可以预留 Workloads 从其他 ClusterQueues 如果工作负载需要借用。 只能配置 Classical Preemption,并且 not with Fair Sharing。 此字段需要指定 policy 子字段,可能的值:

    • Never(默认):如果借用是必需的,不要预留 cohort 中的 Workloads。
    • LowerPriority:如果待处理的工作负载需要借用,则仅预留 cohort 中优先级较低的 Workloads。 此预留策略仅在 reclaimWithinCohort 启用时支持(与 Never 不同)。 此外,仅在配置的优先级阈值内可以预留该场景中的工作负载。
  • withinClusterQueue 确定是否可以预留 待处理的工作负载,如果它不适合其 ClusterQueue 的配额,则可以预留 ClusterQueue 中的活动 Workloads。可能的值是:

    • Never(默认):不要预留 ClusterQueue 中的 Workloads。
    • LowerPriority:仅预留 ClusterQueue 中优先级较低的 Workloads。
    • LowerOrNewerEqualPriority:仅预留 ClusterQueue 中优先级较低或等于待处理工作负载的 Workloads。

请注意,新进入的工作负载可以预留 ClusterQueue 和 cohort 中的 Workloads。

阅读 Preemption 以了解 Kueue 实现以预留尽可能少的工作负载的启发式方法。

FlavorFungibility

当 ResourceFlavor 中没有足够的名义配额资源时,新进入的工作负载可以借用 配额或预留 ClusterQueue 或 cohort 中的运行工作负载。

Kueue 按顺序评估 ClusterQueue 中的规格。你可以影响是否优先 预留或借用规格,在尝试适应 Workload 到下一个规格之前,设置 flavorFungibility 字段。

一个配置 ClusterQueue 以配置此行为的示例如下:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-a-cq"
spec:
  flavorFungibility:
    whenCanBorrow: TryNextFlavor
    whenCanPreempt: Preempt

上述字段如下:

  • whenCanBorrow 确定是否应该停止寻找更好的分配,如果工作负载可以通过借用当前 ResourceFlavor 获得足够的资源。可能的值是:
    • Borrow(默认):ClusterQueue 停止寻找更好的分配。
    • TryNextFlavor:ClusterQueue 尝试下一个 ResourceFlavor 以查看工作负载是否可以获得更好的分配。
  • whenCanPreempt 确定是否应该尝试预留当前 ResourceFlavor 中的工作负载,然后再尝试下一个。可能的值是:
    • Preempt:ClusterQueue 停止尝试预留当前 ResourceFlavor 并从下一个开始,如果预留失败。
    • TryNextFlavor(默认):ClusterQueue 尝试下一个 ResourceFlavor 以查看工作负载是否适合 ResourceFlavor。

默认情况下,新进入的工作负载停止尝试下一个风味,如果工作负载可以获得足够的借用资源。 并且 Kueue 仅在 Kueue 确定剩余 ResourceFlavors 无法适应工作负载时触发预留。

请注意,每当时机允许且配置策略允许时,Kueue 避免预留,如果它可以借用 Workload 以适应。

停止策略

StopPolicy 允许集群管理员通过在 spec 中设置其值来临时停止 ClusterQueue 中工作负载的接纳,如下所示:

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "team-a-cq"
spec:
  stopPolicy: Hold

上述示例将停止 ClusterQueue 中新的工作负载接纳,同时允许已经接纳的工作负载完成。 HoldAndDrain 将具有类似的效果,但除此之外,它还会触发已接纳工作负载的驱逐。

如果设置为 Nonespec.stopPolicy 被删除,ClusterQueue 将恢复正常接纳行为。

AdmissionChecks

AdmissionChecks 是一个机制,允许 Kueue 在接纳工作负载之前考虑其他标准。

例如,使用 admission checks 的 ClusterQueue 配置,请参阅 Admission Checks

下一步是什么?