LabHub
学习 学习路径 课程

备份与恢复

用 rsync 组织多代备份

在 LabHub 中继续学习

目标

使用 rsync 配置 mirror sync 与 generation backup,并以实际运维形式加入 --delete guard 和负载控制。

为什么重要

--link-dest generation backup 是 rsync 最优雅的功能之一。每一代目录看起来都像完整 snapshot,但磁盘只占用变更部分。其原理是 hard link,因此用 du 测量时会比预期小得多——相同 inode 不会被重复计算。

而且,--delete 必须配合 guard 使用。如果 source 为空或 mount 已断开时执行,它会删除 target 中的全部数据。这是现实中反复发生的事故。

步骤

  1. /root/rs/src 下创建 app/main.pyconf/app.yamllogs/app.logtmp/cache.bin,并同步到 /root/rs/dst/。最终必须存在 /root/rs/dst/app/main.py
  2. 使用 dry-run + itemize 执行并保存到 /root/rs/itemize.txttarget 必须是尚未同步过的新目录——如果再次指向第 1 步的 dst/,由于状态已经相同,rsync 不会输出任何行,文件会为空。itemize 用来查看“什么会因为什么原因改变”,因此必须先有待变更内容。
  3. 编写 /root/rs/backup.sh。以 set -euo pipefail 开始;source 为空时以退出码 1 中止;使用 --delete/root/rs/src/ 同步到 /root/rs/mirror/。source 路径必须通过第一个参数接收,没有参数时使用 /root/rs/src/——评分器会把空目录作为第一个参数传入,检查 guard 是否真的执行。运行脚本创建 /root/rs/mirror
  4. /root/rs/exclude.txt 中写入排除模式(logs/tmp/),并使用它同步到 /root/rs/clean/。不得存在 /root/rs/clean/logs/root/rs/clean/tmp
  5. 创建 generation backup。先完整同步到 /root/rs/gen/daily.1/,再通过 --link-dest 创建 /root/rs/gen/daily.0/。未变更文件必须在两代中共享同一个 inode。
  6. 使用 --numeric-ids 同步到 /root/rs/numeric/,并把 app/main.py 的 owner UID 作为单独一行数字写入 /root/rs/uid.txt
  7. 编写 /root/rs/throttled.sh。其中的 rsync 命令必须同时包含 ionicenice--bwlimit 三个元素。运行脚本创建 /root/rs/slow/
  8. 创建包含以下 4 行的 /root/rs/report.txtMIRROR_FILES=<mirror 안 파일 수> / CLEAN_HAS_LOGS=no / SHARED_INODE=yes / GEN_COUNT=2

参考

执行基础 mirror sync

/root/rs/src 下创建 app/main.pyconf/app.yamllogs/app.logtmp/cache.bin,并同步到 /root/rs/dst/。最终必须存在 /root/rs/dst/app/main.py

记住 trailing slash 规则。target 内不应再出现 source 目录名称。

显示变更明细

使用 dry-run + itemize 执行并保存到 /root/rs/itemize.txttarget 必须是尚未同步过的新目录——如果再次指向第 1 步的 dst/,由于状态已经相同,rsync 不会输出任何行,文件会为空。itemize 用来查看“什么会因为什么原因改变”,因此必须先有待变更内容。

dry-run 与 itemize 一起使用时,会逐行显示哪些内容将如何改变。

编写带 guard 的 backup 脚本

编写 /root/rs/backup.sh。以 set -euo pipefail 开始;source 为空时以退出码 1 中止;使用 --delete/root/rs/src/ 同步到 /root/rs/mirror/。source 路径必须通过第一个参数接收,没有参数时使用 /root/rs/src/——评分器会把空目录作为第一个参数传入,检查 guard 是否真的执行。运行脚本创建 /root/rs/mirror

source 为空时必须立即停止,并通过退出码区分该情况。

创建排除列表文件

/root/rs/exclude.txt 中写入排除模式(logs/tmp/),并使用它同步到 /root/rs/clean/。不得存在 /root/rs/clean/logs/root/rs/clean/tmp

把 pattern 集中在文件中更容易管理,每行写一个 pattern。

创建 generation backup

创建 generation backup。先完整同步到 /root/rs/gen/daily.1/,再通过 --link-dest 创建 /root/rs/gen/daily.0/。未变更文件必须在两代中共享同一个 inode。

为 --link-dest 使用绝对路径更安全。可通过 link count 验证。

保留数字 owner

使用 --numeric-ids 同步到 /root/rs/numeric/,并把 app/main.py 的 owner UID 作为单独一行数字写入 /root/rs/uid.txt

有一个选项可使用数字 ID 而不是名称,这是为恢复到不同系统做准备。

控制负载

编写 /root/rs/throttled.sh。其中的 rsync 命令必须同时包含 ionicenice--bwlimit 三个元素。运行脚本创建 /root/rs/slow/

同时使用 bandwidth 限制选项和降低磁盘优先级的命令,并把完整命令保存在脚本中。

生成各代容量报告

创建包含以下 4 行的 /root/rs/report.txtMIRROR_FILES=<mirror 안 파일 수> / CLEAN_HAS_LOGS=no / SHARED_INODE=yes / GEN_COUNT=2

由于 hard link,du 的结果会比预期小,而这一事实本身就是本实验的结论。