MOSN 擴充套件機制解析

rootsongjc發表於2020-04-15

本文根據 SOFAChannel#14 直播分享整理,主題:雲原生網路代理 MOSN 擴充套件機制解析。

作者 永鵬(螞蟻金服)

本文根據 SOFAChannel#14 直播分享整理,主題:雲原生網路代理 MOSN 擴充套件機制解析。

大家好,我是今天的講師永鵬,來自螞蟻金服,目前主要負責 MOSN 的開發,也是 MOSN 的 Committer。今天我為大家分享的是雲原生網路代理 MOSN 的擴充套件機制,希望通過這次分享以後,能讓大家瞭解 MOSN 的可程式設計擴充套件能力,可以基於 MOSN 的擴充套件能力,按照自己實際的業務需求進行二次開發。

前言

今天我們將從以下幾個方面,對 MOSN 的擴充套件機制進行介紹:

  • MOSN 擴充套件能力和擴充套件機制的詳細介紹;
  • 結合示例對 MOSN 的 Filter 擴充套件機制與外掛擴充套件機制進行詳細介紹;
  • MOSN 後續擴充套件能力規劃與展望;

歡迎大家有興趣一起共建 MOSN。在本次演講中涉及到的示例就在我們的 Github 的 examples/codes/mosn-extensions 目錄下,大家有興趣的也可以下載下來執行一下,關於這些示例我們還做了一些小活動,也希望大家可以踴躍參與。

MOSN:https://github.com/mosn/mosn

MOSN 簡介

MOSN 作為雲原生的網路代理,旨在為服務提供多協議、模組化、智慧化、安全的代理能力。在實際生產使用中,不同的廠商會有不同的使用場景,通用的網路代理能力面對具體的業務場景會顯得有些不足,通常都需要進行二次開發以滿足業務需求。MOSN 在核心框架中,提供了一系列的擴充套件機制和擴充套件點,就是為了滿足需要基於業務進行二次開發的場景,同時 MOSN 提供的部分通用邏輯也是基於擴充套件機制和擴充套件點的實現。

image.png

比如通過 MOSN “內建實現” 的透明劫持的能力,就是通過 MOSN Filter 機制實現。而要實現訊息的代理,則可以通過類似的擴充套件實現。在通用代理的情況下,可以通過 Filter 機制實現業務的認證鑑權,也可以實現定製的負載均衡邏輯;除了轉發流程可以擴充套件實現以外,MOSN 還可以擴充套件日誌的實現,用於對標已有的日誌系統,也可以擴充套件 XDS 實現定製的配置更新;根據不同的業務場景還會有很多具體的擴充套件情況,就不在此展開了,有興趣的可以關注 MOSN 社群正在建設的原始碼分析系列文章與文件。

圖片 1.png

MOSN 作為一款網路代理,在轉發鏈路上的網路層、協議層、轉發層,在非轉發鏈路上的配置、日誌、Admin API 等都提供了擴充套件能力,對於協議擴充套件的部分,有興趣的可以看一下上期直播講的 MOSN 多協議機制解析,我們今天將重點介紹一下轉發層的 Stream Filter 擴充套件機制與 MOSN 的外掛機制。

Stream Filter 機制

在實際業務場景中,在轉發請求之前或者回寫響應之前,都可能需要對請求/響應做一些處理,如判斷是否需要進行轉發的認證/鑑權,是否需要限流,又或者需要對請求/響應做一些具有業務語義的記錄,需要對協議進行轉換等。這些場景都與具體的業務高度耦合,是一個典型的需要進行二次開發的情況。MOSN 的 Stream Filter 機制就是為了滿足這樣的擴充套件場景所設計的,它也成為目前 MOSN 擴充套件中使用頻率最高的擴充套件點。

image.png

在目前的內建 MOSN 實現中,Stream Filter 機制暫時與內建的 network filter: proxy 是繫結的,後面我們也考慮將這部分能力進行抽象,讓其他 network filter 也可以複用這部分能力。

