Featured image of post VPS 自动化备份与灾难恢复完整方案:从本地快照到异地容灾

VPS 自动化备份与灾难恢复完整方案:从本地快照到异地容灾

VPS 数据无价,但意外无处不在。本文教你搭建一套完整的自动化备份体系——本地快照、异地同步、加密存储、一键恢复,让数据安全事故不再让你失眠

引言

如果你运行着重要的 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/月远低于数据丢失成本

总结

一套完善的备份与灾难恢复体系,核心在于三点:

  1. 自动化:人类会忘记,但脚本不会。确保备份任务自动执行、自动通知。
  2. 多层次:本地快照 + 增量备份 + 异地容灾,每一层应对不同风险。
  3. 可验证:定期恢复演练是唯一能确认备份有效的办法。

数据是无价的,而构建这套体系的成本,不过是一顿外卖的钱。今天花 10 分钟配置备份,明天可能救回你整个业务。

立即行动吧——先运行一次备份,再试着恢复一个文件。你的未来自己会感谢现在的你。

📺 看视频版教程 → DuckDB Lab YouTube

Subscribe for more DuckDB & AI automation tutorials