Linux 基础体系 · 第 26/85 篇。示例面向现代主流 Linux 发行版;发行版差异、权限和生产风险会明确说明。

Linux 备份与恢复:文件、块设备、快照、加密和恢复演练

备份不是“把文件复制到另一块盘”这么简单。一个可恢复的备份系统至少要回答五个问题:

  1. 备份对象是什么:文件、文件系统、逻辑卷,还是整块磁盘?
  2. 数据处于什么状态:复制时是否有并发写入?数据库是否处于一致状态?
  3. 备份保存在哪里:是否会随源主机、源磁盘或勒索软件一起损坏?
  4. 备份能否被读取:权限、ACL、扩展属性、加密密钥是否完整?
  5. 恢复是否真正可行:恢复速度、依赖条件和结果是否经过演练?

本文区分文件级备份、块级备份、快照和加密,并以恢复为最终目标建立一套完整的 Linux 备份思路。


一、先定义备份系统要解决的问题

1. 备份、快照和复制不是同一个概念

**备份(backup)**是独立保存的数据副本,目标是在原始数据不可用时重建数据。

**快照(snapshot)**是在某个时间点记录数据状态的机制。它通常依赖原始存储系统,例如 LVM、Btrfs 或 ZFS。快照便于快速回滚和读取历史状态,但不一定能承受原始存储故障。

**复制(replication)**是把数据持续或周期性发送到另一个位置。复制可以是备份的一部分,但如果删除操作、损坏数据或勒索软件也被同步过去,单纯复制无法提供足够的历史恢复点。

因此:

  • 快照解决“快速获得某个时间点的视图”;
  • 复制解决“把数据送到另一个位置”;
  • 备份解决“在故障后恢复数据和业务”。

一个生产方案通常会组合三者,而不是用其中一个替代全部机制。

2. RPO 和 RTO

**RPO(Recovery Point Objective,恢复点目标)**表示最多可以丢失多长时间的数据。

例如:

  • RPO = 24 小时:每天备份一次,最坏可能丢失一天的变更;
  • RPO = 15 分钟:需要更高频备份、日志归档或持续复制。

**RTO(Recovery Time Objective,恢复时间目标)**表示从故障发生到业务恢复所允许的最长时间。

如果系统有 2 TB 数据,网络恢复速度只有 100 MB/s,那么仅传输数据的理论时间约为:

T=2×1024×1024 MB100 MB/s20972 s5.8 小时T = \frac{2 \times 1024 \times 1024\ \text{MB}}{100\ \text{MB/s}} \approx 20972\ \text{s} \approx 5.8\ \text{小时}

这还没有计入校验、解密、文件系统创建、数据库恢复和人工确认时间。由此可见,RTO 不能只由“备份是否成功”决定,还受恢复路径和吞吐量约束。

3. 一致性是备份的核心条件

假设一个文件包含两个相互关联的部分:

文件头:记录长度为 100 MB
文件体:实际只写入了 60 MB

如果复制发生在程序更新文件头和文件体之间,备份可能得到:

  • 新文件头;
  • 旧文件体;
  • 或者只复制了一部分新文件体。

这种文件可能存在于备份中,但无法使用。

因此需要区分三种一致性:

  1. 崩溃一致性(crash consistency)
    类似机器突然断电后磁盘上的状态。文件系统可能可以挂载,但应用数据未必完整。
  2. 文件系统一致性(filesystem consistency)
    文件系统元数据和数据块符合文件系统自身规则。
  3. 应用一致性(application consistency)
    数据库、队列、索引等应用内部约束也保持一致。

文件级复制通常无法自动保证应用一致性。数据库应优先使用数据库自身的逻辑备份、物理备份或日志归档功能。


二、备份对象:文件级、块级和应用级

1. 文件级备份

文件级备份以目录树中的文件为对象,例如:

/etc/
/home/
/var/lib/myapp/
/srv/uploads/

常见工具包括:

  • rsync:目录之间同步;
  • tar:把目录打包成归档;
  • cpio:在某些系统恢复和打包流程中使用;
  • Borg、Restic 等:通常在文件级之上增加去重、压缩、加密和仓库管理。

文件级备份的优点是:

  • 可以只恢复单个文件;
  • 可以跨文件系统和跨磁盘布局恢复;
  • 备份数据通常较小;
  • 容易查看内容。

