OPPO在FaaS領域的探索與思考

infoq 發佈 2022-09-27T21:46:41.323515+00:00

雖然按需或「pay as you go」模型的想法可以追溯到 2006 年和一個名為 Zimki 的平台,但Serverless一詞的第一次出現是 Iron.io 在 2012 年推出了 IronWorker 產品,一個基於容器的分布式按需工作平台。


ESA Stack(Elastic Service Architecture) 是 OPPO 雲計算中心孵化的技術品牌,致力於微服務相關技術棧,幫助用戶快速構建高性能,高可用的雲原生微服務。產品包含高性能 Web 服務框架、RPC 框架、服務治理框架、註冊中心、配置中心、調用鏈追蹤系統,Service Mesh、Serverless 等各類產品及研究方向。

當前部分產品已經對外開源:

開源主站:https://www.esastack.io/

Github: https://github.com/esastack

00 Serverless及FaaS簡介

Serverless是什麼,下面引用的這段來自伯克利大學的論文《A Berkeley View on Serverless Computing》

Put simply, serverless computing = FaaS + BaaS. In our definition, for a service to be considered serverless, it must scale automatically with no need for explicit provisioning, and be billed based on usage.

我們暫且不管FaaS和BaaS是什麼,這段話提到了2個核心的點,自動彈性伸縮和按用量付費,一般來說各大雲產商會多強調的一個賣點是少運維甚至免運維,開發者可以少關注機器層面的事情。有一個很形象的比喻是,Serverless就像網約車,需要的時候我們再叫車,不需要的時候我們也不需要承擔汽車的閒置、養護成本,我們只需要按需付費。

關於Serverless的歷史,下面摘錄的這段來自Serverless白皮書

雖然按需或"pay as you go"模型的想法可以追溯到 2006 年和一個名為 Zimki 的平台,但Serverless一詞的第一次出現是 Iron.io 在 2012 年推出了 IronWorker 產品,一個基於容器的分布式按需工作平台。

而Serverless能真正被大家所熟知,大體是在2014年AWS推出Lambda的時候,後續各大雲產商都推出了自己的產品。限於篇幅,本文後述內容會更多的聚焦在FaaS的層面。

那FaaS又是什麼,其實並不是一個容易回答的問題,但我還是想先嘗試概括性地描述下自己的理解。FaaS是一種開發模式,相比傳統模式,開發者向基礎設施平台讓渡了一部分選擇的自由,比方語言框架、依賴庫、函數必須實現雲廠商定義的接口規範等等。與之相對的,平台會為開發者提供一些能力,比方按需執行和計費,彈性伸縮,少運維甚至免運維機器等。正如雲計算的發展史,總的趨勢都是基礎設施平台管的或者提供的通用能力越來越多,為開發者屏蔽了越來越多的複雜性。

文字描述可能有些蒼白,下面的代碼是OPPO FaaS平台一個基於Java語言的Web函數示例

@Path("/")
public class HelloWorldFunction {


    private static final Logger LOGGER = LoggerFactory.getLogger(HelloWorldFunction.class);


    @POST
    public String handle(@HeaderParam("name") String name) {
        LOGGER.info("function is running! name {}", name);
        return "hello " + name;
    }
}

這是一個比較簡單的例子,函數收到請求後,列印了一些日誌,然後往客戶端返回了"hello ${name}"。上述的開發者對平台讓渡了一些選擇自由,在這個例子裡面。

  1. 開發者不知道代碼底層跑在一個什麼樣的web框架之上,是tomcat還是jetty還是其它,多提一句,OPPO FaaS平台java語言的Runtime是基於我們自己開源的web框架restlight實現。
  2. 此外,大部分雲廠商的FaaS產品是需要開發者實現廠商定義的API接口的,這裡平台限定了開發者只能使用JAX-RS這個社區規範而不是提供私有接口。
  3. 開發者需要使用平台定義的logger來列印日誌,這樣平台可以做日誌路徑統一定義方便採集,同時往logger里注入諸如traceid等方便排查問題的信息。