關於 Stream Filter,今天會為大家講解兩個部分的內容:

  1. 一個 Stream Filter 包含哪些部分以及在 MOSN 中是如何工作的;
  2. 通過一個 Demo 演示來加深對 Stream Filter 的實現與應用;

一個完整的 Stream Filter

一個完整的 StreamFilter,包含三個部分的內容:

  • 一個 StreamFilter 物件,存在於每一個請求/響應當中,在 MOSN 收到請求的時候發揮作用,我們稱為 ReceiverFilter,在 MOSN 收到響應時發揮作用,我們稱為 SenderFilter。一個 StreamFilter 可以是其中任意一種,也可以是兩種都是;
  • 一個 StreamFilterFactory 物件,用於 MOSN 在每次收到請求時,生成 StreamFilter 物件。在 Listener 配置解析時,一個 StreamFilter 的配置會生成一個其對於的 StreamFilterFactory。同一個 StreamFilter 在不同的 Listener 下可能對應不同的 StreamFilterFactory,但是也有的特殊情況下,StreamFilterFactory 可能需要實現為單例;
  • 一個 CreateStreamFilterFactory 方法,配置解析時生成 StreamFilterFactory 就是呼叫它;

image.png

Stream Filter 在 MOSN 中是如何工作的

接下來,我們看下 Stream Filter 在 MOSN 中是如何工作的。

image.png

當 MOSN 經過協議解析,收到一個完整的請求時,會建立一個 Stream。此時收到請求的 Listener 中每存在 StreamFilterFactory,就會生成一個 StreamFilter 物件,隨後進入到 proxy 流程。

進入 proxy 流程以後,如果存在 ReceiverFilter,那麼就會執行對應的邏輯,ReceiverFilter 包括兩個階段,“路由前” 和 “路由後”,在每個 Filter 處理完成以後,會返回一個狀態,如果是 Stop 則會中止後續尚未執行的 ReceiverFilter,通常情況下,返回 Stop 狀態的 Filter 都會回寫一個響應。如果是 Continue 則會執行下一個 ReceiverFilter,直到本階段的 ReceiverFilter 都執行完成或中止;路由前階段的 ReceiverFIlter 執行完成後,就會執行路由後階段,其邏輯和路由前一致。如果是正常轉發,那麼隨後 MOSN 會收到一個響應或者發現其他異常直接回寫一個響應,此時就會進入到 SenderFilter 的流程中,完成 SenderFilter 的處理。SenderFilter 處理完成以後,MOSN 會寫響應給 Client,並且完成最後的收尾工作,收尾工作包括一些資料的回收、日誌的記錄,以及 StreamFilter 的 “銷燬”(呼叫 OnDestroy)。

Stream Filter Demo

對 StreamFilter 有了一個基本的認識以後,我們來看一個實際的 Demo 程式碼來看下如何實現一個 StreamFilter 並且讓它在 MOSN 中發揮作用。

image.png

按照剛才我們的介紹,一個 Stream FIlter 要包含三部分:Filter、Factory、CreateFactory。

  • 首先我們實現一個 Filter,其邏輯是模擬一個鑑權的 Filter:只有請求的 Header 中包含所配置的 Key-Value 時,MOSN 才會對請求做繼續轉發,否則直接返回 403 錯誤;
  • 然後我們實現一個 Factory,它負責生成我們實現的 Filter,並且說明 Filter 應該發揮作用的階段(在請求階段、路由匹配之前);
  • 最後我們定義了一個生成 DemoFactory 的函式 CreateDemoFactory,並且通過 init 將其 “註冊”,註冊完成以後,MOSN 配置解析就可以識別這個 StreamFilter;

image.png

完成實現以後,我們就可以通過具體的配置來實現對應的功能了。在示例的配置中,配置 StreamFilter 為我們剛才實現的 Filter,只轉發 Header 中包含 user:admin 的請求。示例配置中監聽的埠是 2046,轉發的後端 server 埠是 8080。在演示之前,我已經完成了 8080 server 的啟動,這個 server 會對收到的任意請求返回 200 。我們來看一下 MOSN 轉發情況。Demo 操作可以在文末直播的視訊回顧中檢視。