缺点是:

  • 不能直接还原分区表、引导程序和文件系统元数据;
  • 如果源文件正在变化,可能产生不一致副本;
  • 必须正确处理权限、ACL、扩展属性、硬链接、稀疏文件和符号链接。

2. 块级备份

块级备份直接复制块设备,例如:

/dev/sdb
/dev/sdb2
/dev/mapper/vg_data-lv_app

典型工具是 dd,也可以使用支持块设备的备份系统。

块级备份保存的是某个块设备的内容,可能包括:

  • 文件系统元数据;
  • 未使用但仍存在的数据块;
  • 分区内的所有文件;
  • 损坏状态;
  • 文件系统内部的碎片和布局。

它可以精确恢复整个文件系统,但也带来限制:

  • 目标设备通常需要足够大;
  • 不能方便地只恢复单个文件;
  • 源文件系统如果正在写入,结果可能只是崩溃一致性;
  • 对含有敏感数据的未使用块也会一并复制;
  • 恢复时可能覆盖目标设备上的全部内容。

3. 应用级备份

数据库、虚拟机磁盘、消息队列和对象存储索引不能简单地视为普通文件。

以数据库为例,复制数据库进程正在修改的数据文件可能得到内部不一致的结果。应使用:

  • 数据库逻辑导出;
  • 数据库官方物理备份工具;
  • 预写日志(WAL、binlog 等)归档;
  • 数据库提供的备份一致性接口。

块级快照可以作为数据库物理备份的一部分,但通常还需要数据库通知、刷盘或日志协调。仅仅执行 lvcreate 并不自动等价于“数据库一致备份”。


三、文件级备份:正确保存目录树的语义

1. 使用 rsync 复制普通目录

假设源目录是 /srv/data,备份目标已经挂载到 /backup

sudo rsync -aHAX --numeric-ids --sparse \
  --one-file-system \
  /srv/data/ /backup/srv-data/

各选项的含义如下:

  • -a:归档模式,包含递归、符号链接、权限、时间、用户组等常见属性;
  • -H:保留硬链接关系;
  • -A:保留 POSIX ACL;
  • -X:保留扩展属性;
  • --numeric-ids:按数字 UID/GID 保存,避免两台机器的用户名映射不同;
  • --sparse:尽量把连续零块恢复为稀疏区域;
  • --one-file-system:不跨入源目录下挂载的其他文件系统;
  • 源路径最后的 /:表示复制目录内容,而不是在目标下再创建一个 data 目录。

如果源目录包含挂载点,必须先决定是否要备份这些挂载点。例如:

/srv/data/
└── database/    # 实际是另一个文件系统

使用 --one-file-system 后,database 挂载点本身会存在,但其挂载文件系统中的内容不会被递归复制。这样可以避免备份任务意外跨入 /proc、NFS、容器挂载点或其他大容量文件系统。

2. 删除同步和备份历史的区别

以下命令会让目标尽量与源完全一致:

sudo rsync -aHAX --numeric-ids --delete \
  /srv/data/ /backup/srv-data/

--delete 会删除目标中源目录不存在的文件。这适合“镜像”,但不等于有历史版本的备份。

例如:

第一次:源目录中有 report.txt
第二次:源目录删除 report.txt

使用 --delete 后,目标中的 report.txt 也会被删除。若没有快照、版本仓库或其他介质,这个文件就无法恢复。

生产环境中,通常把以下两类用途分开:

  • 镜像副本:便于快速切换或快速恢复,允许删除同步;
  • 历史备份:保留多个时间点,源端删除不会立即删除所有历史版本。

首次使用 --delete 前应先模拟:

sudo rsync -aHAXn --delete \
  /srv/data/ /backup/srv-data/

-n--dry-run 只显示计划操作。必须检查输出中的删除列表,确认源和目标没有写反。rsync 的路径写反可能造成大规模数据破坏。

3. tar 保存目录树

创建归档:

sudo tar \
  --create \
  --file=/backup/etc-$(date +%F).tar \
  --acls \
  --xattrs \
  --numeric-owner \
  --sparse \
  -C / etc

这里:

  • -C / etc 表示先切换到根目录,再把 etc 放入归档;
  • --acls 保存 ACL;
  • --xattrs 保存扩展属性;
  • --numeric-owner 保存数字 UID/GID;
  • --sparse 识别稀疏文件。

查看归档内容:

tar --list --file=/backup/etc-2025-01-01.tar | head

