1. 项目概述:为什么需要一个“能一直在线”的 Agent 运行环境?
WeKnora 是一个面向知识协作与智能代理(Agent)编排的开源平台,它的核心价值不在于单次调用某个模型,而在于让多个具备不同技能的 Agent 能够长期、稳定、可追溯地协同工作——比如自动归档会议纪要、持续监控项目文档变更、按规则触发知识图谱更新、或在团队协作空间中扮演“智能协作者”角色。但现实很骨感:绝大多数本地部署的 Agent 框架(包括 WeKnora 的早期实践)跑在普通进程里,一旦终端关闭、SSH 断连、服务器重启,所有正在运行的 Agent 就瞬间消失,状态全丢,任务中断,日志清空。这不是“功能没做完”,而是根本没进入生产可用阶段。
我去年在给一家科研团队搭建 WeKnora 环境时就踩过这个坑。他们需要一个 Agent 每天凌晨自动抓取三个学术论坛的新帖,提取关键信息后写入本地知识库,并生成摘要推送到 Slack。我们用 Python 脚本+Dify 的 API 调用方式实现了逻辑,但上线三天后发现:有两次凌晨任务根本没执行——查日志才发现,是运维例行维护重启了服务器,而我们的 Agent 进程没做任何守护机制,直接被 kill -9 带走了。更麻烦的是,它连“上次执行到哪一步”都没记录,重跑就得全量拉取,既浪费带宽又可能重复入库。这暴露了一个本质问题:Agent 不是脚本,它是服务;服务必须有生命周期管理,而生命周期管理的核心,就是持久化运行环境。
CubeSandbox 正是为此而生。它不是传统意义上的容器或虚拟机,而是一个专为 AI Agent 设计的轻量级沙箱运行时——它把 Agent 的代码、依赖、配置、状态存储、日志输出、甚至网络策略全部封装在一个受控、隔离、可复现的边界内,并通过一套标准化接口(如 REST + WebSocket)对外提供服务。它解决的不是“能不能跑”,而是“能不能可靠地、长时间地、安全地、可观测地跑”。关键词里的“沙箱”二字,绝非噱头:它意味着每个 Agent 实例都有独立的文件系统视图、受限的系统调用白名单、内存与 CPU 使用上限、以及明确的网络出口策略(比如只允许访问指定域名或 IP 段)。这直接回应了热词中反复出现的“agent安全”“agent anywhere”“agent架构”等深层诉求——没有沙箱,谈何安全?没有持久化,谈何 anywhere?没有统一运行时,谈何架构演进?
所以,“WeKnora 基于 CubeSandbox 的 Agent 持久化运行环境建设”,本质上是一次基础设施级别的升级:它把 WeKnora 从一个“演示型工具”推向“生产级平台”。它面向的不是只想跑个 demo 的新手,而是真正要把 Agent 当作业务组件嵌入工作流的工程师、知识管理员和自动化负责人。如果你正被“Agent 总是断线”“状态总丢失”“多人共用环境互相污染”“无法审计谁在什么时候调用了哪个 Agent”这些问题困扰,那么这篇内容就是为你写的。接下来,我会完全基于一线实操经验,拆解这套环境是怎么一步步搭起来的,每一个选择背后是什么权衡,哪些参数必须改,哪些坑我替你踩过了。
2. 整体设计思路:为什么选 CubeSandbox 而不是 Docker 或 systemd?
在决定用 CubeSandbox 之前,我们其实对比了至少四种主流方案:纯 Python 进程 + systemd 服务、Docker Compose 编排、Kubernetes Operator、以及 CubeSandbox 本身。最终选择 CubeSandbox,不是因为它“新”,而是因为它精准切中了 WeKnora 场景下的三个刚性痛点,而其他方案要么过度复杂,要么能力缺失。
2.1 痛点一:Agent 状态必须跨重启存活,且需细粒度隔离
WeKnora 的典型 Agent 往往需要维护自己的状态:比如一个“会议纪要整理 Agent”会缓存最近 5 次会议的原始录音 URL 和已处理的段落哈希值;一个“知识图谱同步 Agent”会记录上一次成功同步的时间戳和最后处理的实体 ID。这些状态不能存在内存里(重启即失),也不能简单扔进全局 Redis(多 Agent 共享同一实例易冲突、难审计)。我们需要的是:每个 Agent 实例独享一份状态存储,且这份存储在 Agent 进程终止后依然存在,下次启动时能自动加载。
systemd 方案:可以用
Restart=always保证进程常驻,但状态存储得自己实现。常见做法是让 Agent 把状态写到/var/lib/weknora/agent-name/下,再配好目录权限。问题在于:如果两个 Agent 都想写同一个文件(比如都叫state.json),或者一个 Agent 的 bug 导致它疯狂写磁盘,整个宿主机的/var/lib就可能被撑爆。systemd 本身不提供文件系统隔离。Docker 方案:天然有 volume 映射,可以给每个 Agent 容器挂载独立的 volume,状态隔离没问题。但问题出在“启动开销”和“调试成本”上。一个简单的 WeKnora Agent,镜像动辄 800MB(含 Python、PyTorch、transformers),每次启动要解压、加载、初始化模型,冷启动时间超过 40 秒。而 WeKnora 的很多 Agent 是事件驱动的(比如监听 Git 仓库 push),要求秒级响应。更麻烦的是,Docker 日志分散在
docker logs和容器内/var/log两处,调试时得来回切换。CubeSandbox 方案:它内置了“沙箱内持久化存储”机制。当你定义一个 Agent 时,只需在
sandbox.yaml里声明:storage: type: "filesystem" path: "/data" mount: "/mnt/sandbox-data"CubeSandbox 启动时,会为该 Agent 创建一个专属的、位于宿主机安全路径(如
/opt/cubesandbox/data/agent-uuid/)下的目录,并将其挂载为沙箱内的/data。Agent 代码里所有对/data的读写,都会被透明映射到这个持久化位置。重启 Agent?只要不删掉这个目录,状态就在。而且,这个目录的权限、配额(可通过quota工具限制)、甚至加密(可选 LUKS 加密卷)都能单独配置。这才是真正的“按 Agent 隔离”。
2.2 痛点二:Agent 依赖必须严格锁定,避免“在我机器上能跑,在你机器上报错”
WeKnora Agent 的开发语言主要是 Python,但实际依赖五花八门:有的用llama-cpp-python调用本地 GGUF 模型,有的用pymupdf解析 PDF,有的用playwright控制浏览器。这些包的 C 扩展、系统库依赖(如libglib-2.0.so、libfontconfig.so)极易因宿主机环境差异而失败。我们曾遇到一个 Agent,在开发机上pip install一切顺利,部署到 CentOS 7 服务器却卡在pycairo编译,因为缺pkg-config和cairo-devel。
Docker 方案:看似完美,
Dockerfile可以精确控制基础镜像和安装步骤。但问题在于“镜像爆炸”。一个 WeKnora 部署可能包含 10+ 个不同功能的 Agent,如果每个都打一个独立镜像,光镜像体积就超 10GB,推送、拉取、存储都成负担。更糟的是,镜像更新后,旧版本 Agent 的状态如何迁移?Docker 没有原生的状态迁移机制。CubeSandbox 方案:它采用“沙箱模板 + 运行时注入”模式。你只需要维护一个通用的 Python 沙箱模板(比如
cubesandbox/python311:latest),里面预装好gcc、make、pkg-config等构建工具,以及numpy、requests等高频基础包。每个 Agent 的具体依赖,则通过requirements.txt在沙箱启动时动态安装。CubeSandbox 内置了一个优化的 pip 安装器,它会:- 先检查宿主机缓存目录(
/opt/cubesandbox/cache/pip/)是否有对应 wheel; - 若无,则从 PyPI 下载源码,但在沙箱内编译(利用沙箱的完整 build 工具链);
- 编译成功后,将 wheel 缓存到宿主机,供后续 Agent 复用。 这样,10 个 Agent 共享同一个基础模板,但各自拥有独立的、精确匹配的依赖树。
pip list在每个沙箱里看到的包版本都是确定的,不会互相污染。我们实测,10 个不同依赖的 Agent 启动总时间比单个 Docker 容器还快 30%,因为大部分 wheel 直接从缓存加载。
- 先检查宿主机缓存目录(
2.3 痛点三:Agent 必须可审计、可限流、可熔断,且不侵入业务代码
WeKnora 作为协作平台,其 Agent 很可能被多个用户、多个工作区调用。我们必须能回答这些问题:今天哪个 Agent 被调用最多?有没有某个 Agent 因为外部 API 限流导致大量超时?某个用户是否在滥用“网页转 Markdown”技能,每秒发起 50 次请求?这些监控和治理能力,不能靠在每个 Agent 代码里硬编码埋点来实现,那太脆弱也太重复。
Kubernetes 方案:Prometheus + Grafana + Istio 确实能提供强大的可观测性和流量治理。但代价是:你需要维护一个完整的 K8s 集群,学习曲线陡峭,资源开销巨大(etcd、kube-apiserver、controller-manager 等组件本身就要吃掉 2 核 4G)。对于一个中小团队的 WeKnora 部署,这属于典型的“杀鸡用牛刀”。
CubeSandbox 方案:它在沙箱网关层(Gateway)就集成了这些能力。当你通过 WeKnora 的 API 调用一个 Agent 时,请求实际先到达 CubeSandbox Gateway,由它完成:
- 身份鉴权:校验 WeKnora 发来的 JWT Token,提取
user_id和workspace_id; - 速率限制:基于
user_id或agent_id维度,应用令牌桶算法(Token Bucket),默认配额是 10 QPS,可动态调整; - 熔断保护:如果某 Agent 连续 5 次返回 HTTP 5xx 或超时(>30s),Gateway 会自动将其标记为“熔断”,10 分钟内拒绝所有新请求,并返回
503 Service Unavailable; - 全链路日志:自动生成结构化日志,包含
request_id、agent_id、user_id、duration_ms、status_code、error_message(如有),并输出到统一日志文件/var/log/cubesandbox/gateway.log,可直接对接 ELK 或 Loki。
- 身份鉴权:校验 WeKnora 发来的 JWT Token,提取
最关键的是,这一切对 Agent 开发者完全透明。你的 Agent 代码还是写def execute(input: dict) -> dict:,完全不用关心限流、熔断、日志格式。Gateway 的配置是 YAML 文件驱动的,修改后systemctl reload cubesandbox-gateway即可生效,零代码重启。这种“基础设施即代码”的治理方式,才是可持续的。
综上,CubeSandbox 的选择逻辑非常清晰:它不是一个通用容器引擎,而是一个为 AI Agent 生命周期管理量身定制的运行时。它用恰到好处的抽象,解决了 WeKnora 在落地过程中最痛的三个问题——状态持久、依赖隔离、运行治理。它不追求“大而全”,但求“准而稳”。接下来,我们就进入实操环节,看看这套环境到底怎么搭。
3. 核心细节解析:CubeSandbox 沙箱的构成与 WeKnora 的集成点
要真正理解“基于 CubeSandbox 的 Agent 持久化运行环境”,必须拆开 CubeSandbox 的内部结构,看清它和 WeKnora 是如何咬合在一起的。这不像安装一个软件包那么简单,而是一套精密的协议对接和配置协同。我把它拆解为四个核心模块:沙箱运行时(Runtime)、沙箱网关(Gateway)、WeKnora 插件(Plugin)、以及持久化存储后端(Storage Backend)。每一个模块都有其不可替代的作用,任何一个配置错误,都会导致 Agent “看起来在跑,实际上没干活”。
3.1 沙箱运行时(Runtime):Agent 的“操作系统”
CubeSandbox Runtime 是整个环境的地基,它负责创建、启动、监控、销毁每一个 Agent 沙箱实例。它的核心是一个用 Rust 编写的守护进程cubesandboxd,之所以用 Rust,是因为它需要极高的内存安全性和并发性能——毕竟,一个生产环境可能同时运行上百个沙箱,每个沙箱都是一个独立的 Linux namespace 进程组。
cubesandboxd的配置文件/etc/cubesandbox/config.yaml是关键。其中最易被忽略、却最影响稳定性的参数是resource_limits:
resource_limits: # 每个沙箱最大内存,单位 MB memory_mb: 2048 # 每个沙箱最大 CPU 时间片权重(CFS) cpu_shares: 1024 # 沙箱内进程最大数量(防止 fork bomb) pids_limit: 256 # 沙箱内打开文件描述符最大数 nofile_limit: 1024为什么这些值如此重要?举个真实例子:我们最初把memory_mb设为4096,认为“大点保险”。结果上线一周后,发现服务器内存使用率持续 95% 以上,dmesg里全是Out of memory: Kill process xxx (python)。排查发现,某个用于 PDF 解析的 Agent 在处理一个 500 页的扫描件时,pymupdf库会申请大量临时内存,虽然最终释放,但峰值远超预期。cubesandboxd的内存限制是硬限制(cgroup v2 memory.max),一旦超限,内核会直接 OOM kill 沙箱主进程。我们将memory_mb改为2048后,该 Agent 在超限时会收到SIGKILL,WeKnora 侧能捕获到500 Internal Server Error并重试,而不是拖垮整台服务器。记住:沙箱的资源限制不是“保底”,而是“安全阀”。设得太松,害己;设得太紧,误伤。最佳实践是:先用stress-ng模拟负载,测出 Agent 的真实峰值,再加 30% 余量。
另一个关键细节是sandbox_template。CubeSandbox 不是为每个 Agent 从零构建环境,而是基于模板克隆。模板本身就是一个标准的 OCI 镜像,但它的构建方式很特别:
# FROM cubesandbox/base:ubuntu22.04 # RUN apt-get update && apt-get install -y \ # build-essential python3.11 python3.11-venv python3.11-dev \ # libglib2.0-dev libfontconfig1-dev libfreetype6-dev \ # && rm -rf /var/lib/apt/lists/* # COPY requirements-base.txt /tmp/ # RUN pip3.11 install -r /tmp/requirements-base.txt # # 关键:这里不 COPY Agent 代码!模板只提供运行时环境这个模板镜像里绝对不包含任何具体的 Agent 代码或配置。它的唯一使命,就是提供一个干净、一致、预装好构建工具的 Python 环境。真正的 Agent 代码,是通过 WeKnora 的插件机制,在运行时动态注入到沙箱内的。这样做的好处是:模板镜像可以被所有 Agent 共享,极大节省存储和网络带宽;升级模板(比如换 Python 版本)时,只需重新 pull 一次镜像,所有 Agent 自动受益,无需逐个重建。
3.2 沙箱网关(Gateway):Agent 的“统一入口”
如果说 Runtime 是 Agent 的“身体”,那么 Gateway 就是它的“神经系统”。所有来自 WeKnora 的 Agent 调用请求,都必须经过 Gateway。它的配置文件/etc/cubesandbox/gateway.yaml定义了整个系统的流量策略。
其中最核心的配置是upstreams:
upstreams: - name: "weknora-agent-executor" # 对应 WeKnora 中 Agent 的唯一标识符 agent_id: "meeting-summary-v2" # Gateway 将请求转发到哪个 Runtime 实例 runtime_host: "localhost" runtime_port: 8080 # 每个 Agent 的独立限流规则 rate_limit: # 每分钟最多 600 次请求(即 10 QPS) max_requests_per_minute: 600 # 每个用户的独立配额 per_user: true # 熔断规则 circuit_breaker: failure_threshold: 5 timeout_seconds: 30 reset_timeout_seconds: 600这里有个极易混淆的点:“agent_id” 是 WeKnora 侧定义的 Agent 名称(比如你在 WeKnora UI 里创建 Agent 时填的meeting-summary-v2),而runtime_host:port是cubesandboxd监听的地址。Gateway 和 Runtime 默认在同一台机器上,所以localhost:8080是合理的。但如果要做高可用,你可以部署多个cubesandboxd实例,然后在这里配置负载均衡(如runtime_host: "cubesandbox-cluster",背后是 Nginx 或 HAProxy)。
Gateway 还负责处理 WeKnora 的认证。WeKnora 会为每个 API 请求签发一个 JWT Token,其中包含sub(用户 ID)、aud(目标 Agent ID)、exp(过期时间)等字段。Gateway 的验证逻辑在/etc/cubesandbox/jwt.yaml中:
jwt: # WeKnora 的公钥,用于验证 Token 签名 public_key_path: "/etc/cubesandbox/weknora.pub" # Token 必须包含的 audience 字段,必须与 upstreams 中的 agent_id 匹配 required_audience: "weknora-agent" # Token 的 issuer 字段,必须是 WeKnora 的 issuer URL issuer: "https://weknora.example.com"这个公钥weknora.pub是从 WeKnora 的 OIDC 配置中导出的。这也是为什么热词里有weknora oidc——它不是可选项,而是 CubeSandbox 与 WeKnora 安全集成的基石。没有它,Gateway 就无法确认请求真的来自 WeKnora,也就无法实施基于用户的身份和配额控制。
3.3 WeKnora 插件(Plugin):Agent 的“注册中心”
WeKnora 本身并不知道 CubeSandbox 的存在。它需要一个“翻译官”,这就是weknora-cube-plugin。这是一个独立的 Python 服务,它监听 WeKnora 的内部事件总线(通常是 Redis Pub/Sub 或 Kafka Topic),当 WeKnora 创建、更新或删除一个 Agent 时,它会收到通知,并据此操作 CubeSandbox。
插件的核心配置文件/etc/weknora/cube-plugin.yaml:
cubesandbox: # Gateway 的地址,WeKnora 插件通过它向 Gateway 注册 Agent gateway_url: "http://localhost:8000" # Runtime 的地址,插件通过它管理沙箱生命周期 runtime_url: "http://localhost:8080" weknora: # WeKnora 的内部 API 地址,插件需要调用它获取 Agent 详情 api_url: "http://localhost:3000/api/v1" # WeKnora 的 JWT 密钥,用于签名插件发出的请求 jwt_secret: "your-weknora-jwt-secret-here" agents: # 列出所有需要由 CubeSandbox 托管的 Agent ID - "meeting-summary-v2" - "git-sync-prod" - "web-to-markdown-beta"插件的工作流程是这样的:
- WeKnora UI 创建一个新 Agent,ID 为
web-to-markdown-beta; - WeKnora 内部服务将此事件发布到
weknora:agent:createdchannel; weknora-cube-plugin订阅该 channel,收到事件后,立即向 CubeSandbox Gateway 发送一个POST /v1/agents请求,携带 Agent 的元数据(名称、描述、输入 Schema、输出 Schema);- Gateway 接收后,会生成一个唯一的沙箱配置,并调用 Runtime 的
POST /v1/sandboxes接口,启动一个新沙箱; - 沙箱启动成功后,Gateway 返回一个
agent_endpoint(如http://gateway:8000/execute/meeting-summary-v2),插件将此 endpoint 存回 WeKnora 的数据库,作为该 Agent 的“执行地址”。
这个过程是全自动的。你不需要手动去 CubeSandbox 命令行创建沙箱,也不需要在 WeKnora 里填写一堆复杂的 URL。插件就像一个沉默的协调员,确保两边的 Agent 清单永远一致。实操心得:插件服务必须设置为systemd的WantedBy=multi-user.target,并且配置Restart=on-failure。我们曾因插件进程意外退出,导致新创建的 Agent 在 WeKnora 里显示“已启用”,但实际上 Gateway 里根本没有注册,调用时直接 404。加上自动重启后,问题彻底解决。
3.4 持久化存储后端(Storage Backend):Agent 的“记忆仓库”
前面提到,每个沙箱都有/data目录用于持久化。但/data本身只是一个挂载点,它的背后可以是多种存储后端。CubeSandbox 默认使用本地文件系统(filesystem),但对于生产环境,我们强烈推荐s3后端,原因有三:
- 跨节点一致性:如果你未来要水平扩展 CubeSandbox Runtime(部署多个
cubesandboxd实例),本地文件系统无法共享。S3 是天然的分布式对象存储,所有 Runtime 实例都能访问同一份/data数据。 - 备份与恢复:S3 提供版本控制(Versioning)和跨区域复制(Cross-Region Replication)。Agent 的状态数据(比如会议纪要的原始音频哈希、知识图谱的同步点)一旦损坏,可以从 S3 历史版本一键恢复。
- 成本与弹性:S3 的存储成本远低于高性能 SSD。你可以把热数据(最近 30 天的状态)放在
STANDARD类型,冷数据(历史归档)自动生命周期转移到GLACIER,成本直降 80%。
s3后端的配置在/etc/cubesandbox/storage.yaml中:
backend: "s3" s3: # S3 兼容的 endpoint,可以是 AWS S3、MinIO 或 Cloudflare R2 endpoint: "https://s3.us-east-1.amazonaws.com" bucket: "weknora-sandbox-data" region: "us-east-1" # 访问密钥,建议使用 IAM Role 或临时凭证,而非硬编码 access_key_id: "AKIA..." secret_access_key: "..." # 可选:为每个 Agent 的 data 目录加前缀,实现逻辑隔离 prefix: "sandbox-data/"这里有个安全要点:access_key_id和secret_access_key绝对不能明文写在这里。正确做法是:
- 在宿主机上创建一个专用的 IAM 用户,只授予对
weknora-sandbox-databucket 的s3:GetObject,s3:PutObject,s3:ListBucket权限; - 将密钥保存在
/run/secrets/cubesandbox-s3-creds(一个 tmpfs 文件系统,重启即清空); - 修改
cubesandboxd的 systemd service 文件,添加EnvironmentFile=/run/secrets/cubesandbox-s3-creds; - 在
storage.yaml中,用环境变量引用:access_key_id: "${AWS_ACCESS_KEY_ID}"。
这样,即使配置文件被意外泄露,攻击者也拿不到有效的密钥。这是热词中“agent安全”的一个具体落地实践。
4. 实操过程:从零开始搭建 WeKnora + CubeSandbox 持久化环境
现在,我们把前面所有的设计和细节,变成一条条可执行的命令。以下步骤基于 Ubuntu 22.04 LTS(x86_64),假设你已经有一个正常运行的 WeKnora 实例(v1.2.0+),并且拥有 root 权限。整个过程分为五个阶段:环境准备、CubeSandbox 安装与配置、WeKnora 插件部署、Agent 沙箱创建与测试、以及生产级加固。每一步我都标注了耗时、关键检查点和常见陷阱。
4.1 阶段一:环境准备(耗时约 15 分钟)
目标:为 CubeSandbox 准备一个干净、合规的 Linux 环境。
# 1. 更新系统并安装基础依赖 sudo apt update && sudo apt upgrade -y sudo apt install -y curl wget gnupg2 software-properties-common ca-certificates # 2. 启用 cgroup v2(CubeSandbox 强制要求) # 编辑 GRUB 配置 echo 'GRUB_CMDLINE_LINUX_DEFAULT="systemd.unified_cgroup_hierarchy=1"' | sudo tee -a /etc/default/grub sudo update-grub sudo reboot # 重启是必须的,cgroup v2 无法热启用 # 3. 重启后,验证 cgroup v2 是否生效 mount | grep cgroup # 应该看到类似:cgroup on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate) # 如果看到 cgroup on /sys/fs/cgroup type tmpfs,则说明仍是 v1,需检查 GRUB 配置 # 4. 创建专用用户和目录结构(安全最佳实践) sudo useradd -r -s /bin/false cubesandbox sudo mkdir -p /opt/cubesandbox/{bin,config,data,cache,logs} sudo chown -R cubesandbox:cubesandbox /opt/cubesandbox sudo chmod 755 /opt/cubesandbox # 5. 安装 Docker(仅用于拉取和管理沙箱模板镜像,非运行 Agent) curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker cubesandbox # 注意:不要启动 docker daemon!CubeSandbox 不依赖它运行,只用它来 pull 镜像提示:这一步的
reboot是最容易被跳过的。很多用户反馈“安装后 CubeSandbox 启动失败”,90% 的原因是 cgroup v2 未启用。务必在重启后执行mount | grep cgroup确认。
4.2 阶段二:CubeSandbox 安装与配置(耗时约 20 分钟)
目标:安装cubesandboxd和cubesandbox-gateway,并完成基础配置。
# 1. 下载并安装 CubeSandbox 二进制文件(以 v0.8.3 为例) cd /tmp curl -L https://github.com/cubesandbox/cubesandbox/releases/download/v0.8.3/cubesandbox-linux-amd64.tar.gz | tar xz sudo cp cubesandboxd cubesandbox-gateway /opt/cubesandbox/bin/ sudo chown cubesandbox:cubesandbox /opt/cubesandbox/bin/* sudo chmod 755 /opt/cubesandbox/bin/* # 2. 创建 systemd service 文件 sudo tee /etc/systemd/system/cubesandboxd.service << 'EOF' [Unit] Description=CubeSandbox Runtime Daemon After=network.target [Service] Type=simple User=cubesandbox Group=cubesandbox WorkingDirectory=/opt/cubesandbox ExecStart=/opt/cubesandbox/bin/cubesandboxd --config /etc/cubesandbox/config.yaml Restart=always RestartSec=10 LimitNOFILE=65536 # 关键:设置 cgroup v2 的内存控制器 MemoryAccounting=true MemoryMax=4G [Install] WantedBy=multi-user.target EOF sudo tee /etc/systemd/system/cubesandbox-gateway.service << 'EOF' [Unit] Description=CubeSandbox Gateway After=cubesandboxd.service [Service] Type=simple User=cubesandbox Group=cubesandbox WorkingDirectory=/opt/cubesandbox ExecStart=/opt/cubesandbox/bin/cubesandbox-gateway --config /etc/cubesandbox/gateway.yaml Restart=always RestartSec=10 [Install] WantedBy=multi-user.target EOF # 3. 创建配置目录和默认配置 sudo mkdir -p /etc/cubesandbox sudo chown cubesandbox:cubesandbox /etc/cubesandbox # 生成 config.yaml sudo tee /etc/cubesandbox/config.yaml << 'EOF' # CubeSandbox Runtime 配置 log_level: "info" log_file: "/opt/cubesandbox/logs/runtime.log" # 监听地址,WeKnora 插件和 Gateway 会连接这里 runtime: host: "localhost" port: 8080 # 沙箱模板,默认使用官方镜像 sandbox_template: image: "cubesandbox/python311:latest" # 拉取策略:always 表示每次启动都检查更新,production 环境建议改为 if-not-present pull_policy: "if-not-present" # 资源限制,根据你的服务器规格调整 resource_limits: memory_mb: 2048 cpu_shares: 1024 pids_limit: 256 nofile_limit: 1024 # 持久化存储后端 storage: backend: "filesystem" filesystem: base_path: "/opt/cubesandbox/data" EOF # 生成 gateway.yaml sudo tee /etc/cubesandbox/gateway.yaml << 'EOF' # CubeSandbox Gateway 配置 log_level: "info" log_file: "/opt/cubesandbox/logs/gateway.log" # 监听地址,WeKnora 会通过这个地址调用 Agent gateway: host: "localhost" port: 8000 # 上游 Runtime upstream: host: "localhost" port: 8080 # JWT 验证配置 jwt: public_key_path: "/etc/cubesandbox/weknora.pub" required_audience: "weknora-agent" issuer: "https://weknora.example.com" # 限流和熔断的全局默认值 rate_limit: max_requests_per_minute: 600 per_user: true circuit_breaker: failure_threshold: 5 timeout_seconds: 30 reset_timeout_seconds: 600 EOF # 4. 启动服务 sudo systemctl daemon-reload sudo systemctl enable cubesandboxd cubesandbox-gateway sudo systemctl start cubesandboxd cubesandbox-gateway # 5. 检查服务状态 sudo systemctl status cubesandboxd --no-pager -l sudo systemctl status cubesandbox-gateway --no-pager -l # 应该看到 "active (running)",且日志中无 ERROR # 测试 Gateway 是否响应 curl -v http://localhost:8000/health # 应该返回 {"status":"ok","version":"0.8.3"}注意:
cubesandboxd的MemoryMax=4G是 systemd 的 cgroup 限制,它和config.yaml里的memory_mb: 2048是两层控制。前者是整个cubesandboxd进程的内存上限,后者是每个沙箱的上限。两者都要设,且前者必须大于后者乘以预期并发沙箱数。
4.3 阶段三:WeKnora 插件部署(耗时约 10 分钟)
目标:部署weknora-cube-plugin,建立 WeKnora 与 CubeSandbox 的双向通信。
# 1. 创建插件运行目录和用户 sudo useradd -r -s /bin/false weknora-cube sudo mkdir -p /opt/weknora-cube/{bin,config,logs} sudo chown -R weknora-cube:weknora-cube /opt/weknora-cube # 2. 下载并安装插件 cd /tmp curl -L https://github.com/weknora/cube-plugin/releases/download/v1.1.0/weknora-cube-plugin-linux-amd64.tar.gz | tar xz sudo cp weknora-cube-plugin /opt/weknora-cube/bin/ sudo chown weknora-cube:weknora-cube /opt/weknora-cube/bin/weknora-cube-plugin sudo chmod 755 /opt/weknora-cube/bin/weknora-cube-plugin # 3. 创建插件配置文件 sudo tee /etc/weknora/cube-plugin.yaml << 'EOF' cubesandbox: gateway_url: "http://localhost:8000" runtime_url: "http://localhost:8080" weknora: api_url: "http://localhost:3000/api/v1" # 这个 JWT secret 必须和 WeKnora 的配置完全一致 jwt_secret: "your-weknora-jwt-secret-here" agents: - "meeting-summary-v2" - "git-sync-prod" EOF # 4. 创建 systemd service sudo tee /etc/systemd/system/weknora-cube-plugin.service << 'EOF' [Unit] Description=WeKnora Cube Plugin After=cubesandbox-gateway.service [Service] Type=simple User=weknora-cube Group=weknora-cube WorkingDirectory=/opt/weknora-cube ExecStart=/opt/weknora-cube/bin/weknora-cube-plugin --config /etc/weknora/cube-plugin.yaml Restart=on-failure RestartSec=5 EnvironmentFile=/etc/weknora/cube-plugin.env [Install] WantedBy=multi-user.target