部署 Ray Serve 应用程序#

先决条件#

本指南专注于 Ray Serve 多应用 API,该 API 自 Ray 版本 2.4.0 起可用。本指南主要关注 KubeRay v1.1.1 和 Ray 2.9.0 的行为。

  • Ray 2.4.0 或更新版本。

  • KubeRay 0.6.0, KubeRay nightly, 或更新版本。

什么是 RayService?#

一个 RayService 管理两个组件:

  • RayCluster:管理 Kubernetes 集群中的资源。

  • Ray Serve 应用: 管理用户的应用。

RayService 提供了什么?#

  • Kubernetes 原生支持 Ray 集群和 Ray Serve 应用: 在使用 Kubernetes 配置定义 Ray 集群及其 Ray Serve 应用后,您可以使用 kubectl 来创建集群及其应用。

  • Ray Serve 应用程序的就地更新: 用户可以在 RayService CR 配置中更新 Ray Serve 配置,并使用 kubectl apply 来更新应用程序。更多详情请参见 步骤 7

  • Ray 集群的零停机升级: 用户可以在 RayService CR 配置中更新 Ray 集群配置,并使用 kubectl apply 来更新集群。RayService 会临时创建一个待处理的集群并等待其准备就绪,然后将流量切换到新集群并终止旧集群。更多详情请参见 步骤 8

  • 高可用服务: 详情请参阅 RayService 高可用性

示例:使用 RayService 提供两个简单的 Ray Serve 应用程序#

步骤 1:使用 Kind 创建一个 Kubernetes 集群#

kind create cluster --image=kindest/node:v1.26.0

步骤 2:安装 KubeRay 操作员#

按照 这个文档 从 Helm 仓库开始。请注意,此示例中的 YAML 文件使用 serveConfigV2 来指定多应用程序 Serve 配置,此功能从 KubeRay v0.6.0 开始可用。

步骤 3:安装 RayService#

kubectl apply -f https://raw.githubusercontent.com/ray-project/kuberay/v1.1.1/ray-operator/config/samples/ray-service.sample.yaml
  • 首先,查看嵌入在 RayService YAML 中的 Ray Serve 配置 serveConfigV2。注意两个高级应用程序:一个水果摊应用程序和一个计算器应用程序。注意一些关于水果摊应用程序的细节:

    • 水果摊应用程序包含在 test_dag 仓库的 fruit.py 文件中的 deployment_graph 变量中,因此配置中的 import_path 指向此变量,以告知 Serve 从何处导入应用程序。

    • 水果应用程序托管在路由前缀 /fruit 下,这意味着以 /fruit 前缀开头的 HTTP 请求会被发送到水果摊应用程序。

    • 工作目录指向 test_dag 仓库,该仓库在运行时下载,RayService 在此目录中启动您的应用程序。更多详情请参见 运行时环境

    • 有关配置 Ray Serve 部署的更多详细信息,请参阅 Ray Serve 文档

    • 同样地,计算器应用从同一仓库的 conditional_dag.py 文件中导入,并且它托管在路径前缀 /calc 下。

    serveConfigV2: |
      applications:
        - name: fruit_app
          import_path: fruit.deployment_graph
          route_prefix: /fruit
          runtime_env:
            working_dir: "https://github.com/ray-project/test_dag/archive/78b4a5da38796123d9f9ffff59bab2792a043e95.zip"
          deployments: ...
        - name: math_app
          import_path: conditional_dag.serve_dag
          route_prefix: /calc
          runtime_env:
            working_dir: "https://github.com/ray-project/test_dag/archive/78b4a5da38796123d9f9ffff59bab2792a043e95.zip"
          deployments: ...
    

步骤 4:验证 Kubernetes 集群状态#

# Step 4.1: List all RayService custom resources in the `default` namespace.
kubectl get rayservice

# [Example output]
# NAME                AGE
# rayservice-sample   2m42s

# Step 4.2: List all RayCluster custom resources in the `default` namespace.
kubectl get raycluster