下圖引用自文章 《Serverless 選型:深度解讀 Serverless 架構及平台選擇》

圖里以業務聚焦程度和運維下放(給雲廠商)程度為坐標軸,給市面上的產品大體做了區間劃分。FaaS產品在這兩個維度都是最高的那檔,意味著這種形態的產品已經在最大限度上讓開發者可以聚焦在自己的業務邏輯處理上而不用關心太多的非功能性需求。

當然,正如上面所描述,開發者是需要讓渡出很多選擇上的自由並且能做的事情有限。此外,想像開發者現在有個普通的web應用提供了十多個接口,如果基於FaaS產品函數這種粒度,不得不拆分成十多個函數,這會帶來管理和操作上的困難。所以,各大雲廠商的FaaS產品,慢慢都開始提供了基於鏡像或者自定義Runtime的方式,讓開發者可以將一些老應用低成本的遷移上FaaS。

01 OPPO在FaaS產品上的探索

(1)探索階段

筆者自己剛開始做Serverless\FaaS相關工作的時候,第一款接觸的產品是大名鼎鼎的Knative

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: helloworld-go
spec:
  template:
    spec:
      containers:
        - image: gcr.io/knative-samples/helloworld-go
          env:
            - name: TARGET
              value: "Go Sample v1"

上面是一個簡單的例子,helloworld-go這個鏡像本身是個非常簡單的http服務,訪問之後會返回一串文本。但如果跑在了knative環境之內,便可以快速地將應用Serverless化,knative可以根據並發訪問量(也支持其它指標)給應用做彈性伸縮甚至把實例數縮容到0。當然,實例從0到1由於底層機制,應用就緒到提供服務的時間大概是分鐘級比較長,這個業界稱為冷啟動耗時,後文會再展開。這裡只涉及knative的一小部分功能,其它的限於篇幅不展開闡述。

當時體驗及研究過一段之後,筆者一直反覆思考的問題其實是,如果自己作為一個普通的業務使用方,存量的業務如果僅僅只是為了一個彈性伸縮的能力,在公司內部的彈性伸縮產品已經有這種能力了,自己沒有動力將服務遷移到knative的環境。所以在經過內部討論及一些嘗試後,我們最終沒有選擇做存量業務的Serverlss化,而是決定專注做FaaS產品,拉一些適合這種形態的新業務來落地。

事實上,Knative也並不是FaaS產品,關於這點官方也是給出過說明的。當然了,我們是可以基於Knative去做FaaS產品,國產FaaS產品openFunction便是一個例子

(2)選型及分析

在決定了專注做FaaS產品之後,便面臨選型問題。雲廠商的FaaS產品是沒有開源的無從得知實現細節,所以只能調研開源產品。市面上大部分的FaaS開源框架都是基於k8s實現的,像fission、openfaas、openfunction等,好處是這樣可以最大程度地利用k8s的基礎能力和整個生態,而不用什麼東西都從0開始建設。

想像一下我們現在需要對外提供一個類似LeetCode那樣的編程環境,給用戶編寫一些相對簡單的代碼然後調試運行。如果使用傳統的方式來做,部署一組機器然後多個用戶編寫的代碼在這些機器上分配調度之後去運行,一個是隔離性不好會互相搶占資源,另外是需要自己實現一套把代碼根據機器的負載情況把任務調度到某台機器的邏輯,再有還需要考慮這些代碼訪問量和彈性伸縮、後續運維等。上面提到的痛點,正是FaaS產品所擅長解決的,所以當時我們就承接了這麼一個需求。

如果這個需求我們直接使用k8s來實現,每份代碼我們跑在一個deployment裡面,彈性伸縮我們使用k8s的hpa解決,pod提供了一定程度的隔離,功能也是可以實現的。但回想一下我們以前在LeetCode改代碼提交之後點擊測試,基本上可以在一兩秒內出結果。如果直接使用k8原生的這些能力,用戶改完代碼之後我們需要走編譯、打鏡像、k8s調度、kubelet拉鏡像、起服務的流程,整個過程至少是分鐘級,用戶體驗會比較差。這個問題和上面提到的冷啟動問題是很接近的,即服務實例在0到1的過程是比較耗時的。

