Kueue を使用してバッチシステムをデプロイする

このチュートリアルでは、Kueue を使用して Google Kubernetes Engine(GKE)で Job をスケジュール設定し、利用可能なリソースを最適化する方法について説明します。このチュートリアルでは、Kueue を使用してバッチジョブを効果的に管理およびスケジュール設定し、リソース使用率を向上させ、ワークロード管理を簡素化する方法について確認します。2 つのテナントチーム用に共有クラスタを設定します。各チームには独自の名前空間があり、各チームはグローバル リソースを共有する Job を作成します。また、定義したリソース割り当てに基づいて Job をスケジュール設定するように Kueue を構成します。

このチュートリアルは、GKE を使用したバッチシステムの実装に関心があるクラウド アーキテクトとプラットフォーム エンジニアを対象としています。 Google Cloudのコンテンツで使用されている一般的なロールとタスクの例の詳細については、一般的な GKE ユーザーのロールとタスクをご覧ください。

このページを読む前に、次のことをよく理解しておいてください。

背景

ジョブは、ML、レンダリング、シミュレーション、分析、CI / CD、その他の同様のワークロードなど、最後まで実行されるアプリケーションです。

Kueue は、デフォルトの Kubernetes スケジューラ、ジョブ コントローラ、クラスタ オートスケーラーと連携して、エンドツーエンドのバッチシステムを提供する、クラウド ネイティブなジョブ スケジューラです。Kueue は Job キューイングを実装しており、チーム間で公平にリソースを共有するための割り当てや階層に基づいて Job を待機するタイミングや開始するタイミングを決定します。

Kueue には次の特長があります。

  • リソースが異種で交換可能でスケーラブルであるクラウド アーキテクチャに最適化されています。
  • 弾力的な割り当てを管理し、ジョブ キューイングを管理するための一連の API を提供します。
  • 自動スケーリング、Pod スケジューリング、ジョブのライフサイクル管理などの既存の機能は再実装されません。
  • Kueue には、Kubernetesbatch/v1.Job API の組み込みサポートがあります。
  • 他のジョブ API と統合できます。

Kueue は、特定の Kubernetes Job API との混乱を避けるため、任意の API で定義されたジョブをワークロードと呼びます。

ResourceFlavor を作成する

ResourceFlavor は、ノードラベルと taint に関連付けることで、クラスタで使用可能なノードのバリエーションを表すオブジェクトです。たとえば、ResourceFlavors を使用して、異なるプロビジョニングの保証(例えば、Spot とオンデマンド)、アーキテクチャ(例えば、x86 と ARM の CPU)、ブランド、モデル(例えば、Nvidia A100 と T4 GPU)を持つ VM を表すことができます。

このチュートリアルでは、kueue-autopilot クラスタに均質なリソースがあります。その結果、CPU、メモリ、エフェメラル ストレージ、GPU 用に、ラベルや taint のない単一の ResourceFlavor を作成します。

apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: default-flavor # This ResourceFlavor will be used for all the resources
ResourceFlavor をデプロイします。

kubectl apply -f flavors.yaml

ClusterQueue を作成する

ClusterQueue は、CPU、メモリ、GPU などのリソースのプールを管理するクラスタ スコープド オブジェクトです。ResourceFlavors を管理して使用量を制限し、ワークロードが許可される順序を指定します。

apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: cluster-queue
spec:
  namespaceSelector: {} # Available to all namespaces
  queueingStrategy: BestEffortFIFO # Default queueing strategy
  resourceGroups:
  - coveredResources: ["cpu", "memory", "nvidia.com/gpu", "ephemeral-storage"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 10
      - name: "memory"
        nominalQuota: 10Gi
      - name: "nvidia.com/gpu"
        nominalQuota: 10
      - name: "ephemeral-storage"
        nominalQuota: 10Gi

ClusterQueue をデプロイするには:

kubectl apply -f cluster-queue.yaml

使用順序は .spec.queueingStrategy によって決定されます。ここで、次の 2 つの構成があります。

  • BestEffortFIFO

    • デフォルトのキューイング戦略構成。
    • ワークロードの承諾は、先入れ先出し(FIFO)ルールに従いますが、キューの先頭にワークロードを承諾できるだけの十分な割り当てがない場合は、その次の行が試行されます。
  • StrictFIFO

    • FIFO のセマンティクスを保証します。
    • キューの先頭のワークロードは、ワークロードが承諾されるまでキューをブロックできます。

cluster-queue.yaml で、cluster-queue という新しい ClusterQueue を作成します。この ClusterQueue は、flavors.yaml で作成されたフレーバーを使用して、cpumemorynvidia.com/gpuephemeral-storage の 4 つのリソースを管理します。割り当ては、ワークロード Pod 仕様のリクエストによって消費されます。

各フレーバーには、.spec.resourceGroups[].flavors[].resources[].nominalQuota で表される使用制限が含まれています。この場合、ClusterQueue は、次の場合にのみワークロードを承諾します。

  • CPU リクエストの合計が 10 以下
  • メモリ リクエストの合計が 10 Gi 以下
  • GPU リクエストの合計が 10 以下
  • ストレージ; 保存容量の合計が 10Gi 以下

LocalQueue を作成する

LocalQueue は、Namespace 内のユーザーからのワークロードを受け入れる Namespace オブジェクトです。異なる Namespace の LocalQueue は、リソースの割り当てを共有できる同じ ClusterQueue を指すことができます。この場合、Namespace team-ateam-b の LocalQueue は、.spec.clusterQueue の下の同じ ClusterQueue cluster-queue を指します。

apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: team-a # LocalQueue under team-a namespace
  name: lq-team-a
spec:
  clusterQueue: cluster-queue # Point to the ClusterQueue
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: team-b # LocalQueue under team-b namespace
  name: lq-team-b
spec:
  clusterQueue: cluster-queue # Point to the ClusterQueue

各チームは、ワークロードを独自の Namespace 内の LocalQueue に送信します。ClusterQueue によってリソースが割り当てられます。

LocalQueues をデプロイします。

kubectl apply -f local-queue.yaml

Job を作成して許可されたワークロードをモニタリングする

このセクションでは、Namespace team-a に Kubernetes Job を作成します。Kubernetes の Job コントローラは、1 つ以上の Pod を作成し、特定のタスクが正常に実行されるようにします。

Namespace team-a の Job には次の属性があります。

  • これは lq-team-a LocalQueue を指します。
  • nodeSelector フィールドを nvidia-tesla-t4 に設定して、GPU リソースをリクエストします。
  • これは、並行して 10 秒間スリープする 3 つの Pod で構成されています。Job は、ttlSecondsAfterFinished フィールドで定義された値に従って 60 秒後に削除されます。
  • この Job には、3 つの Pod があるため、1,500 milliCPU、1,536 Mi のメモリ、1,536 Mi のエフェメラル ストレージ、3 つの GPU が必要です。
apiVersion: batch/v1
kind: Job
metadata:
  namespace: team-a # Job under team-a namespace
  generateName: sample-job-team-a-
  annotations:
    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:
      nodeSelector:
        cloud.google.com/gke-accelerator: "nvidia-tesla-t4" # Specify the GPU hardware
      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"
            ephemeral-storage: "512Mi"
            nvidia.com/gpu: "1"
          limits:
            cpu: "500m"
            memory: "512Mi"
            ephemeral-storage: "512Mi"
            nvidia.com/gpu: "1"
      restartPolicy: Never

Job はファイル job-team-b.yaml の下に作成され、その Namespace は team-b に属し、異なるニーズが異なるチームを表すリクエストがあります。

詳細については、Autopilot での GPU ワークロードのデプロイをご覧ください。

  1. 新しいターミナルで、2 秒ごとに更新される ClusterQueue のステータスを確認します。

    watch -n 2 kubectl get clusterqueue cluster-queue -o wide
    
  2. 新しいターミナルで、ノードのステータスを確認します。

    watch -n 2 kubectl get nodes -o wide
    
  3. 新しいターミナルで、Namespace team-ateam-b から LocalQueue への Job を 10 秒ごとに作成します。

    ./create_jobs.sh job-team-a.yaml job-team-b.yaml 10
    
  4. Job がキューに格納され、ClusterQueue に承諾され、ノードが GKE Autopilot とともに起動することを確認します。

  5. Namespace team-a から Job を取得します。

    kubectl -n team-a get jobs
    

    結果は次のようになります。

    NAME                      COMPLETIONS   DURATION   AGE
    sample-job-team-b-t6jnr   3/3           21s        3m27s
    sample-job-team-a-tm7kc   0/3                      2m27s
    sample-job-team-a-vjtnw   3/3           30s        3m50s
    sample-job-team-b-vn6rp   0/3                      40s
    sample-job-team-a-z86h2   0/3                      2m15s
    sample-job-team-b-zfwj8   0/3                      28s
    sample-job-team-a-zjkbj   0/3                      4s
    sample-job-team-a-zzvjg   3/3           83s        4m50s
    
  6. 前の手順の Job 名をコピーし、Workloads API を通じて Job の承諾ステータスとイベントを確認します。

    kubectl -n team-a describe workload JOB_NAME
    
  7. 保留中の Job が ClusterQueue から増加し始めたら、実行中のスクリプトで CTRL + C を押してスクリプトを終了します。

  8. すべての Job が完了したら、ノードがスケールダウンされています。