测试归档是否可以读取:

tar --extract \
  --file=/backup/etc-2025-01-01.tar \
  --directory=/tmp/restore-test

恢复到临时目录比直接覆盖生产目录安全。确认内容、权限和符号链接后,再选择性恢复。

4. 文件级备份容易遗漏的对象

普通 cp -r 通常不能完整表达生产系统中的文件语义。需要特别注意:

ACL

传统模式位只有:

rwxrwxrwx

ACL 可以为多个用户或组定义额外权限。检查:

getfacl /srv/data/file

复制时使用 rsync -Atar --acls

扩展属性

扩展属性可能保存:

  • SELinux 安全标签;
  • 文件能力(capability);
  • 用户自定义元数据;
  • 某些应用需要的属性。

检查:

getfattr -d -m- /srv/data/file

复制时使用 rsync -Xtar --xattrs

硬链接

两个目录项可能指向同一个 inode:

ls -li file-a file-b

如果 inode 号相同,二者是硬链接。未使用 rsync -H 时,备份可能把它们恢复成两个独立文件,内容虽然相同,但链接关系已经改变。

稀疏文件

稀疏文件的逻辑大小很大,但中间的大块零区域没有实际分配磁盘块。检查:

ls -lh image
du -h image

前者显示逻辑大小,后者通常显示实际占用。未使用稀疏选项时,复制可能把“空洞”写成真实的零块,浪费备份空间。

符号链接

符号链接保存的是路径,不是目标文件内容。目标路径在恢复后的目录布局中不存在时,链接仍然会是悬空链接。备份时需要决定:

  • 保留符号链接本身;
  • 还是跟随链接复制目标。

一般系统备份应保留链接本身,避免跨出备份范围或重复复制数据。

挂载点和伪文件系统

以下目录不应按普通目录递归备份:

/proc
/sys
/dev
/run

它们通常是内核或用户空间运行时提供的伪文件系统。恢复时应由系统重新挂载和生成,而不是从备份中复制其当前内容。


四、文件一致性:并发写入时复制了什么

1. rsync 不是事务快照

rsync 通常按文件读取源数据。一个大文件在复制期间被修改时,可能出现:

  • 读到修改前的内容;
  • 读到修改后的内容;
  • 读到混合内容;
  • 检测到文件大小或时间变化后重新传输;
  • 由于文件持续变化而反复重试,最终失败或得到不可用副本。

rsync 可以通过临时文件和原子替换减少目标端暴露半成品的时间,但它不能让源端多个文件同时处于同一个事务时间点。

例如,一个应用同时更新:

data/index
data/records

即使两个文件分别复制成功,也不能保证它们属于同一个应用事务。

2. 常见的一致性处理顺序

从强到弱通常可以选择:

  1. 停止应用后备份;
  2. 使用应用的备份接口;
  3. 让应用进入只读或备份模式;
  4. 对文件系统做快照,再从快照中复制;
  5. 直接在线复制,接受崩溃一致性风险。

对数据库,应优先选择数据库官方备份机制。fsfreeze 可以冻结文件系统写入,但它只解决文件系统层面的写入协调,不会自动完成数据库内部检查点、日志截断或跨多个数据库实例的事务协调。


五、块设备备份:复制文件系统之外的内容

1. 认识块设备和逻辑卷

Linux 中常见的数据路径如下:

物理磁盘
  └── 分区
       └── RAID / LVM 物理卷
            └── 卷组
                 └── 逻辑卷
                      └── 文件系统
                           └── 挂载目录

例如:

/dev/sdb
└── /dev/sdb1
    └── /dev/vgdata/lvapp
        └── /srv/app

备份对象的层级不同,恢复含义也不同:

  • 备份 /srv/app:恢复文件;
  • 备份 /dev/vgdata/lvapp:恢复整个文件系统;
  • 备份 /dev/sdb:可能还包括分区表和多个分区;
  • 备份 RAID 成员盘:通常不能替代 RAID 阵列级备份。

RAID 主要提供可用性或读写性能,不等于历史备份。误删除、逻辑损坏和勒索软件会正常地同步写入所有 RAID 成员。

2. 使用 dd 做块设备镜像

以下示例假设源设备是未挂载或已停止写入的测试设备:

sudo dd if=/dev/vgdata/lvapp \
  of=/backup/lvapp-2025-01-01.img \
  bs=16M \
  status=progress \
  conv=sync,noerror

