< SOFA:Channel/ >,有趣實用的分佈式架構頻道。
回顧視頻以及 PPT 查看地址見文末。歡迎加入直播互動釘釘群 : 21992058,不錯過每場直播。
本文根據 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 提供的部分通用邏輯也是基於擴展機制和擴展點的實現。
比如通過 MOSN “內置實現”的透明劫持的能力,就是通過 MOSN Filter 機制實現。而要實現消息的代理,則可以通過類似的擴展實現。在通用代理的情況下,可以通過 Filter 機制實現業務的認證鑑權,也可以實現定製的負載均衡邏輯;除了轉發流程可以擴展實現以外,MOSN 還可以擴展日誌的實現,用於對標已有的日誌系統,也可以擴展 XDS 實現定製的配置更新;根據不同的業務場景還會有很多具體的擴展情況,就不在此展開了,有興趣的可以關注 MOSN 社區正在建設的源代碼分析系列文章與文檔。
MOSN 作為一款網絡代理,在轉發鏈路上的網絡層、協議層、轉發層,在非轉發鏈路上的配置、日誌、Admin API 等都提供了擴展能力,對於協議擴展的部分,有興趣的可以看一下上期直播講的 MOSN 多協議機制解析,我們今天將重點介紹一下轉發層的 Stream Filter 擴展機制與 MOSN 的插件機制。
Stream Filter 機制
在實際業務場景中,在轉發請求之前或者回寫響應之前,都可能需要對請求/響應做一些處理,如判斷是否需要進行轉發的認證/鑑權,是否需要限流,又或者需要對請求/響應做一些具有業務語義的記錄,需要對協議進行轉換等。這些場景都與具體的業務高度耦合,是一個典型的需要進行二次開發的情況。MOSN 的 Stream Filter 機制就是為了滿足這樣的擴展場景所設計的,它也成為目前 MOSN 擴展中使用頻率最高的擴展點。
在目前的內置 MOSN 實現中,Stream Filter 機制暫時與內置的 network filter: proxy 是綁定的,後面我們也考慮將這部分能力進行抽象,讓其他 network filter 也可以複用這部分能力。
關於 Stream Filter,今天會為大家講解兩個部分的內容:
- 一個 Stream Filter 包含哪些部分以及在 MOSN 中是如何工作的;
- 通過一個 Demo 演示來加深對 Stream Filter 的實現與應用;
一個完整的 Stream Filter
一個完整的 StreamFilter,包含三個部分的內容:
- 一個 StreamFilter 對象,存在於每一個請求/響應當中,在 MOSN 收到請求的時候發揮作用,我們稱為 ReceiverFilter,在 MOSN 收到響應時發揮作用,我們稱為 SenderFilter。一個 StreamFilter 可以是其中任意一種,也可以是兩種都是;
- 一個 StreamFilterFactory 對象,用於 MOSN 在每次收到請求時,生成 StreamFilter 對象。在 Listener 配置解析時,一個 StreamFilter 的配置會生成一個其對於的 StreamFilterFactory。同一個 StreamFilter 在不同的 Listener 下可能對應不同的 StreamFilterFactory,但是也有的特殊情況下,StreamFilterFactory 可能需要實現為單例;
- 一個 CreateStreamFilterFactory 方法,配置解析時生成 StreamFilterFactory 就是調用它;
Stream Filter 在 MOSN 中是如何工作的
接下來,我們看下 Stream Filter 在 MOSN 中是如何工作的。
當 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 中發揮作用。
按照剛才我們的介紹,一個 Stream FIlter 要包含三部分:Filter、Factory、CreateFactory。
- 首先我們實現一個 Filter,其邏輯是模擬一個鑑權的 Filter:只有請求的 Header 中包含所配置的 Key-Value 時,MOSN 才會對請求做繼續轉發,否則直接返回 403 錯誤;
- 然後我們實現一個 Factory,它負責生成我們實現的 Filter,並且說明 Filter 應該發揮作用的階段(在請求階段、路由匹配之前);
- 最後我們定義了一個生成 DemoFactory 的函數 CreateDemoFactory,並且通過 init 將其“註冊”,註冊完成以後,MOSN 配置解析就可以識別這個 StreamFilter;
完成實現以後,我們就可以通過具體的配置來實現對應的功能了。在示例的配置中,配置 StreamFilter 為我們剛才實現的 Filter,只轉發 Header 中包含 user:admin 的請求。示例配置中監聽的端口是 2046,轉發的後端 server 端口是 8080。在演示之前,我已經完成了 8080 server 的啟動,這個 server 會對收到的任意請求返回 200 。我們來看一下 MOSN 轉發情況。Demo 操作可以在文末直播的視頻回顧中查看。
Stream Filter Demo: https://github.com/mosn/mosn/tree/master/examples/codes/mosn-extensions/simple_streamfilter
Demo Readme:https://github.com/mosn/mosn/tree/master/examples/cn_readme/mosn-extensions
MOSN Plugin 機制
下面我們來了解一下 MOSN 的 Plugin 機制。
剛才我們對 Stream Filter 有了一個瞭解,MOSN 中其餘的擴展實現也是類似的方法,思路就是編碼實現 MOSN 擴展點所需要的接口然後利用 MOSN 的框架運行擴展的實現。
但是這裡會發現一個問題,就是有時候我們需要的擴展能力已經有現成可用的實現了,那麼我們是否可以做簡單的改造就讓 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 和獨立進程進行交互的方式,它包含兩部分:
- 獨立的進程通過 MOSN Plugin 框架管理,作為 MOSN 的子進程;MOSN 的 Plugin 框架可以管理它們,如啟動、關閉等;
- 通過在 MOSN 中實現的 agent,使用 gRPC 的方式和子進程進行交互,gRPC 可以是基於 tcp 的,也可以是基於 domain socket 的;
基於這個框架,我們只需要開發或者進行一些改造,讓程序滿足 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 請求,獲取響應,完成相關邏輯;
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。在示例中我們把之前的“鑑權”認為是一個“現成的”能力。
獨立進程中實現和之前一樣的“鑑權”能力,其配置來自進程的啟動參數。Stream Filter 作為 agent 實現,其中“校驗”邏輯修改為和子進程交互,在生成 Factory 時完成子進程的啟動和配置設置。
這個示例運行以後和之前 Stream Filter 的效果是一樣的。
Stream Filter Plugin demo: https://github.com/mosn/mosn/tree/master/examples/codes/mosn-extensions/plugin/filter
Demo Readme:https://github.com/mosn/mosn/tree/master/examples/cn_readme/mosn-extensions
動態庫(SO)擴展機制
在目前的多進程框架中,雖然擴展能力可以通過一個獨立的子程序實現,但是仍然需要在 MOSN 中實現一個 agent 用於交互,依然需要在MOSN中編寫一部分代碼;而我們希望引入動態庫(SO)加載的機制,實現在不重新編譯 MOSN 的情況下,通過加載不同的 SO,做到不同的擴展能力。
與子程序模式相比,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 發揮作用。
通用 CreateFactory 包括:
- 配置解析,解析出兩部分內容:一是需要加載的 SO 路徑,二是 SO 中對應 Filter 所需要的配置;
- SO 路徑就代表了 SO 中 Filter 的“註冊”,以及本次會選擇這個 Filter;
- 加載 SO,基於其中約定好的函數名,獲取真正的 CreateFactory 函數;
- 調用真正的 CreateFactory 函數,實現 SO 中 StreamFilter 的加載;
由此,我們可以看到,SO 中的 StreamFIlter 也和普通的 FIlter 有些區別:
- 生成 StreamFilterChainFactory 的函數必須是固定的名字;
- 不再需要 init “註冊”該函數;
Stream Filter SO Demo: https://github.com/mosn/mosn/tree/master/examples/codes/mosn-extensions/plugin/so
Demo Readme:https://github.com/mosn/mosn/tree/master/examples/cn_readme/mosn-extensions
下面我們來看一下這個 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:https://github.com/mosn/mosn
MOSN 官網:https://mosn.io/
以上就是本期分享的全部內容,如果大家對 MOSN 有問題以及建議,歡迎在群內與我們交流。