# [Example output]
# NAME                                 DESIRED WORKERS   AVAILABLE WORKERS   STATUS   AGE
# rayservice-sample-raycluster-6mj28   1                 1                   ready    2m27s

# Step 4.3: List all Ray Pods in the `default` namespace.
kubectl get pods -l=ray.io/is-ray-node=yes

# [Example output]
# ervice-sample-raycluster-6mj28-worker-small-group-kg4v5   1/1     Running   0          3m52s
# rayservice-sample-raycluster-6mj28-head-x77h4             1/1     Running   0          3m52s

# Step 4.4: List services in the `default` namespace.
kubectl get services

# NAME                                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                                                   AGE
# ...
# rayservice-sample-head-svc                    ClusterIP   10.96.34.90     <none>        10001/TCP,8265/TCP,52365/TCP,6379/TCP,8080/TCP,8000/TCP   4m58s
# rayservice-sample-raycluster-6mj28-head-svc   ClusterIP   10.96.171.184   <none>        10001/TCP,8265/TCP,52365/TCP,6379/TCP,8080/TCP,8000/TCP   6m21s
# rayservice-sample-serve-svc                   ClusterIP   10.96.161.84    <none>        8000/TCP                                                  4m58s

KubeRay 根据 RayService YAML 中定义的 spec.rayClusterConfig 为 RayService 自定义资源创建一个 RayCluster。接下来,一旦 head Pod 运行并准备就绪,KubeRay 会向 head 的 dashboard 端口提交请求,以创建在 spec.serveConfigV2 中定义的 Ray Serve 应用程序。

当 Ray Serve 应用程序健康且准备就绪时,KubeRay 为 RayService 自定义资源创建一个头部服务和一个 serve 服务(例如,在步骤 4.4 中的 rayservice-sample-head-svcrayservice-sample-serve-svc)。用户可以通过 RayService 管理的头部服务(即 rayservice-sample-head-svc)和 RayCluster 管理的头部服务(即 rayservice-sample-raycluster-6mj28-head-svc)访问头部 Pod。然而,在零停机升级期间,会创建一个新的 RayCluster,并为新的 RayCluster 创建一个新的头部服务。如果你不使用 rayservice-sample-head-svc,你需要更新入口配置以指向新的头部服务。但是,如果你使用 rayservice-sample-head-svc,KubeRay 会自动更新选择器以指向新的头部 Pod,从而无需更新入口配置。

注意:默认端口及其定义。

端口

定义

6379

Ray GCS

8265

Ray 仪表盘

10001

Ray 客户端

8000

Ray Serve

52365

Ray 仪表盘代理

步骤 5:验证 Serve 应用程序的状态#

# Step 5.1: Check the status of the RayService.
kubectl describe rayservices rayservice-sample

# Status:
#   Active Service Status:
#     Application Statuses:
#       fruit_app:
#         Health Last Update Time:  2024-03-01T21:53:33Z
#         Serve Deployment Statuses:
#           Fruit Market:
#             Health Last Update Time:  2024-03-01T21:53:33Z
#             Status:                   HEALTHY
#           ...
#         Status:                       RUNNING
#       math_app:
#         Health Last Update Time:  2024-03-01T21:53:33Z
#         Serve Deployment Statuses:
#           Adder:
#             Health Last Update Time:  2024-03-01T21:53:33Z
#             Status:                   HEALTHY
#           ...
#         Status:                       RUNNING

# Step 5.2: Check the Serve applications in the Ray dashboard.
# (1) Forward the dashboard port to localhost.
# (2) Check the Serve page in the Ray dashboard at http://localhost:8265/#/serve.
kubectl port-forward svc/rayservice-sample-head-svc 8265:8265
  • 有关 RayService 可观测性的更多详细信息,请参阅 rayservice-troubleshooting.md。以下是 Ray 仪表板中 Serve 页面的截图示例。Ray Serve 仪表板

步骤6:通过Kubernetes服务向Serve应用程序发送请求#

# Step 6.1: Run a curl Pod.
# If you already have a curl Pod, you can use `kubectl exec -it <curl-pod> -- sh` to access the Pod.
kubectl run curl --image=radial/busyboxplus:curl -i --tty

# Step 6.2: Send a request to the fruit stand app.
curl -X POST -H 'Content-Type: application/json' rayservice-sample-serve-svc:8000/fruit/ -d '["MANGO", 2]'
# [Expected output]: 6

# Step 6.3: Send a request to the calculator app.
curl -X POST -H 'Content-Type: application/json' rayservice-sample-serve-svc:8000/calc/ -d '["MUL", 3]'
# [Expected output]: "15 pizzas please!"
  • rayservice-sample-serve-svc 在所有拥有 Ray Serve 副本的工作者之间进行流量路由。

步骤 7:Ray Serve 应用程序的就地更新#

你可以通过修改 RayService 配置文件中的 serveConfigV2 来更新应用程序的配置。使用 kubectl apply 重新应用修改后的配置会将新配置应用到现有的 RayCluster,而不是创建一个新的 RayCluster。

将水果摊应用中芒果的价格从 3 更新为 4,在 ray-service.sample.yaml 文件中进行此更改。此更改将重新配置现有的 MangoStand 部署,未来的请求将使用更新后的芒果价格。

# Step 7.1: Update the price of mangos from 3 to 4.
# [ray-service.sample.yaml]
# - name: MangoStand
#   num_replicas: 1
#   max_replicas_per_node: 1
#   user_config:
#     price: 4

# Step 7.2: Apply the updated RayService config.
kubectl apply -f ray-service.sample.yaml

# Step 7.3: Check the status of the RayService.
kubectl describe rayservices rayservice-sample
# [Example output]
# Serve Deployment Statuses:
# - healthLastUpdateTime: "2023-07-11T23:50:13Z"
#   lastUpdateTime: "2023-07-11T23:50:13Z"
#   name: MangoStand
#   status: UPDATING

# Step 7.4: Send a request to the fruit stand app again after the Serve deployment status changes from UPDATING to HEALTHY.
# (Execute the command in the curl Pod from Step 6)
curl -X POST -H 'Content-Type: application/json' rayservice-sample-serve-svc:8000/fruit/ -d '["MANGO", 2]'
# [Expected output]: 8

步骤 8:Ray 集群的无停机升级#

在第7步中,修改 serveConfigV2 不会触发Ray集群的零停机升级。相反,它会将新配置重新应用到现有的RayCluster。然而,如果你在RayService YAML文件中修改 spec.rayClusterConfig ,它会触发Ray集群的零停机升级。RayService会暂时创建一个新的RayCluster并等待其准备就绪,然后通过更新由RayService管理的头服务的选择器(即 rayservice-sample-head-svc )将流量切换到新的RayCluster,并终止旧的RayCluster。

在零停机升级过程中,RayService 会临时创建一个新的 RayCluster 并等待其准备就绪。一旦新的 RayCluster 准备就绪,RayService 会更新由 RayService 管理的头部服务的选择器(即 rayservice-sample-head-svc),使其指向新的 RayCluster 以切换流量到新的 RayCluster。最后,旧的 RayCluster 将被终止。

某些异常不会触发零停机升级。只有 Ray 自动缩放器管理的字段,replicasscaleStrategy.workersToDelete,不会触发零停机升级。当你更新这些字段时,KubeRay 不会将更新从 RayService 传播到 RayCluster 自定义资源,因此什么也不会发生。

# Step 8.1: Update `spec.rayClusterConfig.workerGroupSpecs[0].replicas` in the RayService YAML file from 1 to 2.
# This field is an exception that doesn't trigger a zero-downtime upgrade, and KubeRay doesn't update the
# RayCluster as a result. Therefore, no changes occur.
kubectl apply -f ray-service.sample.yaml

# Step 8.2: Check RayService CR
kubectl describe rayservices rayservice-sample
# Worker Group Specs:
#   ...
#   Replicas:  2

# Step 8.3: Check RayCluster CR. The update doesn't propagate to the RayCluster CR.
kubectl describe rayclusters $YOUR_RAY_CLUSTER
# Worker Group Specs:
#   ...
#   Replicas:  1

