Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HwMNZEzdzmrbPhuRNmZe3C
12 KiB
12 KiB
TASK · 内测安全网:每日备份、探活告警、错误汇总、日志轮转与按 requestId 查日志(2026-10-14)
- 日期:2026-10-14
- 来源:产品 10-14 内测体检后拍板「内测跑生产」。体检结论:生产没有定时备份、没有探活告警、没有错误上报、容器日志不轮转,出了事既不知道也查不到(BUG-1306 取证就因日志只在容器控制台而失败)。
- 执行方:Grok(Windows),分支
codex/beta-safety-net-20261014,只推这个分支。Grok 碰不到生产主机:主机侧的安装与演练写成脚本 + runbook,由产品执行。 - 验收:Claude(Linux)。
- BUG 编号:开工时核对最大号顺延(写作时 BUG-1317)。其他已推任务书在
docs/tasks/里已预占到 BUG-1346,开工时取docs/BUG_HISTORY.md与docs/tasks/已引用编号两者的最大号顺延。 - 串行:在
TASK-release-path-production-20261014合入 staging 之后开工(两单都改deploy/README.md,本单的定时工作流也要沿用那一单的发布 SHA 规则)。与 issue #58(TASK-node-runtime-resources-20261013,Node 内存泄漏 / Postgres 往返 / 内存表)边界:#58 可能改deploy/docker-compose.server.yml的环境变量透传;本单改同一文件的logging段——开工前看 #58 是否已合入,合入则在其之上改,未合入则在回报里写明需 Claude 合并时解冲突。不碰 #57(校正重算)任何文件。
1. 基线
- 基线 commit:
origin/staging开工时最新(写作时48a2fadd;需包含发布通路单的合入提交)。 - 现有备份能力:
.gitea/workflows/create-production-recovery.yml(只workflow_dispatch)调用deploy/run-production-recovery.sh:pg_dump自定义格式 → AES-256-CBC/PBKDF2 加密 → 还原到一次性库校验行数 → 删一次性库 → 上传 Gitea artifact(30 天);主机上/opt/jyotisha-production/backups保留加密文件;磁盘使用 ≥70% 拒绝。deploy/backup-staging-postgres.sh只给 staging 手动用。 - 编排:
deploy/docker-compose.server.yml三个服务api、web、caddy(PostgreSQL 另在deploy/docker-compose.postgres.yml),都有restart: unless-stopped与 healthcheck,都没有logging段(主机 docker daemon 的默认日志配置未知)。 - 观测:
frontend/src/lib/agent-observability.ts用console.info("[agent-observability]", JSON.stringify(event))写到 web 容器标准输出;Python API 的错误也只在容器标准输出。
2. 事故实证
| 缺口 | 证据 | 后果 |
|---|---|---|
| 无定时备份 | 仓库内无 schedule: 触发的工作流、无 cron / systemd timer 文件;生产恢复点最后一次是 08-16(run 1881) |
内测开始写入真实用户数据后,任何一次误操作或磁盘故障都可能丢失最多「上次手动备份以来」的全部数据 |
| 无探活告警 | 仓库内无 Uptime Kuma / Prometheus / 告警配置;只有容器 healthcheck(只会重启,不会通知人) | 站点挂了只能等用户来说 |
| 无错误上报 | 无 Sentry 类集成;错误只在容器 stdout | 用户遇到报错,团队不知道,也无法统计 |
| 日志不轮转、留存不可控 | compose 无 logging;docker compose logs --tail=100 是唯一查法(deploy/README.md) |
磁盘可能被日志写满;或日志在重建容器时丢失;BUG-1306 取证失败 |
| 不能按 requestId 查 | 观测 JSON 里有 requestId,但没有按 requestId 检索的工具或流程 | 用户报「这一轮不对」时无法还原 |
3. 根因
上线基础设施按「单人维护、手动发布」设计,备份只作为迁移前的一次性保险;没有任何「持续运行时」的观测与告警。内测引入真实用户后这些从可选变成必需。
4. 决策记录(产品 2026-10-14 授权)
- 内测跑生产,本单是内测开放前的必需项。
- 授权新增定时工作流(
.gitea/workflows/下新增文件,属于 10-14 对 workflow 的授权范围):每日生产备份。新增文件不得改动已有四个生产工作流的语义(那是发布通路单的范围)。 - 红线(产品 10-14):错误上报不得把用户资料、出生资料、对话内容、邮箱、Cookie、JWT 发给任何第三方。 因此本单不接入托管 Sentry 类服务;错误汇总在自己的主机上完成,外发的告警只含「哪个服务、什么错误码、多少次、requestId」,不含正文。
- 告警渠道由产品提供(见 §10);本单实现「Webhook 地址从环境 / 主机配置读取」,默认按企业微信 / 钉钉 / 飞书群机器人 Webhook 格式(三者都只要一个 URL),没配置时只写本地日志不外发。
- 主机侧安装全部写成脚本 + runbook,由产品执行;Grok 不 SSH、不 dispatch 生产工作流。
5. 硬红线
- 备份文件始终加密;不打印、不提交加密密钥、
.env.production*、数据库口令;告警消息与日志汇总不含任何用户内容(只含计数、错误码、路由、requestId、时间)。 - 不改现有
create-production-recovery.yml/run-production-recovery.sh的加密、还原校验、锁、磁盘检查逻辑;定时备份复用它们,不另写一套pg_dump。 - 定时备份不要求
main == staging == 发布 SHA(备份不该被发布状态卡住),但仍只从main运行、只用main上的脚本。 - 日志轮转只改
logging段,不改服务的镜像、环境、端口、healthcheck。 - 不新增对外开放的端口;探活从外部访问公网
https://jyotisha.chat/api/health,不暴露 5200 / 3000。 - 不写真实用户资料;测试用虚构数据。
- Grok 不 push main、不推 staging、不 dispatch 生产工作流。
6. 任务分解
| 项 | 内容 | 验收标准 |
|---|---|---|
| T1 每日备份工作流 | 新增 .gitea/workflows/backup-production-daily.yml:schedule 每天北京时间 04:00 + workflow_dispatch;从 main 取 deploy/run-production-recovery.sh 执行(复用恢复点工作流的 SSH、锁、产物上传步骤,能抽成共用脚本就抽);artifact 保留天数可配,默认 14。主机侧本地加密文件保留策略:新增清理步骤只删 backups/ 下由本工作流生成、超过 N 天(默认 7)的 *.dump.enc 与对应 verify 文件,按文件名前缀严格匹配,绝不删其他文件 |
合同测试断言:有 schedule、只从 main 运行、调用同一个 recovery 脚本、清理只匹配本工作流前缀、不打印密钥;清理逻辑写成独立 shell 函数并有 bats 风格或 Python 子进程测试(虚构目录:只删过期的本前缀文件,其他文件不动) |
| T2 恢复演练 runbook | docs/operations/production-restore-drill-20261014.md:从某一天的备份 artifact 还原到一次性库(不碰 jyotisha 主库)→ 核对表行数 → 删除;以及真正整库还原的步骤(停写、还原、核对、恢复服务)与预计停机时间。演练结果模板 docs/testing/production-restore-drill-20261014.md |
步骤可照做;演练由产品在内测开放前执行一次,结果回填模板 |
| T3 探活与告警 | deploy/monitor/healthcheck-alert.sh:每分钟 GET https://jyotisha.chat/api/health(超时 10s),连续 3 次失败或 status != ok 或 deployment.gitCommit 与上次不同且非发布窗口时发告警;恢复时发「已恢复」;同时检查主机磁盘 ≥80% 告警。Webhook URL 从 /opt/jyotisha-production/.monitor.env(0600)读取。runbook:在哪台机器装(建议 Gitea runner 所在机器,不在生产主机本身——主机挂了它也就挂了;磁盘检查例外,需在生产主机上单独跑)、用 cron 还是 systemd timer。不在 Gitea Actions 里每分钟跑 job(runner 负担大、间隔不可靠) |
Shell 测试:用本地桩服务模拟 200 / 500 / 超时 / 字段缺失;连续 3 次才告警、恢复只发一次、无 Webhook 时只写本地日志;告警正文只含允许字段 |
| T4 错误汇总(不外发正文) | deploy/monitor/error-digest.sh:每 5 分钟读最近 5 分钟的 web / api 容器日志(docker compose logs --since),按「服务 + 错误类别 / 状态码 / agent 观测里的 errorCode」计数;超过阈值(默认 5 分钟内同类 ≥10 次,或任何 5xx ≥20 次)发告警,消息里附最多 5 个 requestId;每天 09:00 发一份前一天汇总。需要时在 web / api 的错误日志里补齐结构化字段(requestId、route、errorCode),不加用户内容 |
用虚构日志样本测试分类与阈值;断言告警文本不含邮箱、出生日期、会话文本(用含这些内容的样本日志验证会被丢弃) |
| T5 日志轮转与留存 | deploy/docker-compose.server.yml 与 docker-compose.postgres.yml 每个服务加 logging: { driver: json-file, options: { max-size: "20m", max-file: "10" } }(约每服务 200 MB 上限);runbook 说明主机 daemon 级默认值如何确认 |
合同测试:每个服务都有 logging 段且值一致;compose config 能解析(本地 docker compose -f ... config 跑一次) |
| T6 按 requestId 查日志 | deploy/find-request-log.sh <requestId> [--since 24h]:在 web / api / caddy 日志里检索并按时间排序输出;只读。文档写进 deploy/README.md 运维段 |
用虚构日志样本测试;拒绝非 UUID / 非约定格式的参数(防命令注入) |
| T7 文档与登记 | deploy/README.md 新增「运维与告警」段:备份在哪、保留多久、怎么还原、告警发到哪、怎么查一轮请求;BLOCKED.md 登记需产品在主机上执行的安装步骤(解除后划掉) |
Claude 逐条对照脚本 |
7. 让步顺序
T1、T3、T5 不可让(内测开放的最低线:有备份、挂了有人知道、磁盘不会被日志写满)。T2 runbook 不可让,演练执行可在开放前最后一天完成。T4 可让为「内测第一周补上」,但 T6 不可让(至少能查)。
8. 验收(执行方自测 + Claude 复验)
- Shell:
bash -n;shellcheck(有则跑);上表各项测试。 - 合同测试:放在
frontend/tests/staging-backend-workflows.test.ts同类文件或新增tests/test_beta_safety_net.py。 - 前端改动(若在 web 错误日志补字段):
tsc --noEmit0 错;npm run lint0 error;npm test失败名单与基线逐名一致。 - Python:快速门
scripts/run_quality_gate.py --profile quick。 - 隐私:
tests/test_repo_privacy_markers.py;新增测试样本只用虚构数据。 - 文档:
docs/BUG_HISTORY.md(无备份 / 无告警 / 日志不轮转各记一条或合并一条,状态写清「代码已交,主机安装待产品」)、CHANGELOG.md(运维变化,用户不可见可写一句)、docs/tasks/PROGRESS-beta-safety-net-20261014.md、docs/tasks/README.md状态。
9. 开工前置
git fetch origin --prune
git status -sb
# 确认发布通路单已合入:git log origin/staging --oneline | grep -i release-path
git worktree add -b codex/beta-safety-net-20261014 .worktrees/beta-safety-net-20261014 origin/staging
10. 需产品提供 / 拍板
- 告警发到哪:建议企业微信 / 钉钉 / 飞书任选一个群,建一个群机器人,把 Webhook 地址交给你自己保管、写进主机
.monitor.env(不要发到聊天里)。不选的话只写本地日志,等于没有告警。 - 探活装在哪台机器:建议 Gitea runner 所在机器(与生产主机分开);磁盘检查另装在生产主机。
- 备份保留:默认 Gitea artifact 14 天 + 主机本地 7 天;有更长要求(如 30 天)告诉 Claude,注意 Gitea 服务器磁盘。
- 恢复演练时间:内测开放前执行一次,约 30 分钟,不影响线上(还原到一次性库)。
- 每日汇总发送时间:默认 09:00。
11. 产品答复(2026-10-14,覆盖上文对应建议值)
- 告警渠道:飞书群机器人(Webhook 地址由产品在主机 / runner 的环境里配置,不进仓库、不进日志)。T3 按飞书机器人消息格式实现(
msg_type: text,可选签名校验)。 - 其余按任务书建议默认值。