1)冷啟動分析

上圖是一次實例數從0到1時冷啟動的過程,步驟1-3耗時大部分時候遠超執行函數的時間。對於步驟1來說,如果底層是基於k8s利用其原生的調度能力,不管怎麼優化,都不可能到毫秒級。對於步驟2,雲廠商一般會限制上傳的代碼包\鏡像大小,另外則是如果用戶使用的依賴已經被做進了運行時Runtime(一般文檔里會有依賴列表),則用戶上傳的代碼不需要帶有這些依賴可以小很多,這樣下載依賴和代碼的時間會短很多。針對步驟3,這裡可能需要岔開單獨說一下Runtime是個什麼東西。

2)Runtime是什麼

由於FaaS的模式開發者只提供業務邏輯那一小部分代碼,肯定是不足以運行起來的,它需要有個環境去承接運行,我們一般把FaaS平台提供的這個運行時環境稱為Runtime。這裡又可以分成兩部分,一個是代碼片段底層運行的語言相關的框架(tomcat、jetty等)。另外一部分是語言無關的環境,像你的服務是跑在容器裡面還是vm或者其他環境裡面。

一般來說,函數的調用和執行有兩種方式。一種是真的完全按需,每個請求由獨立的進程(容器)來執行,執行完就銷毀,這種一般也稱為Exec模式。另外一種是執行完還是會保持一段時間,後續沒有請求再銷毀,也叫Runtime模式。關於這兩種模式的討論,參考連結5的文章寫的挺好,這裡摘抄一段

調用和執行方式。Exec 模式雖然開發成本比較小,但運行效率上有差距,本質上是把所有的語言都當做腳本語言對待,等於放棄了各種語言這麼多年性能的優化成果。如果是比較重的事件處理,還可以承受,但如果要承載高並發任務就比較困難,這也是為什麼 Azure 後來放棄自己的第一版 Function 實現的原因。當前看來,如果要做通用的 FaaS 兼顧性能和效率,還是要通過 Runtime 的方式。但 Runtime 的方式帶來的一個問題是依賴的管理和隔離,如果 Runtime 沒有很好的機制隔離用戶代碼和 Runtime 的代碼,二者的依賴就有耦合,導致用戶的 Function 的依賴選擇上受限於 Runtime 的平台。

Runtime的啟動速度及隔離性是最重要的兩部分。一般來說基於k8s的FaaS產品底層當前跑的都是容器,啟動速度並不慢。而傳統上來說我們會覺得vm的啟動速度遠低於容器,但從公開資料來看,一些雲廠商會使用輕型vm來跑FaaS,這樣可以兼顧啟動速度和隔離性。

Firecracker 目前已經能提供小於125ms 的 MircroVM 啟動速度,每秒150台的啟動能力,小於5MiB 的內存開銷,並發運行4000台的極限承載容量(AWS i3.metal EC2 作為宿主機),以及熱升級能力等。這些都是傳統虛擬機所遙不可及,但現代化彈性工作負載又有強烈需求的性能指標。 ——此處引用可參考引用文章4

此外,現在業界已經有些項目在調研甚至落地使用WebAssembly(後文簡稱wasm)跑FaaS,公有雲暫時沒有看到直接支持,但是可以在docker鏡像裡面跑wasm這樣的方式間接支持。主要還是考慮隔離性、啟動速度、還有wasm可以支持多語言編譯成.wasm文件運行。雖然當前wasm技術整個配套並不十分完善,但相信隨著發展後面市面上應該會有越來越多的FaaS產品支持跑在wasm的Runtime裡面。

至此,我們分析了冷啟動問題的多個環節,及Runtime在FaaS產品里的重要性,聊完了問題我們講講解決方案。

3)熱pod池技術