MOSN Plugin 機制

下面我們來了解一下 MOSN 的 Plugin 機制。

剛才我們對 Stream Filter 有了一個瞭解,MOSN 中其餘的擴充套件實現也是類似的方法,思路就是編碼實現 MOSN 擴充套件點所需要的介面然後利用 MOSN 的框架執行擴充套件的實現。

image.png

但是這裡會發現一個問題,就是有時候我們需要的擴充套件能力已經有現成可用的實現了,那麼我們是否可以做簡單的改造就讓 MOSN 可以獲取對應的能力,哪怕目前可用的實現不是 Go 語言的實現,比如現成的限流能力的實現、注入能力的實現等;又或者對於某些特定的能力,它需要有更嚴格的控制,更高的標準,比如安全相關的能力。

類似這樣的場景,我們引入了 MOSN 的 Plugin 機制,它支援我們可以對 MOSN 需要的能力進行獨立開發或者我們對現有的程式進行適當的改造以後,就可以將它們引入到 MOSN 當中來。

MOSN 的 Plugin 機制包含了兩部分內容,一是 MOSN 自定義的 Plugin 框架,它支援通過在 MOSN 中實現 agent 與一個獨立的程式進行互動來完成 MOSN 擴充套件能力的實現。二是基於 Golang 的 Plugin 框架,通過動態庫(SO)載入的方式,實現 MOSN 的擴充套件。其中動態庫載入的方式目前還存在一些侷限性,還處於 beta 階段。

我們先來看一下多程式 Plugin 框架。

多程式 Plugin 框架

MOSN 的 Plugin 框架是 MOSN 封裝的一個可以讓 MOSN 通過 gRPC 和獨立程式進行互動的方式,它包含兩部分:

  1. 獨立的程式通過 MOSN Plugin 框架管理,作為 MOSN 的子程式;MOSN 的 Plugin 框架可以管理它們,如啟動、關閉等;
  2. 通過在 MOSN 中實現的 agent,使用 gRPC 的方式和子程式進行互動,gRPC 可以是基於 tcp 的,也可以是基於 domain socket 的;

image.png

基於這個框架,我們只需要開發或者進行一些改造,讓程式滿足 MOSN 框架的規範,就可以作為 MOSN 多程式外掛的一部分。

首先我們需要提供一個 gRPC 的服務,並且滿足 MOSN 框架下的 proto 定義。當 gRPC server 啟動完成以後,向標準輸出(stdout)輸出一段約定的字串,作為 MOSN 和子程式之間的握手協議。MOSN 中的對應 agent 會通過握手協議完成與子程式之間的連線建立。握手協議的字串包含 5 個欄位,每個欄位之間用”|“分割,其中帶 $ 符號的是根據實際程式情況需要填寫的值,其餘的是當前約定的固定欄位。network 支援 tcp/unix,代表通過 tcp 方式還是 unix domain socket 的方式進行通訊,addr 表示 gRPC server 監聽的地址。

MOSN 提供了 go 語言的子程式 server 封裝,在 go 語言場景下,作為子程式的程式只需要實現一個 MOSN 框架下的 plugin.Service 介面,並且通過 plugin.Serve 方法啟動即可。

通過 Plugin 框架,讓 MOSN 做到在擴充套件功能實現的時候,支援隔離性、支援異構語言擴充套件能力、支援模組化,以及具備程式管理的能力。

對於 MOSN 通過多程式方式完成擴充套件,今天準備了兩個示例和大家進行分享。一個是基於 MOSN 的 TLS 擴充套件,模擬了通過一個安全等級比較高的證照管理程式來獲取 TLS 配置證照、私鑰等敏感資訊的能力;第二個是將之前演示的 Stream Filter 修改為了 “子程式”,模擬 “如何將現成的能力” 引入 MOSN。

