btrbk备份到非btrfs存储:LUKS与GPG加密方案实战 早先我用 btrbk 一直把它当做一个“高级 cron 包装器”每天对几个 btrfs 子卷拍快照再靠 btrfs send/receive 把增量推到另一块 btrfs 盘上省心得很。直到某天要把冷备放到一块普通移动硬盘上——那块盘是 ext4不是 btrfs——我才发现事情没那么简单btrfs receive在非 btrfs 目标上根本没法用而 btrbk 确实能在这种目标上生成归档文件但这些文件本质就是 btrfs send 的原始流里面完全是明文数据。谁拿到这个流文件谁就能用一条btrfs receive命令把整份数据还原出来。这篇文章就来拆解这个痛点怎么用 btrbk 把数据安全备份到非 btrfs 存储设备并且把“加密”这件事做成可复现的实操。适合正在用 btrfs、又需要把冷备放到普通硬盘、移动盘、网络盘上的朋友。1. 为什么 btrbk 备份到非 btrfs 存储是个问题1.1 btrbk 的正常工作方式快照 send/receivebtrbk 的设计思路很清晰在同一块 btrfs 卷里先做只读快照然后通过btrfs send/receive把快照复制到另一个 btrfs 文件系统。快照是子卷的 COW 副本创建时几乎瞬间完成也不会马上占额外空间。而 send/receive 最厉害的是支持增量传输指定一个父快照只发送两个时间点之间发生变化的数据块这样每日备份的 IO 压力可以压得很低。打个比方快照相当于给整个目录拍了一张“状态照片”send/receive 相当于把照片集同步到另一本相册。后续的备份只需要把新拍的差异照片寄过去但前提是整本相册的序列必须完整。btrbk 负责的就是维护这套序列什么时候拍快照、拍完往哪送、送完保留多久、什么时候清理旧快照全部按配置自动执行。如果目标是另一块 btrfs 盘这套机制跑得非常顺。但问题也恰恰出在这里btrfs receive的接收端必须是一个 btrfs 文件系统而你手头的移动硬盘、NAS 共享目录、对象存储挂载点往往是 ext4、xfs、exFAT 甚至 NTFS。1.2 非 btrfs 目标把快照变成流文件当目标路径所在的文件系统不是 btrfs 时btrbk 会走“归档archive”路线它调用btrfs send把快照导出成字节流然后作为普通文件直接写到目标目录里。文件后缀通常是.btrfs如果你开了流压缩则会变成.btrfs.gz或.btrfs.zst。从存储介质的角度看这时候的非 btrfs 目标只是一个“文件保管箱”箱子里装的是 btrfs 快照的流式描述而不是块设备镜像。恢复的时候需要把这个流文件送回一个真正的 btrfs 环境里执行btrfs receive来重建子卷。这里有个关键认知流文件是逻辑文件系统的完整描述包含文件内容、目录结构、扩展属性和元数据。压缩不等于加密.btrfs.gz只是变小了内容依然是明文任何拿到文件的人都能解压后用btrfs receive还原。所以如果你打算把这些归档放到没有物理隔离保护的硬盘上比如随身携带的移动盘、送修的整机磁盘、同步到云盘的目录加密是必须考虑的一步。1.3 流文件是明文的安全风险从这里开始可能有人觉得备份数据本身就是私有的谁会去翻一块旧硬盘但实际情况是移动硬盘遗失、设备返厂维修、服务器退役后硬盘被转卖这些都是真实发生过的数据泄露路径。一个.btrfs流文件泄露造成的后果和一份未加密的 tar 包泄露没有本质区别拿到文件的人不需要知道你的系统密码不需要挂载你的源盘只要有一个 Linux 环境、装上 btrfs-progs执行btrfs receive就能把整个子卷还原。也就是说你的文件内容会以完整形态出现在对方手里。所以接下来要做的不是讨论“要不要加密”而是“在哪一层加密”。加密层放得越靠下对应用层越透明操作越省心加密层放得越靠上灵活性越高代价是自动化和管理复杂度上升。下一章我列出几种方案再给一套我实际用下来的选型建议。2. 加密方案的选型思路2.1 第一选择LUKS 全盘加密LUKS 是 Linux 上最成熟的全盘加密方案它在块设备层工作。也就是说数据在写入物理磁盘之前已经被 dm-crypt 加密挂载解锁之后对上层应用完全透明。对 btrbk 来说LUKS 加密盘和普通盘没有任何区别你照样创建一个文件系统照样在某个挂载点下建目录btrbk 照常写归档文件。它不需要知道下面有加密层也不需要额外配置任何密码。这是我最推荐的方式尤其是本地冷备盘和移动盘。LUKS 的代价也很实在每次插上盘都要手动解锁输入口令后盘才能挂载如果 LUKS 头部损坏或者口令丢失数据基本无法找回。所以它适合“你知道自己要保管好钥匙”的场景不适合那种随手插到别人机器上的共享盘。2.2 第二选择GPG 文件级加密GPG 加密的粒度在文件层。你可以对每个归档文件做对称加密也可以生成一套公钥/私钥用公钥加密归档私钥解密恢复。文件级加密的好处是灵活目录可以放在任何地方包括对象存储、FTP、别人的服务器单个加密文件泄露出去了没有口令或私钥也还原不出内容。坏处是自动化时密钥管理比较麻烦而且增量备份链中的任何一个加密文件损坏都会影响后续文件的恢复。如果你的目标是“把 btrbk 归档上传到远程不可信存储”那 GPG 是比 LUKS 更合理的选择因为远程那块磁盘你没法控制它是否需要解锁、谁来解锁。2.3 方案对比与我的建议维度LUKS 全盘加密GPG 文件级加密加密位置块设备层文件层对 btrbk 的影响透明无需改配置需要脚本或事后处理适用目标本地移动盘、冷备盘远程目录、对象存储、共享存储恢复复杂度先解锁再按序 receive先解密再按序 receive密钥管理LUKS 口令/密钥文件GPG 口令或密钥对单文件泄露影响无影响整体仍然加密该文件内容可被解密但链上其他文件仍安全我的建议很简单本地物理介质用 LUKS远程网络存储用 GPG也可以两者叠加。实际操作中我主力备份方案是 LUKS 加密移动硬盘加 btrbk 归档下面这一章就是完整的操作过程。3. 实战LUKS 加密移动硬盘 btrbk 归档3.1 安装环境与准备先说环境一台 Linux 机器内核支持 dm-crypt源数据所在磁盘是 btrfs安装了 btrfs-progs、cryptsetup 和 btrbk。不同发行版包名略有差异但大致就是这三个包。安装完之后确认一下 btrbk 版本和 btrfs 工具可用btrbk --version btrfs --version cryptsetup --version然后确认源子卷和快照目录都在同一个 btrfs 文件系统上。我的机器上btrfs 池挂在/mnt/btr_pool快照目录是/mnt/btr_pool/.snapshots打算备份的子卷是home这是后面配置里要反复用到的路径。再确认目标移动硬盘还没有挂载或者你确定要拿它做备份盘。这一步非常重要因为下面这条命令会清空整块盘。3.2 创建 LUKS 加密盘先找到设备名。插上移动硬盘后执行lsblk根据容量和厂商信息确认是哪一块。假设是/dev/sdb然后执行sudo cryptsetup luksFormat /dev/sdb这条命令会提示你输入大写的 YES然后设置 LUKS 口令。口令要足够长建议至少 20 位别用生日和常见短语。确认无误后把加密盘打开并映射到/dev/mapper/coldbackupsudo cryptsetup open /dev/sdb coldbackup sudo mkfs.ext4 /dev/mapper/coldbackup注意我这里故意创建的是 ext4而不是 btrfs。因为我们的目标就是演示“非 btrfs 存储设备”。如果你不小心把目标格式化成 btrfsbtrbk 会按 send/receive 模式走那就看不到归档文件的效果了。接着挂载并验证文件系统类型sudo mkdir -p /mnt/coldbackup sudo mount /dev/mapper/coldbackup /mnt/coldbackup findmnt /mnt/coldbackup -o FSTYPE输出应该是ext4。到这一步你已经有一块 LUKS 加密的移动盘了里面的文件系统是 ext4btrbk 会把快照流以普通文件形式写进去。提示创建 LUKS 后立刻备份 LUKS 头部。头部损坏比忘记口令更麻烦口令忘了只是打不开头部损坏连重建机会都没有。备份命令是sudo cryptsetup luksHeaderBackup /dev/sdb --header-backup-file /root/luks-header-backup.bin把备份文件放到另一块盘上。3.3 编写 btrbk 配置我用的 btrbk 配置文件在/etc/btrbk/btrbk.conf。核心配置如下你可以根据自己的子卷名和路径调整# /etc/btrbk/btrbk.conf # 全局目标路径这里指向 LUKS 解密后的 ext4 挂载点 target /mnt/coldbackup/btrbk # 归档到非 btrfs 目标时对流文件做 gzip 压缩 stream_compress gzip # 保留策略 snapshot_preserve_min 2d snapshot_preserve 14 target_preserve_min 7d target_preserve 90 # 要备份的卷 [home] volume /mnt/btr_pool snapshot_dir /mnt/btr_pool/.snapshots subvolume home配置里几个关键点target必须指向一个非 btrfs 路径也就是/mnt/coldbackup下面的子目录。btrbk 检测到目标文件系统不是 btrfs 时会自动走归档模式把快照导出成流文件。如果你希望显式管理也可以在执行命令时直接用btrbk archive。stream_compress gzip是给归档文件加一层压缩。对于文本、代码、日志这类数据gzip 压缩率通常不错如果是已经压缩过的视频、图片收益就很小这时可以把这一行去掉。snapshot_preserve_min 2d的意思是源机上至少保留 2 天内的所有快照snapshot_preserve 14是最多保留最近 14 个快照。target_preserve_min 7d和target_preserve 90对应目标端归档文件的保留策略。3.4 执行备份与验证先检查配置是否正确再跑一次完整备份sudo btrbk -v config sudo btrbk runrun会按配置完成“拍快照 归档”整个流程。跑完之后到目标目录看看生成的文件find /mnt/coldbackup/btrbk -type f | head正常情况下会看到类似这样的归档文件/mnt/coldbackup/btrbk/home/home-2025-01-01.btrfs.gz /mnt/coldbackup/btrbk/home/home-2025-01-02.btrfs.gz文件名里的日期就是快照时间。此时物理盘上的数据已经由 LUKS 加密拔掉移动硬盘后即使别人拿到盘也只能看到一堆无法解读的密文区块。只有输入 LUKS 口令、挂载成 ext4 之后才能看到这些.btrfs.gz文件。一个容易忽略的细节LUKS 保护的是块设备层但不是运行中的系统层。一旦盘被解锁并挂载系统内有权限的用户就能直接读明文归档流。所以加密盘不是“贴上就安全”它仍然依赖登录权限和物理保管。如果一台机器本身已经被攻破LUKS 和 GPG 都救不了你它保护的是“盘丢了”这种场景。3.5 从加密归档恢复到 btrfs备份只是半个故事恢复才是另一半。我把恢复流程也完整走一遍这样你确认整个链路是通的。准备一个空的目标 btrfs 文件系统比如新盘上的 btrfs 分区挂载到/mnt/restore。然后把移动硬盘插上解锁并挂载sudo cryptsetup open /dev/sdb coldbackup sudo mount /dev/mapper/coldbackup /mnt/coldbackup接着进入归档目录按时间顺序恢复。如果归档文件是 gzip 压缩的需要先解压再喂给btrfs receivecd /mnt/coldbackup/btrbk/home for f in home-*.btrfs.gz; do echo restore $f gzip -dc $f | sudo btrfs receive /mnt/restore done拆开这条管道理解gzip -dc解压输出原始 btrfs send 流btrfs receive /mnt/restore从标准输入读取流并在目标 btrfs 上重建子卷。如果归档没有压缩文件名没有.gz就直接用btrfs receive -f home-2025-01-01.btrfs /mnt/restore。恢复完成后用btrfs subvolume list /mnt/restore查看生成的子卷然后挂载它检查文件是否完整。我习惯跑一个diff -r把源子卷当前内容与恢复出来的子卷做对比确认关键目录没问题。注意多个归档文件之间的顺序不可以乱。对 btrfs 来说后一个流文件通常依赖前一个充当父快照跳过一个会导致后续全部无法恢复。这也是为什么保留策略里不能让目标归档被过早清理。4. 进阶GPG 文件级加密归档4.1 先归档再加密还是管道即时加密LUKS 方案很省心但它依赖一个前提你能控制目标存储介质。如果备份目标是云盘同步目录、公司文件服务器、或者一台不在你手里的远程主机LUKS 就不太现实了因为你没法要求远程也做全盘加密更不想把解锁口令交给远程环境。这时候可以把加密下沉到文件层用 GPG 对每个归档流加密。有两种处理方式第一种是先让 btrbk 生成明文归档再用 GPG 加密并删除明文。优点是简单缺点是明文归档会在目标盘上存在一段时间如果这个期间盘被别人拿走数据就泄露了。第二种是用管道把btrfs send的输出直接送进 GPG明文流不落盘。代价是自动化脚本要自己管理增量链和快照列表不能完全依赖 btrbk 的归档命令。我建议优先用第二种因为加密备份的意义就在于“流不落地”。4.2 管道命令实战先准备一个口令文件。GPG 对称加密需要一个 passphrase我习惯用随机长字符串openssl rand -base64 48 /root/backup.key chmod 600 /root/backup.key然后看源快照。假设有两个快照/mnt/btr_pool/.snapshots/home-2025-01-01 /mnt/btr_pool/.snapshots/home-2025-01-02第一个是完整备份第二个可以基于第一个做增量。完整备份的命令是sudo btrfs send /mnt/btr_pool/.snapshots/home-2025-01-01 \ | zstd -1 \ | sudo gpg -c --batch --cipher-algo AES256 --passphrase-file /root/backup.key \ /mnt/remote_backup/home-2025-01-01.btrfs.zst.gpg增量备份则指定父快照sudo btrfs send -p /mnt/btr_pool/.snapshots/home-2025-01-01 /mnt/btr_pool/.snapshots/home-2025-01-02 \ | zstd -1 \ | sudo gpg -c --batch --cipher-algo AES256 --passphrase-file /root/backup.key \ /mnt/remote_backup/home-2025-01-02.btrfs.zst.gpgzstd -1是压缩速度比较快的档位如果你想要更高压缩率可以换成zstd -5甚至xz但耗时和 CPU 占用会明显上升。gpg 命令里的--passphrase-file是批处理模式必需的它让脚本不需要交互输入口令。这条管道的安全性在于任何一步只要不是明文文件就永远不会在磁盘上形成可读的.btrfs流。中间确实有进程间的管道传递明文但普通用户拿不到另一个进程的管道内容和 LUKS 方案在解锁盘上写明文是不同层面的风险。4.3 解密与恢复恢复就是管道反过来。先找到加密归档然后sudo gpg -d --batch --passphrase-file /root/backup.key /mnt/remote_backup/home-2025-01-01.btrfs.zst.gpg \ | zstd -d \ | sudo btrfs receive /mnt/restore增量文件也按顺序执行同样的操作先恢复最老的完整备份再恢复后续的增量文件。GPG 解密失败时常见原因是口令文件内容不一致比如换行符被编辑器改过或者从 Windows 复制文件时被加了 CRLF。遇到这种情况优先检查head -c 10 /root/backup.key | xxd看内容是否正常。4.4 与 btrbk 结合的做法这里要提一句我的 btrbk 版本里归档模式主要提供流压缩加密并不是内置默认功能。如果你也想保持“btrbk 管快照、加密自己管”的节奏通常有两种做法。做法一用 btrbk 负责创建快照和清理然后用脚本对最新快照执行btrfs send | zstd | gpg管道。脚本要自己记录上一次成功备份的快照名作为下一次的父快照。快照尽量在 btrbk 的保留策略里多留一段时间一旦脚本出错你有机会手动补一条增量而不是从零全量。做法二btrbk 正常归档到本地一个临时目录生成明文.btrfs.gz随后立即用 gpg 加密并删除明文文件。这个方案改动最小但明文文件会短暂存在适合本地目录本身可信任、只是远程同步不可信的场景。无论哪种做法都要记住增量链的完整性是自己维护的。btrbk 的snapshot_preserve只会根据保留策略清理源快照如果脚本某一次失败了而旧的父快照已经被清理掉那下一次就只能做全量备份或者干脆报错。所以自动化脚本一定要有失败告警别把链跑断。5. 常见问题与避坑实录5.1 目标盘不是 btrfs 导致的报错btrbk 检测到目标路径不是 btrfs 时不同版本的提示和处理方式略有差别。我遇到过最典型的现象是配置好 target 后执行btrbk run日志里出现类似 “not a btrfs filesystem” 的报错。排查思路是先确认目标路径的文件系统类型findmnt /mnt/coldbackup -o TARGET,FSTYPE如果确认是 ext4 或其他非 btrfs 类型那就明确走归档模式。部分版本需要你在配置里把target_type设为 archive或者直接执行btrbk archive而不是btrbk run。最稳妥的做法是btrbk -v run看详细日志它会告诉你它想把命令当作 send 还是 archive 处理。5.2 增量链断裂与保留策略增量链断裂是最隐蔽的坑。现象是某次备份突然变成了全量占用大量空间或者恢复时提示找不到父快照。原因基本都是源端快照被清理得太早。比如你设置snapshot_preserve_min 1d但备份任务因为停电连续三天没跑第四天恢复时前一天的快照已经在清理范围之外btrbk 找不到可用的父快照只能从头全量发一遍。对策是让保留策略留出足够余量。我的经验是snapshot_preserve_min至少是备份间隔的两倍比如每日备份就设2dtarget_preserve_min同理保证目标端始终有可用的增量起点。别把保留时间卡得太紧备份这行余地比效率重要。5.3 LUKS 解锁失效怎么办LUKS 解锁失败最常见的原因是口令输错、键盘布局不同、或者用了错误的密钥文件。可以先试试清除键盘映射干扰用sudo cryptsetup open /dev/sdb coldbackup交互式输入。如果 LUKS 头部本身有问题系统会提示找不到密钥槽。这时候之前备份的 header 文件就派上用处了sudo cryptsetup luksHeaderRestore /dev/sdb --header-backup-file /root/luks-header-backup.bin注意luksHeaderRestore 会覆盖当前头部信息执行前务必确认这是一块确定性的备份盘不要拿错盘。头部恢复之后再用原口令打开通常就能救回数据。另外移动硬盘在备份过程中被强制拔出或者写入时断电最容易导致文件系统损坏。btrbk 写归档不是单条原子操作中断后可能会留下半截文件。所以写完归档后多做一步校验至少确认文件大小和 gzip 完整性gzip -t /mnt/coldbackup/btrbk/home/home-2025-01-02.btrfs.gz5.4 权限、定时任务与恢复演练的重要性btrfs send/receive 和 cryptsetup 都需要 root 权限所以 cron 任务里记得用 root 执行。如果用了 GPG 口令文件文件权限别给大chmod 600是底线。还有一点容易被忽略cron 的环境变量很少PATH里可能没有 sbin 目录。写定时任务时最好用绝对路径或者脚本开头export PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin否则会出现“手动执行成功cron 里一直失败”的灵异现象。最后恢复演练必须做。备份做了一百次恢复一次都没跑过等于没备份。我每次调整加密方案后都会找一台测试机把归档流完整恢复到空 btrfs 盘上然后对比文件列表。恢复流程如果没写成文档插盘几年后再操作会非常慌写成脚本之后心里就有底了。5.5 快速排查表现象可能原因处理方式btrbk 报错 target 不是 btrfs目标文件系统类型不符归档模式未触发确认 FSTYPE改用btrbk archive或显式配置归档归档恢复时btrfs receive失败流文件顺序颠倒、中间缺文件、目标路径不是 btrfs按时间顺序重试检查目标盘文件系统gpg 解密失败口令文件不一致、密钥 ID 错误检查 passphrase 文件和密钥环.btrfs.gz解压 CRC 错误文件损坏或传输不完整用gzip -t定位重新生成该归档某次备份突然变成全量源快照保留不足父快照被清理调大snapshot_preserve_min检查上一次备份时间cron 里 btrbk 没输出PATH 不全、环境变量不同用绝对路径脚本开头手动 export PATH最后提醒一句备份链路上的每一个环节都要演练过才算数。我第一次做 LUKS 加归档恢复时因为没按顺序喂流文件恢复出来的子卷缺了不少文件后来把恢复流程写成一个脚本每季度跑一次冷备恢复演练。加密备份的最终目标不是加密本身而是需要的那一天你能把数据原封不动拿回来。