Sandbox CLI
k8e-sandbox-cli 是一个独立二进制(约 44MB),让 AI Agent 直接访问 K8E 沙箱基础设施 —— 无需安装服务器端。
AI Agent(claude code / codex / pi)
│ shell 命令
▼
k8e-sandbox-cli run "print('hello')" --lang python
│ gRPC (TLS)
▼
sandbox-grpc-gateway:50051
│
▼
隔离 Pod(gVisor / Kata / Firecracker)
安装 Skill
三步:给服务器配上公网名字 → 创建 API key → 在客户端执行 connect。
1. 告诉服务器客户端会拨的公网名字(仅远端/云主机需要)
沙箱网关证书(:50051,mTLS)是根据主机网卡地址生成的 —— AWS 上这些都是私有 VPC IP,客户端拨公网名字时 TLS 握手会失败。按你的部署方式选一种设置(KIP-22):
# /etc/k8e/config.yaml (服务端守护进程配置 —— 键名与 CLI 参数一致)
sandbox-advertise-hostname: sandbox.example.com # 或弹性 IP,如 203.0.113.10
# 等价的一次性启动参数
k8e-server --sandbox-advertise-hostname sandbox.example.com
# 旧版环境变量兜底 —— 注入到 k8e-server 进程环境(如 systemd),
# 不要在登录 shell 里设置;与上面的参数合并生效
# K8E_SANDBOX_ADVERTISED_HOSTNAME=sandbox.example.com
只能是裸 DNS 名或裸 IP。 带 scheme(
https://…)、带端口(host:50051)、 带路径或空格都会被拒绝:k8e-server会直接启动失败并报invalid --sandbox-advertise-hostname: …。能解析成 IP 的值进证书的 IP SAN,其余进 DNS SAN。/etc/k8e/config.yaml里的tls-san不是这个开关 —— 它只作用于 kube-apiserver 的服务证书。重启
k8e-server即可 —— SAN 集合变化时网关证书会自动重新生成,下一次 connect 就能用新名字,无需手动删除/var/lib/k8e/server/tls/sandbox-server.{crt,key}。
2. 在服务器端创建 API key(默认 TTL 30 天)
k8e sandbox-apikey create my-agent
# → {"name":"my-agent","key":"849a5302e66f98d1d5064ef8501703574af4053b7bf9cf5337f9533326ce2bc9",
# "e2b_key":"e2b_849a5302e66f98d1d5064ef8501703574af4053b7bf9cf5337f9533326ce2bc9",
# "ttl_days":30,"created_at":"...","expires_at":"..."}
# 当服务器的公网名字已知时,还会打印:
# mcp client config:
# {"url":"https://sandbox.example.com/mcp","headers":{"Authorization":"Bearer e2b_849a5302e66f98d1…"}}
# 可选:更长或永不过期的 bootstrap key
# k8e sandbox-apikey create my-agent --ttl 90d # 也支持 720h 或纯数字天数
# k8e sandbox-apikey create my-agent --ttl never # 也支持 none / 0
key(裸 hex)传给 connect --apikey。e2b_key 给官方 e2b SDK 用 —— 它们要求 e2b_ 前缀,服务端会剥掉。末尾的 mcp client config 块可以直接粘进 MCP 客户端 (Claude Code、Codex);只有在服务器公网名字已知时才会打印,没有公网名字的主机 自然不会输出它。
3. 在客户端下载 CLI 并连接
connect 会做四件事:mTLS 认证、验证网关、把 k8e-sandbox-cli 放进 PATH、记住 这个网关,以及把 /k8e-sandbox skill 装进你的 harness —— 之后的命令不再需要 --endpoint。
# 1. 下载对应平台二进制(约 44MB)
# 选择你的平台:linux-amd64 | linux-arm64 | darwin-amd64 | darwin-arm64 | windows-amd64.exe
curl -sLO https://github.com/xiaods/k8e/releases/latest/download/k8e-sandbox-cli-linux-amd64
chmod +x k8e-sandbox-cli-linux-amd64
# 2. 为下载的文件创建普通命令名软链接(不要重命名)
# 可选:把两者都移动到 PATH 目录,例如 ~/.local/bin/
ln -s k8e-sandbox-cli-linux-amd64 k8e-sandbox-cli
# 3. 连接:mTLS 认证 + 验证网关 + 安装 /k8e-sandbox skill
# (Claude / Codex / Pi / dsh;--agent 指定目标,默认自动探测)
# 注意:--endpoint 和 --apikey 是全局参数,放在子命令之前
./k8e-sandbox-cli --endpoint sandbox.example.com:50051 --apikey 849a5302e66f98d1d5064ef8501703574af4053b7bf9cf5337f9533326ce2bc9 connect
# 参数也可以放在子命令后面(两种写法都有效)
# ./k8e-sandbox-cli connect --endpoint sandbox.example.com:50051 --apikey 849a… --ca-file ./sandbox-ca.crt
# 多集群:使用命名 profile(见下方「配置与认证」)
# ./k8e-sandbox-cli --profile prod connect --apikey 849a5302e66f98d1d5064ef8501703574af4053b7bf9cf5337f9533326ce2bc9
和服务器在同一台机器上? 直接运行
k8e-sandbox-cli connect—— CLI 会自动 发现本地网关及其证书,无需 API key。
4. 把网关 CA 拿到客户端
网关用的是私有 CA,你的机器不信任它,所以首次远程 connect 需要这个 CA。 connect 和 login 通过系统信任、已缓存的 CA 或 --ca-file 校验网关。
在服务器本机上 CLI 可以直接导出:
k8e-sandbox-cli tls export-ca --output ~/sandbox-ca.crt
# 或输出到标准输出: k8e-sandbox-cli tls export-ca -
服务器上没装 CLI 的话,同一个文件就在磁盘上:
sudo cat /var/lib/k8e/server/tls/sandbox-ca.crt > ~/sandbox-ca.crt
把它复制到客户端机器,首次 connect 时带上:
scp user@server:~/sandbox-ca.crt ./
./k8e-sandbox-cli --endpoint <服务器 IP>:50051 --apikey <key> \
connect --ca-file ./sandbox-ca.crt
CA 是公开证书,不是秘密 —— 它只用来证明「这个网关就是这个网关」。真正的秘密是 API key;客户端私钥永远不离开客户端。
tls export-ca在客户端有 kubeconfig 时也会去找sandbox-matrix/sandbox-caConfigMap ——但集群目前并不发布这个 ConfigMap,所以上面这条「在服务器上导出」的路径才是 真正能用的。如果你就在服务器本机,k8e-sandbox-cli connect(不带参数)会跳过这一切: 它自动发现本地网关及其证书。
5. 验证整条链路通了
一条命令就能证明 API key、mTLS 客户端证书、gRPC 网关、warm pool 和运行时全部对得上:
k8e-sandbox-cli run 'echo hello from k8e'
# → {"duration_ms":…,"exit_code":0,"session_id":"sess-…","status":"completed",
# "stdout":"hello from k8e\n","stderr":"","…":…}
stdout 里有 hello from k8e 就说明你已经在一个隔离的 gVisor Pod 里执行代码了。 如果失败,下面的故障排查表会把症状对应到原因。
status 不会创建会话
k8e-sandbox-cli status 只是探测网关——在 run 或 create 真正建出会话之前, 它会报 "session_id":""。这不是错误,先跑点东西就行。
平台二进制:
| 平台 | 下载名称 |
|---|---|
| Linux amd64 | k8e-sandbox-cli-linux-amd64 |
| Linux arm64 | k8e-sandbox-cli-linux-arm64 |
| macOS amd64 | k8e-sandbox-cli-darwin-amd64 |
| macOS arm64 | k8e-sandbox-cli-darwin-arm64 |
| Windows amd64 | k8e-sandbox-cli-windows-amd64.exe(通过 mklink k8e-sandbox-cli.exe k8e-sandbox-cli-windows-amd64.exe 建软链接) |
同一个二进制,两个名字: 下载的带平台后缀文件就是
k8e-sandbox-cli命令 —— 软链接只是普通名字的别名。下文所有示例均使用k8e-sandbox-cli。
然后在你的 Agent 工具链中:
/k8e-sandbox <目标> # Claude Code
$k8e-sandbox <目标> # Codex(或从 /skills 里选)
/skill:k8e-sandbox <目标> # Pi
dsh(DeepSeek Harness)通过它的 skill 工具加载同一份 skill,也可以直接在对话里 点名使用 —— 插件挂载路径见 dsh + k8e-sandbox。
或自然提问:"在沙箱里运行这段 Python 代码" —— skill 会自动驱动 k8e-sandbox-cli run。
Skill 没出现? 运行
k8e-sandbox-cli connect --skill-only刷新 harness 安装 (不拨号网关),然后开一个新的 agent 会话。k8e-sandbox-cli doctor会检查 二进制 / PATH / profile / 证书 / skill 安装 / dsh 插件(--json输出机器可读 报告,--fix自动修掉能本地修复的问题)。
可用命令
| 命令 | 说明 |
|---|---|
k8e-sandbox-cli --profile <name> … | 使用 ~/.k8e/sandbox/profiles.yaml 中的命名 profile(不是服务器的 /etc/k8e/config.yaml) |
k8e-sandbox-cli connect | 连接本地/远程网关、验证它、确保 PATH、记住网关,并安装 /k8e-sandbox skill |
k8e-sandbox-cli connect --agent <auto|claude|codex|pi|dsh|all> | 指定 skill 安装目标(默认 auto:探测本机已装的 harness) |
k8e-sandbox-cli connect --skill-only | 仅重装 Agent skill(不拨号网关) |
k8e-sandbox-cli connect --skip-skill | 只认证,不安装 Agent skill |
k8e-sandbox-cli login | 仅认证(mTLS 证书;不安装 skill);可选 --device-name |
k8e-sandbox-cli doctor | 诊断 CLI / 网关 / 证书 / skill 安装 / dsh 插件,并逐项给出修复命令(--json、--fix) |
k8e-sandbox-cli tls export-ca | 把沙箱网关 CA 写到 ~/.k8e/sandbox-ca.crt(--output <path> / - 输出到 stdout),供只用 IP 的网关、MCP 和 e2b 客户端信任 |
k8e-sandbox-cli status | 检查沙箱服务可用性和当前会话 |
k8e-sandbox-cli run <code> | 运行代码或 shell 命令(自动创建/管理会话;--lang、--timeout、--raw、--session-id、--tenant、--background、--manifest、--git-repo、--allowed-hosts) |
k8e-sandbox-cli create | 创建新会话(自定义运行时、出口、env、secret、manifest、git-repo) |
k8e-sandbox-cli get <sid> | 检查会话(阶段、运行时、env 键) |
k8e-sandbox-cli sessions | 列出会话 |
k8e-sandbox-cli destroy <sid> | 销毁会话并释放资源 |
k8e-sandbox-cli write <sid> <path> | 写入文件到 /workspace(内容经 stdin) |
k8e-sandbox-cli read <sid> <path> | 从 /workspace 读取文件 |
k8e-sandbox-cli push <sid> <local> [remote] | 将本地文件流式传入沙箱——分块 4MiB 窗口、恒定内存、二进制安全、任意大小(--chunk-mb 可调) |
k8e-sandbox-cli pull <sid> <remote> [local] | 将沙箱文件流式取回本地(同样的分块传输) |
k8e-sandbox-cli list <sid> | 列出 /workspace 中的文件(按 --since 时间戳过滤,用于变更文件 diff) |
k8e-sandbox-cli log <sid> | 重放执行记录(--offset、--limit、--follow) |
k8e-sandbox-cli events <sid> | 读取守护进程 NDJSON 事件流(--limit) |
k8e-sandbox-cli ps <sid> | 列出沙箱 Pod 中的进程(pid、comm、state) |
k8e-sandbox-cli poll <run-id> | 轮询后台运行(--follow) |
k8e-sandbox-cli subagent <parent-sid> | 在父会话下生成子沙箱(共享父 Pod + workspace,不新建 Pod) |
k8e-sandbox-cli confirm <sid> <action> | 将不可逆操作置于人工审批门禁(--timeout、--no-wait) |
k8e-sandbox-cli approve <approval-id> | 批准待处理的 confirm 请求(--reject、--reason) |
k8e-sandbox-cli snapshot save <sid> <name> | 保存 workspace 快照(内容寻址、去重) |
k8e-sandbox-cli snapshot list | 列出已保存的快照 |
k8e-sandbox-cli snapshot restore <name> | 从快照新建会话(--base <snap> 增量恢复) |
k8e-sandbox-cli snapshot delete <name> | 删除快照 |
k8e-sandbox-cli expose <port> | 通过 k8e API Gateway 暴露沙箱内服务,返回公网 URL(--host、--session-id) |
k8e-sandbox-cli unexpose <port> | 停止暴露某端口(幂等;--session-id) |
k8e-sandbox-cli exposed | 列出当前会话的活跃暴露 |
k8e-sandbox-cli allow-hosts | 自由配置会话出网白名单,实时生效(--hosts 替换、--add、--remove、--clear) |
k8e-sandbox-cli mcp-serve | 为具备 MCP 能力的 Agent 提供沙箱 MCP 端点(--listen、--gateway、TLS + API-key 标志、--bootstrap-api-key) |
k8e-sandbox-cli benchmark | 热池延迟指标(--pool-size、--iterations) |
k8e-sandbox-cli catalog | 输出机器可读的命令面(SDK 生成) |
k8e sandbox-apikey create <name> | 创建 API key(默认 TTL 30 天;--ttl 90d / 720h / 45 / never)—— 输出含 key、e2b_key 和可直接粘贴的 mcp client config 块 |
k8e sandbox-apikey list | 列出 API key 名称与过期时间(不回显 secret) |
k8e sandbox-apikey delete <name> | 删除 API key(服务器端) |
完整用法参见 pkg/sandboxcli/skills/k8e-sandbox/SKILL.md,以及 KIP-17(profile 与 API-key TTL)和 KIP-22(上面的 advertise hostname)。
示例 —— 从单行命令到 Vue 网站
上手演练请见 沙箱范例 —— 由你的 AI Agent 驱动:你在 Agent 工具链(pi / Claude Code / Codex)中通过 /k8e-sandbox skill 驱动一切。本页是原始命令参考;范例页展示的是同样的命令由 skill 代表你执行。
先快速感受一下 skill 会替你运行什么:
快速示例
# Shell 命令(默认 lang=bash)
k8e-sandbox-cli run "ls -la /workspace"
# TypeScript —— 类型注解通过 tsx 运行
k8e-sandbox-cli run "const nums: number[] = [1, 2, 3]; console.log(nums.reduce((a, b) => a + b, 0))" --lang ts
# 后台执行(立即返回 run_id,然后轮询)
k8e-sandbox-cli run "sleep 30; echo done" --background
k8e-sandbox-cli poll <run-id>
# 基于租户的跨进程会话复用
k8e-sandbox-cli run "echo hello" --tenant my-project
# 流式查看长时间运行的输出
k8e-sandbox-cli run "python3 train.py" --session-id $SID --raw
# 子 Agent:共享父 Pod + workspace 的子会话
k8e-sandbox-cli subagent <parent-sid>
# 快照:内容寻址的保存/恢复
SID=$(k8e-sandbox-cli create | jq -r .session_id)
k8e-sandbox-cli run "echo work > /workspace/state.txt" --session-id $SID
k8e-sandbox-cli snapshot save $SID my-state
k8e-sandbox-cli snapshot list
# ... 之后从快照新建会话:
k8e-sandbox-cli snapshot restore my-state
# 基于现有快照增量恢复(KIP-16 M2):
k8e-sandbox-cli snapshot restore my-state --base older-state
暴露服务(KIP-24)
当你的代码在沙箱里启动了长驻服务(web 应用、API 服务)时,通过 k8e API Gateway 把它暴露出去,得到一个可以直接访问的 URL:
k8e-sandbox-cli run "python3 -m http.server 8080 --bind 127.0.0.1" --background
k8e-sandbox-cli expose 8080
# -> {"url":"http://<gateway>/k8e/expose/<sid>/8080/","port":8080,"session_id":"..."}
curl http://<gateway>/k8e/expose/<sid>/8080/
k8e-sandbox-cli exposed # 列出活跃暴露
k8e-sandbox-cli unexpose 8080 # 停止暴露
URL 链路:Cilium Gateway API → 内嵌 e2b HTTP server → 反向代理到 pod, 无需端口转发、无 pod 入站暴露。
故障排查
先跑 k8e-sandbox-cli doctor —— 它会检查二进制、PATH、profile、证书和 skill 安装, 并为每个失败项打印修复方式(--json 输出机器可读报告,--fix 自动应用能本地修复的问题)。
| 症状 | 原因 | 修复 |
|---|---|---|
connect 报 TLS / 证书错误 | 网关的私有 CA 不被你的机器信任 | 在服务器上导出 CA(k8e-sandbox-cli tls export-ca 或 sudo cat /var/lib/k8e/server/tls/sandbox-ca.crt),首次 connect 时带 --ca-file ./sandbox-ca.crt |
connect 找不到网关 | 客户端拨的地址或端口不对 | gRPC 网关是 :50051;E2B HTTP 面是 :3676(或 Gateway 的 :80)。k8e-sandbox-cli doctor 会打印它将要使用的端点 |
connect 成功,但后续调用报 sandboxd …: connection timed out | 会话的 Pod 被换掉了(回收、重启、节点驱逐),缓存的 Pod IP 已失效 | k8e-sandbox-cli destroy <sid> 后重跑 —— 新会话会认领新的 Pod。不带 --session-id 的 run 总是用当前会话 |
tls export-ca 报 sandbox CA not found | 你不在服务器本机,且客户端没有 kubeconfig | 到服务器本机执行,或在服务器上 sudo cat /var/lib/k8e/server/tls/sandbox-ca.crt 后把文件拷过来 |
run 返回 warm pool full: … | 节点放不下另一个沙箱 Pod(小节点上先见顶的是 CPU request) | 等某个 Pod 被回收,或给节点加 CPU。kubectl -n sandbox-matrix get pods 能看到活着的 Pod |
poll <run-id> 在任务还在跑时 stdout 是空的 | poll 返回的是状态快照,输出要等任务完成才出现 | 继续轮询直到 status 为 completed(或用 poll --follow) |
run --background 明明跑了但输出为空 | 用的 sandboxd 版本在 JSON 字符串里吐了裸 ANSI 转义,导致响应无法解析——控制面吞掉了解析错误,于是报「没有输出」 | 把沙箱镜像升级到 v1.37.0-20261022-rc2 或更高 |
| 文件监听报的是被监视的目录,而不是变化的文件 | 用的 sandboxd 版本把 inotify 记录解析错了,事件名变成空串 | 同上——inotify 解析修复在 v1.37.0-20261022-rc2 |
template '<name>' not found | e2b API 的 template 字段选的是 Kubernetes RuntimeClass,不是 E2B 模板 | 用 gvisor、kata、firecracker、crun 或 lunatic |
| Agent 里看不到 skill | harness 缓存了 skill 列表 | k8e-sandbox-cli connect --skill-only,然后新开一个 agent 会话 |
配置与认证
配置文件(不要混用)
| 路径 | 归属 | 用途 |
|---|---|---|
/etc/k8e/config.yaml | k8e server/agent | 守护进程 CLI flags —— 包括 sandbox-advertise-hostname(第 1 步)。CLI 从不读它。 |
~/.k8e/sandbox/profiles.yaml | k8e-sandbox-cli | 命名网关 / 每 profile 的 cert_dir、device_name |
~/.k8e/sandbox/config.json | k8e-sandbox-cli | 最近一次 connect / login 记录 |
~/.k8e/sandbox-ca.crt | k8e-sandbox-cli | k8e-sandbox-cli tls export-ca 导出的 CA,供只用 IP 的网关使用 |
~/.k8e/sandbox/{ca.crt,client.crt,client.key} | k8e-sandbox-cli | mTLS 材料(客户端证书 90 天;剩余不足 30 天自动续期) |
/etc/k8e/config.yaml 的键名与 CLI flags 完全一致(flag-name: value), 所以任何能传给 k8e-server 的参数都能写进文件 —— 这正是 sandbox-advertise-hostname 能在重启后依然生效的原因。
mTLS 引导
远程 connect / login 仅用 API key 一次 签发客户端证书;私钥始终留在本机。之后的 RPC 只走 mTLS,线上不再传 API key。
首次认证会依次尝试系统信任、已缓存的 CA、或管理员通过 --ca-file 提供的 CA —— 请通过可信渠道取得该 CA。--insecure-bootstrap 只在没有可选 CA 时跳过服务端 校验;它会让 API key 暴露在中间人风险下,因此不应作为常规步骤。并发 CLI 进程会 串行化凭据初始化与续期。
客户端证书有效期 90 天,剩余不足 30 天 时自动续期。如果服务器重装或 CA 轮换,用新凭据和新 CA 重新连接 —— --reset-certs 会先验证再替换缓存文件:
k8e-sandbox-cli --endpoint sandbox.example.com:50051 --apikey <key> connect \
--reset-certs --ca-file /path/to/trusted-new-ca.crt
多集群 profile
# ~/.k8e/sandbox/profiles.yaml —— 不是 /etc/k8e/config.yaml
version: 1
current_profile: default
profiles:
default:
endpoint: 10.0.0.1:50051
prod:
endpoint: sandbox.prod.example:50051
cert_dir: ~/.k8e/sandbox-prod
device_name: laptop-prod
k8e-sandbox-cli --profile prod connect --apikey <64 位 hex key>
k8e-sandbox-cli --profile prod run 'echo hi'
# 或:export K8E_SANDBOX_PROFILE=prod
connect 会把它认证成功的网关写成 default profile,因此后续 CLI 调用完全不 需要再带 --endpoint。
解析优先级:flag → env → profile → 最近一次连接记录(config.json)→ 默认值。
cert_dir 没有对应 flag:K8E_SANDBOX_CERT_DIR → profile cert_dir → ~/.k8e/sandbox。
环境变量:K8E_SANDBOX_ENDPOINT、K8E_SANDBOX_APIKEY、K8E_SANDBOX_CERT_DIR、K8E_SANDBOX_PROFILE、K8E_SANDBOX_CONFIG(显式指定 profiles 文件路径)、K8E_SANDBOX_DEVICE_NAME、K8E_SANDBOX_SESSION_ID、K8E_SANDBOX_TENANT。
覆盖方式
# 只认证(mTLS 证书;不安装 skill)—— 之后命令不再需要 --apikey
k8e-sandbox-cli --endpoint sandbox.example.com:50051 --apikey <key> login
k8e-sandbox-cli run "echo hello"
# 或通过环境变量:
K8E_SANDBOX_ENDPOINT=sandbox.example.com:50051 K8E_SANDBOX_APIKEY=<key> k8e-sandbox-cli login
# 按命令覆盖端点:
K8E_SANDBOX_ENDPOINT=10.0.0.2:50051 k8e-sandbox-cli run "echo hello"