雲上共享檔案系統的相容性大比拼

語言: CN / TW / HK

「一切皆檔案」是 UNIX 的基本設計哲學。檔案按照層級關係組織為樹形目錄,構成了檔案系統 的基本形態。使用者使用檔案系統來儲存資料時,不必關心資料底層的儲存方式,便可以按照約定的介面規範進行訪問。

概念篇

關於檔案系統的介面規範,應用最為廣泛的莫過於 POSIX,源於 IEEE 委員會編寫的相關標準,其中有些章節是關於檔案及目錄操作的。標準本身比較冗長晦澀,在此不作深入探討。我們可以參考 Quora 上的一個問答 “What does POSIX conformance/compliance mean in the distributed systems world?” ,對此概括的比較全面。

POSIX 相容要求檔案系統具備以下幾項特徵:

  • 層級化的目錄結構,支援任意深度
  • 檔案通過 open(O_CREAT),目錄通過 mkdir 建立等等
  • 目錄可以通過 opendir/readdir 遍歷
  • 路徑/名稱空間可以通過 renamelink / unlinksymlink / readlink 等修改
  • 資料通過 writewritev 寫入, fsync 時要求持久化,通過 readreadv 讀取
  • 其他一些介面如 stat, chmod / chown
  • 與某些流行的說法相悖,擴充套件屬性看起來並不是 POSIX 的一部分,參見The Open Group Base Specifications Issue 7, 2018 edition 裡的函式列表

測試篇

一個檔案系統是否真正滿足 POSIX 相容性,我們可以通過測試工具來檢驗。比較流行的一個測試用例集是 pjdfstest,來源於 FreeBSD,也適用於 Linux 等系統。pjdfstest 的測試用例需要以 root 身份來執行,並且要求系統裡安裝了 Perl 和 TAP::Harness(Perl 軟體包),測試過程如下:

cd /path/to/filesystem/under/test
sudo prove --recurse --verbose /path/to/pjdfstest/tests

我們選取了幾種雲環境中的共享檔案系統進行測試,統計測試結果中的失敗用例如下:

因為 Amazon EFS 失敗的測試用例相比其他產品大了幾個數量級,為了方便比較,上圖的橫座標使用了對數座標。

我們還同時測試了 S3FS 和 Goofys,失敗的用例數均為數百項乃至上千項,其根本原因是這兩個專案並不是嚴格按照檔案系統來設計的:

  • Goofys 可以將 S3 掛載為檔案系統,但僅僅是 “POSIX-ish” 介面的 “Filey” 系統(這兩個描述來自於官方專案介紹,翻譯成中文即“似是而非”或“貌合神離”)。Goofys 在設計理念上為了效能而犧牲了 POSIX 相容性,所支援的檔案操作極大地受限於 S3 等物件儲存本身。測試結果也驗證了這一點。建議在生產使用之前全面評審應用的資料訪問方式,以免落入陷阱。

  • S3FS 儘管名為檔案系統,但實際上更接近於用檔案系統檢視管理 S3 bucket 中物件的一種方法。儘管 S3FS 支援了 POSIX 的一個較大子集,但只是將系統呼叫一一對映為物件儲存請求,並不支援常規檔案系統的語義及一致性(例如目錄的原子重新命名,獨佔模式開啟時的互斥,附加檔案內容會導致重寫整個檔案以及不支援硬連線等等)。這些缺陷導致 S3FS 並不能用於替代常規檔案系統(即便不考慮效能問題),因為當應用訪問檔案系統時,預期的行為應該是符合 POSIX 規範的,而 S3FS 遠遠不能滿足這一點。

分析篇

下面我們將測試的失敗用例進行分類統計,挑選幾類比較有代表性的來分析下會對應用造成何種限制。

總的來說,無論從數量還是類別來看,JuiceFS 的失敗用例都更少,有更好的相容性。Amazon EFS 的失敗用例無論從總數及類別均大大超出其它幾種檔案系統,無法放入同一圖表對比,後面將單獨分析。

JuiceFS

