作者:LeanCloud 王子亭
在 2016 年我擁有了第一臺 NAS —— 群暉的 DS215J,其實在之後的很長一段時間其實並沒有派上多大用場,因為我的資料並不多,大都儲存在雲端,更多的是體驗一下 NAS 的功能和工作流。
直到最近我才開始真正地將 NAS 利用上,於是準備升級一下,但考慮到群暉的價效比實在太低,再加上去年配置 Linux 軟路由讓我對基於「原生 Linux」的開源解決方案信心和興趣大增,於是準備自己 DIY 一臺 NAS,計劃解決未來十年的儲存需求。
我依然選擇了我最熟悉的 Ubuntu 作為作業系統、Ansible 作為配置管理工具,因此這個 NAS 的大部分配置都可以在我的 GitHub 上找到。
注意這個倉庫中的 Ansible 配置僅供參考,不建議直接執行,因為我在編寫這些配置時並未充分考慮相容性和可移植性。
檔案系統
對於一臺 NAS 來說最重要的當然是檔案系統,不需要太多調研就可以找到 ZFS —— 可能是目前在資料可靠性上下功夫最多的單機檔案系統了,於是我的整個選型就圍繞 ZFS 展開了。
ZFS 既是檔案系統,同時又是陣列(RAID)管理器,這為它帶來了一些其他檔案系統難以提供的能力:
- ZFS 為每個塊都儲存了校驗和,同時會定期掃描整個硬碟,從 RAID 中的其他硬碟修復意外損壞的資料(如宇宙射線導致的位元翻轉)。
- 在 RAID 的基礎上可以 指定某些目錄以更多的份數冗餘儲存,對於重要的資料即使損壞的硬碟超過了 RAID 方案的限制,依然有可能找回。
ZFS 還支援資料加密、壓縮和去重,這三項功能以一種巧妙的順序工作,並不會互相沖突,同時這些所有選項都可以設定在目錄(dataset)級別、可以隨時更改(只對新資料生效)。
ZFS 當然也支援快照,快照可以被匯出為二進位制流,被儲存到任何地方。這個功能可以讓你在不丟失任何元資訊的情況下對 ZFS 的檔案系統進行備份、傳輸和恢復。
硬體
我並不擅長淘硬體,於是就選擇了 HPE 的 MicroServer Gen10,一個四盤位的成品微型伺服器,CPU 是 AMD X3421 ,8G ECC 記憶體,也是標準的 x86 通用硬體,應該不太容易遇到坑。
我用轉接卡在 PCI-E 插槽上裝了一塊 NVME SSD,用作系統盤和 ZFS 的讀快取(L2ARC,不過從後面的統計來看效果並不明顯),資料盤則暫時用的是舊的硬碟,最終會升級到四塊 4T 的硬碟。這裡需要注意的是因為 ZFS 不支援更改 RAID 的結構,所以必須在一開始就配置足夠的硬碟來佔位,後續再升級容量,我甚至用 USB 接了一塊行動硬碟來湊數。
ZFS
因為是四盤位,所以我採用了 raidz1(RAID5),冗餘一塊盤作為校驗,如果最終所有的盤都升級到 4T,一共是 12T 的實際可用容量。
root@infinity:~# zpool status
pool: storage
state: ONLINE
config:
NAME STATE READ WRITE CKSUM
storage ONLINE 0 0 0
raidz1-0 ONLINE 0 0 0
sda ONLINE 0 0 0
sdb ONLINE 0 0 0
sdc ONLINE 0 0 0
sdd ONLINE 0 0 0
cache
nvme0n1p4 ONLINE 0 0 0
root@infinity:~# zpool list
NAME SIZE ALLOC FREE CKPOINT FRAG CAP DEDUP HEALTH
storage 7.27T 3.52T 3.75T - 10% 48% 1.00x ONLINE
通常認為 RAID5 在出現硬碟故障的恢復過程中存在著較高的風險發生第二塊盤故障、最終丟失資料的的情況;或者硬碟上的資料隨著時間推移發生位元翻轉導致資料損壞。但考慮到 ZFS 會定期做資料校驗來保證資料的正確性,再綜合考慮盤位數量和容量,我認為這個風險還是可以接受的,後面也會提到還有異地備份作為兜底措施。
我開啟了 ZFS 的加密功能,但這帶來了一個問題:我不能把金鑰以明文的方式儲存在 NAS 的系統盤 —— 否則金鑰和密文放在一起的話,這個加密就失去意義了。所以每次 NAS 重啟後,都需要我親自輸入密碼、掛載 ZFS 的 dataset,然後再啟動其他依賴儲存池的服務。
我還開啟了 ZFS 的資料壓縮,預設的 lz4 只會佔用少量的 CPU 卻可以在一些情況下提高 IO 效能 —— 因為需要讀取的資料量變少了。因為去重對資源的需求較高,相當於需要為整個硬碟建立一個索引來找到重複的塊,我並沒有開啟去重功能。
一些評論認為 ZFS 對記憶體的需求高、必須使用 ECC 記憶體。這其實是一種誤解:更多的記憶體可以提升 ZFS 的效能,ECC 則可以避免系統中所有應用遇到記憶體錯誤,但這些並不是必須的,即使沒有更多的記憶體或 ECC,ZFS 依然有著不輸其他檔案系統的效能和資料完整性保證。
儲存服務
小知識:SMB 是目前應用得最廣泛的區域網檔案共享協議,在主流的作業系統中都有內建的支援。CIFS 是微軟(Windows)對 SMB 的一個實現,而我們會用到的 Samba 是另一個實現了 SMB 協議的自由軟體。
作為 NAS 最核心的功能就是通過 SMB 協議向外提供儲存服務,所有的成品 NAS 都有豐富的選項來配置 SMB 的功能,但我們就只能直接去編輯 Samba 的配置檔案了,Samba 直接採用了 Linux 的使用者和檔案許可權機制,配置起來也不算太麻煩:
# 可以在 path 中使用佔位符來為每個使用者提供單獨的 Home 目錄
# 可以在 valid users 中使用使用者組來控制可訪問的使用者
[Home]
path = /storage/private/homes/%U
writeable = yes
valid users = @staff
# Samba 預設以登入使用者建立檔案,但 NextCloud 以 www-data 執行,可以用 force user 覆蓋為特定的使用者
[NextCloud]
path = /storage/nextcloud/data/%U/files
writeable = yes
valid users = @staff
force user = www-data
# 通過這些設定可以讓 macOS 的 TimeMachine 也通過 SMB 進行備份
# 詳見 https://www.reddit.com/r/homelab/comments/83vkaz/howto_make_time_machine_backups_on_a_samba/
[TimeMachine]
path = /storage/backups/timemachines/%U
writable = yes
valid users = @staff
durable handles = yes
kernel oplocks = no
kernel share modes = no
posix locking = no
vfs objects = catia fruit streams_xattr
ea support = yes
inherit acls = yes
fruit:time machine = yes
# 對於共享的目錄可以用 force group 覆蓋檔案的所屬組、用 create mask 覆蓋檔案的許可權位
[VideoWorks]
path = /storage/shares/VideoWorks
writeable = yes
valid users = @staff
force group = staff
create mask = 0775
# 還可以設定遊客可讀、指定使用者組可寫的公開目錄
[Resources]
path = /storage/public/Resources
guest ok = yes
write list = @staff
force group = +staff
create mask = 0775
從上面的配置中也可以看到這些共享目錄分散在幾個不同的路徑,為了匹配不同的資料型別、方便在目錄級別進行單獨設定,我劃分了幾個 dataset:
db
存放應用的資料庫檔案,將 recordsize 設定為了 8k(預設 128k)。nextcloud
NextCloud 的資料目錄,也可被 SMB 訪問。private
每個使用者的個人檔案。shares
家庭內部共享的檔案(如拍攝的視訊)。public
可以從網際網路上下載到的檔案,不參與異地備份。backups
備份(Time Machine 等),不參與異地備份。
root@infinity:~# zfs list
NAME USED AVAIL REFER MOUNTPOINT
storage 2.27T 286G 169K /storage
storage/backups 793G 286G 766G /storage/backups
storage/db 741M 286G 339M /storage/db
storage/nextcloud 207G 286G 207G /storage/nextcloud
storage/private 62.2G 286G 62.2G /storage/private
storage/public 648G 286G 613G /storage/public
storage/shares 615G 286G 609G /storage/shares
應用
首先我安裝了 Netdata,這是一個開箱即用的監控工具,在僅佔用少量資源的情況下提供秒級精度的大量統計指標,非常適合用於監控單臺伺服器的效能瓶頸。
其餘的應用都被我執行在了 Docker 中(使用 docker-compose 來管理),這樣可以隔離應用的執行環境,提升宿主機的穩定性,安裝、升級、解除安裝應用也會更方便。
其中最重要的一個應用是 NextCloud,這是一個開源的同步盤,我主要看中它的 iOS 應用和 iOS 有不錯的整合,可以正確地同步 Live Photo,也可以在 iOS 的檔案應用中被呼叫。
NextCloud 服務端會直接讀寫檔案系統中的檔案,而不是將檔案儲存在資料庫裡,這意味著 NextCloud 的資料目錄同時也可以通過 Samba 來訪問,這一點非常方便(不過需要一個定時任務來重新整理 NextCloud 資料庫中的元資訊)。
我還在 Docker 中執行了這些服務,它們都是開源的:
- Miniflux,一個 RSS 服務端,通過 Fever API 支援絕大部分的 RSS 客戶端。
- Bitwarden(非官方實現),一個密碼管理器,提供有各平臺的客戶端和瀏覽器外掛。
- Transmission,一個 BitTorrent 客戶端,提供基於 Web 的管理介面。
外部訪問
如果要真正地用 NAS 來替代網盤的話,還是需要保證不在家裡的內網的時候也可以訪問到檔案的。
通常的做法是使用 DDNS(動態 DNS)將一個域名解析至家庭寬頻的 IP,這要求家庭寬頻有公網 IP,而且運營商允許在 80 或 443 埠提供 Web 服務。我不想依賴這一點,所以想到了用 frp 來進行「反向代理」,如果你確實有公網 IP 的話,也可以使用 DDNS 的方案,這樣會省去一箇中轉伺服器,也可以有更好的速度。
為了讓 NextCloud 能有一個固定的地址(如 https://nextcloud.example.com
)我將域名在內外網分別進行了解析,在家時解析到內網地址,在外解析到中轉伺服器。無論是內外網,資料流都會經過 Let’s Encrypt 的 SSL 加密,這樣就不需要中轉伺服器有較高的安全保證。
雖然不需要先撥一個 VPN 確實很方便,但將 NextCloud 開放在公網上 並不安全,在社群中已有使用者 要求 NextCloud 客戶端支援雙向 SSL 認證,我也非常期待這個功能,可以在公網訪問上提供更好的安全性。
我還在 NAS 上安裝了 WireGuard,這是一個內建在 Linux 核心中的 VPN 模組,同樣通過 frp 暴露在外網,除了 NextCloud 之外的服務,如 SMB、SSH 和 Nextdata 都可以通過 WireGuard 來訪問。
如果你不執著於開源方案的話,也可以試試 ZeroTier,它提供了 NAT 穿透的能力,讓你的裝置和 NAS 之間可以不借助中轉伺服器直接傳輸,改善連線速度。
備份和資料完整性
在 raidz1 的基礎上,我設定了定時任務讓 ZFS 每天生成一個快照,還寫了一個指令碼來按照類似 Time Machine 的規則來清理備份:保留最近一週的每天快照、最近一個月的每週快照、最近一年的每月快照、以及每年的快照。
root@infinity:~# zfs list storage/nextcloud -t snapshot
NAME USED AVAIL REFER MOUNTPOINT
storage/nextcloud@2020-09-05 83.9M - 182G -
storage/nextcloud@2020-09-15 35.2M - 207G -
storage/nextcloud@2020-09-21 30.2M - 207G -
storage/nextcloud@2020-09-23 29.7M - 207G -
storage/nextcloud@2020-09-26 29.3M - 207G -
storage/nextcloud@2020-09-27 28.2M - 207G -
storage/nextcloud@2020-09-28 28.2M - 207G -
storage/nextcloud@2020-09-29 29.1M - 207G -
storage/nextcloud@2020-09-30 33.5M - 207G -
快照主要是為了防止人工的誤操作,除了單純的、當場就能發現的手滑之外,有時你會誤以為你不會用到這個檔案而將它刪除,直到很久之後才發現並非如此。
同時每週會有定時任務使用 restic 備份一個快照到 Backblaze B2 作為異地備份,這是一個價格較低的物件儲存,非常適合備份。restic 支援增量的快照備份,也支援加密。出於成本考慮,異地備份僅包括由我產生的資料,並不包括 public 和 backups 目錄。
我曾考慮過直接在遠端執行一個 ZFS 來進行備份,zfs send / recv 支援以二進位制流的形式傳輸一個快照 —— 不需要遠端安裝其他任何的工具,只需要用 shell 的管道操作符將 zfs send 的位元組流重定向到 ssh 命令即可。這個方案非常具有技術美感,但考慮到塊儲存的價格是物件儲存的十倍以上,最後還是放棄了這個方案。
成本核算
硬體上其實我預算並不緊張,留的餘量也比較大,如果換一些價效比更高的硬體的話,價格還可以下降很多。
- 主機(主機板、CPU、記憶體、系統盤) 3500 元
- 硬碟(4 \* 4T) 2200 元(其實目前只買了一塊,其他三塊是舊的)
考慮到我之前的群輝用了五年,新的 NAS 設計使用壽命定在十年:
- 硬體成本摺合每年 570 元
- 電費(35W)每年 110 元
- 遠端訪問每年 100 元(國內年付促銷伺服器,如有公網 IP 使用 DDNS 則無需此項)
- 異地備份每年 415 元(按量付費,這裡按 1T 需要異地備份的資料計算)
總共 12T 的容量每年 1195 元,摺合 1T 每月 8 元,如果去掉遠端訪問和異地備份的話則是 1T 每月 5 元。
為什麼要用自部署方案
相比於使用雲服務,第一個理由自然是對資料的「掌控感」,雖然沒有什麼確鑿的理由說雲服務就一定不安全,但有些人就是喜歡這種對個人資料的掌控感。
還有一個技術原因是部署在家中內網的 NAS 可以通過 SMB 簡單地支援一些「線上編輯」,如直接載入 NAS 上的素材進行視訊剪輯、甚至將整個工程檔案都直接放在 NAS 上。使用雲服務的話一方面是沒有 SMB 協議的支援,即使支援延遲對於線上編輯來說也是無法接受的。
另外一個不能忽略的話題就是成本,在這裡我們只考慮以容量為計價方案的網盤服務,iCloud、Google Drive、Dropbox 的價格方案都非常接近,在超過 200G(大概 $3)這一檔之後就直接跳到了 2T(大概 $10),這時雲服務按量付費的優勢其實就沒有了,是一個切換到自部署方案的一個不錯的時間點,一次性投入之後只需 2 - 3 年即可回本。
當然最重要的一點是興趣,在這個折騰的過程中你需要做很多決定、遇到很多困難,最後搭建出來一個幾乎是獨一無二的自部署方案。如果你能在這個過程中找到樂趣的話,那當然是非常值得的;反過來如果你沒有興趣,算上投入的時間成本,自部署方案的價效比將會非常低。
任何自部署的方案都需要長期的維護才能保持工作,對後端運維完全沒有興趣怎麼辦,不如瞭解一下 LeanCloud,領先的 BaaS 提供商,為移動開發提供強有力的後端支援。