天天看點

Fabric 源碼學習:如何實作批量管理遠端伺服器?

Fabric 源碼學習:如何實作批量管理遠端伺服器?

前不久,我寫了一篇《Fabric教程》,簡單來說,它是一個用 Python 開發的輕量級的遠端系統管理工具,在遠端登入伺服器、執行 Shell 指令、批量管理伺服器、遠端部署等場景中,十分好用。

Fabric 2 是其最新的大版本,跟早前的 Fabric 1 有挺大的不同,更加好用了,但是沒填上的坑也挺多的……

本文繼續來聊聊 Fabric,不過我不想再面面俱到了,而是專注于這一個話題:它是如何實作對批量伺服器的串行/并發管理的?(友情提示:為了有更好的閱讀體驗,如果你還不了解 Fabric 的基礎用法,建議先閱讀前面的教程。)Fabric 通過 Group 來組合多台伺服器。差別在于由 fabric.group.Group 基類(父類)派生出的兩個子類:

  • SerialGroup(*hosts, **kwargs):按串行方式執行操作
  • ThreadingGroup(*hosts, **kwargs):按并發方式執行操作

下面先看看這個基類:

Fabric 源碼學習:如何實作批量管理遠端伺服器?

我把一些沒用的資訊折疊了,比較值得注意的内容有:

  • Group 繼承了 list,是以能夠 extend() ,對傳入的伺服器分别建立 connection
  • 核心的 run() 方法沒有寫實作,用意是留給子類再實作
  • 最後的 __enter__() 和 __exit__() 實作了上下文管理器

有了這個基類,接下來就要看 SerialGroup 和 ThreadingGroup 的具體實作了。

Fabric 源碼學習:如何實作批量管理遠端伺服器?

SerialGroup 類很簡單,隻實作了一個 run() 方法。因為類在初始化時為所有 host 建立了連接配接而且存了起來,是以這裡隻需用 for 循環依次取出,再執行 Connection 的 run() 方法。

這裡可以看到一種非常實用的開發技巧:建立類時,讓它繼承内置的資料結構(如 list、dict), 這樣可以直接使用  self.append()、self.extend()、self.update() 等方法把關鍵的資訊存到“自身”,再到取出時則“for xxx in self”,這樣就免了建立臨時的 list 或 dict,也免得要在參數中傳來傳去。

GroupResult 和 GroupException 是對執行結果和異常的處理,不是我們關注的重點,這裡略過。

接下來看看 ThreadingGroup,它也隻有一個 run() 方法:

Fabric 源碼學習:如何實作批量管理遠端伺服器?

ExceptionHandlingThread 是一個繼承了 threading.Thread 的類,這是一種建立多線程的方式。每個線程執行的方法主要做兩件事:執行 connection 的 run() 方法,以及将執行成功的結果存入隊列中。

Fabric 源碼學習:如何實作批量管理遠端伺服器?

接下來再分别把執行成功的結果與出異常的結果都存入到 results 中。

是以,Fabric 是使用了 threading 多線程的方式來實作并發。網絡請求是 IO 密集型的,使用多線程是不錯的方式。

但是,通過分析這兩種 Group 的實作代碼(以及使用的實踐),我們也可以發現 Fabric 的缺陷:

  • Group 隻實作了 run() 方法,但是 Connection 的 put()、get()、sudo() 等方法都沒有,這意味着用這種方式管理伺服器叢集時,隻能在上面執行 shell 指令……
  • 每次調用 run() 方法時,它要等所有主機都執行完,才會傳回結果,這意味着先執行完的主機會被阻塞。更為緻命的是,如果其中一台主機執行時出了異常,整個 run() 方法就抛異常,這意味着每次使用 run() 方法時,都需要作異常捕獲
  • run() 方法支援執行單條 shell 指令,但是指令的狀态不會傳遞。假設先在一個 run() 方法中運作 cd 指令切到 A 目錄(非根目錄),再在下一個 run() 方法建立一個檔案,最終結果是該檔案并不在 A 目錄,而是在預設目錄。解決辦法是用“&&”連接配接起多條指令,略顯麻煩

感謝閱讀。最後,附上 Fabric 教程:如何高效地遠端部署?自動化運維利器 Fabric 教程

Fabric 源碼學習:如何實作批量管理遠端伺服器?