基於 MOSN 的 TLS 擴充套件示例

首先來看 TLS 的擴充套件,示例包含兩部分內容:

  • 獨立的子程式,用 Go 語言實現,實現了 plugin.Service 介面,並通過 plugin.Serve 方法啟動;
  • MOSN 擴充套件點實現互動 agent。在這裡就不詳細展開 TLS 擴充套件點的細節了,只關注互動過程:通過 Call 方法傳送 gRPC 請求,獲取響應,完成相關邏輯;

image.png

load cert demo: https://github.com/mosn/mosn/tree/master/examples/codes/mosn-extensions/plugin/cert_loader Demo Readme:https://github.com/mosn/mosn/tree/master/examples/cn_readme/mosn-extensions

下面我們來看一下效果,首先配置依然是監聽 2046 的埠,配置了擴充套件的 TLS 配置,就需要 HTTPS 才可以訪問 MOSN。

Stream Filter 作為 agent 示例

下面我們來看下 Stream Filter 作為 agent,與多程式之間的示例,模擬 “如何將現成的能力” 引入 MOSN。在示例中我們把之前的 “鑑權” 認為是一個 “現成的” 能力。

image.png

獨立程式中實現和之前一樣的 “鑑權” 能力,其配置來自程式的啟動引數。Stream Filter 作為 agent 實現,其中 “校驗” 邏輯修改為和子程式互動,在生成 Factory 時完成子程式的啟動和配置設定。

這個示例執行以後和之前 Stream Filter 的效果是一樣的。

動態庫 (SO) 擴充套件機制

在目前的多程式框架中,雖然擴充套件能力可以通過一個獨立的子程式實現,但是仍然需要在 MOSN 中實現一個 agent 用於互動,依然需要在 MOSN 中編寫一部分程式碼;而我們希望引入動態庫(SO)載入的機制,實現在不重新編譯 MOSN 的情況下,通過載入不同的 SO,做到不同的擴充套件能力。

image.png

與子程式模式相比,SO 雖然也是一個獨立的二進位制,但是最終啟動的時候,不會有額外的子程式存在,其生命週期可以和 MOSN 完全保持一致,而且動態庫機制還有一個優勢:它可以讓擴充套件程式碼和 MOSN 完全解耦合。

但是,目前使用動態庫載入的方式還存在一些限制,因此 MOSN 對於這個能力也還處於 Beta 階段,並沒有投入實際使用,需要完善。相關的原因包括:

  • 部分 MOSN 擴充套件的實現需要用到 MOSN 中的一些定義,因此在動態庫實現時不能完全做到解耦合。

為了解決這個問題,MOSN 將一些基礎庫(如日誌、buffer 等),一些 API 定義從 MOSN 的核心倉庫中獨立出來,這樣擴充套件實現和 MOSN 核心都引用這些 “獨立” 的庫,減少擴充套件對 MOSN 核心程式碼的依賴。

如果某一個擴充套件點要支援完全解耦合的動態庫擴充套件,那麼對應的擴充套件點都需要進行支援動態庫載入的改造,包括配置模型與實現。

  • MOSN 動態庫載入的方式,其實是基於 Go 語言的 plugin 包實現的,它可以載入用 Go 語言編譯的動態庫。但是對於動態庫的編譯環境存在一些限制,編譯它時必須和 MOSN 編譯時的 GOPATH 保持一致;同時引用的程式碼路徑都需要保持一致,如果存在 vendor 目錄,那麼意味著編譯動態庫時的專案路徑也得和 MOSN 核心保持一致。

為了解決這個問題,我們考慮使用 Docker 編譯,在編譯時統一 GOPATH,強制修改程式碼目錄結構,遮蔽掉 Vendor 目錄差異的方式來解決,這種方式目前仍然在驗證中。

因此理論上 MOSN 目前所有的擴充套件點都可以使用 Go 語言原生機制通過載入 SO 的方式來實現,而目前 MOSN 最適合實現這個能力的一個擴充套件點就是 Stream Filter。

