Python-Ray-扩展指南-四-

582 阅读24分钟

Python Ray 扩展指南(四)

原文:annas-archive.org/md5/95872ff5b3ec96901f7e3cfb51cd271f

译者:飞龙

协议:CC BY-NC-SA 4.0

附录 B. 安装和部署 Ray

Ray 的强大之处在于它支持各种部署模型,从单节点部署——允许您在本地进行 Ray 实验——到包含数千台机器的集群。在本附录中,我们将展示编写本书时评估的一些安装选项。

在本地安装 Ray

最简单的 Ray 安装是使用 pip 在本地进行的。使用以下命令:

pip install -U ray

此命令安装了运行本地 Ray 程序或在 Ray 集群上启动程序所需的所有代码(参见 “使用 Ray 集群”)。该命令安装了最新的官方发布版本。此外,还可以从 每日发布特定提交 安装 Ray。还可以在 Conda 环境 中安装 Ray。最后,您可以按照 Ray 文档 中的说明从源代码构建 Ray。

使用 Ray Docker 镜像

除了在本地机器上进行本地安装外,Ray 还提供了通过运行提供的 Docker 镜像 的选项。Ray 项目提供了丰富的 Docker 镜像,适用于各种 Python 版本和硬件选项。这些镜像可以用来通过启动相应的 Ray 镜像来执行 Ray 的代码:

docker run --rm --shm-size=<*shm-size*> -t -i <*image name*>

在这里 <*shm-size*> 是 Ray 内部用于对象存储的内存大小。对于此值的一个良好估计是使用您可用内存的大约 30%;<*image name*> 是所使用的镜像的名称。

执行此命令后,您将收到一个命令行提示符,并可以输入任何 Ray 代码。

使用 Ray 集群

尽管本地 Ray 安装对于实验和初始调试非常有用,但 Ray 的真正强大之处在于其能够在机器集群上运行和扩展。

Ray 集群节点是基于 Docker 镜像的逻辑节点。Ray 项目提供的 Docker 镜像包含了运行逻辑节点所需的所有代码,但不一定包含运行用户应用程序所需的所有代码。问题在于用户的代码可能需要特定的 Python 库,而这些库并不包含在 Ray 的 Docker 镜像中。

为了解决这个问题,Ray 允许在集群安装的一部分中向节点安装特定库,这对于初始测试非常有帮助,但可能会显著影响节点的创建性能。因此,在生产安装中,通常建议使用从 Ray 提供的自定义镜像派生的镜像并添加所需的库。

Ray 提供了两种主要的安装选项:直接安装在硬件节点或云提供商的 VM 上,以及在 Kubernetes 上安装。在这里,我们将讨论 Ray 在云提供商和 Kubernetes 上的安装。有关 Ray 在硬件节点上的安装信息,请参阅Ray 文档

官方文档描述了 Ray 在包括 AWS、Azure、Google Cloud、阿里巴巴和自定义云在内的多个云提供商上的安装。在这里,我们将讨论在 AWS 上的安装(因为它是最流行的)和 IBM Cloud 上的安装(因为其中一位合著者在 IBM 工作,采用了独特的方法)。¹

在 AWS 上安装 Ray

AWS 云安装利用了 Python 的 Boto3 AWS SDK,并且需要在~/.aws/credentials文件中配置您的 AWS 凭证。²

一旦凭证创建并安装了 Boto3,您可以使用从Ray GitHub 存储库适配的ray-aws.yaml文件,通过以下命令在 AWS 上安装 Ray:

ray up <*your location*>/ray-aws.yaml

此命令创建了集群,并提供了一组您可以使用的有用命令:

Monitor autoscaling with
    ray exec ~/Projects/Platform-Infrastructure/middleware\
    /ray/install/ray-aws.yaml\
    'tail -n 100 -f /tmp/ray/session_latest/logs/monitor*'
Connect to a terminal on the cluster head:
    ray attach ~/Projects/Platform-Infrastructure/middleware\
    /ray/install/ray-aws.yaml
Get a remote shell to the cluster manually:
    ssh -o IdentitiesOnly=yes\
    -i /Users/boris/Downloads/id.rsa.ray-boris root@52.118.80.225

