简单记录一下Percona Xtrabackup常用的每天定时备份的压缩脚本
目录
场景说明
- Linux环境
- Docker容器运行的备份目标MySQL
- Docker容器运行的Percona Xtrabackup
- 每天定时全量备份一次
- 使用操作系统自带的cron定时任务管理器
- 使用工具镜像版本为 percona/percona-xtrabackup:8.0.35-34.1
镜像信息:
shell
[root@cn1920vm0064 backup]# docker image ls | grep -E '*backup|mysql*'
percona/percona-xtrabackup 8.0.35-34.1 11c6f241e3b3 6 weeks ago 1.78GB
mysql 8.0.22 d4c3cafb11d5 4 years ago 545MB
运行容器:
shell
[root@cn1920vm0064 backup]# docker ps | grep -E '*backup|mysql*'
99c8448c9387 mysql:8.0.22 "docker-entrypoint.s..." 19 hours ago Up 18 hours mysql8
上面的 Docker容器 mysql8 为要备份的业务数据库,已经做了数据目录映射,之前创建该业务数据库的Docker容器命令如下:
shell
docker run -u root \
--cap-add=SYS_NICE \
--network=host \
--restart=always \
--name mysql8 \
-v /data/apps/mysql/conf/my.cnf:/etc/mysql/my.cnf \
-v /data/apps/mysql/files:/var/lib/mysql-files \
-v /data/apps/mysql/data:/var/lib/mysql \
-e MYSQL_ROOT_PASSWORD=YourPassword \
-d mysql:8.0.22
可以看到数据库的数据目录映射到物理宿主机的 /data/apps/mysql/data 目录下了,这里假设数据库端口为3309
一、创建备份脚本文件
shell
sudo touch mysqlbackup.sh
sudo chmod +x mysqlbackup.sh
假设备份数据放在如下目录中:
shell
sudo mkdir -p /data/apps/xtrabackup/backup
假设运行产生的日志放在文件/var/log/xtrabackup/backup.log 中:
shell
sudo mkdir -p /var/log/xtrabackup
sudo touch /var/log/xtrabackup/backup.log
sudo chmod 644 /var/log/xtrabackup/backup.log
需要在目标业务数据库中创建专门的用于备份的账号密码:
sql
CREATE USER 'xtrabackup'@'%' IDENTIFIED WITH mysql_native_password BY 'yourbackpassword';
GRANT SELECT, RELOAD, PROCESS, LOCK TABLES, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO `xtrabackup`@`%`;
GRANT BACKUP_ADMIN ON *.* TO `xtrabackup`@`%`;
FLUSH PRIVILEGES;
脚本内容如下:
bash
#!/usr/bin/env bash
#
# MySQL XtraBackup Daily Backup Script (Docker)
# 每天备份一次,保留最近3天(可调)
set -euo pipefail
# 建议:限制新建文件/目录权限,提升安全性
umask 077
########## 可配置变量 BEGIN ##########
# 备份保留天数(只保留最近多少个备份目录)
RETENTION_DAYS=3
# Docker 镜像
XTRABACKUP_IMAGE="percona/percona-xtrabackup:8.0.35-34.1"
# 宿主机 MySQL 数据卷所在的容器名称
MYSQL_DATA_CONTAINER="mysql8"
# 备份容器运行时名称(临时)
BACKUP_CONTAINER_NAME="pxb"
# 备份根目录(宿主机路径)
BACKUP_ROOT="/data/apps/xtrabackup/backup"
# MySQL 连接信息(从备份容器访问 MySQL)
MYSQL_HOST="172.17.0.1"
MYSQL_PORT="3309"
MYSQL_USER="xtrabackup"
MYSQL_PASSWORD="yourbackpassword"
# MySQL 数据目录(在 mysql8 容器里的路径)
MYSQL_DATADIR="/var/lib/mysql"
# 备份选项(可按需修改压缩等参数)
XTRABACKUP_OPTIONS="--backup --compress --compress-threads=4"
# 备份目录命名前缀
BACKUP_PREFIX="full_backup_"
# 日志文件
LOG_FILE="/var/log/xtrabackup/backup.log"
########## 可配置变量 END ##########
# 确保日志目录存在
mkdir -p "$(dirname "$LOG_FILE")"
# 从这里开始,所有输出(包括 docker run 的 stdout/stderr)都写入日志
exec >> "$LOG_FILE" 2>&1
echo "==== $(date '+%F %T') 开始 MySQL XtraBackup 备份 ===="
# 安全检查:防止 BACKUP_ROOT 为空或指向根目录
if [[ -z "${BACKUP_ROOT:-}" || "${BACKUP_ROOT}" == "/" ]]; then
echo "ERROR: BACKUP_ROOT 未定义或为 '/',为防止误删数据,中止执行!"
exit 1
fi
# 创建备份根目录(如不存在)
mkdir -p "${BACKUP_ROOT}"
# 生成当日备份目录名,例如 full_backup_2025-12-01
TODAY_DATE="$(date +%F)"
TARGET_DIR_NAME="${BACKUP_PREFIX}${TODAY_DATE}"
TARGET_DIR="${BACKUP_ROOT}/${TARGET_DIR_NAME}"
echo "== 当日备份目录: ${TARGET_DIR}"
# 如果当天目录已存在,可以选择退出或覆盖,这里选择退出防止覆盖
if [[ -d "${TARGET_DIR}" ]]; then
echo "WARN: 备份目录已存在:${TARGET_DIR},本次备份中止以避免覆盖。"
exit 1
fi
echo "== 启动 Docker 容器执行备份 =="
echo " 镜像: ${XTRABACKUP_IMAGE}"
echo " MySQL: ${MYSQL_HOST}:${MYSQL_PORT}"
echo " 数据目录: ${MYSQL_DATADIR}"
echo " 目标目录: ${TARGET_DIR}"
docker run --name "${BACKUP_CONTAINER_NAME}" \
--rm \
-u root \
--volumes-from "${MYSQL_DATA_CONTAINER}" \
-v "${BACKUP_ROOT}":/backup \
"${XTRABACKUP_IMAGE}" \
/bin/bash -c "
xtrabackup ${XTRABACKUP_OPTIONS} \
--datadir=${MYSQL_DATADIR} \
--target-dir=/backup/${TARGET_DIR_NAME} \
--host=${MYSQL_HOST} \
--port=${MYSQL_PORT} \
--user=${MYSQL_USER} \
--password=${MYSQL_PASSWORD}
"
echo "== 备份完成: ${TARGET_DIR} =="
########## 清理旧备份(安全版) ##########
echo "== 清理旧备份,保留最近 ${RETENTION_DAYS} 个 =="
cd "${BACKUP_ROOT}" || {
echo "ERROR: 无法进入备份目录 ${BACKUP_ROOT}"
exit 1
}
# 收集所有符合前缀的备份目录,按时间倒序排列(最新在前)
BACKUP_DIRS=( $(ls -dt ${BACKUP_PREFIX}* 2>/dev/null || true) )
TOTAL=${#BACKUP_DIRS[@]}
if (( TOTAL == 0 )); then
echo "当前没有任何备份目录,无需清理。"
elif (( TOTAL <= RETENTION_DAYS )); then
echo "当前备份数量 ${TOTAL} 个,不超过保留上限 ${RETENTION_DAYS},无需删除。"
else
TO_DELETE_COUNT=$(( TOTAL - RETENTION_DAYS ))
echo "当前共有 ${TOTAL} 个备份,将删除最旧的 ${TO_DELETE_COUNT} 个:"
for (( i=RETENTION_DAYS; i<TOTAL; i++ )); do
OLD_BACKUP="${BACKUP_DIRS[$i]}"
# 双重保险:只删除以 BACKUP_PREFIX 开头的目录
if [[ -d "${OLD_BACKUP}" && "${OLD_BACKUP}" == ${BACKUP_PREFIX}* ]]; then
echo " 删除旧备份目录: ${OLD_BACKUP}"
rm -rf "${OLD_BACKUP}"
else
echo " 跳过(非备份目录或不存在): ${OLD_BACKUP}"
fi
done
fi
echo "==== $(date '+%F %T') 备份及清理完成 ===="
要点说明:
- BACKUP_ROOT 替换实际备份文件存放的目录
- MYSQL_USER MYSQL_PASSWORD 替换实际创建的备份账号密码
- MYSQL_DATA_CONTAINER 替换实际业务数据库mysql容器名称
- RETENTION_DAYS 这里设置备份保存3天,根据实际情况修改
- MYSQL_DATADIR 指的是实际mysql容器里面的数据目录,一般默认就是 /var/lib/mysql
二、创建对应的日志管理配置
上面脚本中设置了输出的日志归档到 /var/log/xtrabackup/backup.log ,为了防止日志太大太多我们需要创建一个用于日志文件自动轮换、压缩、删除的工具。
Linux系统自带了一个工具:Logrotate !
Logrotate 是一个 Linux/Unix 系统工具,主要用来管理系统生成的日志文件,这里我们直接创建对应我们脚本的日志管理配置内容:
shell
sudo sh -c 'echo "/var/log/xtrabackup/backup.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
create 644 root root
dateext
dateformat -%Y%m%d
}" | tee /etc/logrotate.d/xtrabackup'
| 配置参数 | 含义说明 |
|---|---|
| 文件路径 | 管理 /var/log/xtrabackup/backup.log 日志。 |
daily |
每天轮换一次。 |
rotate 7 |
保留最近 7 份旧日志。 |
compress |
压缩旧日志。 |
delaycompress |
延迟压缩旧日志。 |
copytruncate |
复制旧日志后,清空原文件,让程序继续写入。 |
create |
创建新日志文件的权限和所有者。 |
dateext |
旧日志以日期命名。 |
missingok |
日志不存在不报错。 |
notifempty |
空日志不轮换。 |
测试一下
shell
sudo logrotate -f /etc/logrotate.d/xtrabackup
正常情况日志目录下会生成:
backup.log-yyyymmdd
backup.log
三、创建定时任务
创建定时任务可以先手动执行一下脚本看下
shell
sudo ./mysqlbackup.sh
上面的脚本放在 /data/apps/xtrabackup/backup/mysqlbackup.sh
shell
sudo crontab -e
输入:
0 6 * * * /data/apps/xtrabackup/backup/mysqlbackup.sh
这里是每天早上6点执行,可以根据实际情况修改时间。
查看定时任务
shell
sudo crontab -l
结束语
本次脚本也是借助AI工具生产,经过了多次修改调试,适用于中小型业务场景的数据库备份操作。
欢迎各位测试交流其中的不足之处。