すべてのプロダクト
Search
ドキュメントセンター

Container Service for Kubernetes:ACK クラスターにおける RayJob 実行の自動化

最終更新日:Mar 27, 2026

リソースが限られ、優先度が競合する場合、さまざまなチームからの大量の RayJob の管理は困難になります。このガイドでは、ElasticQuotaTree と Kube Queue を使用して、チーム間の RayJob スケジューリングを自動化する方法を説明します。リソースクォータ、優先度に基づくプリエンプション、ギャングスケジューリング (協調スケジューリング) を活用することで、優先度の高いジョブが常に最初にリソースを確保できるようにします。

前提条件

開始する前に、以下をご確認ください。

重要

このガイドでは、公式の Ray イメージ rayproject/ray:2.36.1 を使用します。ネットワークの問題で Docker Hub からのイメージのプルに失敗する場合は、次のいずれかの代替策を使用してください。

仕組み

このガイドでは、3 レイヤーのリソース管理パイプラインをセットアップします。

  1. ElasticQuotaTree でクォータを定義 — 各ノードがチームまたは部門ごとの保証される最小リソースと最大上限を設定するツリー構造です。

  2. キュー作成の自動化 — Kube Queue は ElasticQuotaTree を読み取り、各リーフノードに対してキューを作成します。バインドされた名前空間で送信された RayJob は、対応するキューに自動的にルーティングされます。

  3. ジョブスケジューリングの制御suspend: true の RayJob はキューに入り待機します。ジョブのリソースリクエストが利用可能なクォータ内に収まると、Kube Queue は自動的に suspendfalse に設定し、ジョブがスケジューリングに進むことを許可します。

すべてのノードが同時に起動する必要がある分散タスクの場合、ギャングスケジューリングによって部分的な割り当てやリソースのデッドロックが防止されます。

リソースクォータの設定

ElasticQuotaTree は、クラスターのクォータ階層を定義します。ツリー内の各ノードは以下を指定します。

  • min:そのチームまたは部門に保証される最小リソース

  • max:使用できる最大リソース (利用可能な場合は兄弟ノードから借用)

ジョブの最小保証を満たせない場合、スケジューラは現在保証されている最小値を超えて使用しているクォータからリソースを回収し、優先度の高いジョブのプリエンプションを可能にします。

クラスターごとにサポートされる ElasticQuotaTree は 1 つだけであり、kube-system 名前空間に作成する必要があります。

  1. ElasticQuotaTree 構成を適用して、組織のクォータ階層を定義します。次の例では、rootalgorithm 部門 → video チームという 3 階層を作成します。video チームは video 名前空間にバインドされます。

    ---
    apiVersion: v1
    kind: Namespace
    metadata:
      name: video
    
    ---
    apiVersion: scheduling.sigs.k8s.io/v1beta1
    kind: ElasticQuotaTree
    metadata:
      name: elasticquotatree        # クラスターごとにサポートされる ElasticQuotaTree は 1 つだけです
      namespace: kube-system        # 有効にするには kube-system に作成する必要があります
    spec:
      root:
        name: root
        min:
          cpu: 100
          memory: 50Gi
          nvidia.com/gpu: 16
        max:
          cpu: 100
          memory: 50Gi
          nvidia.com/gpu: 16
        children:
        - name: algorithm
          min:
            cpu: 50
            memory: 25Gi
            nvidia.com/gpu: 10
          max:
            cpu: 80
            memory: 50Gi
            nvidia.com/gpu: 14
          children:
          - name: video
            min:
              cpu: 12
              memory: 12Gi
              nvidia.com/gpu: 2
            max:
              cpu: 14
              memory: 14Gi
              nvidia.com/gpu: 4
            namespaces:
            - video                 # この名前空間のジョブは video クォータに対してカウントされます

    intern-text または intern-video とマークされたジョブの保証最小値は 0 です。これは、algorithm チームからの優先度の高いジョブがリソースを必要とするときに、スケジューラがそれらのリソースをプリエンプトできることを意味します。

  2. ElasticQuotaTreekube-system でアクティブであることを確認します。

    kubectl -n kube-system get elasticquotatree elasticquotatree -o yaml

ジョブキューの表示

ElasticQuotaTree が適用されると、Kube Queue は各リーフノードに対して自動的にキューを作成します。algorithm 配下の video チームの場合、キュー root-algorithm-videokube-queue 名前空間に作成されます。

