CloudMap
全部简历LinuxKubernetesPrometheusArgo CDPostgreSQLDocker其他

© 2026 云原生洞见 · Cloud Native Insights · 保留所有权利

文章目录

19 项
  • 1.目的
  • 2. 技术环境与前置条件
  • 3.操作流程
  • 3.1 前置条件检查
  • 3.2 确认密码文件权限
  • 3.3 部署备份脚本
  • 3.4 配置脚本参数
  • 3.5 手动执行测试
  • 3.6 设置 Crontab 定时任务
  • 3.7 恢复数据库
  • 4.脚本关键机制详解
  • 4.1 严格错误控制
  • 4.2 密码安全“三不”原则
  • 4.3 文件锁防并发
  • 4.4 原子化备份与临时文件
  • 4.5 过期清理与日志审计
  • 5.日常检查与维护
  • 6.常见问题处理
  • 7.标准备份操作脚本模板
PostgreSQL2026年2月10日

PostgreSQL 数据库容器自动备份


1.目的

确保 Docker 上的 PostgreSQL 数据库能够得到定时、完整、安全的逻辑备份,当发生数据误删、容器故障或人为错误时,能够快速将全集群数据恢复至备份时间点,满足业务连续性要求。


2.技术环境与前置条件

  • 操作系统:Ubuntu Server 24.04 LTS(宿主机)

  • 运行环境:Docker 部署的 PostgreSQL 容器

  • 备份方式:pg_dumpall 全集群逻辑备份(包含所有数据库、角色、表空间)

  • 调度方式:crontab 定时任务

  • 保留策略:本地保留最近 7 天的压缩备份文件


3.操作流程

3.1 前置条件检查

在开始部署前,请逐项确认以下条件:

  • 宿主机已安装 Docker,且 docker 命令可正常运行docker --version

  • 目标 PostgreSQL 容器已启动,容器名为 TestContainer(以实际为准)docker ps | grep TestContainer

  • 已创建具有 超级用户权限(或能读取所有数据库)的数据库账号 TestUser

  • 备份目录 /data/backups 磁盘空间充足

  • 拥有 root 权限或具备 docker 执行权限的操作系统用户

3.2 确认密码文件权限

sudo ls -l /opt/secrets/postgres-password.txt   # 权限应为 -rw------- (600)

3.3 部署备份脚本

将附录中的完整脚本保存到宿主机,例如 /usr/local/bin/postgres_backup.sh,并赋予执行权限。

sudo nano /usr/local/bin/postgres_backup.sh   
sudo chmod +x /usr/local/bin/postgres_backup.sh   # 赋予执行权限

3.4 配置脚本参数

根据实际环境修改脚本配置区域的以下变量(直接编辑脚本):

变量

示例值

说明

CONTAINER_NAME

"prod-postgres"

Docker 容器名

DB_USER

"backup_admin"

具有足够权限的数据库用户

BACKUP_DIR

"/data/backups/pgsql"

备份存放目录(需预先创建)

RETENTION_DAYS

7

保留天数,可按需调整

SECRET_FILE

"/opt/secrets/postgres-password.txt"

密码文件路径,与 3.2 一致

保存后,建议使用 bash -n /usr/local/bin/postgres_backup.sh 检查脚本语法。

3.5 手动执行测试

在接入定时任务前,请务必手动运行一次,确保整条链路畅通。

sudo /usr/local/bin/postgres_backup.sh

成功标准:

  • 终端输出包含 备份成功: ... .sql.gz

  • 备份目录下生成 .sql.gz 文件

  • 日志文件 /wjw/data/backups/backup.log 中无错误记录

若失败,请参照 第6章 常见问题处理。

3.6 设置 Crontab 定时任务

推荐在 业务低峰期(例如凌晨 3:00) 执行备份。

sudo crontab -e

添加以下行(每天凌晨 3:00 执行):

0 3 * * * /usr/local/bin/postgres_backup.sh

详解:

