|
|
@@ -0,0 +1,385 @@
|
|
|
+# 容器接入上线手册(唯一入口)
|
|
|
+
|
|
|
+| 字段 | 值 |
|
|
|
+|---|---|
|
|
|
+| 文档编号 | 手册-20261020-容器接入 |
|
|
|
+| 版本 | v2.0 |
|
|
|
+| 创建日期 | 2026-10-20 |
|
|
|
+| 取代 | `reporter部署方案-20261020.md`、`容器上报标准部署包-20261020.md`(内容已并入本文,原文件保留作历史) |
|
|
|
+| 配套 | `reporter字段规范-20261020.md`(字段契约细节,改脚本时查)|
|
|
|
+| 适用 | **所有新容器上线 / 存量容器修复 / "容器看不到"类故障排查** |
|
|
|
+
|
|
|
+> 本文每一条都来自 2026-10-17 ~ 10-20 的真实故障。
|
|
|
+> 写进「踩坑记录」的,都是已经真实发生过、且**会重复发生**的错误。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 零、先看这个:当前全平台真实状态
|
|
|
+
|
|
|
+2026-10-20 全量盘点 54 个容器:
|
|
|
+
|
|
|
+| 指标 | 数量 |
|
|
|
+|---|---|
|
|
|
+| **24 小时内没有任何上报**(= 死的) | **45 个** |
|
|
|
+| 5 分钟内活跃上报 | 9 个 |
|
|
|
+| `balance` 字段为空的 | 42 个 |
|
|
|
+| `channels` 为标准结构的 | 12 个 |
|
|
|
+| `host.cpuPercent` 标准的 | 23 个 |
|
|
|
+| 自定义 `cny` 键(格式偏离) | 1 个 |
|
|
|
+
|
|
|
+**结论:问题不是"格式不统一",而是"八成容器根本没在用"。**
|
|
|
+
|
|
|
+新容器上线时,**先用第一章的 3 条命令确认存活**,再谈格式。格式再好、容器是死的,一样看不到。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 一、上线三步(照做即可)
|
|
|
+
|
|
|
+### 第 1 步 · 确认存活(最重要,先做这个)
|
|
|
+
|
|
|
+新容器上来,**先不配任何东西**,跑这三条:
|
|
|
+
|
|
|
+```bash
|
|
|
+# ① 有没有进程在跑上报
|
|
|
+ps aux | grep -iE "status|report" | grep -v grep
|
|
|
+
|
|
|
+# ② 它实际跑的是哪个脚本、在哪个目录
|
|
|
+ls -l /proc/<上面的 pid>/cmdline
|
|
|
+ls -l /proc/<pid>/cwd
|
|
|
+
|
|
|
+# ③ 平台侧有没有记录
|
|
|
+curl -s 'https://server.fmode.cn/parse/classes/FmodeAgent?where={"agentId":"<容器名>"}' \
|
|
|
+ -H 'X-Parse-Application-Id: ncloudmaster' | jq '.results[0] | {agentId, updatedAt, lastField}'
|
|
|
+```
|
|
|
+
|
|
|
+判读:
|
|
|
+
|
|
|
+| 现象 | 结论 | 下一步 |
|
|
|
+|---|---|---|
|
|
|
+| ①② 有,③ 有且 `updatedAt` 在 5 分钟内 | ✅ 活着 | 跳到第 2 步查格式 |
|
|
|
+| ①② 有,③ 无记录 | 在跑但上报失败 | 查 token / agentId / 云函数 id(见 §4.1)|
|
|
|
+| ①② 无,③ 有记录且在更新 | ⚠️ **数据不是这个进程写的** | 查 `host.hostname` 反查真实来源(见 §4.2)|
|
|
|
+| ①② 无,③ 停更超过 24 小时 | ❌ 死了 | 从第 2 步开始部署 |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 第 2 步 · 部署原版脚本
|
|
|
+
|
|
|
+**路径固定**:
|
|
|
+
|
|
|
+```
|
|
|
+/opt/data/git-repos/agent-status-reporter/scripts/status-report.py
|
|
|
+/opt/data/git-repos/agent-status-reporter/state/
|
|
|
+```
|
|
|
+
|
|
|
+**脚本来源**(按优先级):
|
|
|
+
|
|
|
+1. **从同宿主正常容器拷**(最快):
|
|
|
+ ```bash
|
|
|
+ docker cp <正常容器>:/opt/data/git-repos/agent-status-reporter/scripts/status-report.py \
|
|
|
+ <目标容器>:/opt/data/git-repos/agent-status-reporter/scripts/
|
|
|
+ ```
|
|
|
+2. 项目仓库:`E:\work\fmode-app\docs\03-interface\reporter-v3\status-report.py`(v11,49410 字节)
|
|
|
+
|
|
|
+> 🔴 **不要自己重写。** 原版 1100 行覆盖了:变化检测、失败队列、token 自愈、
|
|
|
+> 任务残留清理、官方 channel 探测、usage 聚合。自己复刻必然漏掉其中一部分。
|
|
|
+>
|
|
|
+> **实证**:54 个容器里唯一的格式偏离(`balance` 用了 `cny` 键)就来自一个自写脚本。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 第 3 步 · 配置与常驻
|
|
|
+
|
|
|
+**改 `/opt/data/.env` 前先备份**:
|
|
|
+
|
|
|
+```bash
|
|
|
+cp /opt/data/.env /opt/data/.env.bak-$(date +%Y%m%d%H%M%S)
|
|
|
+```
|
|
|
+
|
|
|
+**必填 4 项**:
|
|
|
+
|
|
|
+```bash
|
|
|
+AGENT_ID=<容器名> # 缺省会退化成 hostname → agent not found
|
|
|
+FMODE_SESSION_TOKEN=<主人 sessionToken> # 决定归属,谁发消息谁持有
|
|
|
+AGENT_FUNCTION_ID=xlosi3m46q # 🔴 填错会走到 OBS 上传(数据进存储桶不进库)
|
|
|
+WECOM_BOT_ID=<企微 BotID> # 有企微通道时必填
|
|
|
+```
|
|
|
+
|
|
|
+**常驻**:容器一般没有 crontab。
|
|
|
+
|
|
|
+| 方式 | 适用 | 可靠性 |
|
|
|
+|---|---|---|
|
|
|
+| **Hermes 原生 cron**(jobs.json)| 容器有 Hermes | ✅ 推荐,gateway 重启后自动继续 |
|
|
|
+| s6 服务 | 有 root 权限 | ✅ 最稳,但普通用户建不了 |
|
|
|
+| `while true` 循环 | 兜底 | ❌ **随会话死,重启就丢** |
|
|
|
+
|
|
|
+> 🔴 **`while true` 循环是陷阱。** lj-test 就是用它,gateway 重启时上报进程一起被带走,
|
|
|
+> 停了 15 分钟没人发现(表现为平台 `updatedAt` 冻住)。
|
|
|
+
|
|
|
+**Hermes cron 写法**:
|
|
|
+
|
|
|
+```json
|
|
|
+{
|
|
|
+ "job_id": "<生成>",
|
|
|
+ "name": "fmode-status-reporter",
|
|
|
+ "schedule": "*/2 * * * *",
|
|
|
+ "no_agent": true,
|
|
|
+ "script": "fmode-status-report.sh"
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+验证:
|
|
|
+
|
|
|
+```bash
|
|
|
+# 看最近几次执行
|
|
|
+sqlite3 <hermes>/executions.db "SELECT * FROM executions ORDER BY started_at DESC LIMIT 5;"
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 二、验收清单(9 条,缺一不可)
|
|
|
+
|
|
|
+```bash
|
|
|
+curl -s 'https://server.fmode.cn/parse/classes/FmodeAgent?where={"agentId":"<容器名>"}' \
|
|
|
+ -H 'X-Parse-Application-Id: ncloudmaster'
|
|
|
+```
|
|
|
+
|
|
|
+| # | 断言 | 通过标准 |
|
|
|
+|---|---|---|
|
|
|
+| 1 | `updatedAt` | **5 分钟内** |
|
|
|
+| 2 | `host.cpuPercent` | 数字,0~100 |
|
|
|
+| 3 | `host.memPercent` / `diskPercent` | 数字 |
|
|
|
+| 4 | `channels` **是对象** | `{...}`,不是 `[...]` |
|
|
|
+| 5 | `channels.matrix` | 与真实通道状态一致 |
|
|
|
+| 6 | `channels.wecomBotId` | 等于 `WECOM_BOT_ID` |
|
|
|
+| 7 | `heartbeatAt` | 60 秒内 |
|
|
|
+| 8 | `usage.totalTokens` | > 0 |
|
|
|
+| 9 | `balance.remainCny` | 有数字(或 `source:"none"` 且有原因)|
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 三、🔴 四条铁律(本轮全部踩过)
|
|
|
+
|
|
|
+### 铁律 1 · 查运行态,不查路径
|
|
|
+
|
|
|
+```
|
|
|
+❌ find / -name "status-report.py" → 找不到就说"没部署"
|
|
|
+✅ ps aux | grep -i status → 拿 pid
|
|
|
+ cat /proc/<pid>/cmdline → 看实际跑的是哪个文件
|
|
|
+```
|
|
|
+
|
|
|
+**踩坑记录**:容器按预设路径 `find` 找不到脚本,报「本机根本没部署 reporter」。
|
|
|
+实际它一直在上报(`host.hostname` 是它自己的 Docker 短 ID,44 秒前刚更新)。
|
|
|
+
|
|
|
+**「找不到文件」≠「进程没在跑」。**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 铁律 2 · 调用失败 ≠ 凭据失效
|
|
|
+
|
|
|
+| 现象 | 容易误判 | 实际 |
|
|
|
+|---|---|---|
|
|
|
+| `Bearer` 头 → 401 | token 过期了 | 调用姿势不对 |
|
|
|
+| body `{"sessionToken":...}` → 200 | — | ✅ 正确姿势 |
|
|
|
+
|
|
|
+**踩坑记录**:容器断定 token 已过期,据此说 balance 拿不到。实际 token 一直是好的,
|
|
|
+只是它用了 Bearer 头。这个误判让排查方向整个偏了一整天。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 铁律 3 · 每个数字都要回读,不许自算
|
|
|
+
|
|
|
+```
|
|
|
+❌ 自己算一遍 cpuPercent=16.4,然后报"已验证"
|
|
|
+✅ 上报后 op=get 读回原文,把字面值贴进报告
|
|
|
+```
|
|
|
+
|
|
|
+**踩坑记录**:
|
|
|
+- 第一轮报「cpuPercent 已验证 16.4」→ 库里**根本没这个键**
|
|
|
+- 第二轮报「balance.remainCny=63.69」→ 实际 `{"cny":91.46}`,键名和值**都不对**
|
|
|
+
|
|
|
+**报告里的每个数字,都必须是回读原文里的字面值。**
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### 铁律 4 · 活跃度 ≠ 健康度
|
|
|
+
|
|
|
+判断「通道是否正常」,不能用「最近有没有收到消息」。
|
|
|
+
|
|
|
+| 指标 | 语义 | 对不对 |
|
|
|
+|---|---|---|
|
|
|
+| 最近收到消息的时间 | **业务活跃度** | ❌ 没人说话就变红,误报 |
|
|
|
+| `channelState` 字段 | **自称状态** | ❌ 进程活着就绿,假绿 |
|
|
|
+| TCP 连接是否存在 | **连接层** | ⚠️ 覆盖不了「连上但不消费」|
|
|
|
+| 真实消费过事件 | **健康度** | ✅ 正确,但难测 |
|
|
|
+
|
|
|
+**踩坑记录**:容器报 `listenerOk: true`(读 `channelState`),实际 15 小时 0 条入站消息。
|
|
|
+翻成读「最近 inbound 时间」后,又变成「20 分钟没人说话就报 false」—— **另一个极端**。
|
|
|
+
|
|
|
+**正确做法**:分开报两个字段,各自标明语义:
|
|
|
+
|
|
|
+```
|
|
|
+listenerOk = 连接层健康(并如实注明判据来源与局限)
|
|
|
+lastInboundAgeSecs = 业务活跃度(只展示,不参与健康判断)
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 四、故障速查表
|
|
|
+
|
|
|
+### 4.1 上报返回 `agent not found` / 数据不落库
|
|
|
+
|
|
|
+| 可能 | 检查 |
|
|
|
+|---|---|
|
|
|
+| `AGENT_ID` 没生效 | `ps auxww \| grep status-report` 看进程的完整命令行有没有带上 |
|
|
|
+| `AGENT_FUNCTION_ID` 填错 | 返回值带 `"key":"user/.../log/...json"` = 走了 OBS 上传(`AlP56LCKFm`),应为 `xlosi3m46q` |
|
|
|
+| token 失效 | 换姿势重试(见铁律 2);仍 401 才考虑重登 |
|
|
|
+
|
|
|
+### 4.2 数据在更新,但容器里找不到写入方
|
|
|
+
|
|
|
+**症状**:平台 `updatedAt` 在跳,但 `ps` 里没有 reporter 进程。
|
|
|
+
|
|
|
+**排查顺序**:
|
|
|
+
|
|
|
+```bash
|
|
|
+# ① 用 host.hostname 反查(容器自证字段,外部推不出来)
|
|
|
+# 平台读 host.hostname,和容器内 hostname 对比
|
|
|
+hostname
|
|
|
+
|
|
|
+# ② 按 agentId 查(比按字段名查更容易命中配置)
|
|
|
+grep -rl "<agentId>" /opt /etc /root /home 2>/dev/null
|
|
|
+
|
|
|
+# ③ 看是否有别的 uid 在跑
|
|
|
+ps -eo pid,ppid,user,cmd --forest | head -60
|
|
|
+```
|
|
|
+
|
|
|
+**踩坑记录**:容器连续三轮说「本机没有 reporter」「找不到 balance 代码」。
|
|
|
+但全平台 54 个容器里**只有它一个**用 `cny` 键 → 排除平台代写 → 确实是它专属代码在写。
|
|
|
+
|
|
|
+**判定技巧**:如果某个字段格式**全平台独一份**,那写入方一定在它自己相关的某处,不是平台统一逻辑。
|
|
|
+
|
|
|
+### 4.3 容器"在线"但不回消息(Matrix)
|
|
|
+
|
|
|
+这是本轮最复杂的故障,**三道门依次拦截**:
|
|
|
+
|
|
|
+| 门 | 症状 | 修复 |
|
|
|
+|---|---|---|
|
|
|
+| ① `MATRIX_IGNORE_USER_PATTERNS` | 消息进了 sync,但被静默丢弃 | 从忽略名单移除该用户 |
|
|
|
+| ② `MATRIX_ALLOWED_USERS` | 抛 `Unauthorized user (ignored)` | 把用户加进允许名单 |
|
|
|
+| ③ 房间白名单 | DM 房间被当非白名单 | 一般 DM 豁免,未触发 |
|
|
|
+
|
|
|
+**排查命令**:
|
|
|
+
|
|
|
+```bash
|
|
|
+# ① 有没有进 sync
|
|
|
+grep -n "<event_id>" /opt/data/logs/gateway.log*
|
|
|
+
|
|
|
+# ② 有没有被忽略
|
|
|
+grep -iE "ignoring sender|ignore pattern" /opt/data/logs/gateway.log*
|
|
|
+
|
|
|
+# ③ 有没有被授权门挡
|
|
|
+grep -iE "Unauthorized user|unrecognized" /opt/data/logs/gateway.log*
|
|
|
+
|
|
|
+# ④ 是否有 inbound 记录
|
|
|
+grep -iE "inbound message" /opt/data/logs/gateway.log | tail -10
|
|
|
+```
|
|
|
+
|
|
|
+**⚠️ 配置读取的假阴性**:`.env` 是 Python 层用 `load_hermes_dotenv()` 加载的,
|
|
|
+**不会出现在 `/proc/<pid>/environ`** 里。用它查「有没有配某变量」永远是空。
|
|
|
+
|
|
|
+正确做法:**让容器打印它自己读到的生效值**:
|
|
|
+
|
|
|
+```
|
|
|
+[env] IGNORE = '...'
|
|
|
+[env] ALLOWED = [...]
|
|
|
+```
|
|
|
+
|
|
|
+### 4.4 上报进程随重启消失
|
|
|
+
|
|
|
+**症状**:gateway 重启后,平台 `updatedAt` 冻住不动。
|
|
|
+
|
|
|
+**原因**:上报是 `while true` shell 循环起的,进程组随 gateway 一起死。
|
|
|
+
|
|
|
+**修复**:改用 Hermes 原生 cron 或 s6(见 §1 第 3 步)。
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 五、给容器的标准操作提示词
|
|
|
+
|
|
|
+```markdown
|
|
|
+【容器状态上报 · 标准接入】
|
|
|
+
|
|
|
+一、先查存活(不要跳过)
|
|
|
+ ps aux | grep -iE "status|report" | grep -v grep
|
|
|
+ ls -l /proc/<pid>/cmdline
|
|
|
+ → 有进程:先确认它在报什么、报给谁,别急着重装
|
|
|
+ → 无进程:按下面从零部署
|
|
|
+
|
|
|
+二、部署(不要自己写脚本)
|
|
|
+ mkdir -p /opt/data/git-repos/agent-status-reporter/{scripts,state}
|
|
|
+ 从同宿主正常容器 docker cp 原版 status-report.py
|
|
|
+
|
|
|
+三、配置 /opt/data/.env(先备份)
|
|
|
+ AGENT_ID=<容器名>
|
|
|
+ FMODE_SESSION_TOKEN=<主人 token>
|
|
|
+ AGENT_FUNCTION_ID=xlosi3m46q
|
|
|
+ WECOM_BOT_ID=<企微 BotID>
|
|
|
+
|
|
|
+四、常驻(不要用 while true 循环 —— 重启会丢)
|
|
|
+ 用 Hermes 原生 cron 或 s6
|
|
|
+
|
|
|
+五、验收(9 条,回读平台值,不许自算)
|
|
|
+ □ updatedAt 5 分钟内
|
|
|
+ □ host.cpuPercent 0~100
|
|
|
+ □ host.memPercent / diskPercent
|
|
|
+ □ channels 是对象不是数组
|
|
|
+ □ channels.matrix 与真实状态一致
|
|
|
+ □ channels.wecomBotId 匹配
|
|
|
+ □ heartbeatAt 60 秒内
|
|
|
+ □ usage.totalTokens > 0
|
|
|
+ □ balance.remainCny 有值
|
|
|
+
|
|
|
+六、纪律
|
|
|
+ · 查运行态(ps/proc),不查路径(find)
|
|
|
+ · 调用失败先换姿势,再怀疑 token
|
|
|
+ · 报告里每个数字必须是回读原文的字面值
|
|
|
+ · 配置要用「打印自己读到的值」验证,不要从 /proc 或文件推测
|
|
|
+
|
|
|
+报告格式:
|
|
|
+REPORTER-OK agentId=<...>
|
|
|
+updatedAt=<平台回读>
|
|
|
+cpuPercent=<回读> memPercent=<回读> diskPercent=<回读>
|
|
|
+channels=<对象/数组> matrix=<true/false>
|
|
|
+heartbeatAt=<回读> usage.totalTokens=<回读> balance.remainCny=<回读>
|
|
|
+常驻方式=<cron/s6/其他>
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 六、上线检查表(复制给执行人)
|
|
|
+
|
|
|
+```markdown
|
|
|
+□ 存活三查(ps / proc / 平台回读)已做
|
|
|
+□ 脚本是原版(不是自写复刻)
|
|
|
+□ .env 已备份
|
|
|
+□ AGENT_ID / FMODE_SESSION_TOKEN / AGENT_FUNCTION_ID / WECOM_BOT_ID 四项已配
|
|
|
+□ 上报走 xlosi3m46q(返回不是 "key":"user/.../log/...json")
|
|
|
+□ 常驻用 cron 或 s6(不是 while true)
|
|
|
+□ 平台回读 updatedAt 在 5 分钟内
|
|
|
+□ 9 条验收断言全过
|
|
|
+□ 机器人在 App 会话里能实际回复消息
|
|
|
+□ 重启 gateway 后上报能自动恢复
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## 七、关联文档
|
|
|
+
|
|
|
+- `docs/03-interface/reporter字段规范-20261020.md` —— 逐字段契约(改脚本时查)
|
|
|
+- `docs/03-interface/容器初始化流程优化建议-20261020.md` —— workshop 流程优化 + 方法论
|
|
|
+- `docs/02-design/architecture/账号与账套体系设计.md` —— 归属模型
|
|
|
+- 云函数 `xlosi3m46q` v0.1.3(`.local-backup/xlosi3m46q-v0.1.3.js`)
|
|
|
+- 原版脚本:`E:\work\fmode-app\docs\03-interface\reporter-v3\status-report.py`(v11)
|
|
|
+
|
|
|
+> **历史文档**(已被本文取代,保留供追溯):
|
|
|
+> `reporter部署方案-20261020.md`、`容器上报标准部署包-20261020.md`
|