请注意,您将看到的 IP 地址与此处显示的不同。在创建集群时,它使用了一个只允许通过 Secure Shell(SSH)连接到集群的防火墙。如果您想访问集群的仪表板,您需要打开 8265 端口;如果需要 gRPC 访问,请使用 10001 端口。为此,请在 Amazon Elastic Compute Cloud(EC2)仪表板中找到您的节点,点击“安全组”选项卡,选择安全组,并修改入站规则。Figure B-1 显示了一个允许来自任何地方的实例端口访问的新规则。有关入站规则配置的更多信息,请参阅AWS 文档

spwr ab01

图 B-1. AWS 控制台中的实例视图

按照您的 YAML 文件的请求,您只能看到一个头部,并且工作节点将被创建以满足提交作业的执行要求。要验证集群是否正常运行,您可以使用 GitHub 上localPython.py中的代码,该代码验证它是否可以连接到集群及其节点。

使用 VM 直接安装 Ray 的替代方法是直接在 VM 上安装 Ray。这种方法的优势在于能够轻松地向 VM 添加附加软件,这在实际中非常有用。一个明显的用例是管理 Python 库。您可以使用基于 Docker 的安装来做到这一点,但随后需要为每个库配置构建 Docker 镜像。在基于 VM 的方法中,无需创建和管理 Docker 镜像;只需适当地使用pip进行安装即可。此外,您还可以在 VM 上安装应用程序,以便在 Ray 执行中利用它们(参见“使用 Ray 包装自定义程序”)。

提示

在 VM 上安装 Ray 需要大量的设置命令,因此 Ray 节点启动可能需要相当长的时间。推荐的方法是先启动 Ray 集群一次,创建一个新镜像,然后使用此镜像并移除额外的设置命令。

在 IBM Cloud 上安装 Ray

IBM Cloud 安装基于Gen2 连接器,该连接器使 Ray 集群可以部署在 IBM 的 Gen2 云基础设施上。与在 AWS 上使用 Ray 一样,您将首先在 YAML 文件中创建集群规范。如果您不想手动创建 YAML 文件,可以交互式地使用 Lithopscloud 完成此操作。您可以像平常一样使用pip安装 Lithopscloud:

pip3 install lithopscloud

要使用 Lithopscloud,您首先需要创建一个API 密钥或重用现有的密钥。有了您的 API 密钥,您可以运行 lithopscloud -o cluster.yaml 来生成一个cluster.yaml文件。启动 Lithopscloud 后,按照提示生成文件(您需要使用上下箭头进行选择)。您可以在GitHub上找到生成文件的示例。

自动生成文件的限制在于它对头节点和工作节点使用相同的镜像类型,这并不总是理想的。通常情况下,您可能希望为这些节点提供不同的类型。要做到这一点,您可以修改自动生成的cluster.yaml文件如下:

available_node_types:
 ray_head_default:
   max_workers: 0
   min_workers: 0
   node_config:
     boot_volume_capacity: 100
     image_id: r006-dd164da8-c4d9-46ba-87c4-03c614f0532c
     instance_profile_name: bx2-4x16
     key_id: r006-d6d823da-5c41-4e92-a6b6-6e98dcc90c8e
     resource_group_id: 5f6b028dc4ef41b9b8189bbfb90f2a79
     security_group_id: r006-c8e44f9c-7159-4041-a7ab-cf63cdb0dca7
     subnet_id: 0737-213b5b33-cee3-41d0-8d25-95aef8e86470
     volume_tier_name: general-purpose
     vpc_id: r006-50485f78-a76f-4401-a742-ce0a748b46f9
   resources:
     CPU: 4
 ray_worker_default:
   max_workers: 10
   min_workers: 0
   node_config:
     boot_volume_capacity: 100
     image_id: r006-dd164da8-c4d9-46ba-87c4-03c614f0532c
     instance_profile_name: bx2-8x32
     key_id: r006-d6d823da-5c41-4e92-a6b6-6e98dcc90c8e
     resource_group_id: 5f6b028dc4ef41b9b8189bbfb90f2a79
     security_group_id: r006-c8e44f9c-7159-4041-a7ab-cf63cdb0dca7
     subnet_id: 0737-213b5b33-cee3-41d0-8d25-95aef8e86470
     volume_tier_name: general-purpose
     vpc_id: r006-50485f78-a76f-4401-a742-ce0a748b46f9
   resources:
     CPU: 8

