2SOMEone | PostgreSQL备份

by CUNOE, April 7, 2024

今天分享一下2SOMEone的PostgreSQL备份方案。

流式传输同步

泡泡树洞后端采用bitnami提供的PostgreSQL镜像,bitnami为原版镜像提供了更丰富的定制化,包括流式备份等。

这里使用bitnami的流式备份方案

services:
  # master节点
  postgres-2someone-master:
    image: bitnami/postgresql:15
    restart: always
    environment:
      - POSTGRESQL_USERNAME=uername
      - POSTGRESQL_PASSWORD=password
      - POSTGRESQL_DATABASE=2someone
      - POSTGRESQL_POSTGRES_PASSWORD=postgres_password
      - POSTGRESQL_TIMEZONE=Asia/Shanghai
      - POSTGRESQL_LOG_TIMEZONE=Asia/Shanghai
      - POSTGRESQL_ENABLE_TLS=yes
      - POSTGRESQL_TLS_CERT_FILE=/bitnami/postgresql/ssl/fullchain.cer
      - POSTGRESQL_TLS_KEY_FILE=/bitnami/postgresql/ssl/private.key
      # 流式备份,设置为master节点,在这里设置的user和password是专用于复制的,不可直接用于连接数据库
      - POSTGRESQL_REPLICATION_MODE=master
      - POSTGRESQL_REPLICATION_USER=repl_user
      - POSTGRESQL_REPLICATION_PASSWORD=repl_password
    volumes:
      - ./postgresql_data/ssl:/bitnami/postgresql/ssl
      - ./postgresql_data/master:/bitnami/postgresql/data
      - ./postgresql_data/scripts:/bitnami/postgresql/scripts
  # slave节点
  postgres-2someone-slave:
    image: bitnami/postgresql:15
    restart: always
    environment:
      # POSTGRESQL_PASSWORD 是该数据库超级用户 postgres 的密码 可以自行修改 登录该slave节点的时候使用 user: postgres password: ${POSTGRESQL_PASSWORD}
      - POSTGRESQL_PASSWORD=postgres_password
      - POSTGRESQL_TIMEZONE=Asia/Shanghai
      - POSTGRESQL_LOG_TIMEZONE=Asia/Shanghai
      # 流式备份,设置为slave节点,在这里使用master节点定义的repl专用的用户和密码
      - POSTGRESQL_REPLICATION_MODE=slave
      - POSTGRESQL_REPLICATION_USER=repl_user # 该用户用于主从同步,和主库的用户密码一致
      - POSTGRESQL_REPLICATION_PASSWORD=repl_password
      - POSTGRESQL_MASTER_HOST=postgres-2someone-master
      - POSTGRESQL_MASTER_PORT_NUMBER=5432
    volumes:
      - ./postgresql_data:/bitnami/postgresql
      - ./postgresql_data/scripts:/scripts

执行该docker-compose.yml文件后,bitnamiPostgreSQL镜像会自动进行流式同步。

数据库定时备份

为了保证数据的安全,我们还需要定期对数据库进行备份。

这里使用pg_dump工具进行备份,通过crontab定时执行备份任务。

#!/bin/sh
# ./postgresql_data/scripts/backup_2someone_db.sh
##################################################
# 环境变量及系统参数
##################################################
# PostgreSQL安装目录
PGHOME=/opt/bitnami/postgresql
# Host
PGHOST=0.0.0.0
# 监听端口
PGPORT=5432
# 用户
PGUSER=postgres
# 密码
PGPASSWORD=postgres_password
# 数据库
DBNAME=2someone_db
# 编码
ENCODING=UTF8

# 备份文件存放目录
BACKUP_DIR=/bitnami/postgresql/backup
# 备份文件名称
BACKUP_FILE_NAME=${DBNAME}-$(date +%Y%m%d%H%M%S).bak
# 备份文件保留天数,默认30天
BACKUP_FILE_RESERVED_DAYS=30

# 脚本名称
SCRIPT_NAME="$(basename "${0}")"
# 备份执行日志存放目录
BACKUP_LOG_PATH=/bitnami/postgresql/backup/logs
# 备份执行日志文件名称
BACKUP_LOG_FILENAME=${BACKUP_LOG_PATH}/${SCRIPT_NAME}-$(date +%Y%m%d%H%M%S).log


# 准备
function prepare() {
    if [ ! -d "${BACKUP_DIR}" ]; then
        mkdir -p "${BACKUP_DIR}"
    fi
    if [ ! -d "${BACKUP_LOG_PATH}" ]; then
        mkdir -p "${BACKUP_LOG_PATH}"
    fi
    if [ ! -f "${BACKUP_LOG_FILENAME}" ]; then
        touch "${BACKUP_LOG_FILENAME}"
    fi
}

# 记录INFO日志
function info() {
  echo "$(date "+%Y-%m-%d %H:%M:%S") [ INFO] ${1}" >> "${BACKUP_LOG_FILENAME}"
}

# 记录ERROR日志
function error() {
  echo -e "$(date "+%Y-%m-%d %H:%M:%S") \033[31m[ERROR]\033[0m ${1}" >> "${BACKUP_LOG_FILENAME}"
}

