关键词: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 目标

  1. 智能体执行 export KEY=VALUE 后,自动持久化
  2. 容器重建后,变量自动恢复
  3. 尽量不改 OpenClaw 主干代码
  4. 兼容 200 容器批量发布与滚动重建

2.2 约束

  • 不能依赖“约束智能体写文件”
  • 不能只靠某个 shell(例如只 hook bash
  • 不能引入破坏性行为(例如污染系统启动脚本)

3. 方案总览(架构图思路)

我们把环境变量分成两层:

  • base.env:运维手工维护的基础变量(静态)
  • runtime.env:运行时自动快照回写(动态)

启动链路:

  1. init.sh 启动时加载 base.env + runtime.env
  2. 安装 /usr/local/bin/bash wrapper
  3. wrapper 在每次 bash -c/-lc 执行后导出 env -0 快照
  4. Python diff 脚本比较 .last_env_snapshot 与当前快照
  5. 将增量写回 runtime.env / runtime.json
  6. 下次容器启动再自动加载

核心设计点:自动捕获 + 增量落盘 + 重启恢复


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 脚本做三件事:

  1. 过滤无意义变量(如 PWDSHLVLHOSTNAME 等)
  2. 只写新增/变更变量(和上次快照比较)
  3. PATH/PYTHONPATH 去重,避免重复叠加

这一步避免了常见问题:runtime.env 每次执行都不断膨胀。

4.4 为什么“看起来成功但没落盘”

我们线上真实踩过两个坑:

坑 1:执行器默认可能是 sh -c,不是 bash

OpenClaw shell 选择逻辑会受环境变量 SHELL 影响;若未显式设置,可能回退到 sh

修复:

  • init.sh 启动网关时显式注入
    • SHELL=/bin/bash
    • CLAWDBOT_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 写入并落盘

docker exec openclaw-user-4 /bin/bash -lc 'export AI_KEY=mykey-123456'
cat data/user_4/config/env/runtime.env

应看到:

AI_KEY=mykey-123456

5.2 重建后恢复

docker-compose up -d --force-recreate --no-deps openclaw-user-4
docker exec openclaw-user-4 /bin/bash -lc 'echo $AI_KEY'

应输出:

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. 方案优点与边界

优点

  1. 不侵入 OpenClaw 主干源码
  2. 自动化程度高,减少“口头成功”假象
  3. 对容器重建友好,天然支持持久化恢复
  4. runtime.json 可用于审计和排障

边界

  1. 仅能捕获经过 shell 执行链的环境变化
  2. 不建议盲目持久化敏感变量(需配合脱敏/权限策略)
  3. 仍需统一发布流程,否则会出现“部分容器旧行为”

8. 可直接复用的最佳实践清单

  1. 所有容器初始化统一入口(如 init.sh
  2. 环境变量分层:base.env(静态)+ runtime.env(动态)
  3. wrapper 必须保留原命令退出码
  4. 快照必须做 diff,不要全量覆盖
  5. 过滤噪声变量,去重 PATH 类变量
  6. 执行器 shell 显式固定(SHELL=/bin/bash
  7. 批量发布只走“删除 + 重建”,不要 restart 伪发布

9. 总结

这次改造的本质不是“写了个脚本”,而是把“执行行为”变成“可观测、可复现、可恢复”的系统能力:

  • 智能体说“成功”不再是口头承诺
  • 变量是否落盘可以直接查证
  • 容器重建后行为一致,不再靠运气

如果你的 AI Agent 系统也有“会话里看起来对,重启后全丢”的问题,这套 init + wrapper + diff 快照 的方案,基本可以直接迁移。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