マルチクラスタ Ray Serve と GKE Inference Gateway を使用して LLM をサービングする

このドキュメントでは、Kubernetes Gateway API と GKE Inference Gateway を構成して、Google Kubernetes Engine(GKE)上の複数の Ray Serve クラスタで推論リクエストを管理する方法について説明します。この構成により、複数のチームのトラフィック管理を一元化し、容量を増やすためにワークロードをリージョンに分散し、リクエスト本文の内容に基づいてモデル認識ルーティングを実装できます。

GKE Inference Gateway と Ray Serve を使用するメリット

GKE Inference Gateway と Ray Serve を使用すると、次のメリットがあります。

  • パスルーティング: 各 RayService をパス接頭辞で構成し、複数の Ray Service にルーティングする 1 つの Gateway で提供します。
  • モデル対応ルーティング: リクエスト本文に基づいてルーティングする RayService を選択します。たとえば、OpenAI-API JSON リクエストからリクエストされたモデルを抽出します。
  • ガバナンス: サービスを使用するために API キーを要求するか、認証と API 管理に Apigee を使用してユーザーの割り当てを適用します。
  • マルチリージョン: マルチクラスタ Gateway を使用して、RayService で複数の GKE クラスタにトラフィックを分散し、可用性または容量を向上させます。
  • 関心の分離: 別々のチームが管理し、別々のロールアウトに従い、異なるトポロジで実行できる���別の RayService を使用します。
  • セキュリティ: Gateway を SSL ターミネータとして使用して、インターネット経由のユーザー トラフィックを保護します。詳細については、Gateway のセキュリティをご覧ください。

ルーティングを構成するには、Gateway、HTTPRoute、RayService をデプロイする必要があります。通常、各ターゲット Ray クラスタの Kubernetes Service は KubeRay によって作成されます。Ray Serve は、InferencePool やエンドポイント選択ツールを作成することなく、クラスタ内でリクエスト負荷を分散します。

GKE の Ray Serve のモデル認識ルーティング

モデル認識ルーティングは、本文ベースのルーティング拡張機能によって有効になります。ボディベースのルーティングを使用すると、ユーザーのリクエストで指定されたモデルに基づいてトラフィックを異なる RayService に転送できます。これにより、複数の Ray クラスタでホストされている多くのモデルを処理できる単一のエンドポイントを作成できます。ユーザーはアクセスが簡素化され、アプリ デベロッパーは各 Ray エンドポイントの構成を制御できます。

モデル認識ルーティングを構成するには、次の主要コンポーネントをデプロイします。

  • JSON ペイロードからモデル名を抽出する本文ベースのルーター拡張機能。このルーター拡張機能は、Helm を使用してデプロイされます。
  • 受信トラフィックを処理する GKE Gateway(L7 リージョン内部アプリケーション ロードバランサ)。
  • ルーター拡張機能によって入力されたヘッダーを使用して、トラフィックを正しい Ray Service に転送する HTTPRoute ルール。
  • サイロ化されたモデルのライフサイクルと自動スケーリングを管理する複数の Ray Serve クラスタ。

始める前に

作業を始める前に、次のタスクが完了していることを確認してください。

  • Google Kubernetes Engine API を有効にする。
  • Google Kubernetes Engine API を有効化
  • このタスクに Google Cloud CLI を使用する場合は、gcloud CLI をインストールして初期化する。gcloud CLI をインストール済みの場合は、gcloud components update コマンドを実行して最新のバージョンを取得します。以前のバージョンの gcloud CLI では、このドキュメントのコマンドを実行できない場合があります。
  • Helm がインストールされていることを確認します。
  • Hugging Face アカウントを作成します(まだ作成していない場合)。
  • Hugging Face トークンがあることを確認します。

環境を準備する

環境変数を設定します。

export CLUSTER=$(whoami)-ray-bbr
export PROJECT_ID=$(gcloud config get-value project)
export LOCATION=us-central1-b
export REGION=us-central1
export HUGGING_FACE_TOKEN=YOUR_HUGGING_FACE_TOKEN

YOUR_HUGGING_FACE_TOKEN は、Hugging Face アクセス トークンに置き換えます。

インフラストラクチャを準備する