suspend: true を指定した RayJob が video 名前空間に送信されると、Kube Queue は次の処理を行います。

  1. 対応する QueueUnit リソースを作成します

  2. ジョブを root-algorithm-video キューに配置します

  3. 総リソース要件を計算します:Head Pod のリクエスト + (レプリカ数 × WorkerGroup ごとの単一 Pod のリクエスト)

  4. クォータが利用可能になると suspendfalse に設定し、ジョブがスケジューリングに進むことを許可します

image
  1. video チーム用に自動作成されたキューを確認します。

    kubectl get queue -n kube-queue root-algorithm-video-k42kq -o yaml

    出力は次のようになります。

    apiVersion: scheduling.x-k8s.io/v1alpha1
    kind: Queue
    metadata:
      annotations:
        kube-queue/parent-quota-fullname: algorithm
        kube-queue/quota-fullname: root/algorithm/video
      generateName: root-algorithm-video-
      name: root-algorithm-video-k42kq
      namespace: kube-queue
    spec:
      queuePolicy: Round
    status:
      queueItemDetails:
        active: []    # スケジューリングを待機しているジョブ、優先度順
        backoff: []   # スケジューリング試行の失敗後にリトライを待機しているジョブ
  2. すべてのキューをリスト表示して、完全な階層を確認します。

    kubectl get queue -n kube-queue

    出力は次のようになります。

    NAME                               AGE
    root-algorithm-n54fm               51s
    root-algorithm-text-hgbvz          51s
    root-algorithm-video-k42kq         51s
    root-devops-2zccw                  51s
    root-infrastructure-devops-d6zqq   51s
    root-infrastructure-vbpkt          51s
    root-k8htb                         51s

RayJob の作成と送信

ConfigMap の作成

video 名前空間に ConfigMap を作成し、RayJob が RayCluster で実行する Python コードを定義します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: rayjob-video
  namespace: video
data:
  sample_code.py: |
    import ray
    import os
    import requests

    ray.init()

    @ray.remote
    class Counter:
        def __init__(self):
            # runtimeEnv を検証するために使用
            self.name = os.getenv("counter_name")
            assert self.name == "test_counter"
            self.counter = 0

        def inc(self):
            self.counter += 1

        def get_counter(self):
            return "{} got {}".format(self.name, self.counter)

    counter = Counter.remote()

    for _ in range(2):
        ray.get(counter.inc.remote())
        print(ray.get(counter.get_counter.remote()))

    # ジョブに正しいランタイム環境が使用されたことを確認
    assert requests.__version__ == "2.26.0"

RayJob の送信

この RayJob マニフェストを使用して、video 名前空間にジョブを送信します。ここで送信されたジョブは、自動的に root-algorithm-video キューと video リソースクォータに関連付けられます。

apiVersion: ray.io/v1
kind: RayJob
metadata:
  labels:
    job-type: video
  generateName: rayjob-video-
  namespace: video
spec:
  entrypoint: python /home/ray/samples/sample_code.py
  runtimeEnvYAML: |
    pip:
      - requests==2.26.0
      - pendulum==2.1.2
    env_vars:
      counter_name: "test_counter"

  # ジョブが終了してから 10 秒後に RayJob を削除
  ttlSecondsAfterFinished: 10

  # 必須:ジョブ終了後に RayCluster をシャットダウンします。
  # リソースリークを防ぎます。キューに入れられたジョブでは true である必要があります。
  shutdownAfterJobFinishes: true

  # 必須:suspend: true の RayJob のみが Kube Queue に入ります。
  # クォータが利用可能になると、Kube Queue はこれを自動的に false に設定します。
  suspend: true

  submissionMode: K8sJobMode

  rayClusterSpec:
    rayVersion: '2.36.1'
    headGroupSpec:
      rayStartParams:
        dashboard-host: '0.0.0.0'
        num-cpus: "0"
      template:
        spec:
          containers:
            - name: ray-head
              image: rayproject/ray:2.36.1
              ports:
                - containerPort: 6379
                  name: gcs-server
                - containerPort: 8265
                  name: dashboard
                - containerPort: 10001
                  name: client
              resources:
                limits:
                  cpu: "4"
                  memory: 4G
                requests:
                  cpu: "4"
                  memory: 4G
              volumeMounts:
                - mountPath: /home/ray/samples
                  name: code-sample
          volumes:
            - name: code-sample
              configMap:
                name: rayjob-video
                items:
                  - key: sample_code.py
                    path: sample_code.py
    workerGroupSpecs:
      - replicas: 2
        groupName: small-group
        rayStartParams: {}
        template:
          spec:
            containers:
              - name: ray-worker
                image: rayproject/ray:2.36.1
                lifecycle:
                  preStop:
                    exec:
                      command: ["/bin/sh", "-c", "ray stop"]
                resources:
                  limits:
                    cpu: "4"
                    memory: 4G
                  requests:
                    cpu: "4"
                    memory: 4G

キューイング動作を制御する主要なパラメーターは次のとおりです。

パラメーター説明
namespace: video名前空間を video に設定し、ジョブを root-algorithm-video キューと video リソースクォータに自動的に関連付けます。
submissionMode: K8sJobModeRayJob をすぐに実行するのではなく、キューに入れます。suspend: true のジョブのみが KubeQueue によって管理されます。
suspend: trueRayJob をキューに配置します。クォータが利用可能になると、Kube Queue はこれを自動的に false に設定します。
shutdownAfterJobFinishes: trueキューに入れられたジョブに必須です。ジョブの終了後に RayCluster を削除して、リソースリークを防ぎます。
ttlSecondsAfterFinished: 10ジョブ完了後、RayJob を削除するまでの待機秒数。

キューイング動作の確認

  1. kubectl create -f を使用して 2 つの RayJob を作成します。

    kubectl get rayjob -n video

    期待される出力:

    NAME                 JOB STATUS   DEPLOYMENT STATUS   START TIME             END TIME   AGE
    rayjob-video-g2lvn                Initializing        2025-01-10T01:36:24Z              6s
    rayjob-video-h4x2q                Suspended           2025-01-10T01:36:25Z              5s

    rayjob-video-g2lvn はすぐにデキューされ、初期化中です。rayjob-video-h4x2q はまだ一時停止状態です — キューには入りましたが、クォータを待機しています。

  2. 最初のジョブのエンキューとデキューのタイムスタンプを確認します。

    kubectl -n video get rayjob rayjob-video-g2lvn -o yaml

    アノテーションには両方のタイムスタンプが表示され、デキューされたことが確認できます。

    annotations:
      kube-queue/job-dequeue-timestamp: 2025-01-10 01:36:24.641181026 +0000 UTC
      kube-queue/job-enqueue-timestamp: 2025-01-10 01:36:24.298639916 +0000 UTC

    2 番目のジョブについては、エンキューのタイムスタンプのみが表示されます — デキューのタイムスタンプがないことは、まだスケジュールされていないことを意味します。

    kubectl -n video get rayjob rayjob-video-h4x2q -o yaml
    annotations:
      kube-queue/job-enqueue-timestamp: 2025-01-10 01:36:25.505182364 +0000 UTC
  3. どの Pod が実行中かを確認します。

    kubectl -n video get pod

    期待される出力 — 最初のジョブの Pod のみが実行中です。

    NAME                                                           READY   STATUS    RESTARTS   AGE
    rayjob-video-g2lvn-9gz66                                       1/1     Running   0          28s
    rayjob-video-g2lvn-raycluster-v8tfh-head-6trq5                 1/1     Running   0          49s
    rayjob-video-g2lvn-raycluster-v8tfh-small-group-worker-hkt7m   1/1     Running   0          49s
    rayjob-video-g2lvn-raycluster-v8tfh-small-group-worker-rbzjn   1/1     Running   0          49s
  4. キューをチェックして、2 番目のジョブが待機していることを確認します。

    kubectl -n kube-queue get queue root-algorithm-video-k42kq -o yaml

    2 番目のジョブは backoff に表示され、リソースが解放されるのを待っています。

    status:
      queueItemDetails:
        active: []
        backoff:
        - name: rayjob-video-h4x2q-ray-qu
          namespace: video
          position: 1

ギャングスケジューリングの有効化

ギャングスケジューリングは、分散ジョブ内のすべての Pod が同時にスケジュールされることを保証します。これがないと、部分的なスケジューリングによって、ジョブが残りのリソースを無期限に待機する可能性があります。

ワークロードですべてのノードが同時に起動する必要がある場合は、ギャングスケジューリングを使用します。

  • 大規模な ML トレーニング:すべてのワーカーが一緒に起動する必要があるマルチノードトレーニングジョブ

  • MPI ジョブ:起動時から通信するマスタープロセスとワーカープロセス

  • ストリーミングと分析:多くのノードでデータを並行して処理するジョブ

  • カスタム分散アプリケーション:ゲームのマッチメイキングサービス、IoT データ収集パイプライン

