关键词:OpenClaw、Docker、环境变量持久化、Shell Wrapper、SRE、可观测性、故障复盘
1. 背景:为什么要做这件事
在多租户 OpenClaw 容器集群里(200 个用户容器),我们遇到一个非常典型的问题:
- 智能体在对话里回复"已设置环境变量成功"
- 但宿主机
data/user_x/config/env/runtime.env里没有对应变量 - 容器重建后变量消失,行为不一致
根因不是一句"智能体不可靠"能解释完的。真正的问题是执行链路和 shell 选择不一致:
- 有的命令走
bash - 有的命令走
sh -c - 有的执行器可能直接用绝对路径
/bin/bash
如果只在某一条路径打补丁,系统就会出现"部分成功、部分失效"的灰色状态。
这篇文章给出我们最终上线的方案:不改 OpenClaw 核心源码,通过 init 层 + shell wrapper 实现自动快照回写,做到可持久、可重建、可审计。
2. 目标与约束
2.1 目标
- 智能体执行
export KEY=VALUE后,自动持久化 - 容器重建后,变量自动恢复
- 尽量不改 OpenClaw 主干代码
- 兼容 200 容器批量发布与滚动重建
2.2 约束
- 不能依赖"约束智能体写文件"
- 不能只靠某个 shell(例如只 hook
bash) - 不能引入破坏性行为(例如污染系统启动脚本)
3. 方案总览(架构图思路)
我们把环境变量分成两层:
base.env:运维手工维护的基础变量(静态)runtime.env:运行时自动快照回写(动态)
启动链路:
init.sh启动时加载base.env+runtime.env- 安装
/usr/local/bin/bashwrapper - wrapper 在每次
bash -c/-lc执行后导出env -0快照 - Python diff 脚本比较
.last_env_snapshot与当前快照 - 将增量写回
runtime.env/runtime.json - 下次容器启动再自动加载
核心设计点:自动捕获 + 增量落盘 + 重启恢复。
4. 关键实现细节
4.1 启动时加载持久变量
init.sh 中先加载:
/home/node/.openclaw/env/base.env/home/node/.openclaw/env/runtime.env
并通过 set -a 导出到进程环境,保证后续 node /app/openclaw.mjs gateway 继承这些变量。
4.2 Bash Wrapper 自动快照
wrapper 的关键逻辑:
- 拦截
bash -c/-lc "cmd" - 在同一 shell 尾部追加:
- 保存原命令退出码
env -0 > 临时快照- 按原退出码退出
这样不会改变原命令成功/失败语义,只是"旁路采样"了环境。
4.3 增量回写,避免 runtime.env 爆炸
通过 Python diff 脚本做三件事:
- 过滤无意义变量(如
PWD、SHLVL、HOSTNAME等) - 只写新增/变更变量(和上次快照比较)
- 对
PATH/PYTHONPATH去重,避免重复叠加
这一步避免了常见问题:runtime.env 每次执行都不断膨胀。
4.4 为什么"看起来成功但没落盘"
我们线上真实踩过两个坑:
坑 1:执行器默认可能是 sh -c,不是 bash
OpenClaw shell 选择逻辑会受环境变量 SHELL 影响;若未显式设置,可能回退到 sh。
修复:
- 在
init.sh启动网关时显式注入SHELL=/bin/bashCLAWDBOT_SHELL=bash
坑 2:把 /bin/bash 链接到 wrapper 后出现递归
如果 wrapper 的 shebang 还是 #!/bin/bash,就会自引用递归(Too many levels of symbolic links)。
修复:
- wrapper shebang 改为
#!/bin/sh - 真正 bash 二进制保留为
/bin/bash.real - wrapper 内部转调
REAL_BASH=/bin/bash.real
5. 最终行为验证(我们如何确认它真的生效)
5.1 写入并落盘
bash
docker exec openclaw-user-4 /bin/bash -lc 'export AI_KEY=mykey-123456'
cat data/user_4/config/env/runtime.env
应看到:
env
AI_KEY=mykey-123456
5.2 重建后恢复
bash
docker-compose up -d --force-recreate --no-deps openclaw-user-4
docker exec openclaw-user-4 /bin/bash -lc 'echo $AI_KEY'
应输出:
text
mykey-123456
5.3 批量滚动重建稳定性
200 容器重建后,状态收敛到 running + healthy/starting,无系统性丢变量。
6. 运维策略:统一"删除 + 重建",禁止 restart 发布
这是另一个关键经验:
.env变更后,docker restart不会重新注入环境变量- 必须走"
docker rm -f+docker-compose up -d"
建议统一使用脚本化发布(如 rollout_env_recreate.sh),避免人工操作漂移。
7. 方案优点与边界
优点
- 不侵入 OpenClaw 主干源码
- 自动化程度高,减少"口头成功"假象
- 对容器重建友好,天然支持持久化恢复
- 有
runtime.json可用于审计和排障
边界
- 仅能捕获经过 shell 执行链的环境变化
- 不建议盲目持久化敏感变量(需配合脱敏/权限策略)
- 仍需统一发布流程,否则会出现"部分容器旧行为"
8. 可直接复用的最佳实践清单
- 所有容器初始化统一入口(如
init.sh) - 环境变量分层:
base.env(静态)+runtime.env(动态) - wrapper 必须保留原命令退出码
- 快照必须做 diff,不要全量覆盖
- 过滤噪声变量,去重 PATH 类变量
- 执行器 shell 显式固定(
SHELL=/bin/bash) - 批量发布只走"删除 + 重建",不要
restart伪发布
9. 总结
这次改造的本质不是"写了个脚本",而是把"执行行为"变成"可观测、可复现、可恢复"的系统能力:
- 智能体说"成功"不再是口头承诺
- 变量是否落盘可以直接查证
- 容器重建后行为一致,不再靠运气
如果你的 AI Agent 系统也有"会话里看起来对,重启后全丢"的问题,这套 init + wrapper + diff 快照 的方案,基本可以直接迁移。