在 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. (可选)使用 Prometheus 部署 kube-prometheus 并监控工作负载
  5. 创建 Job 并观察允许的工作负载
  6. 使用同类群组借用未使用的配额
  7. 添加管理 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

  5. 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

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

  7. 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

(可选)使用 Prometheus 部署 kube-prometheus 并监控工作负载

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

  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. 启动一个新终端以通过端口转发服务访问 Prometheus:

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

  8. 如果使用 Cloud Shell,请点击网页预览,选择“更改端口”,将端口号设置为 9090,然后选择 Change and Preview

  9. 输入监控活跃 ClusterQueue cq-team-a 的第一个面板的查询:

    kueue_pending_workloads{cluster_queue="cq-team-a", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-a"}
    
  10. 添加另一个面板,并输入监控活跃 ClusterQueue cq-team-b 的查询:

    kueue_pending_workloads{cluster_queue="cq-team-b", status="active"} or kueue_admitted_active_workloads{cluster_queue="cq-team-b"}
    
  11. 添加另一个面板,并输入监控集群中的节点的查询:

    count(kube_node_info)
    

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

为这两个 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
    

后续步骤