验证目的
验证阿里云 ACS(ECI)沙箱环境下,ESSD 持久卷(PVC)在以下操作中的数据持久性,以及 OpenClaw Web UI 在各升级路径下的可用性:
- E2B SDK pause/resume(休眠/唤醒)
- kubectl patch pod 原地换镜像
- kubectl patch pod 原地调整 CPU/内存
- SandboxUpdateOps 声明式升级
环境信息
| 项 | 值 |
|---|---|
| 集群 | 阿里云 ACS(ECI),节点 x86_64 |
| 命名空间 | default |
| SandboxSet | openclaw |
| 基础镜像 | registry-cn-hangzhou-vpc.ack.aliyuncs.com/ack-demo/openclaw:2026.3.23-2 |
| 升级镜像 | registry.cn-hangzhou.aliyuncs.com/fclaw/fclaw:1.2(amd64) |
| 存储类 | alicloud-disk-essd,20Gi |
| E2B SDK | e2b==2.24.0,e2b-code-interpreter==2.7.0 |
| ALB 地址 | 121.43.128.121 |
前置准备
SandboxSet YAML
文件 openclaw-sandboxset-essd.yaml:
apiVersion: agents.kruise.io/v1alpha1
kind: SandboxSet
metadata:
name: openclaw
namespace: default
spec:
replicas: 1
persistentContents:
- filesystem
runtimes:
- name: csi
- name: agent-runtime
volumeClaimTemplates:
- metadata:
name: workspace-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: alicloud-disk-essd
resources:
requests:
storage: 20Gi
template:
metadata:
labels:
alibabacloud.com/acs: "true"
alibabacloud.com/compute-class: agent-sandbox
alibabacloud.com/compute-qos: default
annotations:
scaling.alibabacloud.com/enable-inplace-resource-resize: "true"
spec:
automountServiceAccountToken: false
terminationGracePeriodSeconds: 30
securityContext:
fsGroup: 1000
containers:
- name: openclaw
image: registry-cn-hangzhou-vpc.ack.aliyuncs.com/ack-demo/openclaw:2026.3.23-2
imagePullPolicy: IfNotPresent
env:
- name: OPENCLAW_SKIP_ONBOARDING
value: "1"
- name: OPENCLAW_TZ
value: Asia/Shanghai
ports:
- containerPort: 18789
name: http-ui
protocol: TCP
resources:
limits:
cpu: "2"
memory: 4Gi
requests:
cpu: "2"
ephemeral-storage: 30Gi
memory: 4Gi
volumeMounts:
- name: workspace-pvc
mountPath: /app/workspaceOpenClaw 配置模板
文件 e2b-test/openclaw_template.json,关键字段 gateway.bind: "lan" 使 gateway 监听 0.0.0.0,外部可通过 ALB 访问:
{
"gateway": {
"port": 18789,
"bind": "lan",
"controlUi": {
"allowedOrigins": ["*"],
"dangerouslyAllowHostHeaderOriginFallback": true,
"allowInsecureAuth": true,
"dangerouslyDisableDeviceAuth": true
},
"auth": {
"mode": "token",
"token": "${GATEWAY_TOKEN}"
}
}
}E2B 配置注入脚本
文件 e2b-test/create_openclaw.py,创建沙箱后通过 SDK 写入配置文件,等待 gateway 重启:
from e2b_code_interpreter import Sandbox
from string import Template
sbx = Sandbox.create("openclaw", metadata={"e2b.agents.kruise.io/never-timeout": "true"})
with open("openclaw_template.json") as f:
template = Template(f.read())
rendered = template.safe_substitute(
GATEWAY_TOKEN="openclaw-sandbox-token",
DASHSCOPE_API_KEY="sk-fake-test-key-12345",
)
sbx.files.write("/home/node/.openclaw/openclaw.json", rendered, user="node")
time.sleep(30) # 等待 gateway 重启验证步骤与结果
创建 SandboxSet
kubectl create --validate=false -f openclaw-sandboxset-essd.yaml结果:
SandboxSet: openclaw, replicas=1, available=1
Sandbox CR: openclaw-bvgr2, Running, claimed=false
Pod: openclaw-bvgr2, 4/4 Running
PVC: workspace-pvc-openclaw-bvgr2, Bound, 20Gi, alicloud-disk-essd
annotation: scaling.alibabacloud.com/enable-inplace-resource-resize=trueE2B SDK 领取沙箱 + 配置注入
运行 create_openclaw.py:
sandbox_id: default--openclaw-bvgr2 耗时: 0.09s
配置已写入 /home/node/.openclaw/openclaw.json
等待 30s gateway 重启验证 Web UI 外部访问
r = requests.get("https://18789-default--openclaw-bvgr2.openclaw-sandbox.infra/?token=openclaw-sandbox-token", verify=False)
print(r.status_code) # 200结果:HTTP 200,返回 <title>OpenClaw Control</title> 页面,body 2732 bytes。
gateway 监听地址确认:
/proc/net/tcp: 00000000:4965 -> 0.0.0.0:18789 (全接口监听)写入测试数据
通过 E2B SDK 向三个位置写入数据:
| 数据 | 位置 | 存储类型 |
|---|---|---|
| pvc-test.txt | /app/workspace/ | PVC (ESSD) |
| pvc-hello.txt | /app/workspace/ | PVC (ESSD) |
| pvc-5mb.bin (5MB) | /app/workspace/ | PVC (ESSD) |
| tmp-test.txt | /tmp/ | 容器可写层 |
| app-test.txt | /app/ | 容器可写层 |
Pause/Resume 验证
sbx = Sandbox.connect("default--openclaw-bvgr2")
sbx.beta_pause() # 休眠
sbx = Sandbox.connect("default--openclaw-bvgr2") # connect 隐式唤醒结果:
| 数据 | 位置 | pause/resume 后 |
|---|---|---|
| pvc-test.txt | /app/workspace/ (PVC) | 保留 |
| pvc-hello.txt | /app/workspace/ (PVC) | 保留 |
| pvc-5mb.bin | /app/workspace/ (PVC) | 保留 |
| tmp-test.txt | /tmp/ (可写层) | 保留 |
| app-test.txt | /app/ (可写层) | 保留 |
kubectl patch 镜像升级
kubectl patch pod openclaw-bvgr2 -n default \
-p '{"spec":{"containers":[{"name":"openclaw","image":"registry.cn-hangzhou.aliyuncs.com/fclaw/fclaw:v1.3"}]}}'ECI 检测到镜像层发生变化(imageID 不同),在 Pod 内重建容器(containerID 变更),Pod 不删除。
状态变化:
| 项 | patch 前 | patch 后 |
|---|---|---|
| image | openclaw:2026.3.23-2 | fclaw/fclaw:v1.3 |
| imageID | sha256:dda9f4... | sha256:ca6fd1... |
| containerID | 093a2184... | 833fbbb... (变更) |
| restartCount | 3 | 5 |
结果:
| 数据 | 位置 | patch 后 |
|---|---|---|
| pvc-test.txt | /app/workspace/ (PVC) | 保留 |
| pvc-hello.txt | /app/workspace/ (PVC) | 保留 |
| pvc-5mb.bin | /app/workspace/ (PVC) | 保留 |
| tmp-test.txt | /tmp/ (可写层) | 丢失 |
| app-test.txt | /app/ (可写层) | 丢失 |
Web UI:HTTP 503,gateway 退回 127.0.0.1:18789(配置文件随可写层销毁,同 SandboxUpdateOps)。
kubectl patch CPU/内存原地扩容
SandboxSet YAML 中 Pod 模板已包含 annotation scaling.alibabacloud.com/enable-inplace-resource-resize: "true",Pod 创建时即携带,ECI 允许原地调整资源。
kubectl patch pod openclaw-bvgr2 -n default \
-p '{"spec":{"containers":[{"name":"openclaw","resources":{"limits":{"cpu":"4","memory":"8Gi"},"requests":{"cpu":"4","memory":"8Gi","ephemeral-storage":"30Gi"}}}]}}'ECI 直接调整 cgroup 限制,不重建容器(containerID 不变),不杀进程。
结果:
| 项 | patch 前 | patch 后 |
|---|---|---|
| CPU (spec) | 2 | 4 |
| CPU (实际 nproc) | 2 | 4 |
| Memory (spec) | 4Gi | 8Gi |
| Memory (实际 cgroup) | 4096 MiB | 8192 MiB |
| containerID | e2476b85... | e2476b85... (不变) |
| restartCount | 1 | 1 (不变) |
| 数据 | 位置 | patch 后 |
|---|---|---|
| pvc-test.txt | /app/workspace/ (PVC) | 保留 |
| pvc-hello.txt | /app/workspace/ (PVC) | 保留 |
| pvc-5mb.bin | /app/workspace/ (PVC) | 保留 |
| tmp-test.txt | /tmp/ (可写层) | 保留 |
| app-test.txt | /app/ (可写层) | 保留 |
Web UI:HTTP 200,gateway 仍监听 0.0.0.0:18789。
注意:该 annotation 必须在 Pod 创建时存在,后补不生效。未携带此 annotation 的 Pod 执行资源 patch 时,ECI 后端返回 400 错误:
The operation of in-place resource resizing has been denied.
pod should created with annotation key:[scaling.alibabacloud.com/enable-inplace-resource-resize] and value:[true]SandboxUpdateOps 镜像升级
先 patch 回基础镜像,再通过 SandboxUpdateOps 声明式升级:
apiVersion: agents.kruise.io/v1alpha1
kind: SandboxUpdateOps
metadata:
name: upgrade-bvgr2
namespace: default
spec:
selector:
matchLabels:
agents.kruise.io/sandbox-template: openclaw
agents.kruise.io/sandbox-claimed: "true"
updateStrategy:
maxUnavailable: 100%
patch:
spec:
containers:
- name: openclaw
image: registry.cn-hangzhou.aliyuncs.com/fclaw/fclaw:v1.3kubectl create --validate=false -f sandbox-updateops.yaml状态变化:
[1] ops=Updating pod=Running ready=true img=2026.3.23-2
[4] ops=Updating pod=Succeeded ready=false <- 旧 Pod 终止
[6] ops=Updating pod=Pending ready=false img=fclaw:1.2 <- 新 Pod 创建
[8] ops=Completed pod=Running ready=true img=fclaw:1.2 <- 新 Pod 就绪结果:
| 数据 | 位置 | SandboxUpdateOps 后 |
|---|---|---|
| pvc-test.txt | /app/workspace/ (PVC) | 保留 |
| pvc-hello.txt | /app/workspace/ (PVC) | 保留 |
| pvc-5mb.bin | /app/workspace/ (PVC) | 保留 |
| tmp-test.txt | /tmp/ (可写层) | 丢失 |
| app-test.txt | /app/ (可写层) | 丢失 |
Web UI:HTTP 503(配置文件随可写层销毁,gateway 退回默认 bind=localhost,ALB 无法访问)。
配置重注入
SandboxUpdateOps 后需重新执行配置注入步骤(同 4.2):
sbx = Sandbox.connect("default--openclaw-bvgr2")
sbx.files.write("/home/node/.openclaw/openclaw.json", rendered, user="node")
time.sleep(30)结果:HTTP 200,<title>OpenClaw Control</title>,gateway 恢复 0.0.0.0:18789 监听。
数据持久性总结
| 数据位置 | 存储类型 | pause/resume | patch 镜像 | patch CPU/内存 | SandboxUpdateOps |
|---|---|---|---|---|---|
| /app/workspace/ | PVC (ESSD) | 保留 | 保留 | 保留 | 保留 |
| /tmp/ | 容器可写层 | 保留 | 丢失 | 保留 | 丢失 |
| /app/ (非 PVC) | 容器可写层 | 保留 | 丢失 | 保留 | 丢失 |
原因:patch CPU/内存走 ECI cgroup 热更新,不重建容器;patch 镜像(不同镜像层)在 Pod 内重建容器,可写层随旧容器销毁;SandboxUpdateOps 删除重建整个 Pod,同样丢失可写层。PVC 是独立卷,独立于 Pod 生命周期。
Web UI 可用性总结
| 操作 | Web UI | 原因 |
|---|---|---|
| 配置注入后 | HTTP 200 | gateway.bind=lan, 监听 0.0.0.0 |
| pause/resume 后 | HTTP 200 | Pod 未删除,配置保留 |
| patch 镜像后(未重注入) | HTTP 503 | 容器重建,配置丢失,gateway 退回 localhost |
| patch CPU/内存后 | HTTP 200 | 不重建容器,配置保留 |
| SandboxUpdateOps 后(未重注入) | HTTP 503 | Pod 删除重建,配置丢失,gateway 退回 localhost |
| SandboxUpdateOps 后(重注入配置) | HTTP 200 | 配置恢复,gateway 重新监听 0.0.0.0 |
结论:patch 镜像(不同镜像层)和 SandboxUpdateOps 均会重建容器,导致可写层丢失,升级后必须重新执行配置注入步骤,否则 Web UI 不可用。仅 patch CPU/内存走 cgroup 热更新,不重建容器,无需重注入配置。
相关文件
| 文件 | 说明 |
|---|---|
| openclaw-sandboxset-essd.yaml | SandboxSet 定义(含 ESSD PVC) |
| sandbox-updateops-fclaw12.yaml | SandboxUpdateOps 升级定义 |
| e2b-test/create_openclaw.py | 沙箱创建与配置注入脚本 |
| e2b-test/openclaw_template.json | OpenClaw 配置模板 |
| e2b-test/.env | E2B 环境变量 |


