docs: Postgres fast-rebuild runbook (HA stage 3, option 1) (#53)
Co-authored-by: host <host@test.local>
This commit is contained in:
1 parent
06b9fd12fd
commit
de81b1643f
1 file changed
+109
@@ -0,0 +1,109 @@
|
||||
# Postgres 快速重建手册(HA 阶段 3,选项 1:单 Postgres + 快速重建)
|
||||
|
||||
前提(2026-10-26 实测值,改配置后同步改这里):
|
||||
- 容器 `codex-postgres`:镜像 `postgres:16`,`--restart unless-stopped`,
|
||||
只绑 `127.0.0.1:5432`,卷 `/var/lib/codex-postgres` 挂到
|
||||
`/var/lib/postgresql/data`,库 `codex_control`,用户 `codexsvc`。
|
||||
- 备份:每天 03:00 CST 由 `codex-pg-backup.timer` 跑 `pg_dump`,
|
||||
在 `/var/lib/codex-app-server/backups/`,轮转 7 份,目录 700、
|
||||
文件 600(含生产全量,含 GitHub App 配置,绝不要放宽权限、
|
||||
绝不要进仓库)。
|
||||
- 连接参数只在 `/etc/codex-app-server/env`(0600 root)里,密码
|
||||
只经环境变量传,不进命令行、不进日志。
|
||||
- RTO:场景 A 约 5 分钟,场景 B 约 30 分钟;RPO = 24 小时
|
||||
(备份间隔,备份后到故障前的数据会丢,写进事故通报里)。
|
||||
|
||||
通用顺序(三个场景都一样):先停两台 server(避免恢复中途有
|
||||
写请求进来),再动数据库,恢复完先 `pg_isready`、再起 server、
|
||||
再看 health 与 monitor。不要在 server 还跑着的时候往生产库里
|
||||
恢复备份。
|
||||
|
||||
```sh
|
||||
systemctl stop codex-cloud-server codex-cloud-server-2
|
||||
# ... 按场景恢复 ...
|
||||
systemctl start codex-cloud-server codex-cloud-server-2
|
||||
curl -s http://127.0.0.1:8765/health # 要求 db_ok=true
|
||||
curl -s http://127.0.0.1:8766/health # 同上
|
||||
sh /opt/codex-app-server/deploy/monitor.sh # 要求 overall pass
|
||||
```
|
||||
|
||||
恢复后 running 任务的处理:恢复出来的 running 行心跳一定过期,
|
||||
会被 worker 的陈旧回收(300 秒)标 `interrupted`;不要手动改
|
||||
状态,让 sweep 走,之后新建任务重跑(goal 任务本来就不支持
|
||||
同任务 `/retry`)。
|
||||
|
||||
## 场景 A:容器丢了,卷还在
|
||||
|
||||
```sh
|
||||
docker ps -a | grep codex-postgres # 确认容器没了、卷还在
|
||||
ls /var/lib/codex-postgres # 卷里应有 PGDATA 内容
|
||||
docker run -d --name codex-postgres --restart unless-stopped \
|
||||
-p 127.0.0.1:5432:5432 \
|
||||
-v /var/lib/codex-postgres:/var/lib/postgresql/data \
|
||||
-e POSTGRES_DB=codex_control -e POSTGRES_USER=codexsvc \
|
||||
postgres:16
|
||||
# 注意:卷已存在时 POSTGRES_PASSWORD 不再生效(只在初始化空卷时
|
||||
# 用),所以这里不用传密码;旧卷的口令就是 env 里的那个。
|
||||
pg_isready -h 127.0.0.1 -p 5432
|
||||
```
|
||||
|
||||
## 场景 B:卷丢了(磁盘坏、误删),用备份重建
|
||||
|
||||
```sh
|
||||
# 1. 先确认旧卷确实不可恢复(不要在还能抢救的卷上直接重建)。
|
||||
# 旧目录还在但已损坏时先 mv 走(保留取证),不要直接 rm。
|
||||
ls /var/lib/codex-postgres || echo "卷已丢"
|
||||
# 2. 新卷 + 新容器。密码必须等于 env 里 CLOUD_DB_PASSWORD 的值,
|
||||
# 否则 server 连不上(新卷初始化时口令就按这个值建)。
|
||||
set -a; . /etc/codex-app-server/env; set +a
|
||||
mkdir -p /var/lib/codex-postgres
|
||||
docker run -d --name codex-postgres --restart unless-stopped \
|
||||
-p 127.0.0.1:5432:5432 \
|
||||
-v /var/lib/codex-postgres:/var/lib/postgresql/data \
|
||||
-e POSTGRES_DB=codex_control -e POSTGRES_USER=codexsvc \
|
||||
-e POSTGRES_PASSWORD="$CLOUD_DB_PASSWORD" \
|
||||
postgres:16
|
||||
# 3. 恢复最新备份(备份是 pg_dump 明文 SQL,不含 CREATE DATABASE;
|
||||
# 库已由容器的 POSTGRES_DB 建好,导入只建表与数据)。
|
||||
# 新卷是空库,正常应干净导入;有报错先看日志尾部再定。
|
||||
LATEST=$(ls -t /var/lib/codex-app-server/backups/cloud-backup-*.sql | head -1)
|
||||
echo "restoring $LATEST"
|
||||
PGPASSWORD="$CLOUD_DB_PASSWORD" psql -h 127.0.0.1 -U codexsvc \
|
||||
-d codex_control -f "$LATEST" > /tmp/pg-restore.log 2>&1 \
|
||||
|| tail -20 /tmp/pg-restore.log
|
||||
# 4. 校验:表在、行数非零(与事故前最后一次 monitor/演练记录对照)。
|
||||
PGPASSWORD="$CLOUD_DB_PASSWORD" psql -h 127.0.0.1 -U codexsvc \
|
||||
-d codex_control -At -c \
|
||||
"SELECT (SELECT count(*) FROM tasks), \
|
||||
(SELECT count(*) FROM projects), \
|
||||
(SELECT count(*) FROM users), \
|
||||
(SELECT count(*) FROM audit_log);"
|
||||
```
|
||||
|
||||
## 场景 C:整机丢了
|
||||
|
||||
按 `deploy/new-host-checklist.md`(HA 阶段 4)先把新机入网
|
||||
(env、代码、units、镜像、代理白名单),再走场景 B。注意新机
|
||||
的 `/etc/codex-app-server/env` 必须与旧机一致(尤其是
|
||||
CLOUD_DB_PASSWORD,否则场景 B 的新卷口令对不上)。
|
||||
|
||||
## 安全红线
|
||||
|
||||
- 演练永远恢复到测试库(`deploy/restore-drill.sh` 已把“目标库
|
||||
名绝不等于生产库名”写进脚本逻辑);只有真实重建(场景 B/C)
|
||||
才以 `codex_control` 为目标,且必须先确认旧卷已丢。
|
||||
- 备份文件含 GitHub App 的 webhook_secret 与 pem:恢复、拷贝、
|
||||
传输时一律按密钥处理,不进仓库、不进聊天、不打印内容。
|
||||
- 重建完先不要急着接流量:health 的 `db_ok` 与 monitor 的
|
||||
`overall pass` 都通过,再用自己的 API key 调一次
|
||||
`GET /projects` 能返回,才算通。
|
||||
|
||||
## 演练节奏
|
||||
|
||||
- 每季度:`sh /opt/codex-app-server/deploy/restore-drill.sh`
|
||||
(新鲜备份 -> 测试库 -> 四表行数比对 -> 删测试库),PASS 才算
|
||||
备份有效;不要只看“备份文件存在”。
|
||||
- 每半年:场景 A 演练一次(`docker restart codex-postgres` 后
|
||||
health 与 monitor 恢复),验证容器重建路径没过期。
|
||||
- 演练结果记进运维记录;手册里的实测值(镜像、端口、卷路径、
|
||||
备份间隔)变了就同步改手册,不要让手册过期。
|
||||
Reference in new issue
Block a user