在 GKE 上使用命名空间配额共享实现 Job 排队系统


本教程使用 Kueue 介绍如何实现 Job 排队系统、配置 Google Kubernetes Engine (GKE) 上不同命名空间之间的工作负载资源和配额共享,以及如何最大限度地提高集群的利用率。

背景

作为基础架构工程师或集群管理员,最大限度地提高命名空间之间的利用率至关重要。一个命名空间中的一批 Job 可能无法充分使用分配给该命名空间的全部配额,而另一个命名空间可能有多个待处理的 Job。如需在不同命名空间的 Job 之间高效地使用集群资源,并提高配额管理的灵活性,您可以在 Kueue 中配置同类群组。同类群组是一组 ClusterQueue,它们可以相互借用未使用的配额。ClusterQueue 用于管理 CPU、内存和硬件加速器等资源池。

您可以参阅 Kueue 文档,了解所有这些概念的详细定义。

目标

本教程适用于希望使用 Kueue 和配额共享在 Kubernetes 上实现 Job 排队系统的基础架构工程师或集群管理员。

本教程将模拟两个不同命名空间中的两个团队,每个团队都有自己的专用资源,但可以相互借用。第 3 组资源可以在 Job 累积时用作溢出。

利用 Prometheus Operator 来监控不同命名空间中的 Job 和资源分配。

本教程介绍了以下必要步骤:

  1. 创建 GKE 集群
  2. 创建 ResourceFlavors
  3. 为每个团队创建一个 ClusterQueueLocalQueue
  4. 创建 Job 并观察允许的工作负载
  5. 使用同类群组借用未使用的配额
  6. 添加管理 Spot 虚拟机的溢出 ClusterQueue

费用

本教程使用 Google Cloud的以下收费组件:

您可使用价格计算器根据您的预计使用情况来估算费用。

完成本教程后,您可以删除所创建的资源以避免继续计费。如需了解详情,请参阅清理

准备工作

设置项目

  1. Sign in to your Google Cloud account. If you're new to Google Cloud, create an account to evaluate how our products perform in real-world scenarios. New customers also get $300 in free credits to run, test, and deploy workloads.
  2. In the Google Cloud console, on the project selector page, click Create project to begin creating a new Google Cloud project.

    Go to project selector

  3. Make sure that billing is enabled for your Google Cloud project.

  4. Enable the GKE API.

    Enable the API

设置 Google Cloud CLI 的默认值

  1. 在 Google Cloud 控制台中,启动 Cloud Shell 实例:
    打开 Cloud Shell

  2. 下载此示例应用的源代码:

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  3. 设置默认环境变量:

    gcloud config set project PROJECT_ID
    gcloud config set compute/region COMPUTE_REGION
    

    替换以下值:

创建 GKE 集群

  1. 创建名为 kueue-cohort 的 GKE 集群:

    您将创建一个集群,该集群在默认池中有 6 个节点(每个可用区 2 个),并且没有自动扩缩功能。 这些是一开始可供团队使用的所有资源,因此它们必须展开竞争。

    稍后您将了解 Kueue 如何管理两个团队将发送到相应队列的工作负载。

      gcloud container clusters create kueue-cohort --region COMPUTE_REGION \
      --release-channel rapid --machine-type e2-standard-4 --num-nodes 2
    

    创建集群后,结果类似于以下内容:

      kubeconfig entry generated for kueue-cohort.
      NAME: kueue-cohort
      LOCATION: us-central1
      MASTER_VERSION: 1.26.2-gke.1000
      MASTER_IP: 35.224.108.58
      MACHINE_TYPE: e2-medium
      NODE_VERSION: 1.26.2-gke.1000
      NUM_NODES: 6
      STATUS: RUNNING
    

    其中,kueue-clusterSTATUSRUNNING

  2. 创建一个名为 spot 的节点池。

    此节点池使用 Spot 虚拟机并启用了自动扩缩功能。它从 0 个节点开始,但稍后您会将其提供给团队以用作溢出容量。

    gcloud container node-pools create spot --cluster=kueue-cohort --region COMPUTE_REGION  \
    --spot --enable-autoscaling --max-nodes 20 --num-nodes 0 \
    --machine-type e2-standard-4
    
  3. 将 Kueue 的发布版本安装到集群:

    VERSION=VERSION
    kubectl apply -f \
      https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/manifests.yaml
    

    VERSION 替换为字母 v 后跟 Kueue 的最新版本,例如 v0.4.0。如需详细了解 Kueue 版本,请参阅 Kueue 版本

    等待 Kueue 控制器准备就绪:

    watch kubectl -n kueue-system get pods
    

    在继续下一步之前,输出应类似于以下内容:

    NAME                                        READY   STATUS    RESTARTS   AGE
    kueue-controller-manager-6cfcbb5dc5-rsf8k   2/2     Running   0          3m
    
  4. 创建两个名为 team-ateam-b 的新命名空间:

    kubectl create namespace team-a
    kubectl create namespace team-b
    

    每个命名空间上都将生成 Job。