JuiceFS 在本次測試中通過了8811項用例中的絕大多數,僅在 utimensat 測試集上失敗了 3 項。對應日誌如下:

…
/root/pjdfstest/tests/utimensat/08.t ........
not ok 5 - tried 'lstat pjdfstest_bfaee1fc7f2c1f80768e30f203f41627 atime_ns', expected 100000000, got 0
not ok 6 - tried 'lstat pjdfstest_bfaee1fc7f2c1f80768e30f203f41627 mtime_ns', expected 200000000, got 0
Failed 2/9 subtests
/root/pjdfstest/tests/utimensat/09.t ........
not ok 5 - tried 'lstat pjdfstest_7911595d91adcf915009f551ac48e1f2 mtime', expected 4294967296, got 0

這幾個測試用例出自utimensat/08.tutimensat/09.t。其中 08.t 是測試亞秒級的檔案訪問時間和修改時間精度,09.t 則是要求支援64位時間戳。

JuiceFS 目前只支援秒,時間戳儲存為32位整數,故無法通過這三個測試(實際上本次測試涉及的所有檔案系統都無法100%通過這個測試集)。如果您的應用場景要求秒以下的時間精度或者更大範圍,歡迎聯絡我們商討解決方案。

GCP Filestore

除了和 JuiceFS 一樣在 utimesat 測試集上存在若干失敗結果之外,GCP Filestore 還在 unlink 測試集中失敗了 1 項。這一項在其他所有檔案系統中也都是失敗的。

/root/pjdfstest/tests/unlink/14.t ...........
not ok 4 - tried 'open pjdfstest_b03f52249a0c653a3f382dfe1237caa1 O_RDONLY : unlink pjdfstest_b03f52249a0c653a3f382dfe1237caa1 : fstat 0 nlink', expected 0, got 1

該測試集(unlink/14.t)用於驗證一個檔案在開啟狀態下被刪除時的行為:

desc="An open file will not be immediately freed by unlink"

刪除檔案的操作在系統層面實際對應於 unlink,即移除該檔名到對應 inode 的連結,對應 nlink 的值減 1,這個測試用例就是要驗證這一點。

# A deleted file's link count should be 0
expect 0 open ${n0} O_RDONLY : unlink ${n0} : fstat 0 nlink

檔案內容只有在連結數(nlink)減少至 0 並且沒有開啟的檔案描述符(fd)指向該檔案時才會被真正刪除。如果 nlink 沒有被正確更新,可能會導致本該刪除的檔案仍然殘留在系統裡。

CFS

CFS 相比 Google Filestore,還未能通過 open 和 symlink 的幾項測試。

open 失敗用例

選取其中一部分失敗日誌如下:

/root/pjdfstest/tests/open/07.t .............
not ok 5 - tried '-u 65534 -g 65534 open pjdfstest_f24a42815d59c16a4bde54e6559d0390 O_RDONLY,O_TRUNC', expected EACCES, got 0
not ok 7 - tried '-u 65533 -g 65534 open pjdfstest_f24a42815d59c16a4bde54e6559d0390 O_RDONLY,O_TRUNC', expected EACCES, got 0
not ok 9 - tried '-u 65533 -g 65533 open pjdfstest_f24a42815d59c16a4bde54e6559d0390 O_RDONLY,O_TRUNC', expected EACCES, got 0
Failed 3/23 subtests

此測試集 open/07.t 用於驗證不具備寫許可權時,應該對 O_TRUNC 模式返回 EACCES 錯誤這一行為。

desc="open returns EACCES when O_TRUNC is specified and write permission is denied"

上面這三個失敗日誌需要結合測試程式碼來分析,分別對應 owner,group 和 other 三種情況。不失一般性,我們僅就 owner 情況進行分析 :

expect 0 -u 65534 -g 65534 chmod ${n1} 0477
expect EACCES -u 65534 -g 65534 open ${n1} O_RDONLY,O_TRUNC

首先設定檔案 owner 許可權為 4,即 r-- 只讀,然後嘗試以 O_RDONLY,O_TRUNC 模式開啟檔案,預期應該返回 EACCES,實際返回了 0。