業界在這個問題上其實大概那麼幾個手段。一個是實例預留,既不把實例數縮容到0。還有就是上文描述的,探索極致的Runtime啟動速度配合調度系統,這個投入比較大。另外就是預先準備實例熱池的方案,提前準備好一組實例,在用戶請求到來時從中選一個實例綁定用戶的代碼然後執行邏輯。業界開源的FaaS產品fission即實現了熱池的方案,並且像nodejs、python這樣的語言,代碼包不大的情況下,冷啟動耗時大概在100+ms左右。

4)fission從0到1的流程分析

上圖是從fission官方文檔拿下來的。左下角Warm Environment Pool即是所謂的熱pod池。裡面的pod是已經啟動了的服務實例,實例已經走完了k8s調度、拉鏡像、起服務等耗時的步驟。我們這裡從步驟3看起,當請求打到入口代理router時,其會從executor處拿一個函數的訪問地址(步驟3),而executor若發現函數實例為0,會從熱pod池裡隨機挑一個pod,然後到pod自己去下載代碼,利用語言的動態加載機制加載用戶代碼(步驟5、6、7),完成上述步驟意味著pod已經具備提供服務能力,router拿到返回的地址直接請求函數即可。

5)不足分析

上述是一個簡要的流程分析,實際會更複雜些。看到這裡讀者可能會有疑問

  1. router請求executor取函數地址,是每次請求都需要這麼做嘛
  2. 流程里沒有體現彈性伸縮和並發控制的流程,這塊是在哪裡實現的

關於問題1,是的,fission原生的流程就是這麼做的。而且在router和executor的流程里還有一些查詢k8s的操作,每次請求都會做,換句話說核心鏈路里是強依賴了k8s的,不僅穩定性有限,吞吐量也非常有限,設計不合理。

關於問題2,並發控制的流程是在executor服務里控制的,它的實現是在內存里存了類似下面這個結構來計數做並發控制,由於把數據存在內存里,executor服務只能部署單台,這也是一個非常不合理的設計。這裡針對的是web函數,fission事件函數的彈性伸縮限於篇幅,本文就不展開敘述了。

{
    "函數id":{
        "實例1":10,  // 記錄當前實例在處理的請求數
        "實例2":3
    },
    ……
}

(3)具體設計及實現

考慮到人員投入情況和需要業務落地的牽引,我們決定先使用開源產品來建設自己的FaaS產品,後面慢慢建設完善。具體來說我們選用了fission,它底層基於kubernetes,像上文描述,其使用了熱pod池技術解決冷啟動的問題,我們另外打通了公司內部許多成熟的產品,支撐了第一個業務落地。

1)產品全景圖

上圖是我們FaaS產品的概覽圖,我們是基於公司內部很多成熟的產品來搭建服務。Kubernetes集群是使用了OPPO定製的版本OKE(OPPO Kubernetes Engine),好處是在多雲的環境裡我們的服務無須做適配即可部署。此外我們也打通了CMDB、日誌、監控、API網關、註冊中心、彈性伸縮等產品,這些都是一個FaaS系統必不可少的。限於篇幅,下文主要涉及的還是Serving部分。

2)函數從0到1流程圖

前文提到fission存在的問題,我們在自己的流程設計里做了改進和優化。下文會分成幾個方面來討論。

3)API網關

我們並沒有使用fission原生的router網關,而是使用公司自己的API網關。主要是基於以下兩點考慮

公司的API網關已經經過很長時間超大規模流量的考驗,遠比fission的router網關來的穩定和可靠。

公司的網關本身的功能也豐富的多,事實上我們也利用了網關的分流能力,實現了一套按一定規則路由到函數不同版本的方案,crd的設計及具體流程為了簡潔上圖未有體現。

4)註冊發現機制

fission原生是沒有註冊發現這套機制的,我們對接了公司的網關及註冊中心產品,實現了函數發現功能,實例數的增刪可以實時通過註冊中心推送給到網關。此時僅在實例數從0到1時會從調用executor組件拿一個函數地址,其餘的實例增減都是通過函數發現的功能來實現。解決了fission原生的缺陷,在核心鏈路里依賴了executor服務及k8s帶來的吞吐量及穩定性問題。

5)彈性伸縮