创建 ResourceFlavor

ResourceFlavor 表示集群节点中的资源变体,例如不同的虚拟机(例如 Spot 与按需)、架构(例如 x86 与 ARM CPU)、品牌和型号(例如 Nvidia A100 与 T4 GPU)。

ResourceFlavors 使用节点标签和污点与集群中的一组节点匹配。

apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: on-demand # This ResourceFlavor will be used for the CPU resource
spec:
  nodeLabels:
    cloud.google.com/gke-provisioning: standard # This label was applied automatically by GKE
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: spot # This ResourceFlavor will be used as added resource for the CPU resource
spec:
  nodeLabels:  
    cloud.google.com/gke-provisioning: spot # This label was applied automatically by GKE

在此清单中:

  • ResourceFlavor on-demand 的标签设置为 cloud.google.com/gke-provisioning: standard
  • ResourceFlavor spot 的标签设置为 cloud.google.com/gke-provisioning: spot

为工作负载分配 ResourceFlavor 时,Kueue 会将工作负载的 Pod 分配给匹配为 ResourceFlavor 定义的节点标签的节点。

部署 ResourceFlavor:

kubectl apply -f flavors.yaml

创建 ClusterQueue 和 LocalQueue

创建两个 ClusterQueue cq-team-acq-team-b,以及相应的 LocalQueue lq-team-alq-team-b,分别加入命名空间 team-ateam-b

ClusterQueue 是集群范围对象,用于管理 CPU、内存和硬件加速器等资源池。批处理管理员可以将这些对象的公开范围限制为批处理用户。

LocalQueue 是批处理用户可以列出的命名空间内对象。它们指向 CluterQueue,资源将从中分配,以运行 LocalQueue 工作负载。

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: cq-team-a
spec:
  cohort: all-teams # cq-team-a and cq-team-b share the same cohort
  namespaceSelector:
    matchLabels:
      kubernetes.io/metadata.name: team-a #Only team-a can submit jobs direclty to this queue, but will be able to share it through the cohort
  resourceGroups:
  - coveredResources: ["cpu", "memory"]
    flavors:
    - name: on-demand
      resources:
      - name: "cpu"
        nominalQuota: 10
        borrowingLimit: 5
      - name: "memory"
        nominalQuota: 10Gi
        borrowingLimit: 15Gi
    - name: spot # This ClusterQueue doesn't have nominalQuota for spot, but it can borrow from others
      resources:
      - name: "cpu"
        nominalQuota: 0
      - name: "memory"
        nominalQuota: 0
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: team-a # LocalQueue under team-a namespace
  name: lq-team-a
spec:
  clusterQueue: cq-team-a # Point to the ClusterQueue team-a-cq

ClusterQueue 允许资源有多个变体。在本例中,每个 ClusterQueue 都有两种变体(on-demandspot),均提供 cpu 资源。ResourceFlavor spot 的配额设置为 0,暂时不使用。

这两个 ClusterQueue 共享在 .spec.cohort 中定义的名为 all-teams 的同类群组。当两个或更多 ClusterQueue 共享一个同类群组时,它们可以向彼此借用未使用的配额。

如需详细了解同类群组的工作原理以及借用语义,请参阅 Kueue 文档

部署 ClusterQueue 和 LocalQueue:

kubectl apply -f cq-team-a.yaml
kubectl apply -f cq-team-b.yaml

(可选)使用 kube-prometheus 监控工作负载

