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 控制器和 HTTP 代理的系统级日志,以及访问日志和用户级日志。有关更多详细信息,请参阅Ray Serve 日志记录Ray 日志记录

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

常见问题#

问题 1:Ray Serve 脚本不正确。#

强烈建议您在将 Ray Serve 脚本部署到 RayService 之前,在本地或 RayCluster 中进行测试。有关更多详细信息,请参阅rayserve-dev-doc.md

问题 2:serveConfigV2 不正确。#

为了灵活性,我们将 RayService CR 中的 serveConfigV2 设置为 YAML 多行字符串。这意味着对于 serveConfigV2 字段中的 Ray Serve 配置没有严格的类型检查。以下是一些帮助您调试 serveConfigV2 字段的技巧:

  • 检查文档中关于 Ray Serve 多应用 API PUT "/api/serve/applications/" 的模式。

  • serveConfig 不同,serveConfigV2 遵循 snake case 命名约定。例如,serveConfig 中使用 numReplicas,而 serveConfigV2 中使用 num_replicas

问题 3-1:Ray 镜像不包含所需的依赖项。#

您有两种选择来解决此问题:

  • 构建包含所需依赖项的自定义 Ray 镜像。

  • 通过 serveConfigV2 字段中的 runtime_env 指定所需的依赖项。

    • 例如,MobileNet 示例需要 python-multipart,该库未包含在 Ray 镜像 rayproject/ray-ml:2.5.0 中。因此,YAML 文件在运行时环境中包含了 python-multipart。有关更多详细信息,请参阅MobileNet 示例

问题 3-2:依赖项问题故障排除示例。#

注意:强烈建议您在将 Ray Serve 脚本部署到 RayService 之前,在本地或 RayCluster 中进行测试。这有助于在早期阶段识别任何依赖项问题。有关更多详细信息,请参阅rayserve-dev-doc.md

MobileNet 示例中,mobilenet.py 由两个函数组成:__init__()__call__()。函数 __call__() 仅在 Serve 应用接收到请求时调用。

  • 示例 1:从MobileNet YAML的运行时环境中移除 python-multipart

    • python-multipart 仅在 __call__ 方法中需要。因此,只有向应用发送请求时,我们才能观察到依赖项问题。

    • 错误消息示例

      Unexpected error, traceback: ray::ServeReplica:mobilenet_ImageClassifier.handle_request() (pid=226, ip=10.244.0.9)
        .
        .
        .
        File "...", line 24, in __call__
          request = await http_request.form()
        File "/home/ray/anaconda3/lib/python3.7/site-packages/starlette/requests.py", line 256, in _get_form
          ), "The `python-multipart` library must be installed to use form parsing."
      AssertionError: The `python-multipart` library must be installed to use form parsing..
      
  • 示例 2:在MobileNet YAML中将镜像从 rayproject/ray-ml:2.5.0 更新为 rayproject/ray:2.5.0。后一个镜像不包含 tensorflow

    • tensorflowmobilenet.py 中导入。

    • 错误消息示例

      kubectl describe rayservices.ray.io rayservice-mobilenet
      
      # Example error message:
      Pending Service Status:
        Application Statuses:
          Mobilenet:
            ...
            Message:                  Deploying app 'mobilenet' failed:
              ray::deploy_serve_application() (pid=279, ip=10.244.0.12)
                  ...
                File ".../mobilenet/mobilenet.py", line 4, in <module>
                  from tensorflow.keras.preprocessing import image
              ModuleNotFoundError: No module named 'tensorflow'
      

问题 4:import_path 不正确。#

有关 import_path 格式的更多详细信息,请参阅文档。以MobileNet YAML 文件为例,import_pathmobilenet.mobilenet:app。第一个 mobilenetworking_dir 中的目录名称,第二个 mobilenetmobilenet/ 目录中的 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,一旦 Head Pod 就绪,KubeRay Operator 就会向 RayCluster 提交创建 Serve 应用的请求。需要注意的是,Dashboard、Dashboard Agent 和 GCS 在 Head Pod 就绪后可能需要几秒钟才能完全启动。因此,请求最初可能会失败几次,直到必要的组件完全运行。

如果等待 1 分钟后仍然遇到此问题,可能是 Dashboard 或 Dashboard Agent 未能启动。有关更多信息,您可以检查 Head Pod 上 /tmp/ray/session_latest/logs/ 路径下的 dashboard.logdashboard_agent.log 文件。此外,您还可以通过手动杀死 Head Pod 上的 Dashboard Agent 进程来重现此原因。

错误消息 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 相关的常见问题:

  • 的值。因此,新旧 RayCluster 具有不同的 RAY_external_storage_namespace 值,并且新的 RayCluster 无法访问旧的集群元数据。另一种解决方案是手动为每个 RayCluster 自定义资源将 RAY_external_storage_namespace 值设置为唯一值。有关更多详细信息,请参阅 kuberay#1296

  • 网络策略会阻止 Ray Pods 与 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 中所述,一旦 head Pod 准备就绪,KubeRay Operator 就会向 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

      • 集群有一个 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 已开始通过在 RayService CRD 中暴露 serveConfigV2 来支持 Ray Serve API V2(多应用)。但是,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 容错已启用,这可能是由于旧的和新的 RayClusters 的 ray.io/external-storage-namespace 注解相同。您可以移除该注解,KubeRay 将自动为每个 RayCluster 自定义资源生成一个唯一键。详见 kuberay#1297

问题 10:启用 GCS 容错的情况下,无停机时间地升级 RayService#

KubeRay 使用注解 ray.io/external-storage-namespace 的值,将环境变量 RAY_external_storage_namespace 分配给由 RayCluster 管理的所有 Ray Pods。此值表示 Ray 集群元数据驻留在 Redis 中的存储命名空间。在 head Pod 恢复过程中,head Pod 尝试使用 RAY_external_storage_namespace 值重新连接到 Redis 服务器以恢复集群数据。

然而,在 RayService 中指定 RAY_external_storage_namespace 值在零停机升级过程中可能会导致停机。具体来说,新的 RayCluster 会访问与旧的 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 值。因此,旧的和新的 RayClusters 具有不同的 RAY_external_storage_namespace 值,新的 RayCluster 无法访问旧的集群元数据。另一种解决方案是手动为每个 RayCluster 自定义资源将 RAY_external_storage_namespace 值设置为唯一值。详见 kuberay#1296