Skip to content

feat/sandbox-k8s&opensandbox-mode#896

Open
voidkey wants to merge 4 commits intoTencent:mainfrom
voidkey:feat/sandbox-new-mode
Open

feat/sandbox-k8s&opensandbox-mode#896
voidkey wants to merge 4 commits intoTencent:mainfrom
voidkey:feat/sandbox-new-mode

Conversation

@voidkey
Copy link
Copy Markdown
Contributor

@voidkey voidkey commented Apr 2, 2026

Pull Request

描述 (Description)

为 sandbox 模块新增 kubernetes 和 opensandbox 两种执行模式,使部署在 K8s 集群中的用户无需依赖 Docker daemon,可直接使用原生 K8s Job 作为脚本沙箱运行环境;同时支持对接外部 alibaba/OpenSandbox 服务。

核心变更:

实现轻量级 K8s REST API 客户端(仅依赖 stdlib,无需 client-go)
实现 KubernetesSandbox:通过 ConfigMap 传递脚本、K8s Job 执行、Pod 日志采集
实现 OpenSandboxSandbox:对接 alibaba/OpenSandbox API,支持 execd 端点发现、流式响应解析、readiness polling
提供完整的 Helm 配置:RBAC、NetworkPolicy、ResourceQuota
新增 docker-compose.yml 环境变量支持
安全:Pod SecurityContext、drop ALL capabilities、只读根文件系统、seccomp、io.ReadAll 限制、URL 编码、sandbox ID 校验、shell 参数转义(含 null byte 过滤)

变更类型 (Type of Change)

  • ✨ 新功能 (New feature)
  • 🔧 配置变更 (Configuration change)

影响范围 (Scope)

  • 后端 API (Backend API)
  • 配置文件 (Configuration)
  • 其他 (Other): Helm Chart、Sandbox 执行引擎

测试 (Testing)

  • 单元测试 (Unit tests)
  • 集成测试 (Integration tests)
  • 手动测试 (Manual testing)
  • 前端测试 (Frontend testing)
  • API 测试 (API testing)

测试步骤 (Test Steps)

  1. go test ./internal/sandbox/ -v — 50+ 单元测试全部通过,覆盖两种新模式的正常执行、失败处理、脚本大小校验、超时、清理、args 传递、stdin 管道等场景
  2. OpenSandbox 集成测试:对接本地真实 OpenSandbox 服务,5 个场景全部通过(简单脚本、带 args、带 stdin、脚本错误、bash 脚本)
  3. OpenSandbox 端到端测试:通过 WeKnora Agent 对话触发 execute_skill_script → data-processor/analyze.py → OpenSandbox 执行成功,exit_code=0,耗时 2.4s (如下面截图所示)
  4. Kubernetes 模式:API 路径和请求格式基于 K8s 公开标准,单元测试全部通过,待实际集群验证

检查清单 (Checklist)

  • 代码遵循项目的编码规范
  • 已进行自我代码审查
  • 代码变更已添加适当的注释
  • 相关文档已更新
  • 变更不会产生新的警告
  • 已添加测试用例证明修复有效或功能正常
  • 新功能和变更已更新到相关文档
  • 破坏性变更已在描述中明确说明

相关 Issue

Fixes #840

截图/录屏 (Screenshots/Recordings)

opensandbox 验证
Screenshot 2026-04-03 at 04 27 19

数据库迁移 (Database Migration)

  • 需要数据库迁移
  • 不需要数据库迁移

配置变更 (Configuration Changes)

新增环境变量:

环境变量 说明 默认值
WEKNORA_SANDBOX_MODE 新增 kubernetes / opensandbox 可选值 disabled
WEKNORA_SANDBOX_KUBE_NAMESPACE K8s sandbox Job 运行的命名空间 weknora-sandbox
WEKNORA_SANDBOX_KUBE_SERVICE_ACCOUNT sandbox Pod 使用的 ServiceAccount weknora-sandbox-runner
WEKNORA_SANDBOX_MAX_CONCURRENT 最大并发沙箱数 5
WEKNORA_SANDBOX_OPENSANDBOX_API_URL OpenSandbox 服务地址
WEKNORA_SANDBOX_OPENSANDBOX_API_KEY OpenSandbox API 密钥

Helm values 新增 app.sandbox.kubernetes.* 和 app.sandbox.opensandbox.* 配置段,其中 API Key 支持通过 existingSecret 从 K8s Secret 注入。

docker-compose.yml 新增以上环境变量的传递(含 SSRF_WHITELIST)

