引言
如果你运行着重要的 VPS 服务——网站、数据库、API、文件存储——那么你应该知道一个残酷的事实:数据丢失是运维中最昂贵的事故之一。
一次磁盘故障、一次误删操作、一次勒索软件攻击,就可能让你数月的努力化为乌有。更可怕的是,当你意识到问题发生时,往往已经来不及了。
备份不是选项,是必需品。 但很多人对备份的理解停留在"定期拷贝文件"的层面,缺乏系统性的灾难恢复策略。本文将带你从零开始,构建一套完整的自动化备份与灾难恢复体系。
核心原则:3-2-1 备份法则
在开始之前,先了解业界公认的备份黄金法则——3-2-1 规则:
- 3 份数据副本:原始数据 + 2 个备份
- 2 种不同的存储介质:例如本地磁盘 + 远程对象存储
- 1 份异地备份:至少有一份备份存放在地理上分离的位置
这套法则能有效抵御磁盘故障、火灾、勒索软件等多种风险。
架构设计
我们构建的备份体系包含四个层次:
┌─────────────────────────────────────────────────────┐
│ Layer 1: 本地快照(秒级) │
│ - LVM 快照 / Btrfs 快照 │
│ - 用于快速回滚误删操作 │
├─────────────────────────────────────────────────────┤
│ Layer 2: 增量备份(小时级) │
│ - Restic 加密增量备份 │
│ - 高效的去重和压缩 │
├─────────────────────────────────────────────────────┤
│ Layer 3: 异地同步(每日) │
│ - 备份推送到远程服务器 / S3 兼容存储 │
│ - AES-256 加密,密钥不随数据同行 │
├─────────────────────────────────────────────────────┤
│ Layer 4: 灾难恢复(按需) │
│ - 一键恢复脚本 │
│ - 完整的恢复演练流程 │
└─────────────────────────────────────────────────────┘
第一步:本地 LVM 快照(快速回滚)
如果你的 VPS 使用 LVM 分区,可以创建秒级快照用于紧急回滚:
# 查看当前卷组信息
vgdisplay
lvdisplay
# 创建快照(需预留足够空间,建议至少 20% 原始卷大小)
lvcreate --size 10G --snapshot \
--name snap_before_upgrade \
/dev/vg_main/lv_root
# 快照挂载点(只读挂载)
mkdir -p /mnt/snapshot
mount -o ro /dev/vg_main/snap_before_upgrade /mnt/snapshot
# 确认无误后删除快照
lvremove /dev/vg_main/snap_before_upgrade
注意:快照不是备份!它依赖于原始卷,原始卷损坏时快照同样失效。快照仅用于快速回滚,真正的备份需要 Layer 2。
第二步:Restic 加密增量备份
Restic 是目前最优秀的开源备份工具之一,支持去重、增量、端到端加密。
安装 Restic
# Ubuntu/Debian
wget https://github.com/restic/restic/releases/download/v0.17.4/restic_0.17.4_amd64.deb
sudo dpkg -i restic_0.17.4_amd64.deb
# 或从源码编译
go install github.com/restic/restic@v0.17.4
初始化仓库
# 设置备份密码(务必牢记!)
export RESTIC_PASSWORD="your-strong-password-here"
# 初始化本地备份仓库
restic init --repo /backup/local
# 初始化远程备份仓库(SSH 方式)
restic init --repo ssh://backup@remote-server:/backup/restic
配置备份策略
创建备份配置文件 /etc/restic/backups.conf:
{
"repositories": [
{
"name": "local",
"repo": "/backup/local",
"paths": ["/home", "/etc", "/var/www", "/opt/app"],
"exclude": ["*.tmp", "*.cache", "/home/*/.local/share/Trash"]
},
{
"name": "remote",
"repo": "ssh://backup@remote-server:/backup/restic",
"paths": ["/home", "/etc", "/var/www", "/opt/app"],
"exclude": ["*.tmp", "*.cache"]
}
],
"schedule": {
"local": "every 6 hours",
"remote": "daily at 03:00"
}
}
执行备份
# 测试备份(dry-run)
restic --repo /backup/local forget --prune --keep-daily 7 --keep-weekly 4
# 执行本地备份
restic --repo /backup/local backup \
/home /etc /var/www /opt/app \
--exclude='*.tmp' --exclude='*.cache' \
--tag="daily-$(date +%Y%m%d)"
# 执行远程备份
restic --repo ssh://backup@remote-server:/backup/restic backup \
/home /etc /var/www /opt/app \
--tag="daily-$(date +%Y%m%d)"
验证备份完整性
# 检查备份仓库健康
restic --repo /backup/local check
# 查看备份历史
restic --repo /backup/local snapshots
# 验证最近备份内容
restic --repo /backup/local ls latest
第三步:异地容灾部署
方案 A:SSH 远程备份服务器
# 在远程备份服务器上创建备份用户
sudo useradd -m -s /bin/bash backup
sudo mkdir -p /backup/restic
sudo chown backup:backup /backup/restic
# 配置 SSH 密钥认证(主服务器 → 备份服务器)
ssh-keygen -t ed25519 -C "vps-backup" -f /root/.ssh/backup_key -N ""
ssh-copy-id -i /root/.ssh/backup_key.pub backup@remote-server
# 限制备份用户权限(/etc/passwd 中添加命令限制)
# backup:x:1001:1001::/home/backup:/command-restricted-shell
方案 B:S3 兼容对象存储
# 安装 AWS CLI
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install
# 配置 S3 访问
aws configure
# Access Key ID: your-key
# Secret Access Key: your-secret
# Default region: ap-southeast-1
# Default output format: json
# Restic 直接支持 S3
restic init --repo s3:s3.ap-southeast-1.amazonaws.com/your-bucket-name
方案 C:Rclone + 多云存储
# 安装 Rclone
curl https://rclone.org/install.sh | sudo bash
# 配置远程存储
rclone config
# 选择 s3, azureblob, google cloud storage 等
# 同步备份到多云
rclone sync /backup/local remote:backup-folder \
--transfers 4 \
--checkers 8 \
--retries 3
第四步:自动化调度
Cron 定时任务
# 编辑 crontab
crontab -e
# 每小时执行本地备份
0 * * * * /usr/local/bin/restic-backup.sh local >> /var/log/restic/local.log 2>&1
# 每日凌晨 3 点执行远程备份
0 3 * * * /usr/local/bin/restic-backup.sh remote >> /var/log/restic/remote.log 2>&1
# 每周日执行备份清理
0 4 * * 0 /usr/local/bin/restic-backup.sh prune >> /var/log/restic/prune.log 2>&1
# 每月 1 日执行恢复演练
0 5 1 * * /usr/local/bin/restic-backup.sh verify >> /var/log/restic/verify.log 2>&1
完整备份脚本
创建 /usr/local/bin/restic-backup.sh:
#!/bin/bash
set -euo pipefail
REPO_TYPE="${1:-local}"
DATE=$(date +%Y%m%d-%H%M%S)
LOG_DIR="/var/log/restic"
LOCK_FILE="/tmp/restic-${REPO_TYPE}.lock"
mkdir -p "$LOG_DIR"
# 防止并发执行
if [ -f "$LOCK_FILE" ]; then
echo "[$DATE] Another backup is already running, exiting." >> "$LOG_DIR/${REPO_TYPE}.log"
exit 1
fi
trap 'rm -f "$LOCK_FILE"' EXIT
touch "$LOCK_FILE"
case "$REPO_TYPE" in
local)
REPO="/backup/local"
;;
remote)
REPO="ssh://backup@remote-server:/backup/restic"
;;
*)
echo "Unknown repo type: $REPO_TYPE"
exit 1
;;
esac
export RESTIC_PASSWORD="your-strong-password-here"
export RESTIC_PROGRESS_FPS=1
echo "[$DATE] Starting $REPO_TYPE backup..." >> "$LOG_DIR/${REPO_TYPE}.log"
# 备份
restic --repo "$REPO" backup \
/home /etc /var/www /opt/app \
--exclude='*.tmp' \
--exclude='*.cache' \
--tag="$DATE" \
>> "$LOG_DIR/${REPO_TYPE}.log" 2>&1
echo "[$DATE] Backup completed successfully." >> "$LOG_DIR/${REPO_TYPE}.log"
通知机制
# 备份成功/失败通知(Telegram Bot 示例)
notify_backup() {
local status="$1"
local repo="$2"
local msg="[VPS Backup] ${status}: ${repo} at $(date)"
curl -s -X POST \
"https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
-d "chat_id=${TG_CHAT_ID}" \
-d "text=${msg}" \
-d "parse_mode=HTML"
}
# 在脚本末尾添加通知
notify_backup "SUCCESS" "$REPO_TYPE"
第五步:恢复演练
没有经过恢复测试的备份,等于没有备份。
单次文件恢复
# 恢复单个文件
restic --repo /backup/local restore latest \
--target /tmp/recovery \
--include "/etc/nginx/nginx.conf"
# 恢复整个目录
restic --repo /backup/local restore latest \
--target /tmp/recovery \
--include "/home/user/documents"
完整系统恢复
#!/bin/bash
# 完整恢复脚本 /usr/local/bin/restore-full.sh
set -euo pipefail
REPO="${1:-/backup/local}"
RESTORE_DATE="${2:-latest}"
MOUNT_POINT="/mnt/recovery"
echo "=== VPS 灾难恢复流程 ==="
echo "备份仓库: $REPO"
echo "恢复时间点: $RESTORE_DATE"
echo ""
# 1. 挂载恢复目录
mkdir -p "$MOUNT_POINT"
# 2. 恢复系统配置
echo "[1/4] 恢复 /etc 配置..."
restic --repo "$REPO" restore "$RESTORE_DATE" \
--target "$MOUNT_POINT" --include "/etc"
cp -a "$MOUNT_POINT/etc/." / 2>/dev/null || true
# 3. 恢复用户数据
echo "[2/4] 恢复 /home 数据..."
restic --repo "$REPO" restore "$RESTORE_DATE" \
--target "$MOUNT_POINT" --include "/home"
cp -a "$MOUNT_POINT/home/." /home/ 2>/dev/null || true
# 4. 恢复应用数据
echo "[3/4] 恢复 /var/www 和 /opt/app..."
restic --repo "$REPO" restore "$RESTORE_DATE" \
--target "$MOUNT_POINT" --include "/var/www"
cp -a "$MOUNT_POINT/var/www/." /var/www/ 2>/dev/null || true
restic --repo "$REPO" restore "$RESTORE_DATE" \
--target "$MOUNT_POINT" --include "/opt/app"
cp -a "$MOUNT_POINT/opt/app/." /opt/app/ 2>/dev/null || true
# 5. 验证恢复结果
echo "[4/4] 验证恢复完整性..."
restic --repo "$REPO" check
echo "=== 恢复完成 ==="
echo "请检查以下目录:"
echo " /etc - 系统配置"
echo " /home - 用户数据"
echo " /var/www - Web 应用"
echo " /opt/app - 其他应用"
恢复演练清单
| 项目 | 频率 | 验证方法 |
|---|---|---|
| 单文件恢复 | 每周 | 随机选择一个文件,恢复并对比 |
| 目录恢复 | 每月 | 恢复 /etc 到临时目录 |
| 完整恢复 | 每季度 | 在新 VPS 上完整恢复并启动服务 |
| 异地恢复 | 每半年 | 从远程备份服务器恢复 |
进阶:数据库专项备份
MySQL/MariaDB 备份
#!/bin/bash
# /usr/local/bin/mysql-backup.sh
DB_USER="backup_user"
DB_PASS="your-db-password"
BACKUP_DIR="/backup/databases"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
# 获取所有数据库列表
dbs=$(mysql -u "$DB_USER" -p"$DB_PASS" -e "SHOW DATABASES;" \
| grep -Ev "(Database|information_schema|performance_schema)")
# 逐个备份
for db in $dbs; do
mysqldump -u "$DB_USER" -p"$DB_PASS" \
--single-transaction --routines --triggers \
"$db" | gzip > "$BACKUP_DIR/${db}-${DATE}.sql.gz"
echo "Backed up: $db"
done
# 只保留最近 30 天的备份
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
PostgreSQL 备份
#!/bin/bash
# /usr/local/bin/pgbackup.sh
PGUSER="backup_user"
BACKUP_DIR="/backup/databases"
DATE=$(date +%Y%m%d-%H%M%S)
mkdir -p "$BACKUP_DIR"
# 备份所有数据库
pg_dumpall -U "$PGUSER" | gzip > "$BACKUP_DIR/all-dbs-${DATE}.sql.gz"
# 只保留最近 30 天
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
安全加固
备份加密
# Restic 内置 AES-256 加密,密钥独立管理
# 将密码存储在安全的密钥管理系统中(如 HashiCorp Vault)
# 备份仓库权限收紧
chmod 700 /backup/local
chown -R root:root /backup/local
密钥管理
# 使用 SSH 密钥而非密码(更安全)
# 主服务器生成专用备份密钥
ssh-keygen -t ed25519 -C "backup-key" -f /root/.ssh/restic_backup -N ""
# 禁用密码登录,仅允许密钥认证
# /etc/ssh/sshd_config
PasswordAuthentication no
PubkeyAuthentication yes
防勒索软件
# 使用 immutable 属性保护备份(即使 root 也无法删除)
chattr +i /backup/local
# 定期解锁→备份→锁定
chattr -i /backup/local
restic backup ...
chattr +i /backup/local
成本估算
| 组件 | 月成本(估算) | 说明 |
|---|---|---|
| 本地 SSD 存储(100GB) | $0(已有 VPS) | LVM 快照 |
| 远程备份服务器(50GB) | $5-10 | 最低配置 VPS |
| S3 兼容对象存储 | $2-5 | 按用量计费 |
| 带宽费用 | $1-3 | 增量备份流量小 |
| 总计 | $8-18/月 | 远低于数据丢失成本 |
总结
一套完善的备份与灾难恢复体系,核心在于三点:
- 自动化:人类会忘记,但脚本不会。确保备份任务自动执行、自动通知。
- 多层次:本地快照 + 增量备份 + 异地容灾,每一层应对不同风险。
- 可验证:定期恢复演练是唯一能确认备份有效的办法。
数据是无价的,而构建这套体系的成本,不过是一顿外卖的钱。今天花 10 分钟配置备份,明天可能救回你整个业务。
立即行动吧——先运行一次备份,再试着恢复一个文件。你的未来自己会感谢现在的你。