ギャングスケジューリングを有効にするには、RayJob のメタデータに ray.io/scheduler-name: kube-scheduler ラベルを追加します。KubeRay Operator は、作成するすべての Pod に必要なギャングスケジューリングラベルを自動的に挿入します。

apiVersion: ray.io/v1
kind: RayJob
metadata:
  generateName: rayjob-sample-
  namespace: algorithm-text
  labels:
    ray.io/scheduler-name: kube-scheduler          # ギャングスケジューリングを有効化
    quota.scheduling.alibabacloud.com/name: algorithm-video  # このクォータにバインド
spec:
  entrypoint: python /home/ray/samples/sample_code.py
  runtimeEnvYAML: |
    pip:
      - requests==2.26.0
      - pendulum==2.1.2
    env_vars:
      counter_name: "test_counter"
  shutdownAfterJobFinishes: true
  suspend: true

  rayClusterSpec:
    rayVersion: '2.9.0'
    headGroupSpec:
      rayStartParams:
        dashboard-host: '0.0.0.0'
      template:
        spec:
          containers:
            - name: ray-head
              image: rayproject/ray:2.9.0
              ports:
                - containerPort: 6379
                  name: gcs-server
                - containerPort: 8265
                  name: dashboard
                - containerPort: 10001
                  name: client
              resources:
                limits:
                  cpu: "1"
                requests:
                  cpu: "1"
              volumeMounts:
                - mountPath: /home/ray/samples
                  name: code-sample
          volumes:
            - name: code-sample
              configMap:
                name: ray-job-code-sample
                items:
                  - key: sample_code.py
                    path: sample_code.py
    workerGroupSpecs:
      - replicas: 30
        groupName: small-group
        rayStartParams: {}
        template:
          spec:
            containers:
              - name: ray-worker
                image: rayproject/ray:2.9.0
                lifecycle:
                  preStop:
                    exec:
                      command: ["/bin/sh", "-c", "ray stop"]
                resources:
                  limits:
                    cpu: "1"
                  requests:
                    cpu: "1"
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: ray-job-code-sample
  namespace: algorithm-text
data:
  sample_code.py: |
    import ray
    import os
    import requests

    ray.init()

    @ray.remote
    class Counter:
        def __init__(self):
            self.name = os.getenv("counter_name")
            assert self.name == "test_counter"
            self.counter = 0

        def inc(self):
            self.counter += 1

        def get_counter(self):
            return "{} got {}".format(self.name, self.counter)

    counter = Counter.remote()

    for _ in range(5):
        ray.get(counter.inc.remote())
        print(ray.get(counter.get_counter.remote()))

    assert requests.__version__ == "2.26.0"

    import time
    time.sleep(30)

KubeRay Operator がこのジョブの Pod を作成すると、ギャングスケジューリングを有効にするために次のラベルを挿入します。

ラベル目的
pod-group.scheduling.sigs.k8s.io/min-available31 (Head 1 つ + Worker 30 個)一緒にスケジュールする必要がある最小 Pod 数
pod-group.scheduling.sigs.k8s.io/nameRayCluster 名Pod を同じスケジューリングユニットにグループ化します
quota.scheduling.alibabacloud.com/namealgorithm-videoPod を指定されたクォータにバインドします
scheduling.x-k8s.io/pod-groupRayCluster 名スケジューラ用の Pod グループ参照

ギャングスケジューリング失敗のトラブルシューティング

リソースが不足している場合、スケジューラは配置できなかった各 Pod に対して GangFailedScheduling 警告イベントをログに記録します。次のコマンドを実行してこれらのイベントをフィルターし、原因を特定します。

kubectl get events -n algorithm-text \
  --field-selector='type=Warning,reason=GangFailedScheduling' \
  | grep "cycle 1"

期待される出力:

5m48s  Warning  GangFailedScheduling  pod/rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-89mlq  rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-89mlq in gang failed to be scheduled in cycle 1: 0/0 nodes are available: 3 Insufficient cpu.
5m48s  Warning  GangFailedScheduling  pod/rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-8fwmr  rayjob-sample-dtmtl-raycluster-r9jc7-small-group-worker-8fwmr in gang failed to be scheduled in cycle 1: 0/0 nodes are available: 3 Insufficient cpu.

各イベントには、スケジューリングの試行を識別する cycle xx 番号と、Pod を配置できなかった理由が含まれています。これを使用して、問題が CPU、メモリ、GPU の不足、または許容されないノードの Taint であるかどうかを判断します。

次のステップ