< 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/mosnMOSN 簡介
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_streamfilterDemo Readme:
https://github.com/mosn/mosn/tree/master/examples/cn_readme/mosn-extensionsMOSN 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下面我們來看一下效果,首先配置依然是監聽 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動态庫(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 的效果。本次 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://mosn.io/以上就是本期分享的全部内容,如果大家對 MOSN 有問題以及建議,歡迎在群内與我們交流。