Ray Serve 生产部署依赖管理实战:runtime_env 与按部署依赖隔离
【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray
导读
在生产环境中把 Ray Serve 应用真正跑起来,绕不开一个基础问题:应用的代码和 Python 依赖如何在集群的每一台机器上保持一致、可用。本文基于 Ray Serve 官方生产指南,系统讲解两条核心路径——通过runtime_env为整个应用注入远程代码与 pip 依赖,以及通过ray_actor_options为单个 Deployment 隔离互不兼容的依赖版本;同时给出 driver 进程与 Deployment 依赖不一致时的"延迟导入"标准写法,并深入仓库源码与配置校验逻辑,帮助你在集群、Kubernetes 与本地开发三种场景下做出正确的依赖管理决策。
为什么生产环境必须显式处理依赖
Ray Serve 的部署入口是一个import_path(例如text_ml:app),它指向应用图(Deployment Graph)的顶层对象。Serve 的 Controller 和 Replica Actor 在运行时必须能够真正 import 到这个路径:
- 在本地开发时,
import_path通常落在当前工作目录(current working directory),Serve 进程能直接找到; - 在集群上运行时,工作目录并不存在,代码文件也不会自动同步到每个节点,因此必须显式地把代码"送到"所有节点上。
官方推荐两种做法,见 集群配置文档 所在的生产指南:
- 把代码构建进集群的容器镜像:适合代码稳定、希望避免运行时下载的场景(KubeRay 的 RayCluster/RayService 镜像构建即属此类);
- 使用带远程 URI(remote URI)的
runtime_env:让每个节点在启动应用时从远程存储拉取代码,灵活、可热更新,也是官方文档强调的最佳实践。
从源码看,runtime_env最终会落到 Replica Actor 的启动选项上,作为 Ray Actor 的运行时环境来解析、下载与缓存,因此它天然具备"集群一致 + 按需安装 + 缓存复用"的特性。
应用级 runtime_env:给整个应用注入代码与依赖
一个可直接复制的完整配置示例
官方给出的 Text ML Models 示例展示了标准写法:用working_dir指向托管在远程的代码压缩包,用pip声明 Python 依赖:
import_path: text_ml:app runtime_env: working_dir: "https://github.com/ray-project/serve_config_examples/archive/HEAD.zip" pip: - torch - transformers这个配置的作用是:部署文本摘要与翻译应用时,即使你的本地机器上没有text_ml.py代码,集群也会:
- 从远程 URL 下载
working_dir指向的压缩包并解压到每个节点的沙箱目录; - 按
pip列表为每个节点安装torch、transformers; - 然后才启动 Replica Actor,此时
text_ml:app已可正常导入。
远程 URI(remote URI)的打包与使用规范
runtime_env中的working_dir与py_modules字段既可以填本地路径,也可以填远程 URI。Ray Core 的 依赖管理文档 对远程 URI 有明确约束,生产环境中极易踩坑,务必注意:
- 远程 URI 必须直接指向一个压缩包(
.zip、.tar.gz、.tgz、.tar.xz),且压缩包顶层只能有一个目录;解压后该目录的内容会被直接作为working_dir或py_module使用; - 打包时需在目标目录的父目录中执行,例如把
example_dir打包:
cd /some_path zip -r archive.zip example_dir # 或用 tar -czf archive.tar.gz example_dir- 打包后可用以下命令检查顶层是否只有一个目录:
zipinfo -1 archive.zip tar -tzf archive.tar.gz # 期望输出示例: # example_dir/ # example_dir/my_file_1.txt # example_dir/subdir/my_file_2.txt- 归档包中的隐藏文件与元数据目录(如
.DS_Store、__MACOSX、__pycache__)会混入顶层,导致解压结构不符合"单一顶层目录"要求,上传前务必检查; - 上传到对象存储后即可通过 URI 引用,例如:
runtime_env: working_dir: "s3://example_bucket/example.zip"也支持tar.gz、tgz、tar.xz等格式。
Serve 配置层面对 URI 的强制校验
在 Serve 的生产配置(Serve config / RayService 中的applications[].runtime_env)中,URI 约束比普通 Ray job 更严格。查看 python/ray/serve/schema.py 中的RayActorOptionsSchema:
runtime_env字段的校验器runtime_env_contains_remote_uris会解析working_dir与py_modules中的每一个 URI;- 校验失败时抛出明确的错误信息:"runtime_envs in the Serve config support only remote URIs in working_dir and py_modules, or "local://" URIs for directories that already exist on every node"。
也就是说,Serve 配置文件的runtime_env只能使用远程 URI(如 HTTP(S)、S3 等)或local://URI,不能直接引用本地 zip 文件或本地目录——因为配置文件要能被整个集群的节点一致地解析,本地路径在每台机器上不一定存在。这一限制在 Serve 官方配置文档的runtime_env字段说明中也有强调,并在 python/ray/serve/tests/test_runtime_env.py 等测试中覆盖了相应失败场景。
关于 PYTHONPATH 与本地开发的取舍
官方文档特别提示:你当然可以把整个部署图打包成独立的 Python 包,然后用PYTHONPATH指过去,实现本地机器的"位置无关"。但最佳实践仍是使用runtime_env——PYTHONPATH依赖的是运行 Serve 的这台机器的环境变量,无法保证集群其他节点一致;而runtime_env由 Ray 在每个节点上统一解析、安装与缓存,能确保所有机器的运行环境完全一致。
使用serve build生成配置后的必做步骤
生产环境常通过serve build自动生成 Serve 配置,但要注意:自动生成的runtime_env字段恒为空字典,必须手动补充。官方配置文档 config.md 的示例即展示了这一点——serve build生成的配置中runtime_env: {},如果torch、transformers没有预装在集群环境中,你需要手动把这两个 pip 包补进runtime_env,否则应用会在启动阶段因依赖缺失而失败。
按部署(Per-Deployment)依赖隔离:一个 Deployment 一套环境
适用场景与原理
Ray Serve 支持让同一个应用内的不同 Deployment 运行互不相同的 Python 依赖,哪怕它们互相冲突也没关系。官方给出的典型例子:同时服务一个依赖 TensorFlow 1 的旧模型,和另一个依赖 TensorFlow 2 的新模型。
实现原理与 Ray 的 runtime-environments 机制一致:runtime_env本质上是一个Ray Actor 选项,Serve 的每个 Replica 都是一个 Ray Actor,因此可以在 Deployment 的ray_actor_options中注入各自的runtime_env,让不同 Replica 各跑各的环境。
前置条件:
- 仅支持Mac OS 与 Linux(不支持 Windows);
- 需先安装
ray[default]以确保 Runtime Environments 功能可用:
pip install "ray[default]"完整可运行示例:同应用内两个 requests 版本共存
官方配套代码 doc/source/serve/doc_code/varying_deps.py 给出了完整实现——同一个应用里,一个 Deployment 用requests==2.25.1,另一个用requests==2.26.0,由一个 Ingress 根据查询参数路由:
import requests from starlette.requests import Request from ray import serve from ray.serve.handle import DeploymentHandle @serve.deployment class Ingress: def __init__( self, ver_25_handle: DeploymentHandle, ver_26_handle: DeploymentHandle ): self.ver_25_handle = ver_25_handle self.ver_26_handle = ver_26_handle async def __call__(self, request: Request): if request.query_params["version"] == "25": return await self.ver_25_handle.remote() else: return await self.ver_26_handle.remote() @serve.deployment def requests_version(): return requests.__version__ ver_25 = requests_version.options( name="25", ray_actor_options={"runtime_env": {"pip": ["requests==2.25.1"]}}, ).bind() ver_26 = requests_version.options( name="26", ray_actor_options={"runtime_env": {"pip": ["requests==2.26.0"]}}, ).bind() app = Ingress.bind(ver_25, ver_26) serve.run(app) assert requests.get("http://127.0.0.1:8000/?version=25").text == "2.25.1" assert requests.get("http://127.0.0.1:8000/?version=26").text == "2.26.0"要点拆解:
requests_version.options(...)返回一个带新配置的 Deployment 副本:name区分两个版本,ray_actor_options={"runtime_env": {...}}为各自注入不同的 pip 依赖;serve.run(app)部署后,两个 Replica 各自安装自己的requests版本,互不干扰;- 通过
http://127.0.0.1:8000/?version=25与?version=26即可验证路由正确且返回各自版本号; - 示例使用
DeploymentHandle做 Deployment 间组合调用——Ingress 不直接访问requests库,而是通过 handle 转发,这正是避免 driver 端依赖冲突的关键。
源码视角:ray_actor_options 如何贯穿配置层
- python/ray/serve/deployment.py 中,
Deployment.ray_actor_options属性直接暴露 Replica 的 Actor 选项,而options()方法(第 222 行起)接受ray_actor_options参数并深拷贝生成新的 Deployment 配置,bind()方法再把它封装成可部署的 Application——这就是requests_version.options(...).bind()链式调用的底层机制; - python/ray/serve/schema.py 中的
RayActorOptionsSchema为runtime_env提供了 Pydantic 校验(仅允许远程 URI 或local://URI),并同时定义了num_cpus、num_gpus、memory、resources、accelerator_type、label_selector等选项——也就是说,ray_actor_options不止承载runtime_env,还能精细化控制每个 Deployment 的算力资源与调度约束; - 配置层面同样支持 per-deployment 覆盖:在 Serve 配置文件的
applications[].deployments[]条目中配置ray_actor_options,即可在不改动代码的前提下为某个 Deployment 单独指定runtime_env。
注意事项:避免 from source 安装耗尽集群资源
官方给出了一条重要生产经验:尽量避免在runtime_env中动态安装需要从源码编译的包(如某些不带 wheel 的 C/C++ 扩展)。这类安装耗时很长,且编译过程会吃满节点 CPU/内存,可能拖垮整个 Ray 集群。建议:
- 将这类包预先编译好,放入私有 PyPI 仓库或直接构建进 Docker 镜像;
- 让
runtime_env的pip列表只保留能快速从 wheel 安装的依赖。
driver 与 Deployment 依赖不一致:延迟导入(Delayed Import)模式
问题根源
实际部署中经常出现这种错位:Deployment 所需的依赖,driver 程序(运行serve.run、发起 Serve API 调用的进程)并没有安装。比如模型服务用到了torch,而提交任务的 driver 机器只有轻量环境。
如果在模块顶层就import torch,driver 进程在加载应用代码时会直接 ImportError 崩溃。这个问题即使不使用 runtime_env 也同样存在,因此需要一种与运行时环境无关的稳健写法。
标准写法:把导入推迟到__call__内
官方配套代码 doc/source/serve/doc_code/delayed_import.py 展示了正确姿势——将重依赖的导入放在 Deployment 的方法内部:
from ray import serve @serve.deployment class MyDeployment: def __call__(self, model_path): from my_module import my_model self.model = my_model.load(model_path)要点解释:
from my_module import my_model被放在__call__内部,只有在请求真正到达、该 Deployment 的 Replica 已就绪时才执行导入;- 由于每个 Replica 运行在自己的
runtime_env中(安装了my_module及其依赖),此时导入必然成功; - driver 进程只需要能导入
MyDeployment类本身的定义(即from ray import serve等轻量依赖),不必安装my_module。
这一模式尤其适合以下场景:模型权重按需加载、大体积推理库只装在生产 Replica 上、driver 机器保持精简。它与按部署依赖隔离配合使用,可以做到"driver 零负担、每个 Replica 各取所需"。
总结与延伸阅读
生产环境的依赖管理可以概括为三层策略:
| 层次 | 手段 | 适用场景 |
|---|---|---|
| 应用级 | applications[].runtime_env(远程 URI + pip) | 整个应用共享一套代码与依赖,部署到多节点集群 |
| 部署级 | ray_actor_options.runtime_env | 同一应用内不同 Deployment 需要互不兼容的依赖版本 |
| 代码级 | 延迟导入(__call__内 import) | driver 与 Replica 依赖不一致,保持 driver 轻量化 |
配套的仓库证据与延伸阅读:
- doc/source/serve/doc_code/varying_deps.py:按部署依赖隔离的完整可运行示例;
- doc/source/serve/doc_code/delayed_import.py:延迟导入标准写法;
- python/ray/serve/schema.py:
RayActorOptionsSchema对runtime_env远程 URI 的强制校验实现; - python/ray/serve/deployment.py:
Deployment.options()与ray_actor_options的底层实现; - doc/source/serve/production-guide/config.md:Serve 配置文件规范,含
runtime_env字段约束与serve build说明; - doc/source/ray-core/handling-dependencies.rst:Ray Core 依赖管理总览,含 Remote URIs 打包细则与 runtime_env 缓存/回收机制;
- python/ray/serve/tests/test_runtime_env.py:runtime_env 相关测试,可验证 working_dir 与失败条件。
在集群上部署时,优先把代码构建进容器镜像(KubeRay 集群配置)或使用带远程 URI 的runtime_env;在单个应用内部需要版本隔离时,用ray_actor_options为每个 Deployment 指定独立环境;最后别忘了用延迟导入守住 driver 进程的轻量边界——三者组合,即可覆盖从本地开发到生产集群的完整依赖管理链路。
【免费下载链接】rayRay is an AI compute engine. Ray consists of a core distributed runtime and a set of AI Libraries for accelerating ML workloads.项目地址: https://gitcode.com/gh_mirrors/ra/ray
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考