Files
crearte-monorepo/docs/specs/2026-09-30-backup-observability-design.md
T

7.7 KiB
Raw Blame History

P3 备份 + 可观测设计 / Backup & Observability Design

  • 状态:设计已定(2026-09-30;三个选型向维护者提问未被应答,按推荐默认拍板并记录 §2,维护者可事后推翻)。
  • 路线图:P3(第一波末项),依赖 P1 拓扑已定型。
  • 涉及仓库:crearte-server(0.11.1 → 0.12.0)、crearte-deploy(0.5.1 → 0.6.0);crearte 前端零改动。

1. 背景与现状(2026-09-30 实地盘点)

  • prod 栈(db-prod postgres:17-alpine / minio-prod pgsty/minio / api-prod / web-prod)数据落命名卷 pgdata-prod、minio-data-prod,没有任何备份面;api-prod 无宿主端口映射(仅 web-prod 的 8080 反代 /api/ 与静态),MinIO console 宿主端口 MINIO_PROD_PORT:-9001。
  • server 日志 = 26 处 stdlib log.Printf 散在 12 个文件(handler×7、service×2、api 中间件×2、cmd/serve.go×1 等);无请求日志中间件;无 /metrics。gin.New() 仅挂 Recovery + CORS。
  • 限流器 internal/api/ratelimit.go:进程内 map[ip]*windowCounter 固定窗口(bundle-key 60/min、login 10/min、register 5/h、upload 10/min、submission 20/h、reaction 30/min),maxTrackedIPs=10000 惰性清理——纯单实例语义。
  • 依赖基线:gin 1.11 / pgx 5.8 / aws-sdk-go-v2;无 prometheus 依赖。go.mod go 1.24.1。

2. 选型拍板(默认=推荐项)

# 岔路 决定 理由 / 放弃项
D1 /metrics 实现 手写极简 exposition(零新依赖,Prometheus 文本格式 v0.0.4) 单实例小站够用;代码可控可测;延续本仓最小依赖风格。放弃 prometheus/client_golang(依赖链重,Grafana 生态是未来事)与"不做"(ROADMAP 明列交付物)。
D2 备份调度 deploy 仓 backup.sh + 主机 crontab(runbook 给一行安装示例) 脚本可手跑、可 CI --dry-run 验证、不绑 systemd。放弃栈内 cron sidecar(挂 docker.sock = 攻击面);systemd timer 留作 runbook 备选写法。
D3 备份落点 主机目录 BACKUP_DIR(默认 ./backups/prod/<UTC 时间戳>/)+ 保留最新 BACKUP_KEEP_DAYS=7 份 防误删/防配置炸;同盘不防磁盘坏——3-2-1 与 rclone 异地钩子写进 runbook 作可选步骤,不配不生效(本轮不引入外部 S3 凭据依赖)。

3. server 设计(0.12.0)

3.1 结构化日志(slog)

  • config.Load() 新增 LOG_FORMAT=text|json(默认 text)、LOG_LEVEL=debug|info|warn|error(默认 info;非法值报错)。serve.go 启动时据此配置 slog.Default(输出 stderr;JSON 供 prod/容器采集)。
  • 26 处 log.Printf 全量迁移到 slog,机械映射规则:原 msg k1=%s k2=%v → slog.<lvl>("<msg 前缀短语>", "k1", v1, "k2", v2);携带 error=%v 的一律 slog.Error(..., "error", err);启动/生命周期一行 slog.Info。文案短语与键名不变。
  • 新增请求日志中间件(internal/observability):每请求一行 http_request,attrs = method、route(c.FullPath(),未匹配路由记 nomatch,基数有界)、status、duration_ms、ip;/healthz 整体跳过(不记日志也不计指标,防 scrape 噪声);status >= 500 用 Error 级。
  • gin.Recovery() 保留(panic 兜底),不动。

