Dify中的sandbox服务- FAQ翻译

1.为什么 Python 代码会报错 “xxx.so: cannot open shared object file: No such file or directory”?

这是因为 dify-sandbox 的实现机制会在 /var/sandbox/sandbox-python/ 目录下生成一个临时文件来保存和执行 Python 代码。在运行 Python 代码前,它会调用 syscall.Chroot 将当前进程的根目录限制为 /var/sandbox/sandbox-python/。Python 进程只能看到该目录结构,因而能导入的 Python 模块/库(特别是基于 C 的共享对象模块 .so)也必须在此目录结构中。

根目录说明:从 Python 进程的视角来看,/var/sandbox/sandbox-python/ 是根目录,目录内容由在 config.yaml 中的 python_lib_path 配置决定,通常包括:

  • etc/目录

  • python.so:由dify-sandbox编译生成的共享对象

  • usr/lib目录

  • usr/local/

如果未在 config.yaml 中配置 python_lib_pathdify-sandbox 会使用默认设置。可在 config_default_amd64.go 查看,ARM 架构请查看 config_default_arm64.go

var DEFAULT_PYTHON_LIB_REQUIREMENTS = []string{
"/usr/local/lib/python3.10"// 一般是 Python 安装目录;如果使用的是 conda 虚拟环境,需改为 conda 路径,例如 /root/anaconda3/envs/{env_name}
"/usr/lib/python3.10",
"/usr/lib/python3",
"/usr/lib/x86_64-linux-gnu/libssl.so.3"// Python 模块的共享对象依赖,将被复制到 /var/sandbox/sandbox-python/usr/lib/x86_64-linux-gnu/,从中加载
"/usr/lib/x86_64-linux-gnu/libcrypto.so.3"// 同上
"/etc/ssl/certs/ca-certificates.crt",
"/etc/nsswitch.conf",
"/etc/hosts",
"/etc/resolv.conf",
"/run/systemd/resolve/stub-resolv.conf",
"/run/resolvconf/resolv.conf",
}

因此,当遇到.so 文件无法找到的报错时,应在config.yaml中手动添加Python代码所依赖的.so 路径。例如:

python_path:/usr/local/bin/python3
python_lib_path:
-/usr/local/lib/python3.10
-/usr/lib/python3.10
-/usr/lib/python3
-/usr/lib/x86_64-linux-gnu/libssl.so.3
-/usr/lib/x86_64-linux-gnu/libcrypto.so.3
-/etc/ssl/certs/ca-certificates.crt
-/etc/nsswitch.conf
-/etc/hosts
-/etc/resolv.conf
-/run/systemd/resolve/stub-resolv.conf
-/run/resolvconf/resolv.conf
-# *** 添加需要的其它路径 ***

注意 Go程序在启动时会加载这些路径,如果python_lib_path过多,会显著拖慢启动速度。对于Serverless 场景,建议改为在 Docker 容器中提前构建该环境。

2.Python 代码报错 “operation not permitted”?

dify-sandbox 使用 Linux 的 seccomp 技术限制系统调用(syscall)。建议阅读源码 add_seccomp.go

出现该报错,通常是因为 Python 代码调用了被禁止的系统调用。默认允许的系统调用见:syscalls_amd64.go。这些配置目前 不能通过配置文件修改,只能修改源码。

快速定位代码所需系统调用的方法如下:

(1)打开 /cmd/test/syscall_dig/test.py,将测试代码添加到 main() 函数中,例如:

import numpy  # 添加想测试的导入
defmain():
    ...

(2)运行 syscall 侦测工具

go run cmd/test/syscall_dig/main.go

输出可能,如下所示:

~/dify-sandbox$ go run cmd/test/syscall_dig/main.go
failed with signal: bad systemcall
...
failedwith signal: bad systemcall
Following syscalls arerequired0,1,3,5,8,9,10,11,12,13,14,15,16,17,24,28,35,39,60,63,105,106,131,186,202,204,217,231,233,234,237,257,262,273,281,291,318,334,435

若没有此输出格式,可能是权限问题,请尝试加 sudo 重新运行。

(3)这些系统调用(syscall)是沙箱中已经添加的:

0, 1, 3, 8, 9, 10, 11, 12, 13, 14, 15, 16, 16, 24, 25, 35, 39, 60, 96, 102, 105, 106, 110, 131, 186, 201, 202, 217, 228, 230, 231, 233, 234, 257, 262, 270, 273, 291, 318, 334

需要将这些已有系统调用与代码所需系统调用进行比较,找出额外缺失的 syscall 编号。可以使用一个简单的脚本,或者让大语言模型(LLM)帮助完成这个操作。

例如,在当前案例中,缺失的系统调用为:

5, 17, 28, 63, 204, 237, 281, 435

(4)在 /internal/static/python_syscall/syscalls_amd64.go 中添加正确的系统调用别名,可在 golang 库中找到它,比如 /usr/lib/go-1.18/src/syscall/zsysnum_linux_amd64.go

var ALLOW_SYSCALLS = []int{
// 文件IO
    syscall.SYS_NEWFSTATAT, syscall.SYS_IOCTL, syscall.SYS_LSEEK, syscall.SYS_GETDENTS64,
    syscall.SYS_WRITE, syscall.SYS_CLOSE, syscall.SYS_OPENAT, syscall.SYS_READ,

    ...

// numpy运行所需
    syscall.SYS_FSTAT, syscall.SYS_PREAD64, syscall.SYS_MADVISE, syscall.SYS_UNAME,
    syscall.SYS_SCHED_GETAFFINITY, syscall.SYS_MBIND, syscall.SYS_EPOLL_PWAIT, 435,
}

如果找不到某些 syscall 的别名(如 SYS_XXXX),也可以直接使用数字编号(如 435)添加进去。

(5)重新构建并运行整个项目。

参考文献

[0] Dify中的sandbox服务- FAQ翻译:https://z0yrmerhgi8.feishu.cn/wiki/VgtJwwGVeiwXvzkYPVPco1oTnjb

[1] dify-sandbox/FAQ.md:https://github.com/langgenius/dify-sandbox/blob/main/FAQ.md


知识星球:Dify源码剖析及答疑,Dify扩展系统源码,AI书籍课程|AI报告论文,公众号付费资料。加微信buxingtianxia21进NLP工程化资料群,以及Dify交流群。

(文:NLP工程化)

发表评论