部署说明 (Deployment Notes)

Kubernetes 模式
适用场景: 在 K8s 集群中部署 WeKnora,需要容器级隔离但集群中没有 Docker daemon(如使用 containerd/CRI-O 运行时)。

前置条件:

  • K8s 集群版本 ≥ 1.21(需要 batch/v1 Job API 和 ttlSecondsAfterFinished 支持)
  • 集群中已存在(或可拉取)sandbox 镜像 wechatopenai/weknora-sandbox:latest
  • 如果集群启用了 PodSecurityAdmission,sandbox 命名空间需允许 restricted 级别的 Pod

Helm 部署(推荐):

# values.yaml
app:
  sandbox:
    mode: kubernetes
    timeout: 60
    image: wechatopenai/weknora-sandbox:latest
    kubernetes:
      namespace: weknora-sandbox          # sandbox Job 运行的独立命名空间
      maxConcurrentSandboxes: 5           # 单实例最大并发数
      quota:
        maxPods: 10                       # 命名空间级 Pod 总数上限
        maxMemory: "2Gi"                  # 命名空间级内存总上限
        maxCPU: "4"                       # 命名空间级 CPU 总上限

Helm 会自动创建以下资源:

Namespace:sandbox 专用命名空间
ServiceAccount:weknora-sandbox-runner(sandbox Pod 使用,禁用 token 自动挂载)
Role + RoleBinding:赋予 WeKnora 的 SA 在 sandbox 命名空间操作 jobs/configmaps/pods 的权限
NetworkPolicy:deny-all,sandbox Pod 无法访问任何网络
ResourceQuota:限制命名空间内资源总量
环境变量部署(非 Helm):

WEKNORA_SANDBOX_MODE=kubernetes
WEKNORA_SANDBOX_IMAGE=wechatopenai/weknora-sandbox:latest
WEKNORA_SANDBOX_TIMEOUT=60
WEKNORA_SANDBOX_KUBE_NAMESPACE=weknora-sandbox           # 默认 weknora-sandbox
WEKNORA_SANDBOX_KUBE_SERVICE_ACCOUNT=weknora-sandbox-runner  # 默认 weknora-sandbox-runner
WEKNORA_SANDBOX_MAX_CONCURRENT=5                          # 默认 5

注意: 非 Helm 部署需手动创建 Namespace、ServiceAccount、RBAC、NetworkPolicy、ResourceQuota。可参考 helm/templates/sandbox-rbac.yaml 和 helm/templates/sandbox-resources.yaml。

Fallback 机制: 如果启动时检测到 K8s API 不可用(如不在集群内运行、SA token 缺失、命名空间无权限),会自动 fallback 到 local 模式并输出 warn 日志。

K8s 模式尚未在真实集群做端到端验证,建议合并前或合并后在测试集群验证完整流程(创建 ConfigMap → 创建 Job → 轮询状态 → 读日志 → 清理)


OpenSandbox 模式:

适用场景: 希望将沙箱执行环境完全隔离到独立的服务中,或在无 K8s 环境下需要容器级隔离。

前置条件:

  • 已部署 alibaba/OpenSandbox 服务(推荐 v1.0+,需支持 execd sidecar)
  • WeKnora 实例需能访问 OpenSandbox lifecycle API 及其返回的 execd endpoint 地址

OpenSandbox API 对接:

本实现对接以下 API(已通过真实服务验证):

功能 API
健康检查 GET {apiURL}/health
创建沙箱 POST {apiURL}/v1/sandboxes (body: {image: {uri}, resourceLimits, entrypoint})
发现 execd 端点 GET {apiURL}/v1/sandboxes/{id}/endpoints/44772
等待 execd 就绪 轮询 GET {execdURL}/ping
上传文件 POST {execdURL}/files/upload (multipart: metadata JSON file + file)
执行命令 POST {execdURL}/command (响应为流式 JSONL)
删除沙箱 DELETE {apiURL}/v1/sandboxes/{id}

Helm 部署(推荐):


# values.yaml
app:
  sandbox:
    mode: opensandbox
    timeout: 60
    image: wechatopenai/weknora-sandbox:latest
    opensandbox:
      apiURL: "https://opensandbox.example.com"
      # 方式一:直接配置(仅用于开发/测试)
      apiKey: "your-api-key"
      # 方式二:通过 K8s Secret 注入(推荐生产环境)
      existingSecret: "opensandbox-credentials"   # Secret 中需包含 key: apiKey

