资讯动态

Sdcb Chats 1.10代码执行器私有化部署与沙箱安全加固实战

发布时间:2026/10/13 7:37:42 来源:尧图企业网站定制
我第一次意识到“代码执行器”不是锦上添花是在一个内部数据分析机器人差点被砍掉的时刻。模型对话、方案生成都没问题可真要让机器人直接处理一份 CSV、跑一段统计脚本时它就只会把步骤写出来剩下的全靠人手动操作。拖了一段时间之后团队里的人都觉得这个机器人是个“动嘴不动手”的摆设。Sdcb Chats 1.10 恰好补上了这块短板。它内置了一个可以私有化部署的代码执行器把“生成代码—运行代码—返回结果”这条链路完整闭环起来。如果你想自托管聊天服务、有基础 Docker 经验但还没碰过沙箱化代码执行这篇教程会非常对路。我会从架构理解讲到部署实操再讲到安全加固和排错把你需要的细节尽量一次说透。1. 为什么这套方案要把“执行”单独拆成服务1.1 一个执行器三块拼图Sdcb Chats 1.10 在做工具调用时并没有让模型直接操作宿主进程。这个设计一开始可能让人不太理解多跑一个服务不是更麻烦吗等你真把它跑到生产环境就会发现“执行”和“对话”必须解耦否则根本没有安全感。代码执行链路由三块拼图组成。主聊天服务负责会话管理、模型调度和意图分析代码执行服务负责任务接收、沙箱调度和结果回传沙箱容器才是脚本真正运行的地方每次用完之后销毁不留下任何状态。角色典型载体负责的事情主聊天服务Sdcb.Chats 宿主进程加载模型、维护会话、决定是否调用执行器执行器服务CodeRunner 独立进程接收代码请求、分配沙箱、返回 stdout/stderr/文件沙箱容器基于 runner 镜像的短生命周期容器真正运行脚本用完即焚拆开之后好处非常直接主服务崩溃不会影响正在运行的脚本脚本失控也不会拖垮聊天主进程。你可以单独给执行器配额、单独升级镜像、单独缩容扩容甚至可以把它挪到另一台专用主机上。我在第一次部署时其实偷懒过直接把执行逻辑塞进聊天主进程里。结果一次脚本里出现了死循环CPU 直接被打满连模型对话都跟着卡了十几分钟。从那时候起我就认定执行器拆成独立服务不是可选项而是必选项。1.2 默认关闭是最大的善意很多框架为了演示效果一上来就把所有工具全开。Sdcb Chats 1.10 不太一样它的代码执行器默认是关闭的。我第一次看到这个情况时还觉得奇怪后来想明白了代码执行是聊天系统里风险最高的能力没有之一。模型生成的代码好不好是一回事它想访问什么资源又是另一回事。如果没有强制隔离一段“看起来人畜无害”的脚本可能删掉临时目录、读取服务器上的敏感文件甚至通过网络访问内网服务。默认关闭这个设计本质上就是提醒你开启之前先想清楚自己的安全边界。等到你真正在配置里打开执行器之后系统行为会变成这样模型在回答过程中如果判断需要精确计算、数据处理或文件操作就会生成一段代码主服务把它打包成一个执行请求发送给 CodeRunnerCodeRunner 启动一个沙箱容器把代码跑完再把结果回传给主服务。整个过程对用户而言是无感的用户只看到机器人最终给出答案。1.3 它和“调用函数工具”的区别你可能见过类似 OpenAI Function Calling 的能力觉得它们是一回事。其实差别很大。普通工具调用是在宿主机里执行预先写好的函数你能调用的东西是开发阶段就固定的而代码执行器接收的是模型现场生成的代码任何逻辑都可能出现。这两者的风险模型完全不同。预定义函数是“受控环境里的已知路径”代码执行器则是“不可信代码进入受控环境”。所以你不能拿配置函数工具的思路去配置执行器必须把它当成一个真正的沙箱系统来设计。后面讲的安全加固部分就是围绕这个核心差异展开的。2. 动手前的架构决策部署形态、端口与模型后端2.1 三种部署拓扑按需选动手敲命令之前我建议先把拓扑定下来。Sdcb Chats 1.10 的代码执行器并不是非要和主服务在同一台机器上不可你可以根据团队规模和安全等级选不同的部署方式。第一种是“开发全合一”所有组件都在同一个 docker-compose 文件里主服务、执行器、数据库一起起。适合个人尝鲜、内网小范围验证。优点是省事缺点是一旦执行器被攻破整个宿主机都有风险。第二种是“单机多服务”执行器单独跑在一个 Docker 守护进程上但和主服务还在同一台物理机。生产环境里很多团队会这样起步配合后面讲的资源限制基本能扛住大部分常规风险。第三种是“专用执行主机”把 CodeRunner 放在独立服务器上主服务通过内网 API 调用。适合多人使用、对隔离要求高的场景。执行器宿主机不开放 SSH只开专用的执行端口甚至在网络层直接做白名单。三者对比如下拓扑管理复杂度安全隔离建议场景全合一最低一般开发验证、Demo单机多服务中较好内部工具、小团队专用执行主机高最好多人共用、数据敏感场景2.2 端口、存储与网络规划部署前把端口和存储规划好后面能少很多麻烦。执行器服务建议使用 8765 端口但不建议把它暴露到公网也不建议直接绑定 0.0.0.0。最稳妥的做法是让它只监听内网 IP 或 127.0.0.1由聊天主服务通过内网访问。存储方面需要关注两个目录一个是执行器的日志目录用来排查问题另一个是会话临时文件目录让代码在多次执行之间可以共享数据。这两个目录建议都独立挂载不要塞进容器可写层。容器一旦重建可写层数据全消失到时候想排查日志都找不到依据。网络层面我习惯创建一个独立 Docker 网络给沙箱容器用而不是直接复用宿主的默认 bridge。这样沙箱之间的网络拓扑可控后续做网络隔离也更方便。2.3 模型后端的决策表代码执行器本身不负责模型推理它只负责“跑代码”。但你需要先想清楚模型从哪里来因为这会影响你后续测试工具调用的方式。当前比较常见的是 OpenAI 兼容 API 和本地模型方案。兼容 API 部署简单只需要配置 baseurl 和 key本地模型则需要准备 GPU 或较强的 CPU并额外部署一个推理服务比如 Ollama。方案部署难度延迟数据隐私典型场景OpenAI 兼容 API低中数据出本地服务快速上线本地模型中高低数据完全保留在内网数据敏感、离线要求高如果你的业务数据不允许出内网那就走本地模型路线。如果只是验证功能先用兼容 API 跑通链路后续再平滑切换也是完全可行的。3. 部署步骤详解构建镜像、服务配置与主服务对接3.1 获取 1.10 代码并构建执行器镜像我以源码构建的方式来讲因为很多人会基于自己的内部依赖做二次修改。先在服务器上把仓库代码拿下来并切到 1.10 版本标签。git clone 你的仓库地址 sdcb-chats cd sdcb-chats git checkout v1.10然后进入执行器项目所在目录构建 Docker 镜像。cd src/Sdcb.CodeRunner docker build -t sdcb-code-runner:1.10 -f Dockerfile .构建过程需要拉取基础 SDK 镜像网络慢的时候会比较煎熬。我建议在正式构建前先手动拉一次基础镜像避免在构建中途卡住日志刷到一半就不动了。构建完成之后用docker images确认镜像已经存在。这里有一个小经验构建上下文尽量只放项目代码不要直接把整个仓库传进去。如果你把.git、docs、test这些目录一起塞进 Context构建镜像会非常臃肿而且每次代码改动后的缓存命中率也低。3.2 一份最小可用的执行器配置执行器的appsettings.json不需要一开始写得太复杂先保证最小可用。我的建议配置如下{ Sandbox: { RunnerImage: sdcb-code-runner:1.10, MaxConcurrency: 6, DefaultTimeoutSeconds: 30, AllowedImages: [ sdcb-code-runner:1.10 ], ContainerNetwork: sdcb-sandbox-net, CpuLimit: 0.5, MemoryLimitMb: 512 }, Auth: { ApiKey: please-change-this-key }, Storage: { SessionRoot: /opt/sdcb-sessions, LogRoot: /var/log/sdcb-coderunner } }这里面最需要理解的是CpuLimit和MemoryLimitMb。它们不是装饰性参数而是每次创建沙箱容器时传给 Docker 的资源限制。0.5表示最多使用半颗 CPU512MB表示容器内存上限。前面提到的死循环案例如果当时就配了这两个参数系统根本不会卡死只会让那个容器被杀掉其他服务毫发无损。AllowedImages这个白名单同样重要。执行器只允许启动白名单内的镜像防止模型在代码里通过某种方式诱导执行器创建意料之外的容器。3.3 启动执行器并验证健康检查配置文件准备好后用 Docker 把执行器运行起来。为了让主服务能通过 Docker API 创建沙箱容器需要把 Docker socket 挂载进来。这个操作有安全风险生产环境请确保执行器所在主机本身已经被隔离保护。docker run -d --name sdcb-coderunner \ --restart unless-stopped \ -p 127.0.0.1:8765:8765 \ -e ASPNETCORE_URLShttp://127.0.0.1:8765 \ -v /var/run/docker.sock:/var/run/docker.sock \ -v /opt/sdcb-sessions:/opt/sdcb-sessions \ -v /var/log/sdcb-coderunner:/var/log/sdcb-coderunner \ sdcb-code-runner:1.10启动后先确认健康检查接口是不是通的。curl http://127.0.0.1:8765/health正常情况下会返回一个包含状态信息的 JSON。看到ok之类的结果就说明服务本体没问题。如果这一步都不通先别往下走去看容器日志。3.4 把主聊天服务接到执行器上主服务那边同样要改配置。把执行器地址和上一步设置的 ApiKey 填进去并显式开启。{ CodeRunner: { Endpoint: http://127.0.0.1:8765, ApiKey: please-change-this-key, Enabled: true } }重启主服务后观察启动日志。如果看到类似CodeRunner connected或CodeRunner health check passed的记录说明连接成功。此时你可以先做一个最直白的验证手动往执行器接口发一个最简单的代码执行请求确认沙箱容器能被正常创建和销毁。4. 会话链接通后的实际调用链路与边界4.1 先让模型能“看到”执行器这个工具代码执行器接通以后模型并不一定会主动去用它。你需要确保工具的注册信息进入了会话上下文。Sdcb Chats 1.10 通常需要你在配置或代码里注册执行器插件这样系统才会把“执行代码”这个工具描述注入到每个会话的请求里。如果注册成功你可以再追问一句“你会执行代码吗”模型可能直接回复它具备这个能力。如果模型完全不知道你在说什么大概率是工具没有注册成功先查主服务的插件加载日志。4.2 一次完整调用链的微观视角我以前面提到的数据分析机器人为例还原一下整条调用链。用户发来消息“帮我统计一下过去 30 天订单金额超过五千的用户数量。”主服务收到消息后把历史对话和工具列表一起发给模型。模型判断这是一个需要精确算数的任务于是生成一段 Python 代码并声明需要调用execute_code工具。主服务接收到工具调用请求后把代码片段连同执行参数发送给 CodeRunner。CodeRunner 根据RunnerImage启动沙箱容器容器内运行脚本把 stdout 结果写回执行器。执行器把 stdout 返回给主服务。主服务将结果包装成工具消息再次发给模型模型基于真实结果组织一段自然语言回答。这里最关键的一点模型不会直接执行代码它只是生成代码和建议的参数真正动手的是沙箱容器。中间任何一环都通过 HTTP 接口传递主服务自己不会去碰那些不可信脚本。4.3 输出太长、任务太慢怎么办代码脚本跑出来的结果可能是一个几百行的大表。如果直接把完整 stdout 扔回给模型轻则浪费 token重则超出模型上下文限制导致请求失败。我一般会给执行器设置输出截断参数。比如限制MaxOutputChars让容器只返回前面若干字符超出的部分提示模型“输出被截断请使用聚合或抽样方法”。这是一个很实用的策略能让模型自动调整自己的代码逻辑而不是被超长输出噎住。长任务则是另一个坑。执行器的默认超时通常是几十秒遇到复杂数据清洗任务很容易超时。建议把这类任务设计成异步流程主服务先提交执行请求获得任务 ID然后轮询结果。聊天回复时可以先告诉用户“任务已提交正在执行中”等结果返回后再补一条消息。{ Sandbox: { UseAsyncExecution: true, TaskPollingIntervalSeconds: 3 } }5. 沙箱安全加固的硬指标与设计取舍5.1 容器本身要锁死开启执行器之后安全优先级会明显提高。我建议至少做到下面几件事。先用--read-only参数把容器根文件系统设成只读同时用--tmpfs /tmp给临时目录单独开一块内存盘。脚本即使想写文件也只能写在被隔离的临时目录里。然后关闭所有 Linux 权限提升能力设置--security-opt no-new-privileges并执行--cap-drop ALL去掉所有不必要的 CAP 能力。运行用户方面不要在沙箱容器里使用 root。默认会让沙箱以低权限用户运行比如runner。再加上pids-limit限制进程数量防止脚本 fork 炸弹把主机进程表打满。docker run --rm -it \ --read-only \ --tmpfs /tmp \ --cap-drop ALL \ --security-opt no-new-privileges \ --pids-limit 64 \ sdcb-code-runner:1.10这套参数组合下来沙箱内的代码能做的事情会被限制在很小的范围里从源头上降低破坏面。5.2 接口鉴权与传输保护执行器是一个 HTTP 服务如果不加保护局域网里的任何人都可以提交代码执行请求那等于把一个解释器裸奔在网络上。千万别这么干。最低标准是加一层 API Key 鉴权。主服务在每次请求头里带上密钥执行器只放行匹配密钥的请求。密钥要足够长、足够随机不要用123456这种。更进一步可以用反向代理做双向 TLS或者只在专用内网 VLAN 里开放访问。执行器也建议只监听本机或指定内网 IP不要开公网端口。即使你有鉴权也不建议直接暴露到公网。如果确实有公网接入需求前面必须加一层成熟的 API 网关和限流策略。5.3 日志与临时数据清理代码执行过程中会留下很多痕迹包括日志和临时文件。日志里理论上不应该包含代码内容更不应该包含用户数据和 API Key。好的日志设计是只记录请求 ID、会话 ID、容器 ID、状态码和耗时。临时文件目录是按会话组织存放的如果长期不清理磁盘会被撑爆。可以写一个 cron 任务把 24 小时前的会话临时目录全部删除。这个操作建议在低峰期执行比如凌晨两点。清理逻辑本身很简单但不要忘了设置目录路径和保留时间否则越积越多。6. 踩坑记录从端口不通到工具调用失败6.1 端口与服务连接类问题常见场景docker ps显示执行器容器是 running但主服务一直连不上。此时先检查执行器容器日志看看有没有异常启动输出。如果日志正常再用curl -v http://127.0.0.1:8765/health验证本机访问。如果本机能通、主服务不通多半是监听地址或防火墙问题。用ss -tlnp | grep 8765看当前监听内容和端口绑定。如果执行器只绑了 IPv6 或某个网段主服务从别的网段连不上就很正常。建议直接固定绑定内网 IP不要留下歧义。如果你在 Windows 上跑 Docker Desktop还要留意端口映射的宿主防火墙规则。这属于“看起来容器没问题实际上被防火墙拦在外面”的典型场景。6.2 镜像拉取超时或预置依赖失败首次启动沙箱容器时执行器需要把目标镜像拉取到本地。如果你的服务器在内网环境镜像仓库访问受限容器创建就会一直失败。解决办法很简单构建完镜像之后在真正服务上线前手动执行一次docker pull sdcb-code-runner:1.10确保镜像已经在本地。更精细的做法是给执行器配置镜像预拉列表让它启动时自动确保依赖镜像存在。还有一类依赖问题是脚本运行时临时下载包。比如 Python 脚本在你构建镜像时没有安装 pandas运行时要现场下载结果内网没有镜像源就挂了。处理方式是把常用依赖在构建镜像时全部装好尽量让沙箱镜像做到“开箱即跑”运行时不再依赖外部网络。6.3 模型返回“想执行代码”却迟迟没动作这类问题最难排查因为模型本身不会直接告诉你它为什么没调用。我遇到过的原因主要有三个。第一个是工具 schema 不匹配。模型输出工具调用参数时字段名和执行器期望的不一致执行器把它当成非法请求丢弃。解决方法是打开主服务的请求日志看看模型传过来的原始 JSON 到底是什么结构。第二个是输出截断。如果max_tokens设置得太低模型生成工具调用时可能被掐断后半段参数直接没了。这种情况下模型看起来“想执行”但调用永远不完整。把模型配置里的最大 token 数调大一些比如 4096 以上通常能缓解。第三个是工具描述不够清晰。模型没有识别出当前场景需要使用执行器单纯用自然语言回答了。这种情况可以优化工具描述明确写出“当需要精确计算、文件处理、数据统计时必须调用 execute_code”再试试对话效果。6.4 乱码、权限与资源问题脚本输出中文乱码是常客。问题多半出在容器环境没有设置 UTF-8 编码。给容器加一个环境变量LANGC.UTF-8基本能解决。如果还不行检查脚本自身是否有写编码声明比如 Python 开头声明源码文件和 stdout 都为 UTF-8。权限问题则表现为脚本读取文件时报 Permission denied。优先确认沙箱是不是以非 root 用户运行。如果要读写某个数据文件应该把文件放到临时挂载目录并赋予正确权限而不是直接改成 777那等于把安全策略全废了。资源问题最典型的状态码是 137表示容器被 OOM 杀掉。这不一定是你写坏了而是内存限制设得太小。用docker stats观察沙箱容器实际内存占用再反推调整MemoryLimitMb。如果任务是合理的内存需求适当调高如果模型生成的代码本身就有内存爆炸那就应该让执行器杀得更快一点。最后再分享一个我反复验证过的经验上线之前一定要专门测试几条“坏路径”。让模型生成死循环代码、申请超大内存、尝试删除目录都是很好的压力测试。平时看这些测试觉得没用可真到了给业务方演示的时候最怕的就是“看起来一切正常关键时刻沙箱崩了”。把你自己的沙箱边界试到足够硬后面维护会省心非常多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