Files
order_site/docs/数据库备份与恢复.md
T

7.2 KiB

数据库备份与恢复

生产数据库(PostgreSQL,Compose 容器)每日备份到阿里云 OSS,与图片存储共用 OSS 基础设施,但使用独立的 backups/ 前缀与(建议)独立的 RAM 凭证。

备份内容与存放

内容 方式 OSS key 前缀
PostgreSQL 全库 pg_dump -Fc(custom 格式,自带压缩,MVCC 快照无需停机) backups/postgres/
平台 JSON 配置(apps/backend/data,排除日志/缓存) tar -czf backups/configs/
  • 每个备份对象附带 <key>.manifest.json 清单:大小、sha256、创建时间、数据库版本、已应用迁移列表。没有有效清单的备份默认不会被 latest 选择,恢复也会拒绝;只有明确使用 --allow-unverified 才兼容历史备份。
  • 不备份的内容:图片(已在 OSS)、Caddy 证书(自动重新签发)、日志(apps/backend/data/logs)。
  • PostgreSQL dump 是数据库内部一致快照,但数据库与 OSS 不共享事务;备份时刻附近的新图片可能只存在一侧。恢复后应按 file_assets.object_key 抽查 OSS 对象,并为图片桶开启版本控制/保留策略。
  • 服务器本地在 backups/ 目录保留最近 DB_BACKUP_KEEP 份(默认 7),OSS 端过期由桶生命周期规则管理。
  • 涉及脚本:deploy/backup-db.sh(备份)、deploy/restore-db.sh(恢复/演练)、后端镜像内 dist/db-backup.js(OSS 传输与校验)。

首次开通

以下步骤在阿里云控制台与服务器上各执行一次。

1. RAM 授权(建议独立,可直接复用现有凭证)

现有 STORAGE_OSS_* 的 RAM 用户拥有整个桶的权限,也能直接跑备份。但备份包含全部订单与用户数据,是最敏感的资产,建议单独创建一个 RAM 用户 order-site-backup,只授予 backups/ 前缀,自定义权限策略:

{
  "Version": "1",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["oss:PutObject", "oss:GetObject"],
      "Resource": ["acs:oss:*:*:你的桶名/backups/*"]
    },
    {
      "Effect": "Allow",
      "Action": ["oss:HeadBucket"],
      "Resource": ["acs:oss:*:*:你的桶名"]
    },
    {
      "Effect": "Allow",
      "Action": ["oss:ListObjects"],
      "Resource": ["acs:oss:*:*:你的桶名"],
      "Condition": {
        "StringLike": { "oss:Prefix": ["backups/", "backups/*"] }
      }
    }
  ]
}

为该用户创建 AccessKey,填入服务器 .envDB_BACKUP_OSS_ACCESS_KEY_ID / DB_BACKUP_OSS_SECRET_ACCESS_KEY(不填则回退 STORAGE_OSS_*)。若用独立备份桶,同时覆盖 DB_BACKUP_OSS_ENDPOINT / DB_BACKUP_OSS_BUCKET / DB_BACKUP_OSS_REGION

2. 桶生命周期规则(控制存储成本)

在 OSS 控制台为目标桶添加生命周期规则:

  • 前缀 backups/:最后一次访问 90 天后删除(按需调整,数据库每日一份,90 份足够覆盖一个季度)。
  • 建议同时开启版本控制:误删备份时可找回旧版本;给非当前版本配置 7 天过期,避免版本堆积。

3. 手动执行一次,验证链路

bash deploy/backup-db.sh
bash deploy/restore-db.sh --list
bash deploy/restore-db.sh --verify

--verify 会把最新带有效清单的备份恢复到一个隔离的临时 PostgreSQL 容器(不接入业务网络,不影响线上库),并打印迁移列表与各表行数。建议之后每月执行一次演练——没验证过的备份等于没有备份。

4. 配置每日定时备份

crontab -e
# 每天 03:00 备份数据库与配置到 OSS(目录需先存在,首次开通时脚本已创建)
0 3 * * * cd /srv/order_site && bash deploy/backup-db.sh >> backups/backup.log 2>&1