参数含义:

  • if:输入设备;
  • of:输出文件;
  • bs=16M:每次读写 16 MiB,减少系统调用次数;
  • status=progress:显示进度;
  • conv=sync,noerror:读到错误时继续,并用零填充错误块。

conv=noerror 的风险很大:输出镜像可能包含无法恢复的数据区域。它适合在“尽可能抢救可读数据”的场景使用,不适合掩盖正常备份中的 I/O 错误。生产备份遇到读错误时,应记录错误、检查磁盘和备份结果,而不是只看命令退出状态。

复制完成后可以计算校验值:

sha256sum /backup/lvapp-2025-01-01.img

但需要注意:源设备在复制期间继续变化时,源端和镜像端即使分别计算出合法校验值,也不能说明镜像代表某个一致时间点。

3. 恢复块设备镜像

恢复前必须确认目标设备:

lsblk -f
findmnt

确认目标逻辑卷没有被错误挂载后:

sudo dd if=/backup/lvapp-2025-01-01.img \
  of=/dev/vgdata/lvapp \
  bs=16M \
  status=progress \
  conv=fsync

该操作会覆盖目标逻辑卷的全部内容。恢复后不要立即假设文件系统可用,应先执行适合该文件系统的检查。

以 ext4 为例,必须在未挂载状态检查:

sudo umount /dev/vgdata/lvapp
sudo e2fsck -f /dev/vgdata/lvapp

如果目标设备比原始文件系统大,恢复后文件系统不会自动利用新增空间。需要先确认文件系统类型,再执行扩容。例如 ext4 常见流程是:

sudo resize2fs /dev/vgdata/lvapp

XFS 不能使用 resize2fs,通常需要在挂载状态下使用:

sudo xfs_growfs /srv/app

这些命令具有文件系统相关性,不能混用。

4. 分区表和引导程序是另一层数据

备份一个分区,例如 /dev/sdb1,不等于备份整个磁盘 /dev/sdb。整机恢复还可能需要:

  • GPT 或 MBR 分区表;
  • EFI System Partition;
  • /boot
  • 引导程序;
  • initramfs;
  • /etc/fstab
  • LUKS 加密容器头部;
  • LVM 元数据。

如果需要整盘镜像,应明确备份对象是整块磁盘:

sudo dd if=/dev/sdb of=/backup/sdb.img bs=16M status=progress

但整盘镜像容量大、恢复粒度粗,也会复制空闲区域。更灵活的方案通常是单独备份:

  1. 分区表;
  2. EFI 分区;
  3. 根文件系统和数据文件系统;
  4. LUKS、LVM 和系统配置;
  5. 应用数据。

六、快照:时间点视图和写时复制

1. 写时复制的基本过程

快照常使用 写时复制(Copy-on-Write,COW)

  1. 创建快照时,不立即复制所有源数据;
  2. 源卷某个块第一次被修改时,系统先把旧块复制到快照区域;
  3. 源卷写入新内容;
  4. 从快照读取该块时,返回保存的旧块;
  5. 未被修改的块仍从原始位置读取。

因此,快照保存的是:

快照视图 = 创建时的原始块 + 后续修改前保存的旧块

快照空间消耗主要取决于快照生命周期内被修改的块数量,而不是源卷总大小。

2. LVM 快照示例

查看逻辑卷:

sudo lvs -a -o lv_name,vg_name,lv_size,origin,data_percent,lv_attr

创建快照:

sudo lvcreate \
  --snapshot \
  --name lvapp-snap \
  --size 20G \
  /dev/vgdata/lvapp

如果源文件系统支持冻结,可以在应用协调后创建快照。一个简化示例:

sudo fsfreeze --freeze /srv/app
sudo lvcreate --snapshot --name lvapp-snap --size 20G /dev/vgdata/lvapp
sudo fsfreeze --unfreeze /srv/app

这段流程的含义是:

  • fsfreeze --freeze 阻止文件系统继续处理写入;
  • lvcreate 记录快照起点;
  • fsfreeze --unfreeze 恢复业务写入。

但它仍然不自动保证应用一致性。数据库应先执行应用级 flush、checkpoint 或官方备份流程。冻结时间也不能无限延长,否则写入请求会阻塞并造成业务超时。

挂载快照进行文件级备份:

sudo mkdir -p /mnt/lvapp-snap
sudo mount -o ro /dev/vgdata/lvapp-snap /mnt/lvapp-snap

