编辑
2025-08-10
环境部署
00

目录

pgBackRest备份Lobechat
案例研究:解决服务器迁移后 pgBackRest 备份系统静默失败的问题
摘要
一、 故障现象
二、 系统性故障排查
阶段一:基础环境验证
阶段二:手动执行与初步诊断
阶段三:深入排查 WAL 归档失败根源
三、 最终解决方案:重置 Stanza
四、 关键经验与总结
在 Docker 中为 PostgreSQL 搭建 pgBackRest 高可靠备份与恢复系统
核心策略
第一部分:环境准备与容器部署
步骤 1.1: 准备宿主机目录与文件
步骤 1.3: 部署容器
第二部分:初始化与手动备份验证
步骤 2.1: 初始化 Stanza
步骤 2.2: 执行首次全量备份
步骤 2.3: 检查备份状态
第三部分:自动化定时任务 (Cron)
步骤 3.1: 编辑 crontab
步骤 3.2: 添加最终的、绝对可靠的定时任务
第四部分:灾难恢复指南
步骤 4.1: 准备还原环境
步骤 4.3: 在“恢复专用容器中”执行还原命令
步骤 4.5: 启动数据库并将其提升为主库

pgBackRest备份Lobechat

2025-09-06 更新 服务器镜像迁移后备份失效的问题处理

案例研究:解决服务器迁移后 pgBackRest 备份系统静默失败的问题

摘要

服务器迁移后,基于 Docker 的 PostgreSQL 与 pgBackRest 备份系统出现定时任务静默失败的完整排查与解决过程。问题初期表现为 Crontab 任务不执行,经过层层深入,最终定位到因数据库时间线与备份仓库不一致导致的 WAL 归档冲突。

一、 故障现象

服务器环境迁移后,原有的 pgBackRest 定时备份任务(每周全量+每日增量)停止工作。通过 pgbackrest info 检查,发现最新的备份记录停留在迁移前。crond 服务日志显示任务被调度,但备份并未生成,且未产生任何明确的错误日志。

二、 系统性故障排查

阶段一:基础环境验证

  1. 服务状态检查: 确认 cronddocker 服务均处于 active (running) 状态。
  2. 脚本与容器检查: 确认备份脚本权限正常,且目标 PostgreSQL 容器 my_postgres 正在运行。
  • 结论: 基础环境与调度层正常,问题源于备份命令执行过程本身。

执行备份信息查看 docker exec -u postgres my_postgres pgbackrest --stanza=db_main info 发现备份信息停留在9月3日

image.png

查看cronb服务状态正常 systemctl status crond

image.png

阶段二:手动执行与初步诊断

手动执行 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

image.png

  • 结论: 问题核心在于 PostgreSQL 的 WAL(预写日志)归档机制失败。pgbackrest 在执行备份前等待 WAL 归档,因归档进程阻塞而超时。

阶段三:深入排查 WAL 归档失败根源

  1. 配置文件审查:

    • 陷阱: 最初修改了容器内默认的 /var/lib/postgresql/data/postgresql.conf,但由于 docker run 命令通过 -v 参数挂载了宿主机的配置文件,该修改无效。
    • 修正: 定位到真正生效的宿主机配置文件 /root/lobechat/config/postgresql.conf,并确保 archive_command 指向 pgbackrest ... archive-push %p
  2. 用户身份验证:

    • 线索: 尝试通过 psql -U postgres 连接数据库失败,报错 FATAL: role "postgres" does not exist
    • 原因: archive_command 由操作系统用户 postgres 触发,pgbackrest 默认使用同名数据库角色连接。而实际业务数据库用户为 default,导致后台认证失败。
    • 修正: 在 pgbackrest.conf[db_main] 节中,明确指定 pg1-user=default
  3. 最终根源定位:分析 PostgreSQL 日志 在修正上述配置后,问题依旧。通过 docker logs my_postgres 查看容器实时日志,捕获到最终的、决定性的错误信息:

    log
    ERROR: [045]: WAL file '...' already exists in the repo1 archive with a different checksum

image.png

  • 结论: 数据库的当前 WAL 时间线与备份仓库中已存在的 WAL 文件发生内容冲突。这通常由不当的恢复操作或数据库时间线重置引发。PostgreSQL 持续尝试归档一个已存在但内容不匹配的 WAL 文件,导致归档进程被永久阻塞。

三、 最终解决方案:重置 Stanza

针对时间线冲突的根本问题,必须废弃旧的、不一致的备份历史,重新建立一个干净的基线。

  1. 停止数据库容器

    bash
    docker stop my_postgres
  2. 停止并删除 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
  3. 重启数据库并重建 Stanza

    bash
    docker start my_postgres docker exec -u postgres my_postgres pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data stanza-create
  4. 执行新的全量备份

    bash
    docker exec -u postgres my_postgres pgbackrest --stanza=db_main --type=full backup

    执行成功后,通过 pgbackrest info 验证,可以看到一个全新的、状态为 ok 的备份集。系统至此完全恢复。

四、 关键经验与总结

本次排查揭示了两个在自动化运维中极易被忽略的细节:

  1. pgbackrest.conf 的解析陷阱: pgbackrest 的配置文件解析器对行内注释敏感。如下配置将导致 repo1-retention-full 参数解析失败:
    ini
    # 错误示例 repo1-retention-full=4 # 保留4个全量备份
    backup 命令通过命令行覆盖此参数时,会因读取到无效值 4 #... 而报错。
    • 解决方案A (推荐): 移除所有行内注释,将注释置于独立行。
    • 解决方案B: 在 crontab 中使用包含所有参数的“自包含”命令,彻底避免依赖配置文件。