k8s裡面的deployment我們都比較熟悉,由於我們的產品實現底層是基於熱pod池技術,加載完代碼之後的pod還是需要有一個工作負載來管理。好處是如果pod所在宿主機有異常,我們可以感知到並做故障轉移。另外出於可擴展性的考慮,我們希望長期來看函數可以在不同的彈性框架上做切換。基於上述考慮,我們實現了一個自定義的工作負載叫fndeployment來管理函數的pod實例,然後對接了公司的彈性框架產品burrfish,同時由於實現了k8s的scale接口,這樣後續需要的話可以很容易的對接上hpa、keda等其他彈性伸縮產品。

函數的指標都採集到了公司的監控產品內,彈性伸縮框架可以基於這些指標做彈性伸縮。由於監控系統對實例的指標採集是周期性的,時效性上會差一些。對於一些需要更加即時擴容的指標,我們在Runtime側做了主動上報的功能,可以實現更加即時的擴容

6)servicemesh產品集成

我們的環境裡集成了linkerd這個servicemesh產品,關於servermesh產品是什麼這裡不多做展開,只聊下我們引入了servicemesh是為了解決什麼問題,主要基於以下幾點考慮

  1. 函數針對各種語言需要實現各自的Runtime,而像可觀測、服務治理這些能力是需要多語言重複建設的,使用了servicemesh之後這部分的工作可以只建設一次
  2. FaaS產品和servicemesh產品結合在業界並不是一個孤例,除了上述提到的可觀測和服務治理,長期來看函數在多語言間還是會有很多共性能力需要建設,引入servicemesh之後可以基於其提供的擴展能力來做建設,多語言共用。
  3. 函數在集群內訪問另外一個函數,同樣需要灰度分流的功能,這一塊由於有sidecar的存在,函數可以直接訪問另外一個函數而不必繞道網關

可能有讀者會好奇為什麼在servicemesh產品上我們選用了linkerd而不是istio,主要是基於幾個方面考慮

  1. linkerd三大設計原則,簡單、夠用就行(just work)、最小化資源消耗,istio相對複雜,而當前函數的訴求用不上istio那麼多功能
  2. linkerd在性能及資源消耗上相對istio更優,數據參考
  3. linkerd是少數幾個cncf畢業的項目,linkerd-proxy基於rust開發,相比envoy基於c++,後續若有擴展需求門檻相對低些

02 小結及未來規劃

本文主要介紹了OPPO在FaaS領域的探索和思考,包括對選型,冷啟動、Runtime、整個FaaS產品的思考及分析,針對fission不足所做的優化改進。行文至此發現篇幅已經較長,還未涉及到的內容如事件體系又是一大主題,還有關於筆者作為Serverless使用者的視角對FaaS及BaaS產品結合的思考,後續有機會再做分享。

我們也會一直持續關注業界動態,對新技術如wasm等長期關注,以期做出更優秀的產品。

03 附錄

  1. Serverless 選型:深度解讀 Serverless 架構及平台選擇:

https://zhuanlan.zhihu.com/p/141217056

2.Serverless Whitepaper:

https://github.com/cncf/wg-serverless/tree/master/whitepapers/serverless-overview

3.一直在說的冷啟動,究竟是個啥子呦:

http://bluo.cn/serverless-code-start

4.深度解析 AWS Firecracker 原理篇 – 虛擬化與容器運行時技術:

https://aws.amazon.com/cn/blogs/china/deep-analysis-aws-firecracker-principle-virtualization-container-runtime-technology/

5.Serverless\FaaS 現在和未來:

https://jolestar.com/serverless-faas-current-status-and-future/

6.容器技術之容器引擎與江湖門派:

https://developer.aliyun.com/article/778752#slide-13

7.在騰訊雲上部署基於 WebAssembly 的高性能 serverless 函數:

https://my.oschina.net/u/4532842/blog/5172639

8.wasm 對比 docker:

https://wasmedge.org/wasm_docker/

9.基於 Knative 打造生產級 Serverless 平台:

https://www.sofastack.tech/blog/knative-serverless-kubecon-na2019/

關鍵字: