本文将简述近期笔者在 Arch Linux 上自建 NAS 的过程,兼谈个人对 NAS 的思考和看法。本文主要起记录作用,并不作为配置参考。
系统构成
- 2x WD Red Pro 4TB HDD (使用主板 RAID 功能构建 RAID 1)
- 1x Samsung PM981a 256GB NVMe SSD (仅用于缓存)
- 作为虚拟机运行于 Proxmox VE 7.2 上
笔者的 NAS 并不是一个「标准」的 NAS。经过对个人需求的分析,笔者选择了不同于传统 NAS 的配置——本文将在后面介绍这样选择的考虑。
Arch Linux 的安装过程在此不再赘述。
分区方案
笔者使用主板自带的 RAID 功能,将两块 4TB WD Red Pro 硬盘组建为 RAID 1。对于 Proxmox VE 宿主机来说,这两块硬盘被映射为乐一块硬盘。
随后,笔者设置了 SATA Passthrough,将组建好的 RAID 和 NVMe SSD Passthrough 给 NAS 虚拟机。
在 NAS 虚拟机中,除系统盘外,可见的磁盘为 sda(组建好的 RAID)和 sdb(NVMe SSD)。笔者不对其分区,直接将其格式化为 LUKS Volume。同时,笔者将配置 LUKS Volume 的自动解锁与 FIDO2 密钥解锁。
然后,笔者在 LUKS 解密映射出的 Plaintext Device 上创建 LVM Volume Group,并在 Volume Group 上创建 LVM Cache Volume,以使用 SSD 缓存读写数据。
在 LVM Cache Volume 上,笔者将创建带有若干个 Thin Volume 的 LVM Thin Pool,以便于管理数据的分配。每个 Thin Pool 都将分配特定的用途,例如 Time Machine 备份、同步数据、云存储备份等。
最后,笔者在 LVM Thin Pool 上创建 Btrfs 文件系统,以支持快照、透明压缩、Subvolume 等功能。
以下是上述分区方案的简要示意图:
sda(RAID 1 HDD)- LUKS (
luks-data)- LVM Thin Pool (
nas-data)- 512GB Thin Volume (
nas-data_timemachine)- Btrfs
- 1TB Thin Volume (
nas-data_sync)- Btrfs subvolume (
/main) - Btrfs subvolume (
/snapshots/<snapshot-id>)
- Btrfs subvolume (
- 1.5TB Thin Volume (
nas-data_backups)- Btrfs subvolume (
/E5): Office E5 OneDrive 备份 - Btrfs subvolume (
/S3): S3 备份 - …
- Btrfs subvolume (
- …
- 512GB Thin Volume (
- LVM Thin Pool (
- LUKS (
sdb(SSD)- LUKS (
luks-cache)- LVM Cache Volume (
nas-cache)
- LVM Cache Volume (
- LUKS (
磁盘分区与格式化
首先安装必要的软件:
1 | pacman -Syu --noconfirm lvm2 libfido2 samba |
配置 LUKS
LUKS 是 Linux 通用的全盘加密格式,目前通用且默认的版本为 LUKS2。
首先,创建一个 LUKS 解密秘钥,用于在系统启动时自动解密 LUKS Volume:
1 | dd if=/dev/urandom of=/luks.key bs=1 count=64 |
格式化为 LUKS Volume(笔者这里使用了 --label 选项以设置 Volume Label):
1 | cryptsetup luksFormat --key-file /luks.key /dev/sda --label LuoTianyiNAS-Data |
如前文所述,笔者计划配置 FIDO2 密钥解锁。首先将 FIDO2 密钥插入服务器的 USB 接口,并在 Proxmox VE 中设置 USB Passthrough。然后,使用 systemd-cryptenroll 命令将 FIDO2 密钥添加为 LUKS Volume 的解锁密钥:
1 | systemd-cryptenroll --unlock-key-file /luks.key /dev/sda --fido2-device=auto |
为了在系统启动时自动解密 LUKS Volum,需要编辑 /etc/crypttab 文件,添加以下内容:
1 | luks-data LABEL=LuoTianyiNAS-Data /etc/luks.key luks |
最后,打开 LUKS Volume:
1 | cryptsetup luksOpen --key-file /luks.key /dev/sda luks-data |
在打开 LUKS Volume 后,Plaintext Device 被映射为 /dev/mapper/luks-data 和 /dev/mapper/luks-cache。
配置 LVM Cache Volume 上的 LVM Thin Pool
LVM 提供了多种不同的缓存方案,本文使用其中的 LVM Cache Volume 方案。关于各种方案的比较,本文将在后面讨论。
首先创建 LVM Volume Group 和 LVM Cache Volume,并在 LVM Cache Volume 上创建 LVM Thin Pool:
1 | vgcreate nas /dev/mapper/luks-data |
然后,根据预定的分区方案,在 LVM Thin Pool 上创建若干个 LVM Thin Volume:
1 | lvcreate -V 512G -T nas/data -n data_timemachine |
格式化 Btrfs 文件系统和创建 Btrfs Subvolume
将 LVM Thin Volume 格式化为 Btrfs 文件系统:
1 | mkfs.btrfs /dev/nas/data_timemachine -L nas-timemachine |
编辑 /etc/fstab 文件以在系统启动时自动挂载:
1 | LABEL=nas-timemachine /data/TimeMachine btrfs rw,relatime,compress=zstd:3,space_cache=v2,nofail 0 0 |
此处有几个值得注意的参数:
compress=zstd:3:启用 Zstd 压缩算法,压缩级别为 3space_cache=v2:启用 Btrfs 空间缓存 v2 特性nofail:如果未识别到磁盘,依然继续启动系统,防止启动失败
创建挂载点并挂载:
1 | mkdir -p /data/{TimeMachine,Sync,Backups} |
最后,创建相关的 Btrfs Subvolume:
1 | btrfs subvolume create /data/Sync/main |
配置 SMB 服务
首先安装相关软件:
1 | pacman -Syu --noconfirm samba avahi |
其中,avahi 为 mDNS 服务,以便其他设备能够发现服务器(例如能在「网上邻居」看到该设备)。
修改 /etc/avahi/avahi-daemon.conf 文件,以更改其他设备发现的设备名和域名:
1 | [server] |
添加用于 SMB 身份验证的 Linux 用户,并为其设置连接 SMB 时使用的密码:
1 | useradd -d /dev/null -s /usr/bin/nologin smb |
然后,编辑 SMB 配置文件 /etc/samba/smb.conf:
1 | [global] |
最后,启动 SMB 服务:
1 | systemctl enable --now avahi-daemon |
配置自动化的备份
笔者使用 Rclone 从云存储(OneDrive、S3 等)单向同步数据、备份到 NAS。同时,笔者使用 Systemd Timer 使其定期自动执行。
安装与配置 Rclone
首先安装 Rclone:
1 | pacman -Syu --noconfirm rclone |
笔者使用一个专门的 Linux 用户(以 rclone 为例)来运行 Rclone,以提高安全性。笔者为该用户分配了一个专门的目录 /etc/rclone 用于存储 Rclone 配置文件,并作为该用户的 Home 目录。
1 | mkdir -p /etc/rclone |
配置 Rclone 的过程不在本文赘述,您可以参见 Rclone 官方文档。您需要切换到 rclone 用户,使用 rclone config 命令来配置 Rclone:
1 | sudo -u rclone rclone config |
提示
在使用 OneDrive 时,Rclone 的最新版本可能存在 Bug,导致在其他机器上执行rclone authorize onedrive的结果无法被识别。因此,您可以使用以下命令启动本地到 NAS 的代理:
1
2 # 将 nas 替换为您的 NAS 主机名
ssh -NL 53682:localhost:53682 nas
创建 Rclone 配置的目录结构
笔者使用如下的目录结构来管理 Rclone 配置文件:
1 | /etc/rclone/ |
以配置 Office E5 OneDrive 备份为例(下同),笔者将其简称(也是文件夹名称和 Systemd 实例名称)命名为 e5。/etc/rclone/defaults/rclone 文件内容如下,您可以根据自己的需求修改配置:
1 | # 同时传输文件数,--transfers 参数的值 |
/etc/rclone/defaults/conf/e5 文件内容如下,您可以根据自己的需求修改配置:
1 | # Rclone 配置的名称 |
/etc/rclone/exclude/e5.exclude 文件内容如下,您可以根据自己的需求修改配置:
1 | # OneDrive 临时文件 |
为了在每次开始备份前进行一次 Btrfs 快照,笔者创建了 /etc/rclone/rclone-shapshot 脚本:
1 |
|
完成以上配置后,使用以下命令来测试备份配置是否能够正常工作:
1 | sudo -u rclone -i |
创建 Systemd Service 和 Systemd Timer
创建 /etc/systemd/system/rclone-backup@.service 文件:
1 | [Unit] |
创建 /etc/systemd/system/rclone-backup@.timer 文件:
1 | [Unit] |
以上文件创建了一个多实例的 Systemd Service 和 Systemd Timer。该 Systemd Service 将在每次执行时,先创建一个 Btrfs 快照,然后使用 Systemd Rclone 同步数据到本地磁盘。而该 Systemd Timer 将定期触发同名的 Systemd Service。而对于多实例的 Systemd Service,能够使多个相似配置的服务复用相同的配置文件。它们使用 service-name@instance-name 的格式来启动,以接受实例名称参数 %i。
该 Systemd Timer 将在 每周六的 12:07:12 执行备份任务。您可以根据自己的需求修改 rclone-backup@.timer 中的 OnCalendar 选项。
最后,启动 Systemd Service 和 Systemd Timer:
1 | systemctl enable --now rclone-backup@e5.timer |
配置 Rclone Web GUI
Rclone 提供了 Rclone Web GUI,但是并未可用于 NAS 用途的功能。本着「来都来了」的原则,笔者还是进行了配置。
Rclone 会在 Rclone Web GUI 启动时自动下载静态资源,但其存在 bug 导致需要输入两次密码(一次 HTTP Basic 认证,一次表单认证)。因此,笔者使用 Nginx 托管静态资源解决这个问题。
首先安装 Nginx:
1 | pacman -Syu --noconfirm nginx |
使用如下配置文件:
1 | server { |
然后,下载 rclone-webui-react 页面上的 currentbuild.zip,解压到 Nginx 配置的 root 目录(/etc/rclone/web)下。
接下来使用 Systemd 配置 Rclone Web GUI 自启动。编辑 /etc/systemd/system/rclone-web.service 文件:
1 | [Unit] |
接下来您需要创建一个 htpasswd 文件,以存储到 /etc/rclone/htpasswd (与上文 ExecStart 中的路径一致),用户名为 rclone 为例:
1 | printf "rclone:$(openssl passwd -apr1)" >> /etc/rclone/htpasswd |
最后,启动 Rclone 服务:
1 | systemctl enable --now rclone-web |
现在,你可以通过 Nginx 配置文件中的域名访问 Rclone Web GUI。若出现问题,您可以使用 systemctl status rclone-web 或 journalctl -u rclone-web 查看状态或日志。
安装 Cockpit
Cockpit 是 RedHat 开发的基于 Web 的服务器系统管理工具,具有查看 Systemd Service、监控系统资源、监控存储等功能。笔者选择安装 Cockpit 以方便管理 NAS。
首先安装 Cockpit 和 Nginx:
1 | pacman -Syu --noconfirm cockpit cockpit-machines cockpit-packagekit cockpit-storaged nginx inetutils |
注意
Cockpit 需要inetutils包以获取主机名。但是由于缺少inetutils包并不影响 Cockpit 工作(Cockpit 会将主机名显示为 localhost),因此inetutils未被 Arch Linux 的包管理者列为 Cockpit 的依赖。在 Arch Linux 上,您可能需要手动安装inetutils包。
创建 Nginx Site 配置文件,配置 Nginx 反向代理:
1 | server { |
提示
记得将/path/to/替换为证书的路径。
现在,你可以通过 Nginx 配置文件中的域名访问 Cockpit。
演练:如果用于缓存的 SSD Offline 会怎样?
为了防止未来意外的数据丢失,因此进行了一次模拟演练:在 Proxmox VE 中取消了 NVMe SSD Passthrough,以模拟 SSD Offline 的情况。
SSD Offline 后,vg nas 会进入 partial 状态。首先移除 LVM Cache Volume:
1 | lvconvert --uncache nas/data |
然后,重新上线 LVM Cache Volume 的 Data LV nas/data:
1 | vgchange -ay nas |
此时,可以如同挂在普通 LV 一样挂载 Data LV(实际上 Data LV 已被转换为普通 LV)。建议使用 -o ro 选项以只读方式挂载:
1 | mount -o ro /dev/nas/data_timemachine /mnt |
完成备份操作(例如使用 dd 命令复制数据)后,下线数据卷:
1 | vgchange -an nas |
若问题解决,可以使用下面的命令重新组成 LVM Cache Volume。此处笔者重新设置 NVMe SSD Passthrough 后,在 NAS 虚拟机中缓存 SSD 重新可见。
警告
运行下面的命令会导致缓存 SSD 上的数据丢失,但这是预期行为。
1 | vgck nas --updatemetadata |
关于 NAS 的一些思考
为什么不使用成品 NAS?
因为穷。
经过对个人需求的分析之后,个人既没有选择成品 NAS,也没有选择一些通用的 NAS 方案。
没有选择成品 NAS,最大的原因是穷。
没有选择通用的 NAS 方案,也是对自己的需求分析的结果。个人对存储空间的需求很小:4TB 足矣,一个盘装得下。不需要太多盘位。
对于数据安全性,一个知名的说法是 3-2-1 原则:
- 至少 3 个备份
- 至少 2 种不同的媒介
- 至少 1 个备份在远程位置
根据该原则,笔者对存储方案的自我评估如下:
- 对于个人数据:存储在 OneDrive 和 NAS 上,同时在中国大陆具有一个同步的本地备份(符合 3-2-1 原则)
- 对于网络资源:储存在 OneDrive 和 NAS 上,无本地备份(2-2-0,但是网络资源是可重复获取的,并不需要符合 3-2-1 原则)
- 对于系统备份:
- 偏好设置的目的是「让系统用的顺手」,本身就会随着系统更新不断调整,即使数据丢失也没什么损失
- 开发相关的数据「备份」在 GitHub 上
- 个人数据已经备份在 OneDrive 和 NAS 上
因此,笔者个人认为,笔者的 NAS 方案虽然并不完美契合 3-2-1 原则,但是已经足够满足个人需求。
为什么选用 Arch Linux?
Beacuse I can.
还有就是:
Arch Linux 的蓝色,和天依的蓝色很像呢!
笔者认为,只要能做到「能够对一个系统进行快照、同时快速回滚」,那么 Arch Linux 或 OpenSUSE Tumbleweed 等滚动发行版是最佳选择。
为什么?因为不怕滚挂!
在这一点上,自建的方案(Proxmox VE、ESXi)可以做到,云服务厂商也可以做到。因此,在这些场景,应该 能上 Arch Linux 就上 Arch Linux。
为什么不使用 ZFS?
ZFS is not in the mainline kernel.
仅仅这一点就是我不选择 ZFS 的原因。当然这只是个人偏好问题,但也是我思考的出发点——有了这个靶子,笔者便能顺着思路思考
- 数据一个盘装得下,不需要那么多盘。组 ZFS 没意义。
- ZFS 对 RAM 需求大,而笔者的 NAS 是运行在虚拟机上的,宿主机也仅有 16GB 内存。
但是又眼馋 ZFS 的高级特性(快照、压缩、数据完整性检查等),恰好 Btrfs 都有,因此选择了 Btrfs。
对于 NAS 的文件系统(和磁盘阵列),笔者一直持有的观点是:对于个人用户,**任何方案都不如 JBOD (JBOD)**。尽管它们声称提供了 _冗余_,但是 冗余不是备份——而对于个人,首要的是 备份而非冗余——大多数人并不需要 99.9999% Uptime 的存储服务。
而它们带来了什么副作用呢?答案是 _复杂性_。是的,RAID 会进行 Checksum、会自动重建;ZFS 也有自动修复,但是在这背后到底发生了什么?这些机制对于用户是 不透明 的。有人说,我完全理解 RAID 或是 ZFS 的算法,但是这一切都不如真正切切、直接 ls 就能看到的 JBOD 简单。
而用户需要的 _备份_,JBOD 可以完美解决。简单的复制粘贴,复杂一点也可以用 rsync 或是 btrfs send | btrfs receive。这些操作是 透明 的,用户可以直接看到数据的变化,理解背后发生的一切,而不是被隐藏在 RAID 或 ZFS 的黑盒子里。
至于文件完整、加密、去重、压缩、快照等功能,笔者更为激进的观点是:这些功能应当作用与 _文件级别_,而非 _文件系统级别_。就像 Android 的 FBE 一样,应当有一个 File-based compression/snapshot/integrity-check 方案。这些方案可以作为 NAS 软件的一部分。而文件系统应当只负责提供基本的文件存储功能,而这些高级功能应当由文件系统之上的应用来实现。
遗憾的是,目前并没有这样的方案——笔者也因此妥协地选用 Btrfs。
为什么有了 Btrfs Subvolume 还要使用 LVM?
因为需要限制 Time Machine 的空间。
至于为什么不用 Quota,Quota 现在在 Arch Wiki 上还是挂着 Unstable 的模板!
LVM 有哪些缓存方案?
LVM 缓存有两种方法:
dm-cache: 缓存读写操作,是 LVM 的默认行为,也是笔者的选择dm-writecache: 仅缓存写操作
同时,LVM 提供两种缓存组件:
cachepool: 缓存的元数据在一个设备上,缓存本身在另一个设备上cachevol: 缓存的元数据和数据在同一个设备上