RayService 故障排除#
RayService 是为 Ray Serve 设计的自定义资源定义 (CRD)。在 KubeRay 中,创建 RayService 会先创建一个 RayCluster,然后在 RayCluster 就绪后创建 Ray Serve 应用程序。如果问题与数据平面(尤其是您的 Ray Serve 脚本或 Ray Serve 配置(serveConfigV2))有关,那么故障排除可能会很困难。本节提供了一些帮助您调试这些问题的技巧。
可观察性#
方法 1:检查 KubeRay operator 的日志以查找错误#
kubectl logs $KUBERAY_OPERATOR_POD -n $YOUR_NAMESPACE | tee operator-log
上述命令会将 operator 的日志重定向到一个名为 operator-log 的文件中。然后您可以搜索文件中的错误。
方法 2:检查 RayService CR 状态#
kubectl describe rayservice $RAYSERVICE_NAME -n $YOUR_NAMESPACE
您可以检查 RayService CR 的状态和事件,以查看是否存在任何错误。
方法 3:检查 Ray Pod 的日志#
您还可以通过访问 Pod 上的日志文件直接检查 Ray Serve 日志。这些日志文件包含来自 Serve controller 和 HTTP 代理的系统级日志,以及访问日志和用户级日志。有关更多详细信息,请参阅 Ray Serve Logging 和 Ray Logging。
kubectl exec -it $RAY_POD -n $YOUR_NAMESPACE -- bash
# Check the logs under /tmp/ray/session_latest/logs/serve/
方法 4:检查 Dashboard#
kubectl port-forward $RAY_POD -n $YOUR_NAMESPACE 8265:8265
# Check $YOUR_IP:8265 in your browser
有关 Dashboard 上 Ray Serve 可观测性的更多详细信息,您可以参考 文档 和 YouTube 视频。
方法 5:Ray State CLI#
您可以在 head Pod 上使用 Ray State CLI 来检查 Ray Serve 应用程序的状态。
# Log into the head Pod
export HEAD_POD=$(kubectl get pods --selector=ray.io/node-type=head -o custom-columns=POD:metadata.name --no-headers)
kubectl exec -it $HEAD_POD -- ray summary actors
# [Example output]:
# ======== Actors Summary: 2023-07-11 17:58:24.625032 ========
# Stats:
# ------------------------------------
# total_actors: 14
# Table (group by class):
# ------------------------------------
# CLASS_NAME STATE_COUNTS
# 0 ServeController ALIVE: 1
# 1 ServeReplica:fruit_app_OrangeStand ALIVE: 1
# 2 ProxyActor ALIVE: 3
# 4 ServeReplica:math_app_Multiplier ALIVE: 1
# 5 ServeReplica:math_app_create_order ALIVE: 1
# 7 ServeReplica:fruit_app_FruitMarket ALIVE: 1
# 8 ServeReplica:math_app_Adder ALIVE: 1
# 9 ServeReplica:math_app_Router ALIVE: 1
# 10 ServeReplica:fruit_app_MangoStand ALIVE: 1
# 11 ServeReplica:fruit_app_PearStand ALIVE: 1
常见问题#
问题 8:当 Kubernetes 集群资源不足时,RayCluster 会出现重启循环。(KubeRay v0.6.1 或更早版本)
问题 11:RayService 卡在 Initializing 状态 — 使用 initializing timeout 快速失败
问题 1:Ray Serve 脚本不正确#
最好在将 Ray Serve 脚本部署到 RayService 之前,在本地或 RayCluster 中进行测试。有关更多详细信息,请参阅 Development Workflow。
问题 2:serveConfigV2 不正确#
RayService CR 将 serveConfigV2 设置为 YAML 多行字符串,以提供灵活性。这意味着 serveConfigV2 字段中的 Ray Serve 配置没有严格的类型检查。以下是一些帮助您调试 serveConfigV2 字段的技巧:
请参阅 文档,了解关于 Ray Serve 多应用 API
PUT "/api/serve/applications/"的 schema。与
serveConfig不同,serveConfigV2遵循 snake_case 命名约定。例如,serveConfig中使用numReplicas,而serveConfigV2中使用num_replicas。
问题 3:Ray 镜像不包含所需的依赖项#
您有两个选项来解决此问题:
构建包含所需依赖项的您自己的 Ray 镜像。
使用
serveConfigV2字段中的runtime_env指定所需的依赖项。例如,MobileNet 示例需要
python-multipart,而该依赖项未包含在 Ray 镜像rayproject/ray:x.y.z中。因此,YAML 文件在运行时环境中包含了python-multipart。有关更多详细信息,请参阅 MobileNet 示例。
问题 4:import_path 不正确。#
有关 import_path 格式的更多详细信息,您可以参考 文档。以 MobileNet YAML 文件为例,import_path 是 mobilenet.mobilenet:app。第一个 mobilenet 是 working_dir 中的目录名称,第二个 mobilenet 是 mobilenet/ 目录下的 Python 文件名,而 app 是 Python 文件中代表 Ray Serve 应用程序的变量名。
serveConfigV2: |
applications:
- name: mobilenet
import_path: mobilenet.mobilenet:app
runtime_env:
working_dir: "https://github.com/ray-project/serve_config_examples/archive/b393e77bbd6aba0881e3d94c05f968f05a387b96.zip"
pip: ["python-multipart==0.0.6"]
问题 5:未能创建/更新 Serve 应用程序。#
当 KubeRay 尝试创建/更新 Serve 应用程序时,您可能会遇到以下错误消息:
错误消息 1:connect: connection refused#
Put "http://${HEAD_SVC_FQDN}:52365/api/serve/applications/": dial tcp $HEAD_IP:52365: connect: connection refused
对于 RayService,KubeRay operator 在 head Pod 就绪后会向 RayCluster 提交一个请求来创建 Serve 应用程序。需要注意的是,Dashboard、Dashboard Agent 和 GCS 可能需要几秒钟才能在 head Pod 就绪后启动。因此,在必要组件完全运行之前,请求可能会在开始时失败几次。
如果您在等待 1 分钟后仍然遇到此问题,可能是 Dashboard 或 Dashboard Agent 未能启动。有关更多信息,您可以检查位于 head Pod 的 /tmp/ray/session_latest/logs/ 目录下的 dashboard.log 和 dashboard_agent.log 文件。
错误消息 2:i/o timeout#
Put "http://${HEAD_SVC_FQDN}:52365/api/serve/applications/": dial tcp $HEAD_IP:52365: i/o timeout"
此问题的一个可能原因是 Kubernetes NetworkPolicy 阻止了 Ray Pod 和 Dashboard Agent 端口(即 52365)之间的流量。
问题 6:runtime_env#
在 serveConfigV2 中,您可以使用 runtime_env 为 Ray Serve 应用程序指定运行时环境。一些与 runtime_env 相关的常见问题:
working_dir指向私有 AWS S3 存储桶,但 Ray Pod 没有访问该存储桶的必要权限。NetworkPolicy 阻止了 Ray Pod 与
runtime_env中指定的外部 URL 之间的流量。
问题 7:未能获取 Serve 应用程序状态。#
当 KubeRay 尝试获取 Serve 应用程序状态时,您可能会遇到以下错误消息:
Get "http://${HEAD_SVC_FQDN}:52365/api/serve/applications/": dial tcp $HEAD_IP:52365: connect: connection refused"
如 问题 5 中所述,KubeRay operator 在 head Pod 就绪后会向 RayCluster 提交一个 Put 请求来创建 Serve 应用程序。在成功向 Dashboard Agent 提交 Put 请求后,会向 Dashboard Agent 端口(即 52365)发送一个 Get 请求。成功的提交表明包括 Dashboard Agent 在内的所有必要组件都已完全运行。因此,与问题 5 不同,Get 请求的失败是不太可能的。
如果您持续遇到此问题,可能有几种原因:
head Pod 上的 Dashboard Agent 进程未运行。您可以检查位于 head Pod 的
/tmp/ray/session_latest/logs/目录下的dashboard_agent.log文件以获取更多信息。此外,您还可以通过手动终止 head Pod 上的 Dashboard Agent 进程来执行实验来重现此原因。# Step 1: Log in to the head Pod kubectl exec -it $HEAD_POD -n $YOUR_NAMESPACE -- bash # Step 2: Check the PID of the dashboard agent process ps aux # [Example output] # ray 156 ... 0:03 /.../python -u /.../ray/dashboard/agent.py -- # Step 3: Kill the dashboard agent process kill 156 # Step 4: Check the logs cat /tmp/ray/session_latest/logs/dashboard_agent.log # [Example output] # 2023-07-10 11:24:31,962 INFO web_log.py:206 -- 10.244.0.5 [10/Jul/2023:18:24:31 +0000] "GET /api/serve/applications/ HTTP/1.1" 200 13940 "-" "Go-http-client/1.1" # 2023-07-10 11:24:34,001 INFO web_log.py:206 -- 10.244.0.5 [10/Jul/2023:18:24:33 +0000] "GET /api/serve/applications/ HTTP/1.1" 200 13940 "-" "Go-http-client/1.1" # 2023-07-10 11:24:36,043 INFO web_log.py:206 -- 10.244.0.5 [10/Jul/2023:18:24:36 +0000] "GET /api/serve/applications/ HTTP/1.1" 200 13940 "-" "Go-http-client/1.1" # 2023-07-10 11:24:38,082 INFO web_log.py:206 -- 10.244.0.5 [10/Jul/2023:18:24:38 +0000] "GET /api/serve/applications/ HTTP/1.1" 200 13940 "-" "Go-http-client/1.1" # 2023-07-10 11:24:38,590 WARNING agent.py:531 -- Exiting with SIGTERM immediately... # Step 5: Open a new terminal and check the logs of the KubeRay operator kubectl logs $KUBERAY_OPERATOR_POD -n $YOUR_NAMESPACE | tee operator-log # [Example output] # Get \"http://rayservice-sample-raycluster-rqlsl-head-svc.default.svc.cluster.local:52365/api/serve/applications/\": dial tcp 10.96.7.154:52365: connect: connection refused
问题 8:当 Kubernetes 集群资源不足时,RayCluster 会出现重启循环。(KubeRay v0.6.1 或更早版本)#
注意:目前,KubeRay operator 没有明确的计划来处理 Kubernetes 集群资源不足的情况。因此,我们建议确保 Kubernetes 集群拥有足够的资源来容纳 Serve 应用程序。
如果 Serve 应用程序的状态在 serviceUnhealthySecondThreshold 秒后仍未达到 RUNNING 状态,KubeRay operator 将认为 RayCluster 不健康,并开始准备新的 RayCluster。此问题的一个常见原因是 Kubernetes 集群没有足够的资源来容纳 Serve 应用程序。在这种情况下,KubeRay operator 可能会继续重启 RayCluster,从而导致重启循环。
我们也可以进行一个实验来重现这种情况:
一个具有 8 个 CPU 的节点的 Kubernetes 集群
ray-service.insufficient-resources.yaml
RayCluster
该集群有 1 个 head Pod,具有 4 个物理 CPU,但
rayStartParams中的num-cpus设置为 0,以防止任何 Serve 副本被调度到 head Pod 上。该集群默认还有一个具有 1 个 CPU 的 worker Pod。
serveConfigV2指定了 5 个 Serve 部署,每个部署有 1 个副本,并且需要 1 个 CPU。
# Step 1: Get the number of CPUs available on the node
kubectl get nodes -o custom-columns=NODE:.metadata.name,ALLOCATABLE_CPU:.status.allocatable.cpu
# [Example output]
# NODE ALLOCATABLE_CPU
# kind-control-plane 8
# Step 2: Install a KubeRay operator.
# Step 3: Create a RayService with autoscaling enabled.
kubectl apply -f ray-service.insufficient-resources.yaml
# Step 4: The Kubernetes cluster will not have enough resources to accommodate the serve application.
kubectl describe rayservices.ray.io rayservice-sample -n $YOUR_NAMESPACE
# [Example output]
# fruit_app_FruitMarket:
# Health Last Update Time: 2023-07-11T02:10:02Z
# Last Update Time: 2023-07-11T02:10:35Z
# Message: Deployment "fruit_app_FruitMarket" has 1 replicas that have taken more than 30s to be scheduled. This may be caused by waiting for the cluster to auto-scale, or waiting for a runtime environment to install. Resources required for each replica: {"CPU": 1.0}, resources available: {}.
# Status: UPDATING
# Step 5: A new RayCluster will be created after `serviceUnhealthySecondThreshold` (300s here) seconds.
# Check the logs of the KubeRay operator to find the reason for restarting the RayCluster.
kubectl logs $KUBERAY_OPERATOR_POD -n $YOUR_NAMESPACE | tee operator-log
# [Example output]
# 2023-07-11T02:14:58.109Z INFO controllers.RayService Restart RayCluster {"appName": "fruit_app", "restart reason": "The status of the serve application fruit_app has not been RUNNING for more than 300.000000 seconds. Hence, KubeRay operator labels the RayCluster unhealthy and will prepare a new RayCluster."}
# 2023-07-11T02:14:58.109Z INFO controllers.RayService Restart RayCluster {"deploymentName": "fruit_app_FruitMarket", "appName": "fruit_app", "restart reason": "The status of the serve deployment fruit_app_FruitMarket or the serve application fruit_app has not been HEALTHY/RUNNING for more than 300.000000 seconds. Hence, KubeRay operator labels the RayCluster unhealthy and will prepare a new RayCluster. The message of the serve deployment is: Deployment \"fruit_app_FruitMarket\" has 1 replicas that have taken more than 30s to be scheduled. This may be caused by waiting for the cluster to auto-scale, or waiting for a runtime environment to install. Resources required for each replica: {\"CPU\": 1.0}, resources available: {}."}
# .
# .
# .
# 2023-07-11T02:14:58.122Z INFO controllers.RayService Restart RayCluster {"ServiceName": "default/rayservice-sample", "AvailableWorkerReplicas": 1, "DesiredWorkerReplicas": 5, "restart reason": "The serve application is unhealthy, restarting the cluster. If the AvailableWorkerReplicas is not equal to DesiredWorkerReplicas, this may imply that the Autoscaler does not have enough resources to scale up the cluster. Hence, the serve application does not have enough resources to run. Please check https://github.com/ray-project/kuberay/blob/master/docs/guidance/rayservice-troubleshooting.md for more details.", "RayCluster": {"apiVersion": "ray.io/v1alpha1", "kind": "RayCluster", "namespace": "default", "name": "rayservice-sample-raycluster-hvd9f"}}
问题 9:在不停机的情况下从 Ray Serve 的单应用 API 升级到多应用 API#
KubeRay v0.6.0 开始支持 Ray Serve API V2(多应用),通过在 RayService CRD 中公开 serveConfigV2。但是,Ray Serve 不支持在集群中同时部署 API V1 和 API V2。因此,如果用户想通过将 serveConfig 替换为 serveConfigV2 来执行原地升级,他们可能会遇到以下错误消息:
ray.serve.exceptions.RayServeException: You are trying to deploy a multi-application config, however a single-application
config has been deployed to the current Serve instance already. Mixing single-app and multi-app is not allowed. Please either
redeploy using the single-application config format `ServeApplicationSchema`, or shutdown and restart Serve to submit a
multi-app config of format `ServeDeploySchema`. If you are using the REST API, you can submit a multi-app config to the
the multi-app API endpoint `/api/serve/applications/`.
要解决此问题,您可以将 serveConfig 替换为 serveConfigV2,并将 rayVersion(当 Ray 版本为 2.0.0 或更高时没有影响)修改为 2.100.0。这将触发一个新的 RayCluster 准备,而不是原地更新。
如果按照上述步骤操作后,您仍然看到错误消息并且 GCS 容错已启用,这可能是由于新旧 RayCluster 的 ray.io/external-storage-namespace 注释相同。您可以删除该注释,KubeRay 将自动为每个 RayCluster 自定义资源生成一个唯一密钥。有关更多详细信息,请参阅 kuberay#1297。
问题 10:在不停机的情况下升级启用了 GCS 容错的 RayService#
KubeRay 使用注释 ray.io/external-storage-namespace 的值,将环境变量 RAY_external_storage_namespace 分配给 RayCluster 管理的所有 Ray Pod。此值代表 Redis 中存储 Ray 集群元数据的命名空间。在 head Pod 恢复过程中,head Pod 会尝试使用 RAY_external_storage_namespace 值重新连接到 Redis 服务器以恢复集群数据。
然而,在 RayService 中指定 RAY_external_storage_namespace 值可能会在零停机升级期间导致停机。具体来说,新的 RayCluster 访问与旧集群相同的 Redis 存储命名空间用于集群元数据。此配置可能导致 KubeRay operator 假设 Ray Serve 应用程序运行正常,因为它在 Redis 中看到了现有的元数据。因此,operator 可能会认为安全地弃用旧的 RayCluster 并将流量重定向到新的 RayCluster,即使后者仍需要时间来初始化 Ray Serve 应用程序。
推荐的解决方案是从 RayService CRD 中删除 ray.io/external-storage-namespace 注释。如果未设置该注释,KubeRay 将自动使用每个 RayCluster 自定义资源的 UID 作为 RAY_external_storage_namespace 的值。因此,旧的 RayCluster 和新的 RayCluster 具有不同的 RAY_external_storage_namespace 值,并且新的 RayCluster 无法访问旧的集群元数据。另一个解决方案是将 RAY_external_storage_namespace 的值手动设置为每个 RayCluster 自定义资源的唯一值。有关更多详细信息,请参阅 kuberay#1296。
问题 11:RayService 卡在 Initializing 状态 — 使用 initializing timeout 快速失败#
如果一个或多个底层 Pod 被调度但未能启动(例如,ImagePullBackOff、CrashLoopBackOff 或其他容器启动错误),RayService 可能会无限期地停留在 Initializing 状态。此状态会消耗集群资源,并使根本原因更难诊断。
怎么办#
KubeRay 通过注释 ray.io/initializing-timeout 暴露了一个可配置的初始化超时。超时到期后,operator 会将 RayService 标记为失败,并开始清理相关的 RayCluster 资源。启用超时只需要将注释添加到 RayService 元数据中 — 不需要其他 CRD 更改。
超时后的 operator 行为#
RayServiceReady条件被设置为False,原因为InitializingTimeout。RayService被置于 **终端(失败)** 状态;更新 spec 不会触发重试。恢复需要删除并重新创建RayService。RayService CR 上的集群名称会被清除,这将触发底层
RayCluster资源的清理。删除仍然遵循RayClusterDeletionDelaySeconds。会发出一个
Warning事件,记录超时和失败原因。
启用超时#
将注释添加到您的 RayService 元数据中。该注释可以接受 Go duration 字符串(例如,"30m" 或 "1h")或整数秒(例如,"1800")。
metadata:
annotations:
ray.io/initializing-timeout: "30m"
指南#
选择一个超时时间,该时间应平衡预期的启动工作与快速失败,以节省集群资源。
有关更多实现细节,请参阅上游讨论 kuberay#4138。