在这里,您定义了两种类型的节点:默认的头节点和默认的工作节点(您可以定义多个工作节点类型,并设置每次的最大工作节点数)。因此,您现在可以拥有一个相对较小的头节点(始终运行),以及会根据需要创建的更大的工作节点。

提示

如果您查看生成的 YAML 文件,您会注意到它包含许多设置命令,因此 Ray 节点启动可能需要相当长的时间。推荐的方法是先启动 Ray 集群一次,创建一个新镜像,然后使用此镜像并移除设置命令。

生成 YAML 文件后,您可以安装 Gen2 连接器以便使用它。运行 pip3 install gen2-connector。然后,通过运行 ray up cluster.yaml 来创建您的集群。

类似于在 AWS 上安装 Ray,此安装显示了一系列有用的命令:

Monitor autoscaling with
    ray exec /Users/boris/Downloads/cluster.yaml \
    'tail -n 100 -f /tmp/ray/session_latest/logs/monitor*'
Connect to a terminal on the cluster head:
    ray attach ~/Downloads/cluster.yaml
Get a remote shell to the cluster manually:
    ssh -o IdentitiesOnly=yes -i ~/Downloads/id.rsa.ray-boris root@52.118.80.225

要访问集群,请确保按照IBM Cloud 文档(图 B-2)开放所需端口。

spwr ab02

图 B-2. IBM Cloud 控制台显示防火墙规则

根据您的 YAML 文件的请求,您只能看到一个头部;工作节点将被创建以满足提交作业的执行需求。要验证集群是否正确运行,请执行 localPython.py 脚本

在 Kubernetes 上安装 Ray

在实际集群在 Kubernetes 上的安装中,Ray 提供了两种基本机制:

集群启动器

与使用虚拟机进行安装类似,这使得在任何云上部署 Ray 集群变得简单。它将使用云提供商的 SDK 创建新的实例或机器,执行 shell 命令以使用提供的选项设置 Ray,并初始化集群。

Ray Kubernetes 操作器

这简化了在现有 Kubernetes 集群上部署 Ray 的过程。操作器定义了一个称为 RayCluster自定义资源,它描述了 Ray 集群的期望状态,以及一个自定义控制器,即 Ray 操作器,它处理 RayCluster 资源并管理 Ray 集群。

提示

当您使用集群启动器和操作器在 Kubernetes 集群上安装 Ray 时,Ray 利用 Kubernetes 的能力创建一个新的 Ray 节点,形式为 Kubernetes Pod。虽然 Ray 自动扩展器的工作方式相同,但它有效地从 Kubernetes 集群中“窃取”资源。因此,您的 Kubernetes 集群要么足够大以支持 Ray 的所有资源需求,要么提供自己的自动缩放机制。此外,由于 Ray 的节点在这种情况下是作为底层 Kubernetes Pod 实现的,因此 Kubernetes 资源管理器可以随时删除这些 Pod 以获取额外的资源。

在 kind 集群上安装 Ray

为了演示两种方法,让我们首先在 kind (Kubernetes in Docker) 集群 上安装并访问 Ray 集群。这个流行的工具通过使用 Docker 容器“节点”来运行本地 Kubernetes 集群,并且经常用于本地开发。为此,您需要首先通过运行以下命令来创建一个集群:

kind create cluster

这将使用默认配置创建一个集群。要修改配置,请参考配置文档。一旦集群启动运行,您可以使用 ray up 或 Kubernetes 操作器来创建 Ray 集群。

使用 ray up

要使用 ray up 创建 Ray 集群,必须在一个 YAML 文件中指定资源需求,例如raycluster.yaml,该文件改编自 Ray GitHub 仓库 中的 Ray Kubernetes 自动缩放器默认值。该文件包含创建 Ray 集群所需的所有信息:

  • 关于集群名称和自动缩放参数的一般信息。

  • 关于集群提供程序(在我们的情况下是 Kubernetes)的信息,包括创建 Ray 集群节点所需的特定于提供程序的信息。

  • 特定于节点的信息(CPU/内存等)。这还包括节点启动命令列表,包括安装所需的 Python 库。