根據 The Single UNIX ® Specification, Version 2 中對 O_TRUNC 的說明

O_TRUNC If the file exists and is a regular file, and the file is successfully opened O_RDWR or O_WRONLY, its length is truncated to 0 and the mode and owner are unchanged. It will have no effect on FIFO special files or terminal device files. Its effect on other file types is implementation-dependent. The result of using O_TRUNC with O_RDONLY is undefined.

O_TRUNC 與 O_RDONLY 組合使用的結果是未知的,而且此用例的被測檔案本身就是空檔案,O_TRUNC 不會產生任何效果。

symlink 失敗用例

對應測試日誌如下:

/root/pjdfstest/tests/symlink/03.t ..........
not ok 1 - tried 'symlink 7ea12171c487d234bef89d9d77ac8dc2929ea8ce264150140f02a77fc6dcad7c3b2b36b5ed19666f8b57ad861861c69cb63a7b23bcc58ad68e132a94c0939d5/.../... pjdfstest_57517a47d0388e0c84fa1915bf11fe4a', expected 0, got EINVAL
not ok 2 - tried 'unlink pjdfstest_57517a47d0388e0c84fa1915bf11fe4a', expected 0, got ENOENT
Failed 2/6 subtests

該測試集(symlink/03.t)用於測試路徑超出 PATH_MAX 長度時 symblink 的行為

desc="symlink returns ENAMETOOLONG if an entire length of either path name exceeded {PATH_MAX} characters"

失敗的用例對應程式碼如下:

n0=`namegen`
nx=`dirgen_max`
nxx="${nx}x"

mkdir -p "${nx%/*}"
expect 0 symlink ${nx} ${n0}
expect 0 unlink ${n0}

該測試用例是要建立長度為 PATH_MAX (包括結尾的0在內)的符號連結,通不過表明無法在 騰訊雲 NAS 上建立長度為 PATH_MAX 的符號連結。

阿里雲 NAS

相比騰訊雲 NAS,阿里雲 NAS 在 symlink 上表現正常,但未能通過 chmod 和 rename 上的幾項測試用例。

chmod 失敗用例

在這個測試集中,阿里雲 NAS 失敗了以下幾個專案

/root/pjdfstest/tests/chmod/12.t ............
not ok 3 - tried '-u 65534 -g 65534 open pjdfstest_db85e6a66130518db172a8b6ce6d53da O_WRONLY : write 0 x : fstat 0 mode', expected 0777, got 04777
not ok 4 - tried 'stat pjdfstest_db85e6a66130518db172a8b6ce6d53da mode', expected 0777, got 04777
not ok 7 - tried '-u 65534 -g 65534 open pjdfstest_db85e6a66130518db172a8b6ce6d53da O_RDWR : write 0 x : fstat 0 mode', expected 0777, got 02777
not ok 8 - tried 'stat pjdfstest_db85e6a66130518db172a8b6ce6d53da mode', expected 0777, got 02777
not ok 11 - tried '-u 65534 -g 65534 open pjdfstest_db85e6a66130518db172a8b6ce6d53da O_RDWR : write 0 x : fstat 0 mode', expected 0777, got 06777
not ok 12 - tried 'stat pjdfstest_db85e6a66130518db172a8b6ce6d53da mode', expected 0777, got 06777
Failed 6/14 subtests

該測試集(chmod/12.t)用於測試 SUID/SGID 位的行為

desc="verify SUID/SGID bit behaviour"

我們選取其中的第11和12個測試用例來詳細解釋一下,同時覆蓋了這兩個許可權位

# Check whether writing to the file by non-owner clears the SUID+SGID.
expect 0 create ${n0} 06777
expect 0777 -u 65534 -g 65534 open ${n0} O_RDWR : write 0 x : fstat 0 mode
expect 0777 stat ${n0} mode
expect 0 unlink ${n0}

此處,我們先以 06777 的許可權建立目標檔案,然後修改檔案內容,檢查 SUID 和 SGID 是否被正確清除。檔案許可權裡的 777 大家會比較熟悉,分別對應 owner,group和 other 的 rwx,即可讀、可寫、可執行。最前面的 0 表示八進位制數。