* * * * * command
┬ ┬ ┬ ┬ ┬
│ │ │ │ └─ 星期 (0-7, 0和7都表示周日)
│ │ │ └─── 月份 (1-12)
│ │ └───── 日期 (1-31)
│ └─────── 小时 (0-23)
└───────── 分钟 (0-59)

✅ 验证:可以通过 crontab -l 查看已配置的任务。

3.7 恢复数据库

可参考文章:PostgreSQL数据库恢复


4.脚本关键机制详解

4.1 严格错误控制

set -Eeuo pipefail
  • -e:只要脚本里任何一条命令执行失败(返回非零值),脚本立刻退出。避免带着错误继续运行,例如备份命令失败了还去清理旧文件。

  • -E:配合 -e,让函数内部出现的错误也能触发退出。

  • -u:使用未定义的变量时报错退出,防止变量名拼写错误导致不可预期的行为。

  • -o pipefail:管道命令中如果左侧命令失败,整体也判断为失败。例如 docker exec … | gzip > file,如果 docker exec 失败,脚本会感知到。

通俗理解:这是一副“安全手套”,保证脚本在任何意外情况下都会立刻停下来,而不会假装一切正常。

4.2 密码安全“三不”原则

  • 不硬编码:密码绝不出现在脚本里,防止代码泄露导致密码外泄。

  • 不在命令行暴露:不使用 docker exec … pg_dumpall -W "password",因为这会让密码直接出现在 ps 进程列表中。

  • 不写进环境变量文件:不在 ~/.bashrc 等文件中存放密码。

我们的做法:
将密码存在单独的文件 /opt/secrets/postgres-password.txt,

通过 docker exec -e PGPASSWORD="${DB_PASSWORD}" 以临时环境变量的方式传给容器内的 pg_dumpall。

该环境变量的生命周期仅限于这条 docker exec 命令执行期间,安全性较高。

同时脚本会检查该文件权限是否为 600 或 400,如果不是则报警。

4.3 文件锁防并发

exec 200>"/var/lock/postgres_backup.lock"
flock -n 200 || { log "已有备份任务正在执行..."; exit 1; }
  • 作用:若前一次备份还没执行完(比如大数据库备份耗时较长),cron 又到了下一次触发时间,会导致两个备份任务同时运行,争抢 CPU 和 I/O,甚至生成冲突的备份文件。

  • 实现原理:flock -n 尝试获得一个非阻塞的排他锁,如果拿不到锁(说明有另一个实例持有着),则立即退出并写日志。这就像厕所门上的“有人/无人”指示器,保证同时只有一个人进去。

4.4 原子化备份与临时文件

TEMP_FILE="...sql.tmp"
FINAL_FILE="...sql"
docker exec ... > "${TEMP_FILE}" && mv "${TEMP_FILE}" "${FINAL_FILE}"
  • 问题场景:如果备份过程中磁盘写满、网络中断或容器重启,直接写正式文件可能会留下一个“半截”的 .sql 文件,后续清理脚本无法识别,且恢复时会报错。

  • 解决方案:先输出到 .tmp 临时文件,只有 docker exec 完全成功(返回 0)才把它重命名为正式文件。失败时立即删除 .tmp。这样备份目录下永远只有完整的备份。

4.5 过期清理与日志审计

find ... -mtime +7 -delete

使用 find 命令基于文件修改时间清理 7 天前的备份。所有操作(成功、失败、清理、列表)都通过 log 函数写入日志并带时间戳,方便未来排查“某天为什么没备份”或“磁盘什么时候被清理过”。


5.日常检查与维护

每日检查:登录服务器执行 tail -20 /data/backups/backup.log,确认最近一次 备份成功。

磁盘监控:df -h /data/backups 确保使用率不超过 80%。

权限复查:每季度检查 /opt/secrets/postgres-password.txt 权限是否为 600。

脚本更新:当数据库用户、容器名、备份目录变更时,及时更新脚本并测试。


6.常见问题处理

故障现象

可能原因

处理方法

日志提示 密码文件 ... 不存在

未创建或路径错误

按 5.2 重新创建,检查 SECRET_FILE 变量

权限为 644,建议设为 600

密码文件权限过于开放