/srv/order_site 换成实际项目目录。日志追加在 backups/backup.log,可用 logrotate 管理;备份失败时 cron 不通知,请定期查看该文件,或后续接入告警 webhook。

日常运维

  • 部署自动备份:bash deploy/ubuntu-deploy.sh 在每次执行数据库迁移前会自动备份一次;--reset-db 会在删除数据卷前抓最后一份旧库备份,并跳过重置后空库备份,避免空库被误选为 latest。备份失败会中止部署,确认为可接受后可用 --skip-backup 继续。
  • 手动备份:bash deploy/backup-db.sh(可加 --keep N 覆盖本地保留份数)。
  • 查看备份列表:bash deploy/restore-db.sh --list
  • 恢复演练:bash deploy/restore-db.sh --verify(可加 --key 指定备份)。
  • 兼容历史备份:仅在确认对象来源可信时加 --allow-unverified,不要作为生产默认参数。

恢复

场景 A:整库恢复到最新备份(最常用)

bash deploy/restore-db.sh

流程:下载最新备份并强制校验 sha256 → 停 backend/web 并确认已停止 → pg_restore --clean --if-exists → 执行迁移补差 → 重启并等待健康检查。任一步失败会尝试恢复原先运行的服务,并明确报告数据库可能处于部分恢复状态。执行前需交互输入 RESTORE 确认(非交互终端用 RESTORE_CONFIRM=1--yes)。

如需同时恢复 apps/backend/data 配置:

bash deploy/restore-db.sh --restore-configs

也可以用 --config-key 指定配置归档。配置归档会先校验路径,再解包到 data 目录。

场景 B:恢复到指定时间点

bash deploy/restore-db.sh --list
bash deploy/restore-db.sh --key backups/postgres/order_site-20260823-030000.dump

场景 C:服务器全损(数据卷丢失/换机)

在新服务器完成部署(docs/部署启动与数据库重建.md)后,把旧服务器 .env(含 OSS 凭证)恢复到新服务器,再执行场景 A。图片不受影响(在 OSS),平台 JSON 配置可从 backups/configs/order_site-<时间戳>.tar.gz 解回 apps/backend/data/

场景 D:外部数据库(DATABASE_URL 非 postgres 服务)

backup-db.shrestore-db.sh 会自动识别 .envDATABASE_URL:指向外部库时,从 postgres 容器经网络直连执行 pg_dump/pg_restore,因此服务器仍需有可用的 postgres 镜像/容器作为客户端。恢复外部库前会额外告警确认目标。

恢复后检查

  • 打开后台登录、近期订单列表,抽查数据到备份时间点为止。
  • bash deploy/restore-db.sh --list 确认下一次定时备份已恢复产生新对象。

故障排查

现象 处理
[db-backup] HeadBucket 403 RAM 策略缺 oss:HeadBucket,按上文策略补齐
ListObjects 403 策略缺 oss:ListObjectsoss:Prefix 条件与实际前缀不匹配
PutObject 403 Resource 里的桶名/前缀与 .envDB_BACKUP_OSS_BUCKET / DB_BACKUP_OSS_PREFIX 不一致
缺少 OSS 配置 .envDB_BACKUP_OSS_*STORAGE_OSS_* 都为空;改完后备份脚本用的是镜像内程序,若同时改了代码需重新构建
下载校验 sha256 校验失败 备份对象被篡改或截断,换更早的备份重试
pg_restore 报版本错误 不会发生在本编排内(pg_dump/pg_restore 与库同镜像);仅外部库场景需保证客户端 ≥ 服务端大版本

安全注意

  • 备份 RAM 凭证只进服务器 .env(已 gitignore),不要提交到仓库。
  • 备份桶/前缀保持私有,不要开公共读写;backups/ 下的对象不要通过前端暴露。
  • 若备份桶与图片桶相同,确认图片访问逻辑只读图片前缀,不会枚举 backups/