# 备份数据
function backup() {
    info "备份数据库${DBNAME}开始"
    export PGPASSWORD
    ${PGHOME}/bin/pg_dump --file ${BACKUP_DIR}/${BACKUP_FILE_NAME} --host ${PGHOST} --port ${PGPORT} --dbname ${DBNAME} --username ${PGUSER} --role postgres --format=c --blobs --encoding ${ENCODING} --verbose >> ${BACKUP_LOG_FILENAME} 2>&1 
 # 获取上一个命令的退出状态(0表示正常退出)
 dump_status=$?
    if [ ! ${dump_status} -eq 0 ]; then
        error "备份数据库${DBNAME}失败"
        exit 1
    fi
    info "数据库${DBNAME}备份文件:${BACKUP_DIR}/${BACKUP_FILE_NAME}"
    info "备份数据库${DBNAME}结束"
}

# 删除历史数据库备份文件
function clear() {
    info "删除数据库${DBNAME}历史备份文件开始"
 # 获取更新时间大于保留天数以前的历史备份文件
 history_bak_zip_files=$(find ${BACKUP_DIR} -type f -mtime +${BACKUP_FILE_RESERVED_DAYS} -name "*.bak")
    if [ -z "${history_bak_zip_files}" ]; then
        info "无${BACKUP_FILE_RESERVED_DAYS}天前的历史备份文件"
        exit 0
    fi
 # 逐个删除历史备份文件
    for history_bak_zip_file in ${history_bak_zip_files}
    do
        rm -f ${history_bak_zip_file}
        info "删除数据库${DBNAME}历史备份文件${history_bak_zip_file}"
    done
    info "删除数据库${DBNAME}历史备份文件结束"
}

function doBackup() {
 # 准备
    prepare
 # 备份数据库
    backup
 # 删除历史备份文件
    clear
}

doBackup

exit 0

crontab中添加定时任务

# 每天凌晨2点执行备份任务
# crontab -e
0 2 * * * docker exec -d postgres-2someone-slave /bitnami/postgresql/scripts/backup_2someone_db.sh

这样就可以实现每天凌晨2点对数据库进行备份。

开发时复制数据库使用

在开发时,由于需要测试一些数据,为了方便开发,在允许的情况下,可以将生产环境的数据库复制到开发环境。

为此我们研究了使用docker-compose和bitnami的数据库初始化方法结合的方案来实现快速复制数据库。

services:
  postgres-2someone-copy:
    image: bitnami/postgresql:15
    restart: always
    environment:
      # 该复制数据库的用户与密码
      - POSTGRESQL_USERNAME=leaperone
      - POSTGRESQL_PASSWORD=password
      - POSTGRESQL_DATABASE=2someone
      - POSTGRESQL_POSTGRES_PASSWORD=password
      - POSTGRESQL_TIMEZONE=Asia/Shanghai
      - POSTGRESQL_LOG_TIMEZONE=Asia/Shanghai
    volumes:
      # 将初始化脚本挂载到容器的/docker-entrypoint-initdb.d/目录下,以便容器进行初始化操作
      - ./init-db.sh:/docker-entrypoint-initdb.d/init-2someone-db.sh
    ports:
      - "5432:5432"
#!/bin/bash
set -e
# 获取远程数据库数据
export REMOTE_HOST=postgre-2someone-slave # 使用master节点也是可以的,但我们建议使用slave节点以确保安全性和降低master的负载
export REMOTE_PORT=5432
export PGPASSWORD=postgres_password
pg_dump --file "/bitnami/postgresql/2someone.backup" --host ${REMOTE_HOST} --port ${REMOTE_PORT} --username "postgres" --dbname "2someone_db" --verbose --role "postgres" --format=c --blobs --encoding "UTF8"
pg_dump --file "/bitnami/postgresql/something.backup" --host ${REMOTE_HOST} --port ${REMOTE_PORT} --username "postgres" --dbname "something_db" --verbose --role "postgres" --format=c --blobs --encoding "UTF8"

export PGPASSWORD=password
# 你可能需要创建一些新数据库
psql -v ON_ERROR_STOP=1 --username "postgres" <<-EOSQL
CREATE DATABASE something_db;
GRANT ALL PRIVILEGES ON DATABASE something_db TO $POSTGRESQL_USERNAME;
ALTER DATABASE something_db OWNER TO $POSTGRESQL_USERNAME;
EOSQL
pg_restore  --host "localhost" --port "5432" --username "postgres"  --role "postgres" --dbname "2someone_db"  --verbose /bitnami/postgresql/2someone.backup
pg_restore  --host "localhost" --port "5432" --username "postgres"  --role "postgres" --dbname "something_db"  --verbose /bitnami/postgresql/something.backup

这样就可以实现快速复制数据库到开发环境。

总结

通过流式备份和定时备份,我们可以保证数据库的安全性,通过快速复制数据库,我们可以方便的在开发环境中使用生产环境的数据用作测试。

我们通过这种方式尽可能的保证了数据库的安全性和可用性,当然,凡是不能绝对,但愿我们不会遇到最不利工况。

这里只是简单的介绍了备份的方法,具体的备份策略还需要根据实际情况进行调整。