文件系統的「代理」

目標:給指定目錄設置「反向代理」,使得其內容經過一個指定的 f() 進行變換。如果源目錄的 file 更新了,f(file) 也要更新。

go-fuse unionfs

網路上的反向代理我們已經非常清楚了。在應用伺服器之前架設一個反向代理的伺服器。通過訪問反向代理,我們可以添加了一些http header,把http轉換成https,諸如此類的。

在文件系統上也可以做這樣的事情嗎?我們能不能提供一個文件目錄,是另外一個目錄的反向代理。在新目錄上實現文件的增減,以及文件內容的變換?

答案是可行的。這個實現基於 hanwen/go-fuse 。它提供一個 golang 實現的用戶態文件系統(FUSE)。利用其中的 unionfs,我們可以得到以下的行為。

$unionfsnUsage:n unionfs MOUNTPOINT RW-DIRECTORY RO-DIRECTORY ...n

其中 MOUNTPOINT 是我們最終訪問的目錄,RO目錄是提供了原始文件的目錄,而RW目錄以COW(Copy-on-Write)的方式記錄了我們在RO目錄上的改動。

假設我們把ro目錄和rw目錄合併到了fuse目錄

$unionfs fuse rw ron

  • RW/RO 內容合併:ro 下的文件為 [a]。rw 下的文件為 [b]。那麼 fuse 下的文件為 [a,b]
  • RW/RO 文件同名:ro 下的文件為 [a]。rw 下的文件也為 [a]。那麼 fuse 下的文件為 [a],其中 a 的內容是 rw 的。
  • 在 fuse 中新增:ro 下的文件為 []。rw 下的文件為 []。如果給 fuse 新增文件a,從 [] 變為 [a]。那麼結果是 ro 下的文件為 [],而 rw 變為了 [a]。
  • 在 fuse 中更新:如果 a 同時存在於 ro, rw 中,更新 fuse 的 a,則等同於更新 rw 的。如果 a 只存在於 rw 中,更新 fuse 的 a,則等於更新 rw 的。如果 a 只存在於 ro 中,更新 fuse 的 a 不會更新 ro 的,而是會在 rw 中新增一個文件 a。
  • 在 fuse 中刪除:如果 a 同時存在與 ro,rw 中,刪除 fuse 中的 a,不僅僅是從 rw 中把 a 刪除了,同時會在 rw/GOUNIONFS_DELETIONS/ 目錄下記錄 a 已經被刪除了。如果 a 只存在於 rw 中,則僅僅是從 rw 里把 a 刪除。如果 a 只存在於 ro 中,則只會在 rw/GOUNIONFS_DELETIONS/ 目錄下記錄 a 已經被刪除了。
  • 在 ro 中新增:新增的文件立即可以在 fuse 中可見
  • 在 ro 中更新:如果沒有被 rw 覆蓋的話,更新也可以在 fuse 中可見
  • 在 ro 中刪除:如果沒有被 rw 覆蓋的話,刪除之後在 fuse 中也看不到了

這個和 overlayfs 的不同在於 fuse 合併了 rw 和 ro 之後,ro 的改動對於 fuse 仍然是可見的。

kernel.org/doc/Document

Changes to underlying filesystemsn---------------------------------nnOffline changes, when the overlay is not mounted, are allowed to eithernthe upper or the lower trees.nnChanges to the underlying filesystems while part of a mounted overlaynfilesystem are not allowed. If the underlying filesystem is changed,nthe behavior of the overlay is undefined, though it will not result inna crash or deadlock.n

似乎 unionfs 要比 overlayfs 要寬鬆很多。

lambdafs

基於 go-fuse 的 unionfs,我們可以實現一個 lambdafs。其原理就是對 ro 里的部分文件進行 f() 的映射,把結果寫入到 rw 目錄里。從而使得最終的 fuse 目錄里,對 ro 的部分文件進行了一個 lambda 的轉換。而且這個轉換是 lazy 的,而且是可更新的。只有當我們 cat 了 fuse 里的文件,這個時候才會觸發 lambda。對於那些沒有讀到的文件,則可以先不處理。

我們要應用的 lambda 是這樣的

UpdateFile: func(filePath string) ([]byte, error) {ntif !strings.HasSuffix(filePath, ".php"){nttreturn nil, nilnt}ntcontent, err := ioutil.ReadFile(filePath)ntif err != nil {nttreturn nil, errnt}ntcontent = append(content, []byte("nhellon")...)ntreturn content, niln}n

對於 .php 結尾的文件,在文件的尾部添加 hello。

在 ro 中添加一個文件 test.php

$cat ro/test.phpnabcn

在 fuse 中 cat 同一個我呢見

$cat fuse/test.phpnabcnnhellon

如果更新了 test.php

$cat ro/test.phpnnew file contentn

在 fuse 中再去 cat 則會發現更新後的文件內容

$cat fuse/test.phpnnew file contentnnhellon

就這樣的效果。這樣的東西可以廣泛應用於源代碼加工的場景(es6轉javascript,css轉換,源代碼注入調試信息等)。可以無縫地實現自動更新。同時可以做到延遲轉換,提高性能。這個代碼在 taowen/lambdafs

推薦閱讀:

Docker 有什麼優勢?
超輕量級「虛擬機」—— Docker 初識
這張圖裡的幾個動物分別是指的哪些軟體項目?
生產環境中使用Docker Swarm的一些建議
深度調查:24%的Docker鏡像都存在嚴重漏洞

TAG:文件系统 | Go语言 | Docker |