我們只需要實現一個通用的、可以載入 SO 的 Filter,然後在具體的 SO 中實現真正的 StreamFilter 邏輯,由於 StreamFilter 實現所需要的介面定義都在 mosn.io/api 中,所以 SO 可以做到和 MOSN 核心框架解耦合。

關鍵點就是這個通用 Filter 的設計和實現,我們也通過 Demo 來看一下。

通用 Filter 的設計和實現

這個通用的 Filter 和普通的 StreamFilter 不同,它只包含一個要素:CreateFactory。思路是通過通用的 CreateFactory,載入 SO 中的 CreateFactory 並執行,讓 SO 中的 Factory 發揮作用。

image.png

通用 CreateFactory 包括:

  • 配置解析,解析出兩部分內容:一是需要載入的 SO 路徑,二是 SO 中對應 Filter 所需要的配置;
  • SO 路徑就代表了 SO 中 Filter 的 “註冊”,以及本次會選擇這個 Filter;
  • 載入 SO,基於其中約定好的函式名,獲取真正的 CreateFactory 函式;
  • 呼叫真正的 CreateFactory 函式,實現 SO 中 StreamFilter 的載入;

由此,我們可以看到,SO 中的 StreamFIlter 也和普通的 FIlter 有些區別:

下面我們來看一下這個 Demo 的效果。本次 Demo 中的 Filter 實現依然是之前的 “鑑權” 示例。經過驗證,我們發現這個思路是可行的,但是離生產實踐還需要完善更多的細節。

程式碼擴充套件活動

經過這些演示,相信大家對 MOSN 的擴充套件能力也有所瞭解了,這裡我們來做一個程式碼擴充套件活動,希望大家可以踴躍參與。完成活動任務,提交相關程式碼 PR 到 MOSN 的倉庫,我們會進行 CodeReview 和驗證,第一個驗證通過的程式碼將合併到 MOSN 的 example 中,並且對提交的同學送上一份獎勵;對於前 3 名提交、同樣結果正確並且是原創的,雖然我們不能合併對應的程式碼,但是我們也將送上獎勵。

活動任務共有五個:

  • 多程式 Demo 中證照載入的獨立程式,使用 python 或者 java 實現以後,demo 執行演示成功。任意一種語言就算完成一個任務。
  • examples/codes/mosn-extensions/plugin/cert_loader/python/
  • examples/codes/mosn-extensions/plugin/cert_loader/java/
  • 多程式 Demo 中 stream filter 的獨立程式,使用 python 或者 java 實現以後,demo 執行演示成功。任意一種語言就算完成一個任務。
  • examples/codes/mosn-extensions/plugin/filter/python/
  • examples/codes/mosn-extensions/plugin/filter/java/
  • SO 動態載入 Demo 中,SO 裡實現的 Stream Filter 結合多程式框架(GO 語言)實現,Demo 執行演示成功。
  • examples/codes/mosn-extensions/plugin/so/subprocess/

跨語言相關的實現可以參考以下示例:

https://github.com/mosn/mosn/tree/master/examples/codes/plugin/across-languages/server/

規劃與展望

最後向大家介紹一下 MOSN 後續擴充套件能力的規劃,也希望大家有需求的可以向我們反饋,有興趣的一起參與到 MOSN 的建設中來。首先就是要完善 SO 動態庫載入機制,讓 MOSN 支援 SO 方式載入擴充套件;然後就是針對 LUA 的指令碼擴充套件以及支援 WASM 的擴充套件能力;最後 MOSN 還會增加更多的擴充套件點,以滿足更多更復雜的場景。非常歡迎大家參與到 MOSN 社群的共建中。

以上就是本期分享的全部內容,如果大家對 MOSN 有問題以及建議,歡迎在群內與我們交流。

本期直播視訊回顧以及 PPT 檢視地址見:https://tech.antfin.com/community/live/1152

更多原創文章乾貨分享,請關注公眾號
  • MOSN 擴充套件機制解析
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章