使用多集群 Ray Serve 和 GKE Inference Gateway 提供 LLM

本文档介绍了如何通过配置 Kubernetes Gateway API 和 GKE Inference Gateway,在 Google Kubernetes Engine (GKE) 上跨多个 Ray Serve 集群管理推理请求。此配置可让您集中管理多个团队的流量,在多个区域分配工作负载以提高容量,并根据请求正文内容实现模型感知型路由。

使用 GKE 推理网关和 Ray Serve 的优势

使用 GKE 推理网关和 Ray Serve 可带来以下优势:

  • 路径路由:为每个 RayService 配置一个路径前缀,然后使用一个网关将它们路由到多个 Ray 服务。
  • 模型感知路由:根据请求正文选择要路由到的 RayService,例如通过从 OpenAI-API JSON 请求中提取所请求的模型。
  • 治理:要求使用 API 密钥才能使用您的服务,或使用 Apigee 进行身份验证和 API 管理,以强制执行用户配额。
  • 多区域:使用 RayService 通过多集群网关在多个 GKE 集群之间分配流量,以实现更高的可用性或容量。
  • 关注点分离:使用单独的 RayService,这些服务可由不同的团队管理,遵循单独的发布流程,并在不同的拓扑上运行。
  • 安全性:使用 Gateway 作为 SSL 终结器,有助于确保用户流量在互联网上的安全。如需了解详情,请参阅网关安全性

如需配置路由,您需要部署网关、HTTPRoute 和 RayService。KubeRay 通常会为每个目标 Ray 集群创建一个 Kubernetes 服务。Ray Serve 在集群内分散请求负载,无需创建 InferencePool 或 Endpoint Picker。

GKE 上 Ray Serve 的模型感知型路由

模型感知路由通过基于正文的路由扩展程序启用。基于正文的路由功能可让您仅根据用户请求中指定的模型名称将流量定向到不同的 RayService,从而让您拥有一个可为多个 Ray 集群中托管的多个模型提供服务的端点。您的用户可以更轻松地访问,而您的应用开发者可以控制每个 Ray 端点的配置。

如需配置模型感知型路由,请部署以下关键组件:

  • 一种基于正文的路由器扩展程序,用于从 JSON 载荷中提取模型名称。 此路由器扩展程序通过使用 Helm 进行部署。
  • 一个 GKE 网关(L7 区域级内部应用负载平衡器),用于处理入站流量。
  • HTTPRoute 规则,用于使用由路由器扩展程序填充的标头将流量定向到正确的 Ray 服务。
  • 多个 Ray Serve 集群,用于管理孤立模型的生命周期和自动扩缩。

准备工作

在开始之前,请确保您已执行以下任务:

  • 启用 Google Kubernetes Engine API。
  • 启用 Google Kubernetes Engine API
  • 如果您要使用 Google Cloud CLI 执行此任务,请安装初始化 gcloud CLI。如果您之前安装了 gcloud CLI,请通过运行 gcloud components update 命令来获取最新版本。较早版本的 gcloud CLI 可能不支持运行本文档中的命令。

准备环境

设置环境变量:

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 访问令牌。

准备基础架构

在本部分中,您将设置一个启用了 Ray 和 Gateway 且配备了 L4 GPU 的 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 Secret:

    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
    

配置路由

如需配置路由,您必须应用 GatewayHTTPRoute 清单。 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
    

测试部署

网关完成配置且两个 Ray 集群均准备就绪后,您可以通过在 JSON 正文中发送具有不同模型名称的请求来测试路由。

  1. 获取网关 IP 地址:

    kubectl get gateways ray-multi-model-gateway
    
  2. 在可访问网关地址的网络中启动 shell。 您可以在某个 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}

后续步骤