有了这个文件,创建集群的命令看起来像这样:

ray up <*your location*>/raycluster.yaml

完成集群创建后,您可以看到有几个 pod 在运行:

> get pods -n ray
NAME                   READY   STATUS    RESTARTS   AGE
ray-ray-head-88978     1/1     Running   0          2m15s
ray-ray-worker-czqlx   1/1     Running   0          23s
ray-ray-worker-lcdmm   1/1     Running   0          23s

根据我们的 YAML 文件的请求,您可以看到一个 head 和两个 worker 节点。要验证集群是否正常运行,可以使用以下 作业

kubectl create -f <your location>/jobexample.yaml -n ray

执行的结果类似于这样:

> kubectl logs ray-test-job-bx4xj-4nfbl -n ray
--2021-09-28 15:18:59--  https://raw.githubusercontent.com/scalingpythonml/...
Resolving raw.githubusercontent.com (raw.githubusercontent.com) ...
Connecting to raw.githubusercontent.com (raw.githubusercontent.com) ...
Length: 1750 (1.7K) [text/plain]
Saving to: ‘servicePython.py’

     0K .                                                     100% 9.97M=0s

2021-09-28 15:18:59 (9.97 MB/s) - ‘servicePython.py’ saved [1750/1750]

Connecting to Ray at service ray-ray-head, port 10001
Iteration 0
Counter({('ray-ray-head-88978', 'ray-ray-head-88978'): 30, ...
Iteration 1
……………………………….
Success!

作业启动后,您还可以通过运行以下命令将 ray-ray-head 服务进行端口转发:³

kubectl port-forward -n ray service/ray-ray-head 10001

然后,使用书籍示例文件中的localPython.py测试脚本从本地机器连接到它。执行此代码会产生与先前显示的相同结果。

此外,您可以将 ray 服务端口转发到 8265 端口以查看 Ray 仪表板:

kubectl port-forward -n ray service/ray-ray-head 8265

完成后,您可以查看 Ray 仪表板(图 B-3)。

spwr ab03

图 B-3. Ray 仪表板

使用以下命令可以卸载 Ray 集群:⁴

ray down <*your location*>/raycluster.yaml

使用 Ray Kubernetes 操作员

对于部署到 Kubernetes 集群,我们还可以使用 Ray 操作员,这是一种推荐的方法。为了简化操作员的使用,Ray 提供了作为 Ray GitHub 仓库的一部分可用的Helm 图表。在这里,我们使用多个 YAML 文件来部署 Ray,以使安装变得更加简单,而不是使用 Helm 图表。

我们的部署分为三个文件:operatorcrd.yaml,其中包含用于 CustomResourceDefinition(CRD)创建的所有命令;operator.yaml,其中包含用于操作员创建的所有命令;以及rayoperatorcluster.yaml,其中包含用于集群创建的所有命令。这些文件假定操作员是在 ray 命名空间中创建的。

要安装操作员本身,我们需要执行这两个命令:

kubectl apply -f <*your location*>/operatorcrd.yaml
kubectl apply -f <*your location*>/operator.yaml

完成后,请使用以下命令确保操作员 pod 正在运行:

> kubectl get pods -n ray
NAME                            READY   STATUS    RESTARTS   AGE
ray-operator-6c9954cddf-cjn9c   1/1     Running   0          110s

一旦操作员启动并运行,您可以使用以下命令启动集群本身:⁵

kubectl apply -f <*your location*>/rayoperatorcluster.yaml -n ray

rayoperatorcluster.yaml 的内容与 raycluster.yaml 类似,但格式略有不同。一旦集群运行起来,你可以使用与之前描述的 ray up 相同的验证代码。

在 OpenShift 上安装 Ray

OpenShift 是一种 Kubernetes 集群类型,因此理论上可以使用 Kubernetes 运算符在 OpenShift 集群上安装 Ray。不过,这种安装过程会稍微复杂一些。

如果你曾经使用过 OpenShift,你会知道默认情况下,所有 OpenShift 中的 pod 都以 限制模式 运行。此模式拒绝访问所有主机功能,并要求 pod 使用分配给命名空间的唯一标识符 (UID) 和安全增强型 Linux (SELinux) 上下文来运行。

不幸的是,对于 Ray operator 来说,设计为以用户 1000 运行的情况不太适用。为了启用此功能,你需要对安装在 kind(以及任何其他纯 Kubernetes 集群)上的文件进行几处更改:

  • 添加 ray-operator-serviceaccount 服务账户,该账户由运算符使用,设置为 anyuid 模式。这允许用户使用任何非根 UID 运行:

    oc adm policy add-scc-to-user anyuid -z ray-operator-serviceaccount
    
  • 修改 operator.yaml,确保运算符 pod 以用户 1000 的身份运行。

此外,必须稍微修改 测试作业,以用户 1000 的身份运行。这需要创建一个 ray-node-serviceaccount 服务账户用于运行作业,并将该服务账户设置为 anyuid 模式,允许用户使用任何非根 UID 运行。

结论

Ray 提供丰富的部署选项。当使用 Ray 解决特定问题时,你需要决定哪个选项最适合你的具体情况。

¹ 为了透明起见:Boris 目前在 IBM 工作,Holden 曾在 IBM 工作过。Holden 也曾在 Google、Microsoft 和 Amazon 工作过。

² 参见 “Boto3 Docs 1.24.95” 文档 获取有关设置 Boto3 配置的信息。

³ 理论上,你也可以创建一个入口来连接 Ray 集群。但遗憾的是,在 NGINX 入口控制器的情况下,它将无法工作。问题在于 Ray 客户端使用的是不安全的 gRPC,而 NGINX 入口控制器仅支持安全的 gRPC 调用。在使用 Ray 集群时,请检查入口是否支持不安全的 gRPC,在将 Ray 的 head 服务公开为入口之前。

⁴ 此命令会删除 pod,并且会留下作为集群一部分创建的服务。你必须手动删除服务以进行完全清理。

⁵ 尽管文档提到了集群范围的部署运算符,但它仅适用于部署运算符的命名空间。

附录 C. 使用 Ray 进行调试

根据您的调试技术,迁移到分布式系统可能需要一套新的技术。幸运的是,像 Pdb 和 PyCharm 这样的工具允许您连接远程调试器,而 Ray 的本地模式则允许您在许多其他情况下使用现有的调试工具。有些错误发生在 Python 之外,使得它们更难调试,如容器内存不足(OOM)错误、分段错误和其他本地错误。

注意

此附录的一些部分与使用 Dask 扩展 Python共享,因为它们是调试所有类型分布式系统的一般良好建议。

使用 Ray 的一般调试技巧

您可能有自己的标准 Python 代码调试技术,本附录不旨在替代它们。以下是一些在使用 Ray 时更有意义的一般技术:

  • 将失败的函数拆分为较小的函数。由于ray.remote在函数块上调度,较小的函数使问题更容易隔离。

  • 注意任何意外的作用域捕获。

  • 使用示例数据尝试在本地重现它(本地调试通常更容易)。

  • 使用 Mypy 进行类型检查。虽然我们并未在所有示例中包含类型,但在生产代码中,使用自由的类型可以捕获棘手的错误。

  • 当问题无论并行化如何出现时,请在单线程模式下调试您的代码,这样更容易理解发生了什么。

现在,有了这些额外的一般技巧,是时候了解更多有关工具和技术,帮助您进行 Ray 调试了。

序列化错误

序列化在 Ray 中起着重要作用,但也可能是头痛的来源,因为小的更改可能导致意外的变量捕获和序列化失败。幸运的是,Ray 在 ray.util 中有一个实用函数 inspect_serializability,您可以使用它来调试序列化错误。如果您有意定义一个捕获非可序列化数据的函数,比如 示例 C-1,您可以运行 inspect_serializability 看看它如何报告失败(如 示例 C-2)。

示例 C-1. 错误的序列化示例
pool = Pool(5)

def special_business(x):
    def inc(y):
        return y + x
    return pool.map(inc, range(0, x))
ray.util.inspect_serializability(special_business)
示例 C-2. 错误的序列化结果
=========================================================================
Checking Serializability of <function special_business at 0x7f78802820d0>
=========================================================================
!!! FAIL serialization: pool objects cannot be passed between processes or pickled
Detected 1 global variables. Checking serializability...
    Serializing 'pool' <multiprocessing.pool.Pool state=RUN pool_size=5>...
    !!! FAIL serialization: pool objects cannot be passed between processes ...
...

在此示例中,Ray 检查元素的可序列化性,并指出非序列化值 pool 来自全局范围。

使用 Ray 本地调试

在本地模式下使用 Ray,可以让您使用习惯的工具,而无需处理设置远程调试的复杂性。我们不会涵盖各种本地 Python 调试工具,因此这一部分存在的意义仅在于提醒您首先尝试在本地模式下重现问题,然后再开始使用本附录其余部分涵盖的高级调试技术。

远程调试

远程调试可以是一个很好的工具,但需要更多对集群的访问权限,这在某些情况下可能并不总是可用。Ray 的特殊集成工具 ray debug 支持跨整个集群的追踪。不幸的是,其他远程 Python 调试器一次只能附加到一个机器上,因此你不能简单地将调试器指向整个集群。

警告

远程调试可能会导致性能变化和安全隐患。在启用集群上的远程调试之前,通知所有用户非常重要。

如果你控制自己的环境,设置远程调试相对比较简单,但在企业部署中,你可能会遇到启用这一功能的阻力。在这种情况下,使用本地集群或请求一个开发集群进行调试是你最好的选择。

提示

对于交互式调试器,你可能需要与系统管理员合作,以公开集群中额外的端口。

Ray 的集成调试器(通过 Pdb)

Ray 支持通过 Pdb 进行调试的集成,允许你跨集群跟踪代码。你仍然需要修改启动命令 (ray start) 来包括 (ray start --ray-debugger-external) 来加载调试器。启用 Ray 的外部调试器后,Pdb 将在额外端口上监听(无需任何认证),以供调试器连接。

一旦你配置并启动了集群,你可以在主节点上启动 Ray 调试器。¹ 要启动调试器,只需运行 ray debug,然后你可以使用所有你喜欢的 Pdb 调试命令

其他工具

对于非集成工具,由于每次对远程函数的调用可能被安排在不同的工作节点上,你可能会发现将你的无状态函数暂时转换为执行器会更容易调试。这将会对性能产生真实的影响,因此在生产环境中可能不适用,但确实意味着重复调用将路由到同一台机器,从而使调试任务更加简单。

PyCharm

PyCharm 是一个集成调试器的流行 Python IDE。虽然它不像 Pdb 那样集成,但你可以通过几个简单的更改使其工作。第一步是将 pydevd-pycharm 包添加到你的容器/要求中。然后,在你想要调试的执行器中,你可以像 示例 C-3 中展示的那样启用 PyCharm 调试。

示例 C-3. 启用 PyCharm 远程调试
@ray.remote
class Bloop():

    def __init__(self, dev_host):
        import pydevd_pycharm
        # Requires ability to connect to dev from prod.
        try:
            pydevd_pycharm.settrace(
                dev_host, port=7779, stdoutToServer=True, stderrToServer=True)
        except ConnectionRefusedError:
            print("Skipping debug")
            pass

    def dothing(x):
        return x + 1

你的执行器将会从执行者回到你的 PyCharm IDE 创建连接。

Python 分析器

Python 分析器可以帮助追踪内存泄漏、热代码路径以及其他重要但不是错误状态。

从安全角度来看,性能分析器比远程实时调试问题少,因为它们不需要从您的计算机直接连接到集群。 相反,分析器运行并生成报告,您可以离线查看。 性能分析仍然会带来性能开销,因此在决定是否启用时要小心。

要在执行器上启用 Python 内存分析,您可以更改启动命令以添加前缀mprof run -E --include-children, -o memory​_pro⁠file.dat --python。 然后,您可以收集memory_profile并在您的计算机上用matplotlib绘制它们,以查看是否有什么异常。

类似地,您可以通过在启动命令中用echo "from ray.scripts.scripts import main; main()" > launch.py; python -m cProfile -o stats launch.py替换ray start来在ray execute中启用函数分析。 这比使用mprof略微复杂,因为默认的 Ray 启动脚本与cProfile不兼容,因此您需要创建一个不同的入口点—但在概念上是等效的。

警告

用于基于注释的分析的line_profiler包与 Ray 不兼容,因此您必须使用整体程序分析。

Ray 和容器退出代码

退出代码是程序退出时设置的数字代码,除了 0 以外的任何值通常表示失败。 这些代码(按照惯例)通常有意义,但不是 100%一致。 以下是一些常见的退出代码:

0

成功(但通常误报,特别是在 shell 脚本中)

1

通用错误

127

命令未找到(在 shell 脚本中)

130

用户终止(Ctrl-C 或 kill)

137

Out-of-memory error kill -9(强制终止,不可忽略)

139

段错误(通常是本地代码中的空指针解引用)

您可以使用echo $?打印出上次运行命令的退出代码。 在运行严格模式脚本(如某些 Ray 启动脚本)时,您可以打印出退出代码,同时传播错误[raycommand] || (error=$?; echo $error; exit $error)。²

Ray 日志

Ray 的日志与许多其他分布式应用程序的日志行为不同。 由于 Ray 倾向于在容器启动之后在容器上启动工作进程,³ 与容器关联的 stdout 和 stderr 通常不包含您需要的调试信息。 相反,您可以通过查找 Ray 在头节点上创建符号链接到的最新会话目录*/tmp/ray/session_latest*来访问工作容器日志。

容器错误

调试容器错误可能特别具有挑战性,因为到目前为止探索的许多标准调试技术都有挑战。 这些错误可以从常见的错误(如 OOM 错误)到更神秘的错误。 很难区分容器错误或退出的原因,因为容器退出有时会删除日志。

在 Kubernetes 上,有时可以通过在日志请求中添加-p来获取已经退出的容器的日志(例如,kubectl logs -p)。你还可以配置terminationMessagePath来指向包含有关终止退出信息的文件。如果你的 Ray worker 退出,定制 Ray 容器启动脚本以增加更多日志记录可能是有意义的。常见的附加日志类型包括从syslogdmesg中获取的最后几行(查找 OOMs),将其记录到稍后可以用于调试的文件位置。

最常见的容器错误之一,本地内存泄漏,可能很难调试。像Valgrind这样的工具有时可以追踪本地内存泄漏。使用类似 Valgrind 的工具的详细信息超出了本书的范围,请查看Python Valgrind 文档。你可能想尝试的另一个“技巧”是有效地二分你的代码;因为本地内存泄漏最常发生在库调用中,你可以尝试注释它们并运行测试,看看哪个库调用是泄漏的来源。

本地错误

本地错误和核心转储与容器错误具有相同的调试挑战。由于这些类型的错误通常导致容器退出,因此访问调试信息变得更加困难。这个问题的“快速”解决方案是在 Ray 启动脚本中(失败时)添加sleep,以便你可以连接到容器(例如,[raylaunchcommand] || sleep 100000)并使用本地调试工具。

然而,在许多生产环境中,访问容器的内部可能比表面看起来更容易。出于安全原因,你可能无法获得远程访问(例如,在 Kubernetes 上使用kubectl exec)。如果是这种情况,你可以(有时)向容器规范添加关闭脚本,将核心文件复制到容器关闭后仍然存在的位置(例如,s3、HDFS 或 NFS)。

结论

在 Ray 中,启动调试工具可能需要更多工作,如果可能的话,Ray 的本地模式是远程调试的一个很好的选择。你可以利用 Ray 的 actors 来使远程函数调度更可预测,这样更容易知道在哪里附加你的调试工具。并非所有错误都是平等的,一些错误,比如本地代码中的分段错误,尤其难以调试。祝你找到 Bug(s)!我们相信你。

¹ Ray 有ray attach命令来创建到主节点的 SSH 连接;但并非所有主节点都会有 SSH 服务器。在 Ray on Kubernetes 上,你可以通过运行kubectl exec -it -n [rayns] [podname] – /bin/bash来到达主节点。每个集群管理器在此处略有不同,因此你可能需要查阅你的集群管理器的文档。

² 这个更改的确切配置位置取决于所使用的集群管理器。对于在 Kube 上使用自动缩放器的 Ray,你可以修改 workerStartRayCommands。对于在 AWS 上的 Ray,修改 worker_start_ray_commands,等等。

³ 这可以通过 sshkubectl exec 完成。