第二位 6 需要著重解釋下,這個八位元組(octet)代表特殊許可權位,其中前兩位分別對應 setuid/setgid(或稱 SUID/SGID),可以應用於可執行檔案及公共目錄。該許可權位被設定時,任何使用者都會以 owner (或 group)身份來執行該檔案。這個特殊的屬性允許使用者獲取通常只對 owner 開放的檔案和目錄訪問許可權。例如 passwd 命令就設定了 setuid 許可權,這允許普通使用者修改密碼,因為儲存密碼的檔案是隻允許 root 訪問的,使用者不可直接修改。

setuid/setgid 設計的出發點是提供一種方法,讓使用者以限定的方式(指定可執行檔案)訪問受限檔案(非當前使用者所有)。因此,當檔案被非 owner 修改時應自動清除此許可權位,以避免使用者通過這個途徑獲取其他許可權。

從測試結果中我們可以看到在阿里雲 NAS 中,檔案被非 owner 修改時,setuid/setgid 均未被清除,這樣實際上使用者可以通過修改檔案內容以該 owner 身份進行任意操作,這將會是個安全隱患

參考閱讀: Special File Permissions (setuid, setgid and Sticky Bit) (System Administration Guide: Security Services)

rename 失敗用例

阿里雲 NAS 在這個測試集上失敗數量較多,達到了 24 項,全部出現在 rename/09.t 中:

desc="rename returns EACCES or EPERM if the directory containing 'from' is marked sticky, and neither the containing directory nor 'from' are owned by the effective user ID"

這個測試集用於檢驗 sticky 位被設定時 rename 的行為:當包含源物件的目錄設定了 sticky 許可權位的時候,並且源物件和包含目錄的 owner 都與有效使用者ID(effective user ID)不同時,rename 應該返回 EACCES 或 EPERM。(這樣的複雜邏輯令人聯想到三國殺的武將技能設定……)。

sticky 位的典型應用是 /tmp 目錄,允許所有人建立內容,但是隻有 owner 才能刪除檔案。FTP 裡面的公共上傳目錄通常也是這種設定。

幾個失敗的測試用例表明阿里雲 NAS 對 sticky 位的支援還不夠完善,非 owner 的 rename 操作沒有被拒絕,並且產生了實際的效果——原始檔被重新命名。這種行為越過了檔案系統的訪問控制,對使用者檔案的安全性造成了威脅。

Amazon EFS 中的失敗用例

Amazon Elastic File System (EFS) 在 pjdfstest 測試中的不僅失敗比例極高(8811個測試用例失敗了1533個),而且幾乎覆蓋了所有類別,這比較令人意外。

EFS 支援以 NFS 方式掛載,但對 NFS 特性的支援並不完整。比如EFS 不支援塊裝置和字元裝置,這直接導致了 pjdfstest 中大量測試用例的失敗。排除這兩類檔案之後,仍然有上百項不同類別的失敗,所以在複雜場景中應用 EFS 必須慎之又慎。

總結篇

通過上面的對比分析,JuiceFS 在相容性方面表現最好,像大多數網路檔案系統一樣,為了效能犧牲了秒以下的時間精度和範圍(1970 - 2106 年)。Google Filestore 和騰訊雲 CFS 次之,有幾類未能通過。而阿里雲 NAS 和 Amazon EFS 的相容性最差,有大量的相容性測試通不過,其中包括有嚴重安全隱患的若干個測試用例,使用前建議做安全性評估。

JuiceFS 一直非常重視對 POSIX 標準的高度相容,我們把 pjdfstest 等相容性測試工具同其他隨機和併發測試工具(比如 fsracer、fstool 等)一起作為整合測試工具,在持續完善功能、提高效能的同時,盡力保持最大程度的 POSIX 相容性,避免使用者在使用過程中落入各種陷阱,從而更加專注於自身業務的發展。

如有幫助的話歡迎關注我們專案 Juicedata/JuiceFS 喲! (0ᴗ0✿)