GKE の Namespace 間で割り当てを共有するジョブ キューイング システムを実装する


このチュートリアルでは、Kueue を使用して、ジョブ キューイング システムの実装、Google Kubernetes Engine(GKE)上の異なる Namespace 間のワークロード リソースと割り当て共有の構成、クラスタの使用率の最大化について説明します。

背景

インフラストラクチャ エンジニアやクラスタ管理者としては、Namespace の間で使用率を最大化することが非常に重要です。ある Namespace 内のジョブのバッチが、その Namespace に割り当てられた割り当てをすべて使用できない場合があります。一方、別の Namespace には保留中のジョブが複数存在する場合があります。異なる Namespace のジョブ間でクラスタ リソースを効率的に利用し、割り当て管理の柔軟性を高めるために、Kueue でコホートを構成できます。コホートとは、未使用の割り当てを互いに借用できる ClusterQueues のグループです。ClusterQueue は、CPU、メモリ、ハードウェア アクセラレータなどのリソースのプールを管理します。

このようなコンセプト全体の定義については、Kueue のドキュメントをご覧ください。

目標

このチュートリアルは、割り当ての共有で Kueue を使用して Kubernetes にジョブ キューイング システムを実装するインフラストラクチャ エンジニアまたはクラスタ管理者を対象としています。

このチュートリアルでは、2 つの異なる Namespace に 2 つのチームを設定し、それぞれのチームが専用のリソースを持ちながら互いのリソースを利用できるようにします。3 つ目のリソースセットは、ジョブが蓄積したときにスピルオーバーとして使用します。

Prometheus オペレーターを使用して、異なる Namespace 内のジョブとリソース割り当てをモニタリングします。

このチュートリアルでは、次の手順について説明します。

  1. GKE クラスタを作成する
  2. ResourceFlavors を作成する
  3. 各チームに ClusterQueueLocalQueue を作成する
  4. (省略可)kube-prometheus をデプロイして Prometheus でワークロードをモニタリングする
  5. Job を作成して許可されたワークロードをモニタリングする
  6. 未使用の割り当てをコホートで借用する
  7. Spot VM を管理するスピルオーバー 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. Google Cloud Console のプロジェクト セレクタのページで、[プロジェクトを作成] をクリックして新しい Google Cloud プロジェクトの作成を開始します。

    プロジェクト セレクタに移動

  3. Google Cloud プロジェクトで課金が有効になっていることを確認します

  4. GKE API を有効にします。

    API を有効にする

  5. Google Cloud Console のプロジェクト セレクタのページで、[プロジェクトを作成] をクリックして新しい Google Cloud プロジェクトの作成を開始します。

    プロジェクト セレクタに移動

  6. Google Cloud プロジェクトで課金が有効になっていることを確認します

  7. GKE API を有効にします。

    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 VM を使用し、自動スケーリングが有効になっています。開始時は 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 という 2 つの新しい Namespace を作成します。

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

    ジョブは Namespace ごとに生成されます。

ResourceFlavors を作成する

ResourceFlavor は、各種 VM(Spot とオンデマンドなど)、アーキテクチャ(x86 CPU と ARM CPU など)、ブランドとモデル(Nvidia A100 GPU と T4 GPU など)などのクラスタノードのリソース バリエーションを表します。

ResourceFlavor は、ノードラベルと taint を使用して、クラスタ内のノードのセットを照合します。

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 は ResourceFlavor に定義されたノードラベルに一致するノードにワークロードの Pod を割り当てます。

ResourceFlavor をデプロイします。

kubectl apply -f flavors.yaml

ClusterQueue と LocalQueue を作成する

2 つの ClusterQueue(cq-team-acq-team-b)とそれに対応する LocalQueues(lq-team-alq-team-b)には、それぞれ team-ateam-b という Namespace が設定されます。

ClusterQueues は、CPU、メモリ、ハードウェア アクセラレータなどのリソースのプールを管理するクラスタ スコープ オブジェクトです。Batch 管理者は、これらのオブジェクトをバッチユーザーにのみ表示するように制限できます。