このセクションでは、L4 GPU を使用して Ray 対応、Gateway 対応の GKE クラスタを設定します。

  1. Ray Operator と Gateway API を有効にしてクラスタを作成します。

    gcloud container clusters create ${CLUSTER} \
        --project ${PROJECT_ID} \
        --location ${LOCATION} \
        --cluster-version 1.35 \
        --gateway-api standard \
        --addons HttpLoadBalancing,RayOperator \
        --enable-ray-cluster-logging \
        --enable-ray-cluster-monitoring \
        --machine-type e2-standard-4
    
  2. モデル ワークロ��ド用の GPU ノードプールを作成します。

    gcloud container node-pools create gpu-pool \
        --cluster=${CLUSTER} \
        --location=${LOCATION} \
        --accelerator="type=nvidia-l4,count=1,gpu-driver-version=latest" \
        --machine-type=g2-standard-8 \
        --num-nodes=4
    
  3. リージョン内部アプリケーション ロードバランサのプロキシ専用サブネットを作成します。これは、ボディベースのルーティングに必要です。

    gcloud compute networks subnets create bbr-proxy-only-subnet \
        --purpose=REGIONAL_MANAGED_PROXY \
        --role=ACTIVE \
        --region=${REGION} \
        --network=default \
        --range=192.168.10.0/24
    
  4. Hugging Face シークレットをデプロイします。

    kubectl create secret generic hf-secret \
        --from-literal=hf_api_token=${HUGGING_FACE_TOKEN}
    

モデル対応のルーティング用に本文ベースのルーターをデプロイする

本文ベースのルーター拡張機能は、リクエストをインターセプトし、JSON 本文を解析して、モデル フィールドを X-Gateway-Model-Name ヘッダーに抽出します。

  1. 次の内容で helm-values.yaml という名前のファイルを作成します。

    bbr:
      plugins:
        - type: "body-field-to-header"
          name: "openai-model-extractor"
          json:
            field_name: "model"
            header_name: "X-Gateway-Model-Name"
    
  2. Helm を使用して本文ベースのルーターをインストールします。

    helm install body-based-router \
        oci://registry.k8s.io/gateway-api-inference-extension/charts/body-based-routing \
        --version v1.4.0 \
        --set provider.name=gke \
        --set inferenceGateway.name=ray-multi-model-gateway \
        --values helm-values.yaml
    

RayService をデプロイする