chmod 600 /opt/secrets/postgres-password.txt

备份失败! 日志无更多信息

容器未启动/用户权限不足/密码错误

手动执行 docker exec -e PGPASSWORD=... 容器名 pg_dumpall -U 用户 查看报错

已有备份任务正在执行,当前任务退出

前一次备份未结束,锁冲突

这是正常保护,检查是否有僵死进程,或备份时间是否过长

找不到备份文件

磁盘满或 RETENTION_DAYS 内文件被误删

检查磁盘空间,确认 find 清理逻辑是否正常

恢复后缺少某些表数据

pg_dumpall 为逻辑导出,无增量,只能恢复到执行时刻

确认备份时间在数据变更之后;考虑增加备份频率或配合 WAL 归档


7.标准备份操作脚本模板

#!/bin/bash
set -Eeuo pipefail

export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"

# ==================== 配置区域 ====================
CONTAINER_NAME="TestContainer"               # Docker 容器名
DB_USER="TestUser"                           # 数据库用户名
BACKUP_DIR="/data/backups"                   # 备份存储目录
RETENTION_DAYS=7                             # 保留最近多少天的备份
LOCK_FILE="/var/lock/postgres_backup.lock"
LOG_FILE="${BACKUP_DIR}/backup.log"

# ---------- 安全:密码从 Docker Secret 对应的宿主机文件读取 ----------
SECRET_FILE="/opt/secrets/postgres-password.txt"   # 与 docker-compose.yml 一致

# ==================== 基础检查 ====================
mkdir -p "${BACKUP_DIR}"
touch "${LOG_FILE}"

log() {
    echo "[$(date '+%F %T')] $*" | tee -a "${LOG_FILE}"
}

# 密码文件存在性检查
if [[ ! -f "$SECRET_FILE" ]]; then
    log "错误:密码文件 $SECRET_FILE 不存在,备份终止。"
    exit 1
fi

# 简单权限提醒(不强行退出,但会写入日志)
file_perm=$(stat -c %a "$SECRET_FILE" 2>/dev/null || echo "unknown")
if [[ "$file_perm" != "600" && "$file_perm" != "400" ]]; then
    log "安全提醒:密码文件 $SECRET_FILE 权限为 $file_perm,建议设为 600 或 400。"
fi

# 安全地读取密码(该文件应只包含密码本身,不含额外换行)
DB_PASSWORD=$(<"$SECRET_FILE")

# 防止重复执行
exec 200>"${LOCK_FILE}"
flock -n 200 || {
    log "已有备份任务正在执行,当前任务退出。"
    exit 1
}

# ==================== 备份逻辑 ====================
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
TEMP_FILE="${BACKUP_DIR}/postgres_backup_${TIMESTAMP}.sql.tmp"
FINAL_FILE="${BACKUP_DIR}/postgres_backup_${TIMESTAMP}.sql"

log "开始备份 PostgreSQL 全集群数据..."

# 注意:这里不再使用 sudo,假设脚本已以 root 或具备 docker 权限的用户运行
if docker exec -e PGPASSWORD="${DB_PASSWORD}" "${CONTAINER_NAME}" \
    pg_dumpall -c -U "${DB_USER}" > "${TEMP_FILE}"; then

    mv "${TEMP_FILE}" "${FINAL_FILE}"
    gzip "${FINAL_FILE}"

    log "备份成功: ${FINAL_FILE}.gz (大小: $(du -h "${FINAL_FILE}.gz" | awk '{print $1}'))"
else
    rm -f "${TEMP_FILE}"
    log "备份失败!"
    exit 1
fi

# ==================== 清理旧备份 ====================
log "清理 ${RETENTION_DAYS} 天前的旧备份..."
find "${BACKUP_DIR}" -type f -name "postgres_backup_*.sql.gz" -mtime +${RETENTION_DAYS} -delete

log "当前备份文件列表:"
ls -lh "${BACKUP_DIR}" | grep "postgres_backup_" || true

log "备份任务完成。"

最后更新:2026年5月27日← 更多「PostgreSQL」文章