1. 程式人生 > >Fabric 原始碼學習:如何實現批量管理遠端伺服器?

Fabric 原始碼學習:如何實現批量管理遠端伺服器?

前不久,我寫了一篇《[Fabric教程](https://mp.weixin.qq.com/s/UHtPaxO2ojql5ps4hTn3Vg)》,簡單來說,它是一個用 Python 開發的輕量級的遠端系統管理工具,在遠端登入伺服器、執行 Shell 命令、批量管理伺服器、遠端部署等場景中,十分好用。 Fabric 2 是其最新的大版本,跟早前的 Fabric 1 有挺大的不同,更加好用了,但是沒填上的坑也挺多的…… 本文繼續來聊聊 Fabric,不過我不想再面面俱到了,而是專注於這一個話題:**它是如何實現對批量伺服器的序列/併發管理的?** (友情提示:為了有更好的閱讀體驗,如果你還不瞭解 Fabric 的基礎用法,建議先閱讀前面的教程。) Fabric 通過 Group 來組合多臺伺服器。區別在於由 fabric.group.Group 基類(父類)派生出的兩個子類: - SerialGroup(\*hosts, \*\*kwargs):按序列方式執行操作 - ThreadingGroup(\*hosts, \*\*kwargs):按併發方式執行操作 下面先看看這個基類: ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gcrgohmwn9j20s90n4wfm.jpg) 我把一些沒用的資訊摺疊了,比較值得注意的內容有: - Group 繼承了 list,所以能夠 extend() ,對傳入的伺服器分別建立 connection - 核心的 run() 方法沒有寫實現,用意是留給子類再實現 - 最後的 \_\_enter\_\_() 和 \_\_exit\_\_() 實現了上下文管理器 有了這個基類,接下來就要看 SerialGroup 和 ThreadingGroup 的具體實現了。 ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gcrgxvq0taj20lh0evwey.jpg) SerialGroup 類很簡單,只實現了一個 run() 方法。因為類在初始化時為所有 host 建立了連線而且存了起來,所以這裡只需用 for 迴圈依次取出,再執行 Connection 的 run() 方法。 **這裡可以看到一種非常實用的開發技巧:** 建立類時,讓它繼承內建的資料結構(如 list、dict), 這樣可以直接使用 self.append()、self.extend()、self.update() 等方法把關鍵的資訊存到“自身”,再到取出時則“for xxx in self”,這樣就免了建立臨時的 list 或 dict,也免得要在引數中傳來傳去。 GroupResult 和 GroupException 是對執行結果和異常的處理,不是我們關注的重點,這裡略過。 接下來看看 ThreadingGroup,它也只有一個 run() 方法: ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gcrhhgo21pj20p90nndgv.jpg) ExceptionHandlingThread 是一個繼承了 threading.Thread 的類,這是一種建立多執行緒的方式。每個執行緒執行的方法主要做兩件事:執行 connection 的 run() 方法,以及將執行成功的結果存入佇列中。 ![](http://ww1.sinaimg.cn/large/68b02e3bgy1gcso6wveg8j20ja03mjrc.jpg) 接下來再分別把執行成功的結果與出異常的結果都存入到 results 中。 所以,Fabric 是使用了 threading 多執行緒的方式來實現併發。網路請求是 IO 密集型的,使用多執行緒是不錯的方式。 至此,對於我們在開頭提的問題,就有了一個初步的答案:Fabric 封裝了兩種 Group 來批量管理伺服器,其中序列方式就是用了簡單的 for 迴圈,而併發方式使用了 threading 多執行緒方式。 但是,通過分析這兩種 Group 的實現程式碼(以及使用的實踐),我們也可以發現 Fabric 的缺陷: - Group 只實現了 run() 方法,但是 Connection 的 put()、get()、sudo() 等方法都沒有,這意味著用這種方式管理伺服器叢集時,只能在上面執行 shell 命令…… - 每次呼叫 run() 方法時,它要等所有主機都執行完,才會返回結果,這意味著先執行完的主機會被阻塞。更為致命的是,如果其中一臺主機執行時出了異常,整個 run() 方法就拋異常,這意味著每次使用 run() 方法時,都需要作異常捕獲 - run() 方法支援執行單條 shell 命令,但是命令的狀態不會傳遞。假設先在一個 run() 方法中執行 cd 命令切到 A 目錄(非根目錄),再在下一個 run() 方法建立一個檔案,最終結果是該檔案並不在 A 目錄,而是在預設目錄。解決辦法是用“&&”連線起多條命令,略顯麻煩 這幾個問題在 Fabric 的 Github issue 中,被不同的人反覆提出,但是還沒有得到很好的迴應…… 言歸正傳,本文主要分析了 Fabric 在批量管理伺服器時的實現方案,閱讀其原始碼,可以瞭解到序列/併發典型場景的用法,以及類定義、類繼承、多執行緒、異常處理等內容,最後,我們還揭示出了它的幾個特性缺陷。 感謝閱讀。最後,附上 Fabric 教程:[https://mp.weixin.qq.com/s/UHtPaxO2ojql5ps4hTn3Vg](https://mp.weixin.qq.com/s/UHtPaxO2ojql5ps4