本文来自依云's Blog,转载请注明。
序
目标:增量式备份整个系统。
怎么做到增量呢?rsync + btrfs 快照。其实只用 rsync 也是可以做到增量式的1,但是支持子卷的 btrfs 可以做得更好:
- 快速删除旧的备份
- 更简单的备份逻辑
- 子卷可以设置成只读(这是个很重要的优点哦~)
- btrfs 支持压缩。系统里有好多文本文件的,遇到压缩效果不好的文件 btrfs 会自动放弃压缩
为什么要备份整个系统呢?——因为配置一个高度定制化的系统麻烦啊,只备份部分数据的话还可能漏掉需要的文件。另一个优点是可以直接启动到某个备份。
备份了整个系统,包括各种公开或者隐私的数据,一堆 cookies 和帐号配置,邮件和聊天记录等。难道就不需要加密一下下吗?于是,在 btrfs 之下再加一层 dm-crypt 加密。
介绍完毕,下边进入正题。
准备工作
首先,需要一个足够新的 Linux 内核,因为 btrfs 还是「实验特性」,每个版本都会有大量改进。如果用比较旧的内核就有可能出事。我用的是 3.12.6 版本。其次,安装 rsync 和 cryptsetup。
当然还要准备硬件:一块希捷 BackupPlus 1T USB 3.0 移动硬盘,以及一枚Express Card 34mm 转 USB 3.0 扩展卡,因为我的笔记本没有 3.0 的接口。注意使用 USB 3.0 扩展卡,内核需要载入 pciehp 模块,否则会出现不能识别 3.0 的设备或者后续接上去的设备的情况。Arch 官方内核将这个模块直接编译进内核了,而我自己编译的很不幸没有,只好重新编译了下内核。
PS: 这俩家伙一起配合工作,写入峰值能达到 110MiB/s,比我笔记本自身的硬盘还要快。
然后是分区、格式化。我使用了 GPT 分区表。为了安装 grub 以便启动,最好在开头分配 2M 空间给 grub 使用,不然会很麻烦2。记得给这个分区 bios_grub 标志(GParted「管理标志」里勾上即可)。下一个分区是 ext4 格式的启动分区。我会在这里放一个 Arch Linux Live 系统用于维护任务,以及用于启动到备份的内核和 initramfs。因为备份的分区会被加密,所以必须把内核和 initramfs 放在另外的地方。接下来的一个分区放加密过的备份数据用的。
像这样初始化加密分区:
cryptsetup luksFormat /dev/sdc3
密码要长,但也一定要记住密码,因为除了穷举外是没有办法恢复的。
初始化完毕之后就可以使用密码打开该设备了:
cryptsetup open /dev/sdc3 lilybackup
最后的参数是一个名字,它会是解密后的设备在 /dev/mapper
下的文件名。
如果一切完毕,要记得(在卸载文件系统之后)关闭该设备:
cryptsetup luksClose lilybackup
dm-crypt 是块设备级的加密。我们还要在其上建立文件系统:
mkfs.btrfs /dev/mapper/lilybackup
然后挂载之,并建立相应的目录和子卷结构。比如我的:
* backup (dir) * home (dir) * current (subvol, rw) * 20131016_1423 (subvol, ro) * 20131116_2012 (subvol, ro) * ... * root (dir) * current (subvol, rw) * 20131016_1821 (subvol, ro) * 20131116_2128 (subvol, ro) * ... * run, for boot up directly, with edited /etc/fstab (dir) * home (dir) * 20131116 (subvol, rw) * ... * root (dir) * 20131116 (subvol, rw) * ... * etc, store information and scripts (subvol, rw)
我把备份数据放到 backup 目录下,/ 和主目录 /home/lilydjwg 分开备份的。每个备份是使用日期和时间命名的快照子卷。除了用于每次同步的 current 目录外其它的子卷都是只读的,以免被意外修改。在 run 目录下是用于直接运行的,可写。这些可以按需建立。
开始备份
这是我备份 / 使用的脚本。是一个 zsh 脚本,这样可以避免 bash 中特殊字符可能带来的问题,虽然 bash 有 shellcheck 可以静态分析出可能有问题的地方。
这个脚本带两到三个参数。第一个是 / 的位置,因为我一般会直接从运行的系统执行备份,但也有可能使用另外的维护系统(比如系统滚挂掉的时候)。第三个参数是用于确认操作的。不加它的话会以 --dry-run
参数来运行 rsync。rsync 很复杂,所以最好先演习一遍以避免不小心手抖了做错事 =w=
为了避免日后对照着 rsync 手册来揣摸每个单字母选项的意义,我在这里全部使用了选项的完整形式。反正我是 zsh 用户,那么长的命令中大部分字符都是 zsh 给我补全出来的 =w=
#!/bin/zsh -e cd $(dirname $0) if [[ $# -lt 2 || $# -gt 3 ]]; then echo "usage: $0 SRC_DIR DEST_DIR [-w]" exit 1 fi src=$1 dest=$2 doit=$3 if [[ $doit == -w ]]; then dry= else dry='-n' fi rsync --archive --one-file-system --inplace --hard-links \ --human-readable --numeric-ids --delete --delete-excluded \ --acls --xattrs --sparse \ --itemize-changes --verbose --progress \ --exclude='*~' --exclude=__pycache__ \ --exclude-from=root.exclude \ $src $dest $dry
比较重要的几个 rsync 选项:
-
--archive
- 我们要备份,所以请保留所有信息
-
--one-file-system
- 只备份这个文件系统的内容,不要跑到 /sys 啊 /proc 啊 /dev 啊 /tmp 这类目录里去了。这也省得自己手动排除
-
--numeric-ids
- 文件的所有者信息使用数字而不要解析成用户名/组名。避免在跨系统使用时出差错
-
--exclude-from=root.exclude
-
从
root.exclude
文件中读取额外的排除列表 -
--acls --xattrs
- 保留文件 ACL 和扩展属性
我发现的 / 里需要排除的目录如下:
/var/cache/*/* /var/tmp/ /var/abs/local/ /var/lib/mongodb/journal/
其中第一项写成那样是因为,我要保留 /var/cache
下的一级目录。
主目录的备份是类似的过程,只是更加复杂罢了。当我写我的主目录的备份脚本的时候,深切地体会到有圣人说过的一句话——过早的优化是万恶之源。因为我使用 eCryptfs 加密主目录时为了避免可以公开的文件被加密造成性能损失,做了一系列的软链接。它们一直在给我带来各种小麻烦和不爽……
注意这些脚本要以 root 的身份运行。待所有备份脚本跑完之后,对那个 current 子卷做一个只读快照就好了:
sudo btrfs subvolume snapshot -r current $(date +'%Y%m%d_%H%M')
下次要更新备份时是一样的步骤:跑同步脚本,创建新快照。第一次同步会比较慢,跑了一个多小时吧。后边的增量备份就比较快了,十几分钟就好。
从备份启动
要启动,首先把 grub 装过去。把 Arch Linux live 系统的配置写好,当然还有启动备份系统的配置,如下:
search --no-floppy --fs-uuid --set=root 090dcc64-2b6d-421c-8ef6-2ab3321aec62 menuentry "Archlinux-2013.12.01-dual.iso (x86_64)" { load_video set gfxpayload=keep insmod gzio insmod ext2 set isofile="/images/archlinux-2013.12.01-dual.iso" echo "Setup loop device..." loopback loop $isofile echo "Loading kernel..." linux (loop)/arch/boot/x86_64/vmlinuz archisolabel=ARCH_201312 img_dev=/dev/disk/by-label/lilyboot img_loop=$isofile earlymodules=loop echo "Loading initrd..." initrd (loop)/arch/boot/x86_64/archiso.img } menuentry "Archlinux-2013.12.01-dual.iso (i686)" { load_video set gfxpayload=keep insmod gzio insmod ext2 set isofile="/images/archlinux-2013.12.01-dual.iso" echo "Setup loop device..." loopback loop $isofile echo "Loading kernel..." linux (loop)/arch/boot/i686/vmlinuz archisolabel=ARCH_201312 img_dev=/dev/disk/by-label/lilyboot img_loop=$isofile earlymodules=loop echo "Loading initrd..." initrd (loop)/arch/boot/i686/archiso.img } set ver=3.12.6 menuentry "Arch Linux $ver backup" { load_video set gfxpayload=keep insmod gzio insmod ext2 echo 'Loading Linux kernel ...' linux /boot/vmlinuz-linux-lily-$ver root=/dev/mapper/lilybackup rw cryptdevice=/dev/disk/by-uuid/815d01ea-6390-460f-8c82-84c9e9497423:lilybackup rootflags=compress=lzo,subvolid=0 break=postmount echo 'Loading initramfs...' initrd /boot/initramfs-$ver-backup.img }
各个设备的卷标、UUID 和文件路径自己调整。最后一项需要的文件在后边准备,先介绍一下几个参数:
-
root
- 根分区所在的设备。是解密后的设备路径或者用 UUID 也可以。不过没关系的,这里不会有冲突的
-
cryptdevice
- 如果使用密码(而不是密钥文件)加密的话,这里是冒号分隔的两个参数:你的加密设备是哪个文件,以及它解密之后叫什么名字。
-
rootflags
-
这个是给我们的 btrfs 用的。指定要启用 lzo 算法压缩,使用根子卷。实际上根子卷里不是 Linux 系统的根。这里我只是让脚本把它挂载到
/new_root
上而已,脚本会因为找不到/sbin/init
而进入一个 shell 的 -
break=postmount
- 即使根没有问题,也请在挂载好它之后给我一个 shell,我可能需要做一些调整
内核很简单,直接 cp 过去就好了。initramfs 要另外生成。这是我用来生成的 mkinitcpio.conf.crypt 文件:
MODULES="btrfs" BINARIES="/usr/bin/btrfs" FILES="" HOOKS="base udev autodetect modconf block encrypt filesystems keyboard fsck shutdown" COMPRESSION="xz"
重要的地方:内核模块 btrfs
一定是要的,另外我还需要 btrfs
程序来操作子卷。在 HOOKS
数组的 filesystems
前添加了 encrypt
,用于在启动时询问密码并解码根分区。
我还准备添加 vi 程序来着,但是它说终端类型不认识,还说 /var/tmp
目录不存在。于是索性把自己静态链接的 vim 扔到内核一块去了。Vim 内建常见终端类型的数据,不那么挑剔的。zsh 所需要的文件太多,也放弃了。
然后使用 mkinitcpio 命令生成 initramfs 镜像:
sudo mkinitcpio -c mkinitcpio.conf.crypt -g initramfs-backup.img
然后把生成的文件复制到启动分区的相应路径下。
注意:如果你在 Arch Linux live 系统中为另外的内核生成该 initramfs,要指定内核和内核模块路径的根。一定不要将内核模块所在的目录软链接到/lib/modules
下,那样 mkinitcpio 不会添加任何块设备的内核模块的。我使用的命令如下:
mkinitcpio --kernel /run/archiso/img_dev/boot/vmlinuz-linux-lily-3.12.6 -r /mnt/backup/root/20131227_2044 --config mkinitcpio.conf -g /run/archiso/img_dev/boot/initramfs-3.12.6-backup.img
文件准备完毕,就可以启动过去了。注意我没有在备份分区的 run 目录下建立子卷,因为我准备进入 initramfs 之后再建立它们。
PS: 因为 BIOS 不支持,所以必须从 USB 2.0 来启动。在 Linux 内核启动的时候,可以将移动硬盘接到 USB 3.0 扩展卡上。具体时机是,initramfs 载入完毕,内核开始打印日志的时候。
进入备份系统
启动之后,会进入 initramfs 的 shell。在这个没有任务管理的 ash 中,使用 btrfs 命令在 /new_root/run
目录下建立新的子卷,注意不要加 -r
这个表示只读的选项了:
btrfs subvolume snapshot ../backup/root/20131016_1423 root/20131016 btrfs subvolume snapshot ../backup/home/20131016_1423 home/20131016
因为文件系统树的挂载结构变了,所以得拿准备好的 vim(或者 vi,如果你没准备 vim 的话)去编辑 root/20131016/etc/fstab
文件,将那些不会成功的挂载项都去掉,添加新的正确的项。PS: 如果使用 vim 的话,记得进去先set nocp
一下,不然会是兼容模式,和 vi 一样只能撒消一步的。
/dev/mapper/lilybackup / btrfs rw,relatime,compress=lzo,subvol=run/root/xxx 0 0 /dev/mapper/lilybackup /home/lilydjwg btrfs rw,relatime,compress=lzo,subvol=run/home/xxx 0 0
然后 cd /
,卸载 /new_root
并重新以子卷挂载之:
cd / umount /new_root mount -o compress=lzo,subvol=run/root/20131016 /dev/mapper/lilybackup /new_root
如果是因为找不到 /sbin/init
而进来这个 shell 的,那么就tail /init
,最后那行是需要执行的命令(当然有些修改):
exec env -i "TERM=$TERM" /usr/bin/switch_root /new_root /sbin/init
如果是因为break=postmount
参数而进来的,直接按Ctrl-D退出 shell 即可。
启动会继续进行,systemd 启动了,各种服务陆续启动中~~
如果很不幸地,忘记修改/etc/fstab
了,或者有错,那么 systemd 会毫不留情地「Welcome to emergency shell」。不过现在更正也为时未晚。在编辑完 fstab 之后,要先执行下systemctl daemon-reload
再退出那个 emergency shell。
接下来应该能一路顺利地到达指定时间的系统啦 =w=
时间机器打造完成哦耶~~
Jan 06, 2014 03:20:51 PM
好麻烦...
话说你那个生成目录 点第一次正常 再点一次就坏了
Jan 06, 2014 03:21:58 PM
test阿斯顿撒
Jan 06, 2014 03:23:20 PM
test阿斯顿撒test
Jan 06, 2014 05:01:08 PM
谁让你点两次的啦……
Jan 11, 2014 09:17:22 PM
博主怎么不更新了
Jan 15, 2014 05:18:16 PM
感觉lz什么都在研究。。。
Jan 15, 2014 05:49:18 PM
需要什么就研究什么 =w=
Jan 29, 2014 11:18:54 PM
我也是用btrfs+rsync来做备份的,用rsync从生产环境同步数据到备份机,每天自动生成相应日期的快照。保留60天,60天前的自动删除。这样200多G的数据60天的完整备份也不过才300G不到。
Jan 30, 2014 11:42:37 AM
哈,我之前搜索时看到过你那篇文章呢。我这里有几个 vbox 虚拟机镜像,开机一次就要整个复制一次,所以之前 100G 的备份现在已经长到 170G 啦。
Jan 30, 2014 02:31:56 PM
博主已经深陷技术的泥潭,难以自拔
Jan 30, 2014 07:51:30 PM
感觉你比我还会折腾啊,各种东西都玩。
Feb 11, 2014 10:32:43 AM
不错的文章,多谢了。
it@powercore.com.cn
May 23, 2017 06:55:53 PM
仙子 请问下你 archlinux 的文件系统是什么的喵
不知道现在全盘 Btrfs 稳不稳定
May 23, 2017 09:35:11 PM
我的 / 目前还是 ext4,源码等数据用的 btrfs,大文件(电影、软件镜像、虚拟机)用的 xfs,备份用的 btrfs 和 zfs。
目前我还没有遇到关于文件系统的任何稳定性问题。
Jun 28, 2017 10:47:02 AM
用了几年,没出现什么问题。
Jun 28, 2017 11:08:48 AM
我的 / 现在换成 XFS 啦~
Dec 02, 2017 08:27:01 PM
使用btrfs的发送子卷功能代替rsync可以吗?我打算用这样的方案备份到移动硬盘上,另外博主的希捷移动硬盘可安好?打算入手一块移动硬盘,纠结于买哪个牌子呢
Dec 02, 2017 09:28:31 PM
如果你已经在使用 btrfs / zfs 了,用 send / receive 挺好的呀。我的根之前是 ext4 现在是 xfs 所以才没法用那个特性。
文章里说的那块盘早挂了。自由落体30cm左右后被数据线扯住,但是盘从此以后转不起来了。前两天拆盘看到是磁头未能归位。手动把磁头推回去了。但是不知道从什么时候起整个机械系统已经无法启动,加电后只能看到驱动在尝试转动硬盘却完全听不到声音。也不知道是不是我拆盘的时候不小心把哪里弄坏了。
现在使用的硬盘是联想和 SONY 各一块,都还安好。九年前买过一块不知道什么牌子的,电脑显示是西数,前两年开始慢慢的接上只亮灯无响应,也挂了。
Dec 03, 2017 02:16:06 PM
嗯嗯,看来硬盘这个东西还是挺脆弱的,,多谢博主指导啦。
Nov 15, 2018 07:20:03 PM
应该对snapshot 做个hash
Nov 15, 2018 08:19:30 PM
做 hash 有什么用呢?btrfs / zfs 有做数据校验的。
Nov 15, 2018 11:12:51 PM
涨知识了 =_= 好高级desu
Feb 07, 2020 10:22:36 AM
我现在在笔记本上装的是LinuxMint,打算下次重装的时候装个Arch试试看……到时候就用得上博主的教程了。
Feb 07, 2020 01:02:50 PM
其实不同发行版都适用的,原理一样的。
Feb 08, 2020 12:05:36 PM
主要是LinuxMint比较“开箱即用”,系统搞崩了之后把数据打个包直接重装也不会有太大问题,毕竟我自己自定义的也很少。Arch就不一样了,虽然我至今还没有安装过,但总觉得很复杂……
Feb 08, 2020 02:17:05 PM
嗯,手动安装自然是复杂一些。不过不作死的话 Arch 很难崩到完全救不回来,所以我从来没有在同一机器上重装过呢。即便是换电脑也可以 rsync 过去。而像 Ubuntu,我只能眼见它慢慢坏掉然后整个地重装。
Mint 出过一次事,官方发布的 iso 文件被篡改过,然后官方在网站上发布了正确版本的 md5 还是 sha1 来着,但是那网站是 http 的。而且那镜像也没有签名。
Mar 11, 2020 06:11:23 PM
之前我试着装Arch,发现驱动还要手动装就放弃了。
又入了Manjaro,用着很舒服,pacman很好用,但滚着滚着Grub挂了,重装了一次无果。
我又试了几个发行版,KDE Noon,kUbuntu之类的,但总感觉对KDE定制得不够好。于是我装了openSUSE,用着很舒服,除了软件少之外没什么问题。备份用的是btrfs加上自带的snapper。
Mar 11, 2020 06:13:10 PM
呀,不知道为什么发到外面来了……
Mar 11, 2020 06:53:41 PM
snapper 那个是快照,不能算是备份。它可以防止误操作或者程序 bug 导致数据丢失,但是不防你的硬盘坏掉。
Mar 13, 2020 01:11:14 PM
home目录我也有用rsync备份到移动硬盘上,重要数据再给加个云同步。