环境变量部署(非 Helm):

WEKNORA_SANDBOX_MODE=opensandbox
WEKNORA_SANDBOX_IMAGE=python:3.11-slim
WEKNORA_SANDBOX_TIMEOUT=60
WEKNORA_SANDBOX_OPENSANDBOX_API_URL=https://opensandbox.example.com
WEKNORA_SANDBOX_OPENSANDBOX_API_KEY=your-api-key
WEKNORA_SANDBOX_MAX_CONCURRENT=5

Fallback 机制: 如果启动时 OpenSandbox health check 失败,会自动 fallback 到 local 模式。

网络配置注意事项:

  • execd 端点可达性:OpenSandbox 的 GET /endpoints/{port} 返回 execd 的直接访问地址。需确保 WeKnora 实例能访问该地址。
    容器化部署:如 WeKnora 与 OpenSandbox 在不同网络中(如 Docker Compose),需在 OpenSandbox 配置 [server] 段设置 eip 为 WeKnora 可达的主机名,并将该地址加入 SSRF_WHITELIST 环境变量。
  • SSRF 白名单:execd endpoint 地址需在 SSRF_WHITELIST 中放行。
  • Fallback 机制: 如果启动时 OpenSandbox health check 失败,会自动 fallback 到 local 模式。

execd 访问模式说明:

  • 当前使用 OpenSandbox 的 direct endpoint 模式(GET /endpoints/{port} 不带 use_server_proxy 参数)访问 execd。实测中 OpenSandbox 的 server proxy 模式(use_server_proxy=true)持续返回 503,疑似 OpenSandbox 自身限制。
  • Direct endpoint 返回的地址默认为 127.0.0.1:{port},仅在同机部署时可直接使用。跨主机或容器化部署时,必须在 OpenSandbox 的 ~/.sandbox.toml 中配置 eip 为 WeKnora 可达的地址,例如:
[server]
host = "0.0.0.0"
port = 9090
eip = "opensandbox.internal"   # 或 host.docker.internal(Docker Compose 场景)

其他信息 (Additional Information)

安全措施:

  • sandbox Pod:runAsNonRoot、drop ALL capabilities、readOnlyRootFilesystem、seccomp RuntimeDefault、automountServiceAccountToken: false
  • K8s API 调用:io.LimitReader 防 OOM、url.Values 防查询注入
  • OpenSandbox:sandbox ID 格式校验防路径穿越、sync.Once 防 double-close panic
  • 命令构建:shellQuote() 单引号转义(含 null byte 过滤)防命令注入,无 stdin 时使用 exec-form 避免 shell 解释
  • stdin 支持:通过 .stdin 文件 + cat | cmd 管道实现,兼容 K8s Job 和 OpenSandbox 两种模式

voidkey added 4 commits April 3, 2026 04:50
- add sandbox type, image, and resource limit settings to values.yaml
- inject sandbox env vars into app deployment
- add K8s RBAC (ServiceAccount, Role, RoleBinding) for sandbox Jobs
- add NetworkPolicy and ResourceQuota for sandbox namespace isolation
- support existingSecret for OpenSandbox API key (recommended for production)
- add opensandbox env vars to docker-compose.yml
- add lightweight K8s REST client (stdlib only) with SA token rotation and cache
- add OpenSandbox REST client aligned with alibaba/OpenSandbox API spec
  - streaming JSONL command response parsing
  - endpoint discovery for execd access
  - multipart metadata file upload format
  - execd readiness polling via /ping
- bound all io.ReadAll calls with LimitReader to prevent OOM
- URL-encode all K8s API query parameters
- validate sandbox ID format to prevent path traversal
- strip null bytes in shellQuote to prevent shell truncation
- implement OpenSandbox backend using alibaba/OpenSandbox API
- create sandbox → discover execd endpoint → wait ready → upload → execute → cleanup
- support stdin via .stdin file upload + shell pipe
- shell-quote all command arguments to prevent injection
- verified end-to-end against real OpenSandbox server
…anager

- implement KubernetesSandbox using K8s Job API with orphan GC
- support stdin via ConfigMap .stdin file + sh -c pipe (exec-form when no stdin)
- support args pass-through in both exec-form and shell-form
- use namespace-scoped configmap list for access check
- use sync.Once for Cleanup() to prevent double-close panic
- wire ServiceAccountName from config into Job spec
- wire both modes into sandbox manager with priority-based selection
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Sandbox mode kubernetes

1 participant