🧩 第06章 · 备份与恢复演练

定时备份/验证/恢复

备份与恢复演练

备份不是为了「有备份」,而是为了出问题后能真正恢复出来。一个没验证过的备份等于没有。本章从备份脚本、cron 定时、到恢复演练(这是大多数人漏掉的关键一步)完整走一遍。

本章目标:能把关键数据(数据库、网站文件、配置)自动备份,并证明「恢复」流程可行——先小规模恢复到一个临时目录验证,再做一次完整恢复演练。

1. 明确备份范围与策略

先列出「丢了会痛」的东西:

对象 路径/对象 频率 方式
MySQL 数据库 bangdream 库 每日 mysqldump
网站代码 /opt/apps/site 每日 tar
Nginx 配置 /etc/nginx/sites-available/ 每日 tar
GitLab 关键数据 /var/opt/gitlab 每日 gitlab-backup
系统配置 /etc/(关键) 每周 tar

⚠️ 易错:先定恢复策略再写备份。许多人只备份「数据」而漏了「配置」(Nginx、GitLab 的 gitlab.rb、systemd 单元),恢复时配置丢失照样起不来。备份要包含「配置文件」这一层。

2. 写一个统一的备份脚本

sudo tee /usr/local/bin/backup-all.sh >/dev/null <<'EOF'
#!/bin/bash
set -euo pipefail
DAY=$(date +%F); DIR=/srv/backup
mkdir -p "$DIR/db" "$DIR/files" "$DIR/config"
cd "$DIR"

# 1) 数据库
mysqldump -u bangdream_app -p'强密码' --single-transaction bangdream | gzip > db/bangdream-$DAY.sql.gz

# 2) 网站应用与数据
tar czf files/site-$DAY.tar.gz -C /opt/apps site 2>/dev/null || true

# 3) 关键配置
tar czf config/nginx-$DAY.tar.gz -C /etc/nginx sites-available 2>/dev/null || true

# 4) 清理 7 天前旧备份
find "$DIR/db" "$DIR/files" "$DIR/config" -type f -mtime +7 -delete

echo "[$(date)] backup done, dir=${DIR}/"
EOF
sudo chmod +x /usr/local/bin/backup-all.sh

🔒 安全:备份脚本里别明文写密码。更安全的做法是把口令放进 ~/.my.cnf 并 chmod 600,或在脚本里从环境变量读取。明文写在仓库/脚本里一旦泄露,等于把数据库拱手相让。

# .my.cnf 方式(避免命令行明文密码)
sudo tee /root/.my.cnf >/dev/null <<'EOF'
[mysqldump]
user=bangdream_app
password=强密码
EOF
sudo chmod 600 /root/.my.cnf

3. 定时执行

crontab -e
# 每天 02:30 执行全量备份
30 2 * * * /usr/local/bin/backup-all.sh >> /var/log/cron-backup.log 2>&1
# 手动跑一次并核对产物
sudo /usr/local/bin/backup-all.sh && ls -lh /srv/backup/db/ /srv/backup/files/ /srv/backup/config/

4. 验证备份(成功的关键)

不验证=没备份。至少做这三类校验:

# 1) 大小非零
find /srv/backup -name "*.gz" -size +0 -ls | head
# 2) 校验 gzip 完整性
gunzip -t /srv/backup/db/bangdream-*.sql.gz && echo "gzip OK"
# 3) 抽查数据库备份能否导入到临时库
zcat /srv/backup/db/bangdream-*.sql.gz | head -20

⚠️ 易错:gunzip -t 只能证明「压缩包没坏」,不能证明 SQL 内容正确。真正的验证是「导入到一个空库并对比行数」。大小正常、gzip 正常、但内容是空的——这类情况只有「恢复演练」能抓出来。

5. 恢复演练(小规模:倒入临时库)

这是本章核心:把备份恢复到临时位置,验证流程可行,且不碰生产库。

# 创建临时库并导入
mysql -e "CREATE DATABASE bangdream_restore_test;"
zcat /srv/backup/db/bangdream-*.sql.gz | mysql -u bangdream_app -p'强密码' bangdream_restore_test
# 对比行数(假设主表是 users,换成真实表名)
mysql -N -e "SELECT COUNT(*) FROM bangdream.users;"
mysql -N -e "SELECT COUNT(*) FROM bangdream_restore_test.users;"
# 清理临时库
mysql -e "DROP DATABASE bangdream_restore_test;"

⚠️ 易错:mysqldump 用 SET GLOBAL/锁表等参数会依赖权限。用 --single-transaction 避免锁全表。若对比行数不一致,立即排查是备份脚本漏表还是备份时间点差异。

6. 完整恢复演练(换机/灾备场景)

从头模拟「这台机器没了,用备份重建」——这是终极验证。先在一台干净的测试机上做,别拿生产机当试验田。

# 假设已经装好 MySQL / Nginx / Node
# 1) 恢复数据库
zcat /srv/backup/db/bangdream-*.sql.gz | mysql -u root bangdream
# 2) 恢复应用代码
sudo tar xzf /srv/backup/files/site-*.tar.gz -C /opt/apps
sudo systemctl restart bangdream-site
# 3) 恢复 Nginx 配置
sudo tar xzf /srv/backup/config/nginx-*.tar.gz -C /etc/nginx
sudo nginx -t && sudo systemctl reload nginx
# 4) 最终验证
curl -I https://bangdream.xyz && systemctl is-active bangdream-site

7. 异地/离线备份(防机器丢失)

单机备份最大的漏洞是「这台机器本身死了」。再同步一份到异地(如对象存储或另一台机器):

# 用 rsync 推到另一台机器(例子)
rsync -avz /srv/backup/ backup@remote-machine:/srv/backup/ --delete
# 或上传到对象存储(以 s3cmd 为例)
# s3cmd sync /srv/backup/ s3://my-bucket/backup/ --delete

🔒 安全:异地副本是灾难恢复的最后底线。若只有本机一份,而且本机被删库/被勒索/整机销毁,备份也一起没了。至少保证「备份的备份」存在本机之外。

8. 完成清单

# 验证点 命令 预期
1 备份生成 ls /srv/backup 三类文件都在
2 gzip 完整 gunzip -t ... OK
3 定时任务 crontab -l 列出备份行
4 行数一致 对比临时库 一致
5 恢复成功 干净机完整恢复 网站可访问

一条命令总验证:

gunzip -t /srv/backup/db/bangdream-*.sql.gz && ls -lh /srv/backup/*/ | tail -6 \
  && crontab -l | grep backup \
  && mysql -N -e "SELECT 1" && echo "DB OK"

备份与恢复闭环达成:数据、代码、配置三层都定时备份、经校验、能恢复,且保留一份异地副本。下一步给整台服务器做一次系统的安全加固。