2025-09-06 更新 服务器镜像迁移后备份失效的问题处理
服务器迁移后,基于 Docker 的 PostgreSQL 与 pgBackRest 备份系统出现定时任务静默失败的完整排查与解决过程。问题初期表现为 Crontab 任务不执行,经过层层深入,最终定位到因数据库时间线与备份仓库不一致导致的 WAL 归档冲突。
服务器环境迁移后,原有的 pgBackRest 定时备份任务(每周全量+每日增量)停止工作。通过 pgbackrest info
检查,发现最新的备份记录停留在迁移前。crond
服务日志显示任务被调度,但备份并未生成,且未产生任何明确的错误日志。
crond
和 docker
服务均处于 active (running)
状态。my_postgres
正在运行。执行备份信息查看 docker exec -u postgres my_postgres pgbackrest --stanza=db_main info
发现备份信息停留在9月3日
查看cronb服务状态正常 systemctl status crond
手动执行 Crontab 中的备份命令 docker exec -u postgres my_postgres pgbackrest --stanza=db_main check
,首次捕获到明确的错误信息:
bash$ docker exec -u postgres my_postgres pgbackrest ... backup
> ERROR: [082]: WAL segment ... was not archived before the 60000ms timeout
pgbackrest
在执行备份前等待 WAL 归档,因归档进程阻塞而超时。配置文件审查:
/var/lib/postgresql/data/postgresql.conf
,但由于 docker run
命令通过 -v
参数挂载了宿主机的配置文件,该修改无效。/root/lobechat/config/postgresql.conf
,并确保 archive_command
指向 pgbackrest ... archive-push %p
。用户身份验证:
psql -U postgres
连接数据库失败,报错 FATAL: role "postgres" does not exist
。archive_command
由操作系统用户 postgres
触发,pgbackrest
默认使用同名数据库角色连接。而实际业务数据库用户为 default
,导致后台认证失败。pgbackrest.conf
的 [db_main]
节中,明确指定 pg1-user=default
。最终根源定位:分析 PostgreSQL 日志
在修正上述配置后,问题依旧。通过 docker logs my_postgres
查看容器实时日志,捕获到最终的、决定性的错误信息:
logERROR: [045]: WAL file '...' already exists in the repo1 archive with a different checksum
针对时间线冲突的根本问题,必须废弃旧的、不一致的备份历史,重新建立一个干净的基线。
停止数据库容器
bashdocker stop my_postgres
停止并删除 Stanza
注意:直接删除可能会报错,最佳实践是先执行 stop
命令。
bash# 启动一个临时维护容器
docker run -it --rm --name maintenance \
-v /root/lobechat/pgbackrest/repo:/var/lib/pgbackrest \
-v /root/lobechat/pgbackrest/config/pgbackrest.conf:/etc/pgbackrest/pgbackrest.conf:ro \
my-postgres-with-backrest bash
# 在临时容器内执行
pgbackrest --stanza=db_main stop
pgbackrest --stanza=db_main stanza-delete # 按提示输入 'y' 确认
exit
重启数据库并重建 Stanza
bashdocker start my_postgres
docker exec -u postgres my_postgres pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data stanza-create
执行新的全量备份
bashdocker exec -u postgres my_postgres pgbackrest --stanza=db_main --type=full backup
执行成功后,通过 pgbackrest info
验证,可以看到一个全新的、状态为 ok
的备份集。系统至此完全恢复。
本次排查揭示了两个在自动化运维中极易被忽略的细节:
pgbackrest.conf
的解析陷阱:
pgbackrest
的配置文件解析器对行内注释敏感。如下配置将导致 repo1-retention-full
参数解析失败:
ini# 错误示例
repo1-retention-full=4 # 保留4个全量备份
当 backup
命令未通过命令行覆盖此参数时,会因读取到无效值 4 #...
而报错。
crontab
中使用包含所有参数的“自包含”命令,彻底避免依赖配置文件。docker logs <container>
能够揭示后台进程(如 archive_command
)执行时的真实错误输出,是突破排查瓶颈的最有力工具。普通指令:docker exec -u postgres my_postgres pgbackrest --stanza=db_main --type=full backup
如果一定要注释,则命令带上全面的指令:docker exec -u postgres my_postgres pgbackrest --stanza=db_main --repo1-path=/var/lib/pgbackrest --repo1-retention-full=4 --pg1-path=/var/lib/postgresql/data --pg1-user=default --pg1-database=verceldb --type=full backup
backup
命令均采用完整的命令行参数,不依赖任何可能产生歧义的配置文件读取,确保在任何环境下行为都完全一致。创建目录结构:
bash# 在宿主机上执行
mkdir -p /root/lobechat/pgbackrest/config
mkdir -p /root/lobechat/pgbackrest/repo
mkdir -p /root/lobechat/pgbackrest/logs
创建 pgBackRest 配置文件 (pgbackrest.conf
):
backup
命令将不依赖此文件,但 archive-push
和其他一些工具仍可能使用它,保留此文件是最佳实践。bash# 在宿主机上执行
cat > /root/lobechat/pgbackrest/config/pgbackrest.conf <<'EOF'
[global]
repo1-path=/var/lib/pgbackrest
log-path=/var/log/pgbackrest
log-level-file=detail
start-fast=y
repo1-retention-full=4 # (!!!配置时不要注释,移除) 保留最近的4个全量备份
[db_main]
pg1-path=/var/lib/postgresql/data
pg1-user=default
pg1-database=verceldb
EOF
(!!!配置时不要注释,移除) 保留最近的4个全量备份,不然执行全量备份指令,例如:docker exec -u postgres my_postgres pgbackrest --stanza=db_main --type=full backup
会报错,移除后,重启下容器读取移除后的配置,就可以执行了。
如果一定要注释,则命令带上全面的指令,也可以:docker exec -u postgres my_postgres pgbackrest --stanza=db_main --repo1-path=/var/lib/pgbackrest --repo1-retention-full=4 --pg1-path=/var/lib/postgresql/data --pg1-user=default --pg1-database=verceldb --type=full backup
配置 PostgreSQL 文件 (postgresql.conf
):
archive_command
是唯一需要配置在 postgresql.conf
中的 pgBackRest 相关命令,它负责将 WAL 日志实时归档。编辑宿主机上的 /root/lobechat/config/postgresql.conf
,确保包含以下三行:
iniwal_level = replica
archive_mode = on
archive_command = 'pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data archive-push %p'
pgbackrest
。
docker run --rm <您的镜像名> which pgbackrest
pgbackrest
的镜像 (如 my-postgres-with-backrest
)。Dockerfile
构建一个新镜像。
构建命令:Dockerfile# 文件名为 Dockerfile FROM pgvector/pgvector:pg16 RUN apt-get update && apt-get install -y --no-install-recommends pgbackrest && rm -rf /var/lib/apt/lists/*
docker build -t my-pgvector-with-backrest .
修正目录权限:
bashchown -R 999:999 /root/lobechat/data
chown -R 999:999 /root/lobechat/pgbackrest
执行最终的 docker run
命令:
bash# 请将 my-postgres-with-backrest 替换为您最终使用的镜像名
docker run -d \
--name my_postgres \
--network pg \
--cpus 2 \
--memory 4G \
--restart unless-stopped \
-e TZ=Asia/Shanghai \
-e POSTGRES_USER=your-user \
-e POSTGRES_PASSWORD=your-password \
-e POSTGRES_DB=your-database \
-p 5432:5432 \
-v /root/lobechat/data:/var/lib/postgresql/data \
-v /root/lobechat/config/postgresql.conf:/etc/postgresql/postgresql.conf:ro \
-v /root/lobechat/pgbackrest/config/pgbackrest.conf:/etc/pgbackrest/pgbackrest.conf:ro \
-v /root/lobechat/pgbackrest/repo:/var/lib/pgbackrest \
-v /root/lobechat/pgbackrest/logs:/var/log/pgbackrest \
my-postgres-with-backrest \
/bin/sh -c "service ssh start && docker-entrypoint.sh postgres -c 'config_file=/etc/postgresql/postgresql.conf'"
bashdocker exec -u postgres my_postgres pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data --pg1-user=default --pg1-database=verceldb stanza-create
bashdocker exec -u postgres my_postgres pgbackrest --stanza=db_main --repo1-path=/var/lib/pgbackrest --repo1-retention-full=4 --pg1-path=/var/lib/postgresql/data --pg1-user=default --pg1-database=verceldb --type=full backup
WARN
警告。info
命令用于查看仓库状态,它不需要连接数据库,因此使用短命令。bashdocker exec -u postgres my_postgres pgbackrest --stanza=db_main info
你应该能看到 status: ok
以及一个 full backup
的记录。
bashcrontab -e
cron
的极简环境下也能 100% 正确执行。crontab# 每周日凌晨 2:05 执行一次全量备份 (Full Backup) 5 2 * * 0 /usr/bin/docker exec -u postgres my_postgres pgbackrest --stanza=db_main --repo1-path=/var/lib/pgbackrest --repo1-retention-full=4 --pg1-path=/var/lib/postgresql/data --pg1-user=default --pg1-database=verceldb --type=full backup > /root/lobechat/pgbackrest/logs/cron_backup_full.log 2>&1 # 每周一至周六凌晨 2:05 执行一次增量备份 (Incremental Backup) 5 2 * * 1-6 /usr/bin/docker exec -u postgres my_postgres pgbackrest --stanza=db_main --repo1-path=/var/lib/pgbackrest --repo1-retention-full=4 --pg1-path=/var/lib/postgresql/data --pg1-user=default --pg1-database=verceldb --type=incr backup > /root/lobechat/pgbackrest/logs/cron_backup_incr.log 2>&1
保存并退出。您的自动化备份系统现已正式上线。
如果配置文件中没有注解的话,就可以采用减量的指令,
优化:使用脚本封装
1. 为每种备份类型创建单独的脚本
shell# 创建 pgBackRest 全量备份脚本
cat > /root/lobechat/pgbackrest/backup_full.sh << 'EOF'
#!/bin/bash
/usr/bin/docker exec -u postgres my_postgres pgbackrest --stanza=db_main --type=full backup > /root/lobechat/pgbackrest/logs/cron_backup_full.log 2>&1
EOF
# 创建增量备份脚本
cat > /root/lobechat/pgbackrest/backup_incr.sh << 'EOF'
#!/bin/bash
/usr/bin/docker exec -u postgres my_postgres pgbackrest --stanza=db_main --type=incr backup > /root/lobechat/pgbackrest/logs/cron_backup_incr.log 2>&1
EOF
# 创建 pgBackRest 差异备份脚本
cat > /root/lobechat/pgbackrest/backup_diff.sh << 'EOF'
#!/bin/bash
/usr/bin/docker exec -u postgres my_postgres pgbackrest --stanza=db_main --type=diff backup > /root/lobechat/pgbackrest/logs/cron_backup_diff.log 2>&1
EOF
# 给脚本添加执行权限
chmod +x /root/lobechat/pgbackrest/backup_*.sh
2. 最终极简、清晰、易于维护的 Crontab
shell# 物理备份方案: 每周六凌晨1点 定时备份
0 1 * * 6 /root/backups-lobechat/backup.sh
# 逻辑备份方案: PgBackrest
# 每周日凌晨 2:05 执行一次全量备份 (Full)
5 2 * * 0 /root/lobechat/pgbackrest/backup_full.sh
# 每周一至周六凌晨 2:05 执行一次增量备份 (Incremental Backup)
5 2 * * 1-6 /root/lobechat/pgbackrest/backup_incr.sh
# 每周一至周六凌晨 2:05 执行一次差异备份 (Differential)
5 2 * * 1-6 /root/lobechat/pgbackrest/backup_diff.sh
验证优化后的增量脚本是否生效:
⚠️ 警告: 还原是一个破坏性操作,将彻底覆盖现有数据。请在确认需要恢复后,严格按步骤操作。
bashdocker stop my_postgres
bashrm -rf /root/lobechat/data/*
bashchown -R 999:999 /root/lobechat/data
docker run -d \ --name my_postgres_restore_shell \ --network pg \ -v /root/lobechat/data:/var/lib/postgresql/data \ -v /root/lobechat/pgbackrest/repo:/var/lib/pgbackrest \ my-postgres-with-backrest \ tail -f /dev/null
说明: restore
命令需要知道将文件恢复到哪里 (--pg1-path
),但通常不需要连接数据库,因此使用较短的命令。
场景A:恢复到最新的可用状态 (最常用)
bashdocker exec -u postgres my_postgres_restore_shell pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data restore
场景B:恢复到指定的时间点
bashdocker exec -u postgres my_postgres_restore_shell pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data --type=time --target="YYYY-MM-DD HH:MM:SS" restore
docker stop my_postgres_restore_shell docker rm my_postgres_restore_shell
或者若容器已删除,则直接执行docker run的原始命令:bashdocker start my_postgres
docker run -d \ --name my_postgres \ --network pg \ --cpus 2 \ --memory 4G \ --restart unless-stopped \ -e TZ=Asia/Shanghai \ -e POSTGRES_USER=your-user \ -e POSTGRES_PASSWORD=your-password \ -e POSTGRES_DB=your-database \ -p 5432:5432 \ -v /root/lobechat/data:/var/lib/postgresql/data \ -v /root/lobechat/config/postgresql.conf:/etc/postgresql/postgresql.conf:ro \ -v /root/lobechat/pgbackrest/config/pgbackrest.conf:/etc/pgbackrest/pgbackrest.conf:ro \ -v /root/lobechat/pgbackrest/repo:/var/lib/pgbackrest \ -v /root/lobechat/pgbackrest/logs:/var/log/pgbackrest \ my-postgres-with-backrest \ /bin/sh -c "service ssh start && docker-entrypoint.sh postgres -c 'config_file=/etc/postgresql/postgresql.conf'"
等待日志显示bashdocker logs -f my_postgres
database system is ready to accept connections
。【关键步骤】若发现只能查询,则需手动提升数据库为主库:
docker exec -it my_postgres psql -U default -d verceldb -c "SELECT pg_promote();"
验证数据:
bash# 检查数据库是否已不再处于恢复模式 (应返回 f)
docker exec -it my_postgres psql -U default -d verceldb -c "SELECT pg_is_in_recovery();"
docker exec -it my_postgres psql -U default -d verceldb
连接数据库,检查您的数据是否已成功恢复。