sudo rsync -aHAX --numeric-ids \
  /mnt/lvapp-snap/ /backup/lvapp/

完成后卸载并删除快照:

sudo umount /mnt/lvapp-snap
sudo lvremove /dev/vgdata/lvapp-snap

3. 快照空间耗尽的故障路径

假设源卷在快照创建后不断修改:

源卷写入块 A → 旧块 A 复制到快照
源卷写入块 B → 旧块 B 复制到快照
...
快照空间达到上限

当快照区域无法继续保存旧块时,快照可能失效。LVM 的具体行为和告警形式取决于快照类型与实现,但生产上不能把“快照还存在”误判为“快照仍然可读”。

应观察:

sudo lvs -o lv_name,lv_attr,origin,data_percent,metadata_percent

data_percent 接近上限时,必须尽快完成从快照读取的备份或扩大快照。扩大快照只能延缓问题,不能替代把数据复制到独立存储。

4. 文件系统原生快照

现代 Linux 环境还可能使用:

  • Btrfs snapshot;
  • ZFS snapshot;
  • LVM thin snapshot;
  • 存储阵列快照;
  • 虚拟化平台的磁盘快照。

它们的语义不同,不能仅凭“都有 snapshot”就假设命令和失败行为相同。

Btrfs 的快照通常基于子卷和 COW;ZFS 快照属于存储池事务状态的一部分。Btrfs 和 ZFS 还可以使用各自的增量发送机制把快照传到另一台主机或另一个存储池。无论采用哪种实现,都应检查:

  • 快照是否只存在于源存储;
  • 快照空间是否受限;
  • 是否支持增量传输;
  • 是否能在另一台机器独立导入和恢复;
  • 删除源数据后历史快照是否仍然保留;
  • 备份仓库是否有独立的保留策略。

七、为什么快照不是备份

考虑如下故障:

同一台主机
├── 原始逻辑卷
└── LVM 快照

如果物理磁盘损坏,原始逻辑卷和快照都可能不可用。若攻击者获得主机权限,也可能删除或加密快照。

所以,至少要把快照中的数据进一步导出到独立介质:

源文件系统
   ↓
时间点快照
   ↓
文件级归档 / 备份仓库
   ↓
另一台主机、离线介质或对象存储

“3-2-1”是常见的经验原则:

  • 至少 3 份数据副本;
  • 使用至少 2 种存储介质或故障域;
  • 至少 1 份位于异机、异地域或离线位置。

它是工程建议,不是 Linux 内核或某个备份工具的规范保证。真正关键的是故障域隔离和恢复验证。


八、加密:保护备份,也保护恢复能力

1. 加密的威胁模型

备份加密主要防止:

  • 备份磁盘被盗;
  • 对象存储或文件服务器被错误公开;
  • 备份介质被未授权人员读取;
  • 备份副本泄露后直接暴露数据。

加密不能防止:

  • 已经获得解密密钥的攻击者;
  • 备份在加密前就被篡改;
  • 恢复主机本身被入侵;
  • 密钥丢失导致无法恢复。

因此必须同时管理:

备份数据 + 完整性校验 + 密钥 + 密钥恢复流程

2. 使用 age 加密文件

age 是现代 Linux 中常见的文件加密工具,但并非所有发行版默认安装。安装方式依发行版而异,使用前应确认版本和包来源。

生成身份文件:

age-keygen -o backup-key.txt

输出通常包含一行私钥标识和对应的公钥。查看公钥:

grep '^# public key:' backup-key.txt

加密归档:

age \
  --recipient age1example-recipient \
  --output /backup/etc-2025-01-01.tar.age \
  /backup/etc-2025-01-01.tar

恢复时:

age \
  --decrypt \
  --identity /secure/restore/backup-key.txt \
  --output /tmp/etc-restore.tar \
  /backup/etc-2025-01-01.tar.age

然后检查归档:

tar -tf /tmp/etc-restore.tar | head

这里的关键关系是:

  • 加密时使用公钥;
  • 解密时使用对应私钥;
  • 备份服务器不必保存私钥;
  • 恢复环境必须能取得私钥。

不要把私钥与备份文件放在同一个只受文件权限保护的目录中。若攻击者能同时取得密文和私钥,加密就失去主要意义。

3. 直接管道加密

可以避免在本地留下未加密归档:

sudo tar \
  --create \
  --file=- \
  --acls \
  --xattrs \
  --numeric-owner \
  -C / etc \
| age --recipient age1example-recipient \
  --output /backup/etc-2025-01-01.tar.age

解密并解包到测试目录:

mkdir -p /tmp/etc-restore
age --decrypt \
  --identity /secure/restore/backup-key.txt \
  /backup/etc-2025-01-01.tar.age \
| tar --extract --file=- --directory=/tmp/etc-restore

管道减少明文落盘,但错误诊断更复杂:前一个命令失败时,后一个命令可能仍然退出或创建部分输出。脚本中应启用严格错误处理,并检查最终文件大小、退出码和校验结果。

4. 密钥丢失和密钥轮换

加密备份有一个不可绕过的条件:

可恢复=备份数据存在密钥可取得解密工具可用\text{可恢复} = \text{备份数据存在} \land \text{密钥可取得} \land \text{解密工具可用}

只保存加密文件,不保存可恢复的密钥路径,等价于保存不可读数据。

生产环境应明确:

  • 私钥由谁保管;
  • 是否需要多方授权;
  • 密钥是否有离线副本;
  • 密钥轮换后旧备份如何恢复;
  • 灾难恢复环境如何取得工具和密钥;
  • 备份任务账号是否能读取私钥。

加密本身不保证备份内容未被恶意替换。备份工具应使用带认证的加密格式、仓库校验或额外的签名和校验机制。仅运行 sha256sum 只能检测“当前文件与某个已知摘要是否相同”,不能自动证明摘要文件本身没有被一并篡改。


九、数据库和动态服务:先保证应用一致性

1. 直接复制数据库文件的反例

假设数据库正在执行事务:

1. 修改数据页
2. 写入部分日志
3. 更新索引页
4. 提交事务

在第 2 步复制数据库目录,可能得到:

  • 数据页已经变化;
  • 索引页尚未变化;
  • 日志文件只复制了一部分。

这不是普通文件备份工具可以自动修复的关系。

2. 更安全的流程

典型顺序是:

应用备份接口或逻辑导出
        ↓
生成一致性备份文件
        ↓
对备份文件做校验、压缩和加密
        ↓
复制到独立备份存储
        ↓
定期在隔离环境导入验证

数据库具体命令依赖数据库产品和版本,不应把 tar /var/lib/database 当作通用解决方案。

对于需要低 RPO 的数据库,还应保存连续日志。例如:

全量备份时间点 T0
归档日志 T0 → T1

恢复时先恢复全量备份,再重放日志到目标时间点。这样恢复点不再局限于每天一次全量备份。


十、备份结果必须验证

1. 验证不是只看退出码

rsynctar 或备份脚本退出码为 0,只能说明这一次操作没有报告错误,不等于:

  • 目标介质未来一定可读;
  • 数据内容符合应用要求;
  • 密钥仍然可用;
  • 恢复流程没有缺少配置;
  • 备份覆盖了所有需要的数据。

应进行分层验证。

2. 文件级验证

比较目录内容:

sudo rsync -aHAXn --delete \
  /srv/data/ /backup/srv-data/

如果没有输出,表示按当前选项比较时没有发现差异。注意,这是对当前源目录和目标目录的比较,不是对历史备份可靠性的证明。

验证归档可读:

tar -tf /backup/etc-2025-01-01.tar >/dev/null
echo $?

验证加密文件能否解密:

age --decrypt \
  --identity /secure/restore/backup-key.txt \
  /backup/etc-2025-01-01.tar.age \
| tar -tf - >/dev/null

3. 校验值的正确使用

对于一个静态文件:

sha256sum backup.img > backup.img.sha256
sha256sum --check backup.img.sha256

输出类似:

backup.img: OK

校验值能检测传输损坏和存储位翻转,但有两个边界:

  1. 计算校验值时源文件必须已经稳定;
  2. 校验文件本身应放在不同的信任位置,否则攻击者可以同时修改数据和校验值。

4. 恢复到临时目录而不是生产目录

例如验证归档:

sudo rm -rf /tmp/restore-check
sudo mkdir -p /tmp/restore-check

tar --extract \
  --file=/backup/etc-2025-01-01.tar \
  --directory=/tmp/restore-check

检查:

sudo stat /tmp/restore-check/etc/passwd
sudo getfacl /tmp/restore-check/etc
sudo getfattr -d -m- /tmp/restore-check/etc 2>/dev/null