モデルをデプロイするには、RayService マニフェストを適用する必要があります。各マニフェストは、特定の LLM を実行する Ray クラスタを定義します。

  1. 次の内容で gemma-2b-it.yaml という名前のファイルを作成します。

    apiVersion: ray.io/v1
    kind: RayService
    metadata:
      name: gemma-2b-it
    spec:
      serveConfigV2: |
        applications:
        - name: llm_app
          route_prefix: "/"
          import_path: ray.serve.llm:build_openai_app
          args:
            llm_configs:
                - model_loading_config:
                    model_id: gemma-2b-it
                    model_source: google/gemma-2b-it
                  accelerator_type: L4
                  log_engine_metrics: true
                  deployment_config:
                    autoscaling_config:
                        min_replicas: 2
                        max_replicas: 2
                    health_check_period_s: 600
                    health_check_timeout_s: 300
      rayClusterConfig:
        headGroupSpec:
          rayStartParams:
            dashboard-host: "0.0.0.0"
            num-cpus: "0"
          template:
            spec:
              containers:
                - name: ray-head
                  image: rayproject/ray-llm:2.54.0-py311-cu128
                  resources:
                    limits:
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                    requests:
                      cpu: "2"
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                  ports:
                    - containerPort: 6379
                      name: gcs-server
                    - containerPort: 8265
                      name: dashboard
                    - containerPort: 10001
                      name: client
                    - containerPort: 8000
                      name: serve
                  env:
                    - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                      value: "1"
                    - name: RAY_SERVE_ENABLE_HA_PROXY
                      value: "1"
                    - name: HUGGING_FACE_HUB_TOKEN
                      valueFrom:
                        secretKeyRef:
                          name: hf-secret
                          key: hf_api_token
        rayVersion: 2.54.0
        workerGroupSpecs:
          - replicas: 2
            minReplicas: 2
            maxReplicas: 2
            groupName: gpu-group
            rayStartParams: {}
            template:
              spec:
                containers:
                  - name: llm
                    image: rayproject/ray-llm:2.54.0-py311-cu128
                    env:
                      - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                        value: "1"
                      - name: RAY_SERVE_ENABLE_HA_PROXY
                        value: "1"
                      - name: HUGGING_FACE_HUB_TOKEN
                        valueFrom:
                          secretKeyRef:
                            name: hf-secret
                            key: hf_api_token
                    resources:
                      limits:
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                      requests:
                        cpu: "6"
                        memory: "24Gi"
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                nodeSelector:
                  cloud.google.com/gke-accelerator: nvidia-l4
    
  2. 次の内容で qwen2.5-3b.yaml という名前のファイルを作成します。

    apiVersion: ray.io/v1
    kind: RayService
    metadata:
      name: qwen-25-3b
    spec:
      serveConfigV2: |
        applications:
        - name: llm_app
          route_prefix: "/"
          import_path: ray.serve.llm:build_openai_app
          args:
            llm_configs:
                - model_loading_config:
                    model_id: qwen-2.5-3b
                    model_source: Qwen/Qwen2.5-3B
                  accelerator_type: L4
                  log_engine_metrics: true
                  deployment_config:
                    autoscaling_config:
                        min_replicas: 2
                        max_replicas: 2
                    health_check_period_s: 600
                    health_check_timeout_s: 300
      rayClusterConfig:
        headGroupSpec:
          rayStartParams:
            dashboard-host: "0.0.0.0"
            num-cpus: "0"
          template:
            spec:
              containers:
                - name: ray-head
                  image: rayproject/ray-llm:2.54.0-py311-cu128
                  resources:
                    limits:
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                    requests:
                      cpu: "2"
                      memory: "8Gi"
                      ephemeral-storage: "32Gi"
                  ports:
                    - containerPort: 6379
                      name: gcs-server
                    - containerPort: 8265
                      name: dashboard
                    - containerPort: 10001
                      name: client
                    - containerPort: 8000
                      name: serve
                  env:
                    - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                      value: "1"
                    - name: RAY_SERVE_ENABLE_HA_PROXY
                      value: "1"
                    - name: HUGGING_FACE_HUB_TOKEN
                      valueFrom:
                        secretKeyRef:
                          name: hf-secret
                          key: hf_api_token
        rayVersion: 2.54.0
        workerGroupSpecs:
          - replicas: 2
            minReplicas: 2
            maxReplicas: 2
            groupName: gpu-group
            rayStartParams: {}
            template:
              spec:
                containers:
                  - name: llm
                    image: rayproject/ray-llm:2.54.0-py311-cu128
                    env:
                      - name: RAY_SERVE_THROUGHPUT_OPTIMIZED
                        value: "1"
                      - name: RAY_SERVE_ENABLE_HA_PROXY
                        value: "1"
                      - name: HUGGING_FACE_HUB_TOKEN
                        valueFrom:
                          secretKeyRef:
                            name: hf-secret
                            key: hf_api_token
                    resources:
                      limits:
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                      requests:
                        cpu: "6"
                        memory: "24Gi"
                        nvidia.com/gpu: "1"
                        ephemeral-storage: "24Gi"
                nodeSelector:
                  cloud.google.com/gke-accelerator: nvidia-l4
    
  3. モデルをデプロイします。

    kubectl apply -f gemma-2b-it.yaml
    kubectl apply -f qwen2.5-3b.yaml
    

ヘルスチェックを構成する

ロードバランサが Ray ワーカーの健全性を正確にモニタリングできるようにするには、HealthCheckPolicy リソースを適用する必要があります。

  1. 次の内容で healthcheck-policy.yaml という名前のファイルを作成します。

    apiVersion: networking.gke.io/v1
    kind: HealthCheckPolicy
    metadata:
      name: gemma-serve-healthcheck
      namespace: default
    spec:
      default:
        checkIntervalSec: 5
        timeoutSec: 5
        healthyThreshold: 2
        unhealthyThreshold: 2
        config:
          type: HTTP
          httpHealthCheck:
            port: 8000
            requestPath: /-/healthz
      targetRef:
        group: ""
        kind: Service
        name: gemma-2b-it-serve-svc
    ---
    apiVersion: networking.gke.io/v1
    kind: HealthCheckPolicy
    metadata:
      name: qwen-serve-healthcheck
      namespace: default
    spec:
      default:
        checkIntervalSec: 5
        timeoutSec: 5
        healthyThreshold: 2
        unhealthyThreshold: 2
        config:
          type: HTTP
          httpHealthCheck:
            port: 8000
            requestPath: /-/healthz
      targetRef:
        group: ""
        kind: Service
        name: qwen-25-3b-serve-svc
    
  2. ヘルスチェック ポリシーを適用します。

    kubectl apply -f healthcheck-policy.yaml
    