您可以使用 Prometheus 监控 Kueue 活跃工作负载和待处理工作负载。如需监控启动的工作负载并观察每个 ClusterQueue 的负载,请将 kube-prometheus 部署到命名空间 monitoring 下的集群:

  1. 下载 Prometheus Operator 的源代码:

    cd
    git clone https://github.com/prometheus-operator/kube-prometheus.git
    
  2. 创建 CustomResourceDefinition (CRD):

    kubectl create -f kube-prometheus/manifests/setup
    
  3. 创建监控组件:

    kubectl create -f kube-prometheus/manifests
    
  4. 允许 prometheus-operator 从 Kueue 组件中抓取指标:

    kubectl apply -f https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/prometheus.yaml
    
  5. 切换到工作目录:

    cd kubernetes-engine-samples/batch/kueue-cohort
    
  6. 将端口转发设置为在 GKE 集群中运行的 Prometheus 服务:

    kubectl --namespace monitoring port-forward svc/prometheus-k8s 9090
    
  7. 在浏览器中通过 localhost:9090 打开 Prometheus 网页界面。

    在 Cloud Shell 中:

    1. 点击网页预览

    2. 点击更改端口,然后将端口号设置为 9090

    3. 点击更改并预览

    系统会显示以下 Prometheus 网页界面。

    Prometheus 网页界面屏幕截图

  8. 表达式查询框中,输入以下查询,以创建第一个用于监控 cq-team-a ClusterQueue 的活跃工作负载的面板:

    kueue_pending_workloads{cluster_queue="cq-team-a", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-a"}
    
  9. 点击添加面板

  10. 表达式查询框中,输入以下查询,以创建另一个用于监控 cq-team-b ClusterQueue 的活跃工作负载的面板:

    kueue_pending_workloads{cluster_queue="cq-team-b", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-b"}
    
  11. 点击添加面板

  12. 表达式查询框中,输入以下查询,以创建一个用于监控集群中节点数量的面板:

    count(kube_node_info)
    

(可选)使用 Google Cloud Managed Service for Prometheus 监控工作负载

您可以使用 Google Cloud Managed Service for Prometheus 监控 Kueue 活跃工作负载和待处理工作负载。如需查看指标的完整列表,请参阅 Kueue 文档

  1. 为指标访问权限设置身份和 RBAC:

    以下配置会创建 4 个 Kubernetes 资源,用于为 Google Cloud Managed Service for Prometheus 收集器提供指标访问权限。

    • 访问 Kueue 指标时,系统会使用 kueue-system 命名空间中名为 kueue-metrics-reader 的 ServiceAccount 进行身份验证。

    • kueue-metrics-reader 服务账号关联的 Secret 会存储收集器使用的身份验证令牌,以通过 Kueue 部署公开的指标端点进行身份验证。

    • kueue-system 命名空间中名为 kueue-secret-reader 的 Role,允许读取包含服务账号令牌的 Secret。

    • kueue-metrics-reader 服务账号授予 kueue-metrics-reader ClusterRole 的 ClusterRoleBinding。

    apiVersion: v1
    kind: ServiceAccount
    metadata:
     name: kueue-metrics-reader
     namespace: kueue-system
    ---
    apiVersion: v1
    kind: Secret
    metadata:
     name: kueue-metrics-reader-token
     namespace: kueue-system
     annotations:
       kubernetes.io/service-account.name: kueue-metrics-reader
    type: kubernetes.io/service-account-token
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
     name: kueue-secret-reader
     namespace: kueue-system
    rules:
    -   resources:
     -   secrets
     apiGroups: [""]
     verbs: ["get", "list", "watch"]
     resourceNames: ["kueue-metrics-reader-token"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
     name: kueue-metrics-reader
    subjects:
    -   kind: ServiceAccount
     name: kueue-metrics-reader
     namespace: kueue-system
    roleRef:
     kind: ClusterRole
     name: kueue-metrics-reader
     apiGroup: rbac.authorization.k8s.io
    
  2. 为 Google Cloud Managed Service for Prometheus 配置 RoleBinding:

    根据您使用的是 Autopilot 集群还是 Standard 集群,您需要在 gke-gmp-systemgmp-system 命名空间中创建 RoleBinding。此资源可让收集器服务账号访问 kueue-metrics-reader-token Secret,以对 Kueue 指标进行身份验证和抓取。

      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: gmp-system:collector:kueue-secret-reader
        namespace: kueue-system
      roleRef:
        name: kueue-secret-reader
        kind: Role
        apiGroup: rbac.authorization.k8s.io
      subjects:
      -   name: collector
        namespace: gke-gmp-system
        kind: ServiceAccount
    
      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: gmp-system:collector:kueue-secret-reader
        namespace: kueue-system
      roleRef:
        name: kueue-secret-reader
        kind: Role
        apiGroup: rbac.authorization.k8s.io
      subjects:
      -   name: collector
        namespace: gmp-system
        kind: ServiceAccount
    
  3. 配置 PodMonitoring 资源:

    以下资源为 Kueue 部署配置了监控,它指定指标通过 HTTPS 在 /metrics 路径上公开。在抓取指标时,该指标会使用 kueue-metrics-reader-token Secret 进行身份验证。

    apiVersion: monitoring.googleapis.com/v1
    kind: PodMonitoring
    metadata:
    name: kueue
    namespace: kueue-system
    spec:
    selector:
     matchLabels:
       control-plane: controller-manager
    endpoints:
    -   port: https
     interval: 30s
     path: /metrics
     scheme: https
     tls:
       insecureSkipVerify: true
     authorization:
       type: Bearer
       credentials:
         secret:
           name: kueue-metrics-reader-token
           key: token
    

查询已导出的指标

用于监控基于 Kueue 的系统的 PromQL 查询示例

借助这些 PromQL 查询,您可以监控关键 Kueue 指标(例如,作业吞吐量、按队列划分的资源利用率和工作负载等待时间),以了解系统性能并找出潜在瓶颈。

Job 吞吐量

这会计算每个 cluster_queue 在 5 分钟内允许的工作负载每秒速率。此指标有助于按队列进行细分,从而有助于查明瓶颈,而将其相加可提供整体系统吞吐量。

查询:

sum(rate(kueue_admitted_workloads_total[5m])) by (cluster_queue)

资源利用率

此处假定已启用 metrics.enableClusterQueueResources。它会计算每个队列的当前 CPU 用量与标称 CPU 配额的比率。值接近 1 表示利用率较高。您可以通过更改资源标签,针对内存或其他资源进行调整。

如需在集群中安装自定义配置的已发布版本的 Kueue,请参阅 Kueue 文档

查询:

sum(kueue_cluster_queue_resource_usage{resource="cpu"}) by (cluster_queue) / sum(kueue_cluster_queue_nominal_quota{resource="cpu"}) by (cluster_queue)

队列等待时间

这会提供特定队列中工作负载的第 90 百分位等待时间。您可以修改分位数值(例如,0.5 表示中位数,0.99 表示第 99 百分位),以了解等待时间分布情况。

查询:

histogram_quantile(0.9, kueue_admission_wait_time_seconds_bucket{cluster_queue="QUEUE_NAME"})

创建 Job 并观察允许的工作负载

在本部分中,您将在命名空间 team-ateam-b 下创建 Kubernetes Job。Kubernetes 中的 Job 控制器会创建一个或多个 Pod,并确保它们成功执行特定任务。

为这两个 ClusterQueue(将休眠 10 秒)生成 Job,包含三个并行 Job,三个 Job 都完成才算完成。然后,Job 会在 60 秒后进行清理。

apiVersion: batch/v1
kind: Job
metadata:
  namespace: team-a # Job under team-a namespace
  generateName: sample-job-team-a-
  labels:
    kueue.x-k8s.io/queue-name: lq-team-a # Point to the LocalQueue
spec:
  ttlSecondsAfterFinished: 60 # Job will be deleted after 60 seconds
  parallelism: 3 # This Job will have 3 replicas running at the same time
  completions: 3 # This Job requires 3 completions
  suspend: true # Set to true to allow Kueue to control the Job when it starts
  template:
    spec:
      containers:
      - name: dummy-job
        image: gcr.io/k8s-staging-perf-tests/sleep:latest
        args: ["10s"] # Sleep for 10 seconds
        resources:
          requests:
            cpu: "500m"
            memory: "512Mi"
      restartPolicy: Never

job-team-a.yaml 在命名空间 team-a 下创建 Job,并指向 LocalQueue lq-team-a 和 ClusterQueue cq-team-a

同样,job-team-b.yamlteam-b 命名空间下创建 Job,并指向 LocalQueue lq-team-b 和 ClusterQueue cq-team-b

  1. 启动新终端并运行以下脚本,每秒生成一个 Job:

    ./create_jobs.sh job-team-a.yaml 1
    
  2. 启动另一个终端,并为 team-b 命名空间创建 Job:

    ./create_jobs.sh job-team-b.yaml 1
    
  3. 观察 Prometheus 中正在排队的 Job。或使用以下命令:

    watch -n 2 kubectl get clusterqueues -o wide
    

输出应类似如下所示:

    NAME        COHORT      STRATEGY         PENDING WORKLOADS   ADMITTED WORKLOADS
    cq-team-a   all-teams   BestEffortFIFO   0                   5
    cq-team-b   all-teams   BestEffortFIFO   0                   4

使用同类群组借用未使用的配额

ClusterQueue 可能无法始终达到最大容量。当工作负载在 ClusterQueue 中分布不均匀时,配额用量无法最大化。如果 ClusterQueue 彼此共享相同的同类群组,则 ClusterQueue 可以从其他 ClusterQueue 借用配额,以最大限度地提高配额利用率。

  1. 当 ClusterQueue cq-team-acq-team-b 都有 Job 排队后,在相应的终端上按 CTRL+c 停止 team-b 命名空间的脚本。

  2. 命名空间 team-b 中的所有待处理 Job 都得到处理后,命名空间 team-a 中的 Job 可以借用 cq-team-b 中的可用资源:

    kubectl describe clusterqueue cq-team-a
    

    由于 cq-team-acq-team-b 共享名为 all-teams 的同类群组,因此这些 ClusterQueue 能够共享未使用的资源。

      Flavors Usage:
        Name:  on-demand
        Resources:
          Borrowed:  5
          Name:      cpu
          Total:     15
          Borrowed:  5Gi
          Name:      memory
          Total:     15Gi
    
  3. 恢复 team-b 命名空间的脚本。

    ./create_jobs.sh job-team-b.yaml 3
    

    观察被借用的 cq-team-a 资源如何变为 0,而 cq-team-b 中的资源用于它自己的工作负载:

    kubectl describe clusterqueue cq-team-a
    
      Flavors Usage:
        Name:  on-demand
        Resources:
          Borrowed:  0
          Name:      cpu
          Total:     9
          Borrowed:  0
          Name:      memory
          Total:     9Gi
    

使用 Spot 虚拟机增加配额

当需要临时增加配额时(例如,为了满足待处理工作负载中的高需求),您可以通过向同类群组添加更多 ClusterQueue 来配置 Kueue 以满足需求。具有未使用资源的 ClusterQueue 可与属于同一同类群组的其他 ClusterQueue 共享这些资源。

在本教程开始时,您使用 Spot 虚拟机创建了名为 spot 的节点池和名为 spot 且标签设置为 cloud.google.com/gke-provisioning: spot 的 ResourceFlavor。创建一个 ClusterQueue 以使用此节点池和表示它的 ResourceFlavor:

  1. 创建一个名为 cq-spot 的新 ClusterQueue,同类群组设置为 all-teams

    apiVersion: kueue.x-k8s.io/v1beta1
    kind: ClusterQueue
    metadata:
      name: spot-cq
    spec:
      cohort: all-teams # Same cohort as cq-team-a and cq-team-b
      resourceGroups:
      - coveredResources: ["cpu", "memory"]
        flavors:
        - name: spot
          resources:
          - name: "cpu"
            nominalQuota: 40
          - name: "memory"
            nominalQuota: 144Gi

    由于此 ClusterQueue 与 cq-team-acq-team-b 共享一个同类群组,因此 ClusterQueue cq-team-acq-team-b 都可以最多借用 15 个 CPU 请求和 15 Gi 内存的资源。

    kubectl apply -f cq-spot.yaml
    
  2. 在 Prometheus 中,观察 cq-team-acq-team-b 的允许工作负载如何因共享一个同类群组的 cq-spot 的配额增加而激增。或使用以下命令:

    watch -n 2 kubectl get clusterqueues -o wide
    
  3. 在 Prometheus 中,观察集群中的节点数。或使用以下命令:

    watch -n 2 kubectl get nodes -o wide
    
  4. CTRL+cteam-ateam-b 命名空间停止这两个脚本。

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

删除项目

  1. In the Google Cloud console, go to the Manage resources page.

    Go to Manage resources

  2. In the project list, select the project that you want to delete, and then click Delete.
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

逐个删除资源

  1. 删除 Kueue 配额系统:

    kubectl delete -n team-a localqueue lq-team-a
    kubectl delete -n team-b localqueue lq-team-b
    kubectl delete clusterqueue cq-team-a
    kubectl delete clusterqueue cq-team-b
    kubectl delete clusterqueue cq-spot
    kubectl delete resourceflavor default
    kubectl delete resourceflavor on-demand
    kubectl delete resourceflavor spot
    
  2. 删除 Kueue 清单:

    VERSION=VERSION
    kubectl delete -f \
      https://github.com/kubernetes-sigs/kueue/releases/download/$VERSION/manifests.yaml
    
  3. 删除集群:

    gcloud container clusters delete kueue-cohort --region=COMPUTE_REGION
    

后续步骤