LocalQueue は、バッチユーザーが一覧表示できる Namespace 付きのオブジェクトです。LocalQueues は CluterQueues を指し、CluterQueues で 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 の 2 つのフレーバーがあり、それぞれが cpu リソースを提供しています。ResourceFlavor spot の割り当ては 0 に設定されており、ここでは使用されません。

どちらの ClusterQueue も、.spec.cohort で定義された all-teams という同じコホートを共有します。複数の ClusterQueue が同じコホートを共有する場合、互いに未使用の割り当てを借用できます。

コホートの仕組みと借用の意味については、Kueue のドキュメントをご覧ください。

ClusterQueues と LocalQueues をデプロイします。

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

(省略可)kube-prometheus をデプロイして Prometheus でワークロードをモニタリングする

Prometheus を使用して、Kueue の保留中のワークロードとアクティブなワークロードをモニタリングできます。起動されているワークロードをモニタリングして各 ClusterQueue の負荷を監視するには、Namespace モニタリングの下にあるクラスタに Prometheus を設定します。

  1. モニタリング用の prometheus オペレーターのソースコードをダウンロードします。

    cd
    git clone https://github.com/prometheus-operator/kube-prometheus.git
    
  2. CustomResourceDefinitions(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 を作成して許可されたワークロードをモニタリングする

10 秒間スリープする両方の ClusterQueue に 3 つの並列 Job を生成し、3 つ 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.yamlteam-a Namespace に Job を作成し、LocalQueue lq-team-a と ClusterQueue cq-team-a を指します。

同様に、job-team-b.yamlteam-b Namespace に Job を作成し、LocalQueue lq-team-b と ClusterQueue cq-team-b を指します。

  1. 新しいターミナルを開始し、このスクリプトを実行すると 1 秒ごとに Job が生成されます。

    ./create_jobs.sh job-team-a.yaml 1
    
  2. 別のターミナルを開始して、team-b Namespace の Job を作成します。

    ./create_jobs.sh job-team-b.yaml 1
    
  3. Job が Prometheus のキューに格納されていることを確認します。次のコマンドを使用します。

    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. Job が、ClusterQueue の cq-team-acq-team-b の両方のキューに入れられたら、対応するターミナルで CTRL+c を押して team-b Namespace のスクリプトを停止します。

  2. Namespace team-b の保留中の Job がすべて処理されると、Namespace team-a の Job が cq-team-b 内の使用可能なリソースを借用できます。

    kubectl describe clusterqueue cq-team-a
    

    cq-team-acq-team-ball-teams という同じコホートを共有しているため、これらの ClusterQueues は使用されていないリソースを共有できます。

      Flavors Usage:
        Name:  on-demand
        Resources:
          Borrowed:  5
          Name:      cpu
          Total:     15
          Borrowed:  5Gi
          Name:      memory
          Total:     15Gi
    
  3. team-b Namespace のスクリプトを再開します。

    ./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 VM で割り当てを増やす

保留中のワークロードの高い需要を満たす場合など、割り当てを一時的に増やす必要がある場合は、コホートに ClusterQueue を追加して需要に対応するように Kueue を構成できます。未使用のリソースを含む ClusterQueue は、それらのリソースを同じコホートに属する別の ClusterQueue と共有できます。

チュートリアルの最初で、Spot VM と spot という名前の ResourceFlavor を使用して spot という名前のノードプールを作成し、ラベルを cloud.google.com/gke-provisioning: spot に設定しました。このノードプールとそれを表す ResourceFlavor を使用するために、ClusterQueue を作成します。

  1. コホートを all-teams に設定して、cq-spot という新しい ClusterQueue を作成します。

    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-spot が割り当てが追加されたので、cq-team-acq-team-b の両方で許可されたワークロードの急増状況を確認します。次のコマンドを使用します。

    watch -n 2 kubectl get clusterqueues -o wide
    
  3. Prometheus で、クラスタ内のノードの数を確認します。次のコマンドを使用します。

    watch -n 2 kubectl get nodes -o wide
    
  4. 両方のスクリプトを停止するには、team-ateam-b の各 Namespace で CTRL+c を押します。

クリーンアップ

このチュートリアルで使用したリソースについて、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
    

次のステップ