En este documento, se explica cómo administrar las solicitudes de inferencia en varios clústeres de Ray Serve en Google Kubernetes Engine (GKE) mediante la configuración de la API de Kubernetes Gateway y la puerta de enlace de inferencia de GKE. Esta configuración te permite centralizar la administración del tráfico para varios equipos, distribuir cargas de trabajo en diferentes regiones para obtener una mayor capacidad y, también, implementar el enrutamiento compatible con el modelo según el contenido del cuerpo de la solicitud.
Beneficios de usar la puerta de enlace de inferencia de GKE y Ray Serve
El uso de la puerta de enlace de inferencia de GKE y Ray Serve ofrece los siguientes beneficios:
- Enrutamiento de rutas: Configura cada RayService con un prefijo de ruta y, luego, entrégalos
con una puerta de enlace que enrute a varios servicios de Ray.
- Para obtener más información sobre cómo configurar reglas de prefijo de ruta, consulta la documentación de la API de Gateway.
- Enrutamiento compatible con el modelo: Elige un RayService al que enrutar según el cuerpo de la solicitud, por ejemplo, extrayendo el modelo solicitado de una solicitud JSON de la API de OpenAI.
- Administración: Requiere claves de API para usar tu servicio o aplica cuotas para los usuarios con Apigee para la autenticación y la administración de APIs.
- Multirregión: Divide el tráfico en varios clústeres de GKE con RayServices para lograr una mayor disponibilidad o capacidad con puertas de enlace de varios clústeres.
- Separación de intereses: Usa RayServices independientes, que pueden ser administrados por equipos separados, seguir implementaciones separadas y ejecutarse en diferentes topologías.
- Seguridad: Usa la puerta de enlace para que actúe como el terminador de SSL y te ayude a proteger el tráfico de los usuarios a través de Internet. Para obtener más información, consulta Seguridad de la puerta de enlace.
Para configurar el enrutamiento, debes implementar una puerta de enlace, una HTTPRoute y un RayService. KubeRay suele crear un servicio de Kubernetes para cada clúster de Ray de destino. Ray Serve distribuye la carga de solicitudes en el clúster, sin necesidad de crear un InferencePool ni un selector de extremos.
Enrutamiento compatible con el modelo para Ray Serve en GKE
El enrutamiento compatible con el modelo se habilita mediante una extensión de enrutamiento basada en el cuerpo. El enrutamiento basado en el cuerpo te permite dirigir el tráfico a diferentes RayServices en función del modelo que se menciona en la solicitud del usuario, lo que te permite tener un solo extremo que puede entregar muchos modelos alojados en varios clústeres de Ray. Tus usuarios tienen acceso simplificado y los desarrolladores de tu app tienen control sobre la configuración de cada extremo de Ray.
Para configurar el enrutamiento compatible con el modelo, debes implementar los siguientes componentes clave:
- Una extensión de enrutador basada en el cuerpo para extraer nombres de modelos de cargas útiles JSON. Esta extensión de enrutador se implementa con Helm.
- Una puerta de enlace de GKE (balanceador de cargas de aplicaciones interno regional de L7) para controlar el tráfico entrante.
- Reglas de HTTPRoute para dirigir el tráfico al servicio de Ray correcto mediante encabezados propagados por la extensión del enrutador.
- Varios clústeres de Ray Serve para administrar el ciclo de vida y el ajuste de escala automático de los modelos aislados.
Antes de comenzar
Antes de comenzar, asegúrate de haber realizado las siguientes tareas:
- Habilita la API de Google Kubernetes Engine. Habilitar la API de Google Kubernetes Engine
- Si deseas usar Google Cloud CLI para esta tarea,
instala y, luego,
inicializa the
gcloud CLI. Si ya instalaste gcloud CLI, ejecuta el comando
gcloud components updatepara obtener la versión más reciente. Es posible que las versiones anteriores de gcloud CLI no admitan la ejecución de los comandos de este documento.
- Asegúrate de tener instalado Helm.
- Crea una cuenta de Hugging Face, si todavía no la tienes.
- Asegúrate de tener un token de Hugging Face.
Prepara el entorno
Configura las variables de entorno:
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
Reemplaza YOUR_HUGGING_FACE_TOKEN por tu token de acceso de Hugging Face.
Prepara tu infraestructura
En esta sección, configurarás un clúster de GKE habilitado para Ray y para la puerta de enlace con GPUs L4.
Crea un clúster con el operador de Ray y la API de Gateway habilitados:
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-4Crea un grupo de nodos de GPU para tus cargas de trabajo de modelos:
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=4Crea una subred de solo proxy para el balanceador de cargas de aplicaciones interno regional, que es necesario para el enrutamiento basado en el cuerpo:
gcloud compute networks subnets create bbr-proxy-only-subnet \ --purpose=REGIONAL_MANAGED_PROXY \ --role=ACTIVE \ --region=${REGION} \ --network=default \ --range=192.168.10.0/24Implementa tu secreto de Hugging Face:
kubectl create secret generic hf-secret \ --from-literal=hf_api_token=${HUGGING_FACE_TOKEN}
Implementa el enrutador basado en el cuerpo para el enrutamiento compatible con el modelo
La extensión del enrutador basado en el cuerpo intercepta las solicitudes, analiza el cuerpo JSON y extrae el campo del modelo en un encabezado X-Gateway-Model-Name.
Crea un archivo llamado
helm-values.yamlcon el siguiente contenido:bbr: plugins: - type: "body-field-to-header" name: "openai-model-extractor" json: field_name: "model" header_name: "X-Gateway-Model-Name"Instala el enrutador basado en el cuerpo con 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
Implementa RayServices
Para implementar tus modelos, debes aplicar los manifiestos de RayService. Cada manifiesto define un clúster de Ray que ejecuta un LLM específico.
Crea un archivo llamado
gemma-2b-it.yamlcon el siguiente contenido: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-l4Crea un archivo llamado
qwen2.5-3b.yamlcon el siguiente contenido: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-l4Implementa los modelos:
kubectl apply -f gemma-2b-it.yaml kubectl apply -f qwen2.5-3b.yaml
Configura las verificaciones de estado
Para garantizar que el balanceador de cargas supervise con precisión el estado del trabajador de Ray, debes aplicar el recurso HealthCheckPolicy.
Crea un archivo llamado
healthcheck-policy.yamlcon el siguiente contenido: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-svcAplica la política de verificación de estado:
kubectl apply -f healthcheck-policy.yaml
Configura el enrutamiento
Para configurar el enrutamiento, debes aplicar los manifiestos de Gateway y HTTPRoute.
HTTPRoute contiene reglas que coinciden con el encabezado X-Gateway-Model-Name (propagado por el enrutador basado en el cuerpo) para enrutar el tráfico al servicio de Ray adecuado.
Crea un archivo llamado
gateway.yamlcon el siguiente contenido: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: 8000Aplica la puerta de enlace y la ruta:
kubectl apply -f gateway.yaml
Prueba la implementación
Una vez que se aprovisione la puerta de enlace y ambos clústeres de Ray estén listos, puedes probar el enrutamiento enviando solicitudes con diferentes nombres de modelos en el cuerpo JSON.
Obtén la dirección IP de la puerta de enlace:
kubectl get gateways ray-multi-model-gatewayInicia un shell en una red que pueda acceder a la dirección de la puerta de enlace. Puedes usar curl en uno de los pods del clúster de Ray:
POD_NAME=$(kubectl get pods -l ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}') kubectl exec -it $POD_NAME -- bashEnvía solicitudes probando el enrutamiento a 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."}] }'Reemplaza
GATEWAY_IP_ADDRESSpor la dirección IP del paso anterior.El resultado es similar a este:
{"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 [...]Prueba el enrutamiento a 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?"}] }'El resultado es similar a este:
{"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. [...]
El enrutador basado en el cuerpo extrae automáticamente el valor del campo model y garantiza que cada solicitud llegue al servicio de backend correcto configurado en el archivo gateway.yaml.
Limpia
Borra el clúster:
gcloud container clusters delete ${CLUSTER}
¿Qué sigue?
- Obtén información sobre las optimizaciones de rendimiento para Ray Serve.
- Obtén más información sobre la puerta de enlace en GKE.