3.2 /metrics(手写)

  • internal/observability:并发安全 Registry(mutex + map[method|route|status]→{count,sumSeconds})。中间件一次计时同时喂日志与指标。
  • 指标族:
    • crearte_http_requests_total{method,route,status}(counter)
    • crearte_http_request_duration_seconds_sum/_count{method,route,status}(counter 语义,够算均值)
    • go_goroutines、go_memstats_alloc_bytes(runtime 现值 gauge)
    • crearte_pgpool_*(pgxpool.Stat():max_conns/total_conns/acquired_conns/idle_conns/acquire_count/empty_acquire_count gauge;pool 为 nil 时省略——纯 mock 测试路由无池)
  • GET /metrics 与 /healthz 同口注册(RouteMetrics="/metrics"),无 auth、无 CORS 例外。
  • 暴露边界:prod 形态 api-prod 宿主零端口 → /metrics 仅容器网内可达,抓取配方 docker compose exec -T api-prod wget -qO- http://127.0.0.1:8080/metrics。外网暴露 + allowlist/basic-auth 明确不做(未来接 Prometheus 抓取器再开 nginx 转发段)。
  • 测试路径无池、无真栈:httptest 经真 router 打请求,断言文本行、计数增长、nomatch 桶存在。

3.3 限流单实例权衡(记录,不改造)

server docs/README.md 新增「已知权衡」节:多实例部署时每实例独立计数(有效限额 ≈ N×、重启清零、跨实例不共享);maxTrackedIPs 上限的内存数量级;扩多实例的前置条件是引入 Redis/网关层共享计数。只记录现状语义,不改限流器。

4. deploy 设计(0.6.0)

  • compose 结构零改动:备份全走 docker compose exec / run --no-deps 一次性容器(db-prod 自带 pg_dump/pg_restore/psql,unix socket trust 免口令;minio-prod 镜像自带 mc,凭据经容器 env 注入而非命令行字面量)。
  • scripts/backup.sh:
    1. --dry-run:不碰 docker,仅校验 .env 存在、POSTGRES_PASSWORD/MINIO_ROOT_* 非空(:? 守卫)并打印计划——CI 步骤用它。
    2. 预检:docker 可用、compose --profile prod config -q、db-prod 存活(pg_isready)。
    3. pg_dump --format=custom 流式 → $BACKUP_DIR/prod/<UTC 时间戳>/database.dump。
    4. mc mirror --overwrite --remove 桶(MINIO_BUCKET:-crearte)→ 同目录 objects/。
    5. 完整性:pg_restore --list 读 dump 头。
    6. manifest:全表精确行数(循环 pg_tables 逐表 count(*))+ 产物体积。
    7. prod/latest 软链指向本次目录;按目录名排序保留最新 BACKUP_KEEP_DAYS 份,只删 ^\d{8}T\d{6}Z$ 白名单形态目录。
  • scripts/restore-drill.sh:latest dump 恢复到 debug 栈 db-test(drop/recreate 仅限该测试库),逐表行数与 manifest 对比,输出 PASS/FAIL。绝不连 prod 库。
  • docs/BACKUP-RUNBOOK.md:定时约定(crontab 一行 + systemd timer 备选写法);恢复步骤(恢复到现有 prod 库 / 恢复到全新栈两条路径);对象侧 mc mirror 反向推送;异地 3-2-1 与 rclone 可选钩子;已知风险(同盘单拷贝、mc mirror 非快照一致性——写侧对象上传少可接受、备份窗口内行数漂移 manifest 与 dump 快照的微小差)。
  • .env.example 增 BACKUP_DIR/BACKUP_KEEP_DAYS 注释示例;README prod 节链 runbook;validate.yml 增 backup.sh --dry-run 步骤(CI 里先 cp .env.example .env 再 append 假值过 :? 守卫)。
  • CHANGELOG 0.6.0(英/中连续行)。

5. 验收标准(本机任务 4 实测)

  1. server:容器四连(gofmt/vet/build/test)+ TEST_DATABASE_URL 集成层全绿、0 skip;新单测覆盖指标格式、中间件计数、nomatch 桶、日志 JSON 合法(jq)、LOG_* 解析。
  2. 实栈冒烟(prod profile 演练栈):打一轮真实请求 → docker compose exec 抓 /metrics 计数吻合、JSON 日志行可读;backup.sh 全绿产出 dump + objects + manifest;restore-drill.sh 对 db-test PASS;连跑两次 + BACKUP_KEEP_DAYS=1 验证 latest 软链与修剪。
  3. backup.sh --dry-run 无栈可跑(CI 等价);deploy 新 workflow 步骤本机过。
  4. crearte e2e:stack full-loop 不回归(中间件动过路由)。

6. 非目标

Prometheus/Grafana 服务端搭建、/metrics 外网暴露与鉴权、异地同步落地实现、PITR/WAL 归档、卷快照级备份、限流器改造、dev/debug 栈备份(仅 prod profile)。