image.png

  1. 日志是最终的真相来源: 当遇到模糊的“超时”错误时,切勿停留在表面。docker logs <container> 能够揭示后台进程(如 archive_command)执行时的真实错误输出,是突破排查瓶颈的最有力工具。

image.png

普通指令: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


在 Docker 中为 PostgreSQL 搭建 pgBackRest 高可靠备份与恢复系统

核心策略

  • 备份方案: 每周日执行一次全量备份,每周一至周六执行一次增量备份。
  • 可靠性原则: 所有手动和自动化的 backup 命令均采用完整的命令行参数,不依赖任何可能产生歧义的配置文件读取,确保在任何环境下行为都完全一致。
  • 数据持久化: 所有备份数据、日志和配置均保存在宿主机,确保容器重建后数据不丢失。

第一部分:环境准备与容器部署

步骤 1.1: 准备宿主机目录与文件

  1. 创建目录结构:

    bash
    # 在宿主机上执行 mkdir -p /root/lobechat/pgbackrest/config mkdir -p /root/lobechat/pgbackrest/repo mkdir -p /root/lobechat/pgbackrest/logs
  2. 创建 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 会报错,移除后,重启下容器读取移除后的配置,就可以执行了。

    image.png

    如果一定要注释,则命令带上全面的指令,也可以: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

  3. 配置 PostgreSQL 文件 (postgresql.conf):

    • 说明: archive_command 是唯一需要配置在 postgresql.conf 中的 pgBackRest 相关命令,它负责将 WAL 日志实时归档。

    编辑宿主机上的 /root/lobechat/config/postgresql.conf,确保包含以下三行:

    ini
    wal_level = replica archive_mode = on archive_command = 'pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data archive-push %p'

步骤 1.2: 准备 Docker 镜像

  • 检查: 首先确认您要使用的镜像是否包含 pgbackrestdocker run --rm <您的镜像名> which pgbackrest
  • 选项 A (推荐): 使用一个已确认包含 pgbackrest 的镜像 (如 my-postgres-with-backrest)。
  • 选项 B (如需自制): 如果您的基础镜像不含此工具,请通过以下 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 .

步骤 1.3: 部署容器

  1. 修正目录权限:

    bash
    chown -R 999:999 /root/lobechat/data chown -R 999:999 /root/lobechat/pgbackrest
  2. 执行最终的 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'"

第二部分:初始化与手动备份验证

步骤 2.1: 初始化 Stanza

  • 说明: 我们使用包含所有参数的长命令来初始化,以消除所有环境不确定性。
bash
docker exec -u postgres my_postgres pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data --pg1-user=default --pg1-database=verceldb stanza-create

步骤 2.2: 执行首次全量备份

bash
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
  • 注意: 此处我们使用了最终确认的、包含所有参数的“自包含”命令。执行后不应有任何 WARN 警告。

步骤 2.3: 检查备份状态

  • 说明: info 命令用于查看仓库状态,它需要连接数据库,因此使用短命令
bash
docker exec -u postgres my_postgres pgbackrest --stanza=db_main info

你应该能看到 status: ok 以及一个 full backup 的记录。


第三部分:自动化定时任务 (Cron)

步骤 3.1: 编辑 crontab

bash
crontab -e

步骤 3.2: 添加最终的、绝对可靠的定时任务

  • 说明: 这两行命令是本手册的核心。它们将所有配置作为参数传入,确保在 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

验证优化后的增量脚本是否生效:

image.png


第四部分:灾难恢复指南

⚠️ 警告: 还原是一个破坏性操作,将彻底覆盖现有数据。请在确认需要恢复后,严格按步骤操作。

步骤 4.1: 准备还原环境

  1. 停止数据库容器:
    bash
    docker stop my_postgres
  2. 清空已损坏的数据目录:
    bash
    rm -rf /root/lobechat/data/*
  3. 确保目录权限正确:
    bash
    chown -R 999:999 /root/lobechat/data

步骤 4.2: 启动临时的“恢复专用容器”

  • 说明: 这是解决“恢复悖论”的标准模式。我们启动一个挂载了所有数据卷的“空转”容器,为 restore 命令创造一个不运行 PostgreSQL 服务的安全环境。
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

步骤 4.3: 在“恢复专用容器中”执行还原命令

  • 说明: restore 命令需要知道将文件恢复到哪里 (--pg1-path),但通常不需要连接数据库,因此使用较短的命令。

  • 场景A:恢复到最新的可用状态 (最常用)

    bash
    docker exec -u postgres my_postgres_restore_shell pgbackrest --stanza=db_main --pg1-path=/var/lib/postgresql/data restore
  • 场景B:恢复到指定的时间点

    bash
    docker 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

步骤 4.4: 清理“恢复专用容器”

  • 说明: 恢复文件的任务已完成,临时容器的使命也结束了。
docker stop my_postgres_restore_shell docker rm my_postgres_restore_shell

步骤 4.5: 启动数据库并将其提升为主库

  1. 启动容器:
    bash
    docker start my_postgres
    或者若容器已删除,则直接执行docker run的原始命令:
    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'"
  2. 监控恢复日志:
    bash
    docker logs -f my_postgres
    等待日志显示 database system is ready to accept connections

image.png

  1. 【关键步骤】若发现只能查询,则需手动提升数据库为主库:

    • 说明: 这是将数据库从只读的恢复状态切换到正常读写状态的命令。
    docker exec -it my_postgres psql -U default -d verceldb -c "SELECT pg_promote();"
  2. 验证数据:

    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

    连接数据库,检查您的数据是否已成功恢复。

image.png