# Step 8.4: Update `spec.rayClusterConfig.rayVersion` to `2.100.0`.
# This field determines the Autoscaler sidecar image, and triggers a zero downtime upgrade.
kubectl apply -f ray-service.sample.yaml

# Step 8.5: List all RayCluster custom resources in the `default` namespace.
# Note that the new RayCluster is created based on the updated RayService config to have 2 workers.
kubectl get raycluster

# NAME                                 DESIRED WORKERS   AVAILABLE WORKERS   STATUS   AGE
# rayservice-sample-raycluster-6mj28   1                 1                   ready    142m
# rayservice-sample-raycluster-sjj67   2                 2                   ready    44s

# Step 8.6: Wait for the old RayCluster terminate.

# Step 8.7: Submit a request to the fruit stand app via the same serve service.
curl -X POST -H 'Content-Type: application/json' rayservice-sample-serve-svc:8000/fruit/ -d '["MANGO", 2]'
# [Expected output]: 8

步骤 9:为什么 1 个工作 Pod 未就绪?#

新的 RayCluster 有 2 个工作 Pod,但只有 1 个工作 Pod 是准备好的。

kubectl get pods
# NAME                                                      READY   STATUS    RESTARTS   AGE
# curl                                                      1/1     Running   0          27m
# ervice-sample-raycluster-ktf7n-worker-small-group-9rb96   0/1     Running   0          12m
# ervice-sample-raycluster-ktf7n-worker-small-group-qdjhs   1/1     Running   0          12m
# kuberay-operator-68f5866848-xx2bp                         1/1     Running   0          108m
# rayservice-sample-raycluster-ktf7n-head-bnwcn             1/1     Running   0          12m

从 Ray 2.8 开始,没有 Ray Serve 副本的 Ray 工作 Pod 将不会有 Proxy 角色。从 KubeRay v1.1.0 开始,KubeRay 为每个工作 Pod 的 Ray 容器添加了一个就绪探针,以检查工作 Pod 是否具有 Proxy 角色。如果工作 Pod 缺少 Proxy 角色,就绪探针将失败,使工作 Pod 处于未就绪状态,因此,它不会接收任何流量。

kubectl describe pod ervice-sample-raycluster-ktf7n-worker-small-group-9rb96
#      Readiness:  exec [bash -c ... && wget -T 2 -q -O- http://localhost:8000/-/healthz | grep success] ...
# ......
# Events:
#   Type     Reason     Age                    From               Message
#   ----     ------     ----                   ----               -------
#   ......
#   Warning  Unhealthy  35s (x100 over 8m15s)  kubelet            Readiness probe failed: success

为 Pod 创建一个 Ray Serve 副本以使其准备就绪。在 RayService YAML 文件中将 num_replicas 的值从 2 更新为 3,以为水果摊应用程序创建一个新的 Ray Serve 副本。此外,由于 max_replicas_per_node 为 1,新的 Ray Serve 副本必须分配给未准备好的工作 Pod。

kubectl apply -f ray-service.sample.yaml
# Update `num_replicas` from 2 to 3.
# [ray-service.sample.yaml]
#   - name: MangoStand
#     num_replicas: 3
#     max_replicas_per_node: 1
#     user_config:
#       price: 4

kubectl get pods
# NAME                                                      READY   STATUS    RESTARTS   AGE
# curl                                                      1/1     Running   0          43m
# ervice-sample-raycluster-ktf7n-worker-small-group-9rb96   1/1     Running   0          28m
# ervice-sample-raycluster-ktf7n-worker-small-group-qdjhs   1/1     Running   0          28m
# kuberay-operator-68f5866848-xx2bp                         1/1     Running   0          123m
# rayservice-sample-raycluster-ktf7n-head-bnwcn             1/1     Running   0          28m

步骤 10:清理 Kubernetes 集群#

# Delete the RayService.
kubectl delete -f ray-service.sample.yaml

# Uninstall the KubeRay operator.
helm uninstall kuberay-operator

# Delete the curl Pod.
kubectl delete pod curl

下一步#