对数据库则应在隔离实例中启动并执行:

  • 版本兼容检查;
  • 表和索引检查;
  • 应用读写测试;
  • 关键业务查询;
  • 备份时间点和日志连续性检查。

十一、恢复流程:从数据恢复到业务恢复

恢复不是一个单一命令,而是一组有顺序的状态转换:

flowchart TD
    A[确认故障范围] --> B[选择恢复点]
    B --> C[取得备份介质和密钥]
    C --> D[校验备份]
    D --> E{备份可读且适用?}
    E -- 否 --> F[切换其他恢复点或介质]
    E -- 是 --> G[准备隔离恢复环境]
    G --> H[恢复块设备或文件系统]
    H --> I[恢复文件权限 ACL xattr]
    I --> J[恢复应用数据和日志]
    J --> K[启动服务并执行一致性检查]
    K --> L[业务验证]
    L --> M[切换流量并记录结果]

每一步都有不同的失败路径:

  • 故障范围判断错误:恢复了错误的主机或目录;
  • 恢复点选择错误:数据虽然完整,但不符合业务需要;
  • 密钥不可用:备份存在但无法解密;
  • 目标空间不足:恢复中途失败;
  • 权限或安全标签丢失:文件存在但服务无法读取;
  • 应用日志未恢复:数据库启动但丢失最近事务;
  • 业务验证缺失:服务端口正常,但实际功能损坏。

1. 文件级恢复

先恢复到临时目录:

sudo mkdir -p /mnt/restore
sudo tar -xf /backup/etc-2025-01-01.tar -C /mnt/restore

确认无误后,才选择性同步到目标目录:

sudo rsync -aHAX --numeric-ids \
  /mnt/restore/etc/ /etc/

如果目标系统启用了 SELinux,恢复文件后可能需要重新应用安全上下文:

sudo restorecon -RFv /etc

restorecon 是否存在、SELinux 是否启用以及策略位置取决于发行版。不能把 SELinux 标签当作普通文件权限处理。

2. 块级恢复

块级恢复适用于完整文件系统或整机恢复:

  1. 确认目标磁盘和逻辑卷;
  2. 停止使用目标文件系统;
  3. 卸载目标文件系统;
  4. 恢复镜像;
  5. 执行文件系统检查;
  6. 检查 UUID、挂载点和容量;
  7. 挂载到临时目录;
  8. 验证关键文件;
  9. 再启动应用。

如果镜像来自加密容器,恢复顺序还包括:

磁盘或分区
  → LUKS 解锁
    → LVM 激活
      → 文件系统检查
        → 挂载

LUKS 容器头部包含解密所需的元数据。只备份容器内部文件系统而丢失容器头部、密钥或恢复口令,可能无法重新打开数据。

3. 系统级恢复还需要配置和引导链

根文件系统恢复后,系统仍可能无法启动。需要检查:

cat /etc/fstab
lsblk -f
findmnt

/etc/fstab 中如果使用 UUID,恢复到新设备后 UUID 可能变化;如果使用设备名,设备枚举顺序也可能变化。应以实际 lsblk -f 结果为准。

UEFI 系统还需要 EFI System Partition。某些恢复场景需要重新安装或修复引导程序并重建 initramfs,具体命令依赖发行版和引导方案。不要把“根分区文件已恢复”误认为“整机已恢复”。


十二、恢复演练:验证 RTO,而不是演示命令

1. 演练必须隔离生产环境

恢复演练应使用:

  • 独立虚拟机;
  • 独立网络或禁止对生产写入;
  • 测试数据库实例;
  • 与生产不同的服务地址;
  • 只读或复制出的备份介质。

最危险的演练方式是把恢复后的服务直接接入生产网络,导致:

  • 重复消费消息;
  • 数据库实例产生冲突;
  • DHCP、DNS 或主机名冲突;
  • 恢复数据反向覆盖生产数据。

2. 一个可执行的文件恢复演练

准备一个测试目录:

sudo rm -rf /tmp/backup-lab
sudo mkdir -p /tmp/backup-lab/source /tmp/backup-lab/restore
sudo sh -c 'printf "important\n" > /tmp/backup-lab/source/a.txt'
sudo chmod 640 /tmp/backup-lab/source/a.txt

创建归档:

sudo tar \
  --create \
  --file=/tmp/backup-lab/data.tar \
  --acls \
  --xattrs \
  --numeric-owner \
  -C /tmp/backup-lab source

