本文将简述近期笔者在 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
: 缓存的元数据和数据在同一个设备上