ルーティングを構成する

��ーティングを構成するには、Gateway マニフェストと HTTPRoute マニフェストを適用する必要があります。HTTPRoute には、X-Gateway-Model-Name ヘッダー(ボディベースのルーターによって入力される)と一致するルールが含まれており、トラフィックを適切な Ray サービスに転送します。

  1. 次の内容で gateway.yaml という名前のファイルを作成します。

    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: ray-multi-model-gateway
      namespace: default
    spec:
      gatewayClassName: gke-l7-rilb
      listeners:
      - allowedRoutes:
          namespaces:
            from: Same
        name: http
        port: 80
        protocol: HTTP
    ---
    apiVersion: gateway.networking.k8s.io/v1
    kind: HTTPRoute
    metadata:
      name: ray-multi-model-route
    spec:
      parentRefs:
      - name: ray-multi-model-gateway
      rules:
      - matches:
        - headers:
          - type: Exact
            name: X-Gateway-Model-Name
            value: gemma-2b-it  # Must match model named in JSON request!
          path:
            type: PathPrefix
            value: /
        backendRefs:
        - name: gemma-2b-it-serve-svc  # Ray service name plus "-serve-svc".
          kind: Service
          port: 8000
    
      - matches:
        - headers:
          - type: Exact
            name: X-Gateway-Model-Name
            value: qwen-2.5-3b  # Matches another extracted model name
          path:
            type: PathPrefix
            value: /
        backendRefs:
        - name: qwen-25-3b-serve-svc  # Target Ray Service.
          kind: Service
          port: 8000
    
  2. ゲートウェイとルートを適用します。

    kubectl apply -f gateway.yaml
    

Deployment をテストする

Gateway がプロビジョニングされ、両方の Ray クラスタの準備ができたら、JSON 本文で異なるモデル名を使用してリクエストを送信し、ルーティングをテストできます。

  1. Gateway の IP アドレスを取得します。

    kubectl get gateways ray-multi-model-gateway
    
  2. Gateway アドレスに到達できるネットワークでシェルを起動します。Ray クラスタ Pod のいずれかで curl を使用できます。

    POD_NAME=$(kubectl get pods -l ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}')
    kubectl exec -it $POD_NAME -- bash
    
  3. Gemma へのルーティングをテストしてリクエストを送信します。

    curl http://GATEWAY_IP_ADDRESS/v1/chat/completions \
        --header 'Content-Type: application/json' \
        --data '{
        "model": "gemma-2b-it",
        "messages": [{"role": "user", "content": "Tell me about GKE."}]
        }'
    

    GATEWAY_IP_ADDRESS は、前の手順で取得した IP アドレスに置き換えます。

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

    {"id":"chatcmpl-594f7cab-f991-4522-9829-acdbb65d9f67","object":"chat.completion","created":1776379509,"model":"gemma-2b-it","choices":[{"index":0,"message":{"role":"assistant","content":"**Google Kubernetes Engine (GKE)** is a fully managed container orchestration service for Kubernetes [...]
    
  4. Qwen へのルーティングをテストします。

    curl http://GATEWAY_IP_ADDRESS/v1/chat/completions \
        --header 'Content-Type: application/json' \
        --data '{
        "model": "qwen-2.5-3b",
        "messages": [{"role": "user", "content": "How does Ray Serve work?"}]
        }'
    

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

    {"id":"chatcmpl-dfe3f3b7-45fc-481c-b53e-2fc09c033cdb","object":"chat.completion","created":1776380249,"model":"qwen-2.5-3b","choices":[{"index":0,"message":{"role":"assistant","content":"Ray Serve facilitates the hosting and deployment of scalable microservices. [...]
    

ボディベースのルーターは、model フィールドの値を自動的に抽出し、各リクエストが gateway.yaml ファイルで構成された正しいバックエンド サービスに到達するようにします。

クリーンアップ

クラスタを削除します。

gcloud container clusters delete ${CLUSTER}

次のステップ