删除源文件,模拟误删:

sudo rm /tmp/backup-lab/source/a.txt

恢复:

sudo tar \
  --extract \
  --file=/tmp/backup-lab/data.tar \
  --directory=/tmp/backup-lab/restore

验证内容和权限:

cat /tmp/backup-lab/restore/source/a.txt
stat -c '%A %U:%G %n' /tmp/backup-lab/restore/source/a.txt

预期输出应包含:

important
-rw-r-----

这个实验只验证普通文件、权限和归档读取。生产演练还应加入 ACL、扩展属性、硬链接、稀疏文件、符号链接、加密密钥和应用启动检查。

3. 演练记录什么

一次有价值的演练至少记录:

  • 使用的备份时间点;
  • 备份大小和校验结果;
  • 解密耗时;
  • 恢复耗时;
  • 目标环境准备耗时;
  • 恢复后的文件数量和关键校验;
  • 应用启动错误;
  • 需要人工介入的步骤;
  • 实际 RPO 和 RTO;
  • 与目标的差距及其原因。

如果恢复依赖某个人记忆中的命令,系统就没有真正形成可重复的恢复能力。


十三、常见失败表现和诊断路径

1. “备份成功,但恢复后权限错误”

检查备份时是否包含:

rsync -A -X
tar --acls --xattrs

还要检查:

  • UID/GID 是否跨主机变化;
  • 服务是否以不同用户运行;
  • SELinux 或其他安全模块标签是否恢复;
  • 文件 capability 是否保留;
  • 目标文件系统是否支持相应属性。

2. “快照创建成功,但后续备份读不出来”

检查:

sudo lvs -a -o lv_name,origin,lv_attr,data_percent,metadata_percent

重点关注快照数据区或元数据区是否接近上限。快照不是静态文件,源卷继续写入会不断消耗 COW 空间。

3. “恢复出的数据库能启动,但数据不完整”

这通常说明恢复的是:

  • 错误时间点;
  • 没有配套日志;
  • 非应用一致性的文件副本;
  • 不完整的逻辑备份。

应查看数据库日志、备份工具的校验结果和日志归档连续性,而不是仅以进程能否启动为判断标准。

4. “dd 很慢或遇到 I/O 错误”

先检查内核和设备错误:

dmesg -T | tail -n 100
sudo smartctl -a /dev/sdb

smartctl 需要安装 smartmontools,并不适用于所有虚拟设备。读错误时不要直接用 conv=noerror 掩盖问题;应判断这是抢救任务还是正常备份任务。

5. “恢复后空间没有变大”

块镜像恢复只恢复了原文件系统当时的大小。即使目标逻辑卷更大,也需要执行与文件系统匹配的扩容操作。先检查:

lsblk
df -hT

lsblk 显示块设备大小,df 显示文件系统可用空间,二者不同并不一定是故障。


十四、一个合理的分层方案

针对常见 Linux 服务,可以把备份设计成以下层次:

配置和脚本
  └── 文件级版本备份

用户上传和业务文件
  └── 文件级增量备份 + 多个历史版本

数据库
  └── 官方物理或逻辑备份 + 日志归档

整机或关键文件系统
  └── LVM/Btrfs/ZFS 快照 + 独立介质导出

灾难恢复
  └── 分区、引导、加密元数据、LVM 元数据和恢复文档

每一层解决不同问题:

  • 文件级备份提供细粒度恢复;
  • 应用级备份提供应用一致性;
  • 块级备份提供完整文件系统恢复;
  • 快照提供快速时间点访问;
  • 加密降低备份介质泄露风险;
  • 恢复演练证明这些组件可以组合工作。

真正的备份状态不是“定时任务执行过”,而是:

恢复能力=可读副本×正确时间点×完整元数据×可用密钥×可执行恢复流程\text{恢复能力} = \text{可读副本} \times \text{正确时间点} \times \text{完整元数据} \times \text{可用密钥} \times \text{可执行恢复流程}

其中任意一项为零,最终恢复结果都可能为零。 Linux 的 rsynctardd、LVM 快照和文件系统工具只是实现手段;备份系统的边界,最终由一致性、故障隔离和实际恢复结果决定。


系列导航与关联阅读

官方资料

本文依据 Linux 内核、systemd 与主流发行版官方文档重新梳理;正文与实验由 WR BLOG 编写。