让用户输入一句提示词,几秒后得到一个可运行、可预览、可分享的小游戏或交互应用,看起来只是一次 AI 生成。对平台工程团队来说,背后却是一条完整的执行链:生成代码、启动环境、运行测试、收集日志、保存状态,再把结果交给下一次编辑或 remix。
SeaVerse 面临的核心问题并不是“如何启动更多容器”,而是如何安全地运行大量动态、多租户、潜在不可信的工作负载。它最终采用 GKE 与 GKE Agent Sandbox,在保留 Kubernetes 调度和运维模型的同时获得更强的运行时隔离,并通过更灵活的资源匹配将基础设施成本最多降低 60%。这个案例真正值得关注的,是隔离、启动速度、可观测性和成本之间如何形成一套系统设计。
普通容器隔离为什么不一定够
SeaVerse 上的每个作品都是一个独立工作负载。用户生成的代码会经历运行、测试、集成和验证,并可能访问文件系统、消耗 CPU 与内存,甚至触发异常进程行为。多租户环境中,一次逃逸或资源失控影响的可能不只是单个作品。
传统容器共享宿主机内核。命名空间、cgroup、seccomp 和 Linux capabilities 可以缩小攻击面,但当平台需要持续执行动态生成的代码时,团队通常还会希望增加一道更明确的内核边界。
GKE Agent Sandbox 的价值在于把这种边界纳入 Kubernetes 的调度模型。SeaVerse 使用了基于 gVisor 的隔离能力,也提到了 Kata Containers 与 Cloud Hypervisor 组成的 microVM 方案,并可根据工作负载在 microVM 和 gVisor 隔离运行时之间切换。两者并非简单的优劣关系:
- gVisor 在应用与宿主机内核之间增加用户态内核层,适合关注启动速度和密度的动态沙箱。
- Kata Containers + microVM 提供更接近虚拟机的隔离边界,适合安全要求更高或需要不同兼容性取舍的任务。
- 普通容器 仍适合可信的控制服务、API、队列消费者和监控组件,没有必要把所有 Pod 都放进成本更高的隔离环境。
合理的架构通常会把控制面与执行面分开:API、调度器和元数据服务运行在普通节点池;真正执行用户或 Agent 代码的 Pod 才进入沙箱节点池。
从“黑盒失败”变成可定位的运行时
强隔离如果带来不可观测的执行环境,故障处理成本会迅速上升。一个作品打不开,原因可能是生成代码报错、依赖下载失败、内存超限、启动探针失败、存储挂载异常,也可能只是调度容量不足。
SeaVerse 之前只能看到沙箱失败,却缺少足够的运行状态、指标和失败信号。接入 GKE 原生日志与监控后,沙箱内的行为能够进入统一的诊断链路。用户不会直接看到 Pod、节点或日志,但会感受到体验能否快速打开、点击后是否响应,以及修改后能否立即再次预览。
平台至少应为每次执行附带以下维度:
tenant_id:租户或用户标识,日志中应使用不可逆或内部 ID;experience_id:作品标识;sandbox_id:单次执行环境标识;revision:生成内容的版本;runtime_class:gVisor、microVM 或普通容器;queue_latency_ms与startup_latency_ms:区分排队慢和启动慢;termination_reason:正常退出、超时、OOM、探针失败或平台回收。
同时需要警惕日志本身成为泄露渠道。不要记录用户提示词中的秘密、访问令牌或完整生成文件;多租户日志查询也必须执行访问控制。
可以这样实践:建立一个最小 gVisor 沙箱节点池
下面是一个可改造的基线示例,用于展示 GKE 中常见的沙箱节点池和 RuntimeClass 使用方式。它不等同于 SeaVerse 的生产配置,也没有覆盖 GKE Agent Sandbox 的全部产品能力。运行前请确认当前 GKE 版本、区域和项目已支持对应功能,并根据最新产品文档核对参数。
先修改项目、集群和区域变量。假设本机已经安装并认证 gcloud,且目标集群已经存在:
export PROJECT_ID="your-project-id"
export CLUSTER="your-gke-cluster"
export REGION="us-central1"
gcloud config set project "$PROJECT_ID"
# 创建专门运行沙箱工作负载的节点池。
gcloud container node-pools create sandbox-pool \
--cluster="$CLUSTER" \
--location="$REGION" \
--machine-type="e2-standard-4" \
--num-nodes=1 \
--enable-autoscaling \
--min-nodes=1 \
--max-nodes=10 \
--sandbox type=gvisor
gcloud container clusters get-credentials "$CLUSTER" \
--location="$REGION"
随后部署一个最小测试工作负载。示例显式设置 CPU、内存和临时存储限制,关闭特权提升,并使用只读根文件系统。真实平台还应增加网络策略、出口代理、镜像签名验证、执行超时和租户配额。
apiVersion: v1
kind: Namespace
metadata:
name: agent-sandbox-demo
---
apiVersion: v1
kind: ConfigMap
metadata:
name: demo-app
namespace: agent-sandbox-demo
data:
server.py: |
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
body = b"hello from an isolated sandbox\n"
self.send_response(200)
self.send_header("Content-Type", "text/plain")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()
---
apiVersion: v1
kind: Pod
metadata:
name: sandbox-demo
namespace: agent-sandbox-demo
labels:
app: sandbox-demo
spec:
runtimeClassName: gvisor
automountServiceAccountToken: false
restartPolicy: Never
securityContext:
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: python:3.12-alpine
command: ["python", "/app/server.py"]
ports:
- containerPort: 8080
resources:
requests:
cpu: "100m"
memory: "64Mi"
ephemeral-storage: "64Mi"
limits:
cpu: "500m"
memory: "128Mi"
ephemeral-storage: "256Mi"
securityContext:
allowPrivilegeEscalation: false
privileged: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: app
mountPath: /app
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: app
configMap:
name: demo-app
- name: tmp
emptyDir:
sizeLimit: 32Mi
---
apiVersion: v1
kind: Service
metadata:
name: sandbox-demo
namespace: agent-sandbox-demo
spec:
selector:
app: sandbox-demo
ports:
- port: 80
targetPort: 8080
将文件保存为 sandbox-demo.yaml 后执行:
kubectl apply -f sandbox-demo.yaml
kubectl wait --for=condition=Ready pod/sandbox-demo \
-n agent-sandbox-demo --timeout=120s
kubectl port-forward -n agent-sandbox-demo \
service/sandbox-demo 8080:80 &
curl http://127.0.0.1:8080
kubectl logs -n agent-sandbox-demo pod/sandbox-demo
清理测试资源:
kubectl delete namespace agent-sandbox-demo
这个示例只是运行时隔离的起点。若要执行真正的生成代码,平台不应直接把任意代码拼进 Deployment,而应建立受控执行协议:使用固定入口程序读取任务包,校验大小和类型,设置硬超时,通过受限的对象存储获取输入,并只把结构化结果写回控制面。
60% 成本下降来自资源匹配,而不只是“换一种运行时”
SeaVerse 报告的“最多降低 60%”是其特定架构和工作负载下的结果,不能直接作为其他系统的预算承诺。值得复用的是背后的成本机制:过去,安全沙箱对特定服务器类型依赖较强,资源很难精确匹配;采用 GKE Agent Sandbox 后,可以在适当规格的云虚拟机上运行隔离工作负载,提高调度和资源分配的灵活性。
可以重点检查四类成本:
- 空闲容量:为突发流量预留多少热节点,冷启动能否由队列吸收。
- 请求与限制差距:请求值过高会降低装箱密度,限制值过低则会增加 OOM 和重试。
- 沙箱生命周期:一次请求一个沙箱最简单,但复用经过清理的沙箱可能更便宜;复用也会增加租户残留风险。
- 持久化策略:不是每个作品都需要永久磁盘。临时预览可使用临时存储,需要持续编辑的作品再挂载持久卷或从对象存储恢复。
SeaVerse 还利用持久文件系统支持创作者跨会话继续修改作品。实践中,应让持久数据与执行沙箱解耦:沙箱可以销毁,作品状态则保存在按租户授权的持久卷、数据库或对象存储中。这样既方便弹性扩缩,也能减少旧环境长期存活带来的安全风险。
扩展到高并发前需要验证什么
GKE Agent Sandbox 在正式可用阶段支持单集群每秒最多分配 300 个沙箱,其中 90% 的分配可在 200 毫秒内完成。这说明基础设施具备很高的分配能力,但端到端体验还取决于镜像拉取、节点扩容、依赖安装、应用初始化和存储挂载。
上线前可以使用下面的检查表:
- 将沙箱分配延迟与应用就绪延迟分开统计;
- 预拉取大镜像,避免每个沙箱启动时重复下载依赖;
- 对租户设置并发、CPU、内存、存储和网络出口配额;
- 默认拒绝不必要的东西向通信和公网出口;
- 禁止挂载宿主机路径、特权容器和默认服务账号令牌;
- 为 OOM、超时、调度失败和异常退出建立独立告警;
- 分别压测 gVisor 与 microVM 路径,不用单一基准替代真实工作负载;
- 定期验证沙箱销毁后是否残留文件、进程、凭据或网络连接。
SeaVerse 的经验表明,AI 创作平台的竞争力不只来自模型。用户能否把一个想法立即变成可玩的结果,还取决于执行基础设施是否把不可信代码隔离起来、把故障暴露出来,并以合理成本承受突发并发。GKE 与 GKE Agent Sandbox 提供了一个可行基础,但真正的收益仍来自清晰的控制面与执行面划分、精细的资源预算,以及贯穿整个沙箱生命周期的安全策略。