雪花新聞

美團Serverless平臺Nest的探索與實踐

Serverless是目前比較熱門的技術話題,各大雲平臺以及互聯網大廠內部都在積極建設Serverless產品。本文將介紹美團Serverless產品在落地過程中的一些實踐經驗,其中包括技術選型的考量、系統的詳細設計、系統穩定性優化、產品的周邊生態建設以及在美團的落地情況。雖然各個公司的背景不盡相同,但總有一些可以相互借鑑的思路或方法,希望能給大家帶來一些啓發或者幫助。

1 背景

Serverless一詞於2012年被提出,2014年由於亞馬遜的AWS Lambda無服務器計算服務的興起,而被大家廣泛認知。Serverless通常被直譯成“無服務器”,無服務器計算是可以讓用戶在不考慮服務器的情況下構建並運行應用程序。使用無服務器計算,應用程序仍在服務器上運行,但所有服務器管理工作均由Serverless平臺負責。如機器申請、代碼發佈、機器宕機、實例擴縮容、機房容災等都由平臺幫助自動完成,業務開發只需考慮業務邏輯的實現即可。

回顧計算行業的發展歷程,基礎設施從物理機到虛擬機,再從虛擬機到容器;服務架構從傳統單體應用架構到SOA架構,再從SOA架構到微服務架構。從基礎設施和服務架構兩條主線來看整體技術發展趨勢,大家可能會發現,不論是基礎設施還是服務架構,都是從大往小或者由巨到微的方向上演進,這種演變的本質原則無非是解決資源成本或者研發效率的問題。當然,Serverless也不例外,它也是用來解決這兩個方面的問題:

雖然AWS在2014年就推出了第一個Serverless產品Lambda,但Serverless技術在國內的應用一直不溫不火。不過近兩三年,在容器、Kubernetes以及雲原生等技術的推動下,Serverless技術迅速發展,國內各大互聯網公司都在積極建設Serverless相關產品,探索Serverless技術的落地。在這種背景下,美團也於2019年初開始了Serverless平臺的建設,內部項目名稱爲Nest

截止到目前,Nest平臺已經過兩年的建設,回顧整體的建設過程,主要經歷了以下三個階段:

2 快速驗證,落地MVP版本

2.1 技術選型

建設Nest平臺,首要解決的就是技術選型問題,Nest主要涉及三個關鍵點的選型:演進路線、基礎設施、開發語言。

2.1.1 演進路線

起初Serverless服務主要包含FaaS(Function as a Service)和BaaS(Backend as a Service),近幾年Serverless的產品領域有所擴張,它還包含面向應用的Serverless服務。

面向應用的Serverless服務:如Knative,它提供了從代碼包到鏡像的構建、部署,以及彈性伸縮等全面的服務託管能力,公有云產品有Google Cloud Run(基於Knative)、阿里雲的SAE(Serverless Application Engine)。

在美團內部,BaaS產品其實就是內部的中間件以及底層服務等,它們經過多年的發展,已經非常豐富且成熟了。因此,在美團的Serverless產品演進主要在函數計算服務和麪嚮應用的Serverless服務兩個方向上。那究竟該如何演進呢?當時主要考慮到在業界FaaS函數計算服務相對於面向應用的Serverless服務來說,更加成熟且確定。因此,我們決定“先建設FaaS函數計算服務,再建設面向應用的Serverless服務”這樣一條演進路線。

2.1.2 基礎設施

由於彈性伸縮是Serverless平臺必備的能力,因此Serverless必然涉及到底層資源的調度和管理。這也是爲什麼當前業界有很多開源的Serverless產品(如OpenFaaS、Fission、Nuclio、Knative等)是基於Kubernetes來實現的,因爲這種選型能夠充分利用Kubernetes的基礎設施的管理能力。在美團內部基礎設施產品是Hulk,雖然Hulk是基於Kubernetes封裝後的產品,但Hulk在落地之初考慮到落地難度以及各種原因,最終未按照原生的方式來使用Kubernetes,並且在容器層採用的也是富容器模式。

在這種歷史背景下,我們在做基礎設施選型時就面臨兩種選項:一是使用公司的Hulk來作爲Nest的基礎設施(非原生Kubernetes),二是採用原生Kubernetes基礎設施。我們考慮到當前業界使用原生Kubernetes是主流趨勢並且使用原生Kubernetes還能充分利用Kubernetes原生能力,可以減少重複開發。因此,最終考量的結果是我們採用了原生Kubernetes作爲我們的基礎設施

2.1.3 開發語言

由於在雲原生領域的主流語言是Golang,並且Kubernetes的生態中,Golang是絕對的主導語言。但在美團,Java纔是使用最廣泛的語言,相比Golang,Java在公司內部生態比較好。因此,在語言的選型上我們選擇了Java語言。在Nest產品開發之初,Kubernetes社區的Java客戶端還不夠完善,但隨着項目的推進,社區的Java客戶端也逐漸豐富了起來,目前已經完全夠用了。另外,我們也在使用過程中,也貢獻了一些Pull Request,反哺了社區。

2.2 架構設計

基於以上的演進路線、基礎設施、開發語言的選型,我們進行了Nest產品的架構設計。

在整體的架構上,流量由EventTrigger(事件觸發源,如Nginx、應用網關、定時任務、消息隊列、RPC調用等)觸發到Nest平臺,Nest平臺內會根據流量的特徵路由到具體函數實例,觸發函數執行,而函數內部代碼邏輯可以調用公司內的各個BaaS服務,最終完成函數的執行,返回結果。

圖1 FaaS架構圖

在技術實現上,Nest平臺使用Kubernetes作爲基礎底座並適當參考了一些Knative的優秀設計,在其架構內部主要由以下幾個核心部分組成:

圖2 Nest架構圖

2.3 流程設計

在具體的CI/CD流程上,Nest又與傳統的模式有何區別呢?爲了說明這個問題,我們先來看一看在Nest平臺上函數的整體生命週期怎樣的?具體有以下四個階段:構建、版本、部署、伸縮。

就這四個階段來看,Nest與傳統的CI/CD流程本質區別在於部署和伸縮:傳統的部署是感知機器的,一般是將代碼包發佈到確定的機器上,但Serverless是要向用戶屏蔽機器的(在部署時,可能函數的實例數還是0);另外,傳統的模式一般是不具備動態擴縮容的,而Serverless則不同,Serverless平臺會根據業務的自身流量需要,進行動態擴縮容。後續章節會詳細講解彈性伸縮,因此這裏我們只探討部署的設計。

部署的核心點在於如何向用戶屏蔽機器?對於這個問題,我們抽象了機器,提出了分組的概念,分組是由SET(單元化架構的標識,機器上會帶有該標識)、泳道(測試環境隔離標識,機器上會帶有該標識)、區域(上海、北京等)三個信息組成。用戶部署只需在相應的分組上進行操作,而不用涉及到具體機器。能夠做到這些的背後,是由Nest平臺幫助用戶管理了機器資源,每次部署會根據分組信息來實時初始化相應的機器實例。

圖3 函數生命週期

2.4 函數觸發

函數的執行是由事件觸發的。完成函數的觸發,需要實現以下四個流程:

圖4 函數觸發

2.5 函數執行

函數不同於傳統的服務,傳統的服務是個可執行的程序,但函數不同,函數是代碼片段,自身是不能單獨執行的。那流量觸發到函數實例後,函數是如何執行的呢?

函數的執行的首要問題是函數的運行環境:由於Nest平臺是基於Kubernetes實現的,因此函數一定是運行在Kubernetes的Pod(實例)內,Pod內部是容器,容器的內部是運行時,運行時是函數流量接收的入口,最終也是由運行時來觸發函數的執行。一切看起來是那麼的順利成章,但我們在落地時是還是遇到了一些困難,最主要的困難是讓開發同學可以在函數內無縫的使用公司內的組件,如OCTO(服務框架)、Celler(緩存系統)、DB等。

在美團的技術體系中,由於多年的技術沉澱,很難在一個純粹的容器(沒有任何其他依賴)中運行公司的業務邏輯。因爲公司的容器中沉澱了很多環境或服務治理等能力,如服務治理的Agent服務以及實例環境配置、網絡配置等。

因此,爲了業務在函數內無縫的使用公司內的組件,我們複用公司的容器體系來降低業務編寫函數的成本。但複用公司的容器體系也沒那麼簡單,因爲在公司內沒有人試過這條路,Nest是公司第一個基於原生Kubernetes建設的平臺,“第一個喫螃蟹的人”總會遇到一些坑。對於這些坑,我們只能在推進過程中“逢山開路,遇水搭橋”,遇到一個解決一個。總結下來,其中最核心的是在容器的啓動環節打通的CMDB等技術體系,讓運行函數的容器與開發同學平時申請的機器用起來沒有任何區別。

圖5 函數執行

2.6 彈性伸縮

彈性伸縮的核心問題主要有三個:什麼時候伸縮,伸縮多少,伸縮的速度快不快?也就是伸縮時機、伸縮算法、伸縮速度的問題。

除了基本的擴縮容能力,我們還支持了伸縮到0,支持配置最大、最小實例數(最小實例即預留實例)。伸縮到0的具體實現是,我們在事件網關內部增加了激活器模塊,當函數無實例時,會將函數的請求流量緩存在激活器內部,然後立即通過流量的Metrics去驅動彈性伸縮組件進行擴容,等擴容的實例啓動完成後,激活器再將緩存的請求重試到擴容的實例上觸發函數執行。

圖6 彈性伸縮

3 優化核心技術,保障業務穩定性

3.1 彈性伸縮優化

上面提到的伸縮時機、伸縮算法、伸縮速度這三要素都是理想情況下的模型,尤其是伸縮速度,當前技術根本做不到毫秒級別的擴縮容。因此,在線上實際場景中,彈性伸縮會存在一些不符合預期的情況,比如實例伸縮比較頻繁或者擴容來不及,導致服務不太穩定的問題。

下圖展示的是線上彈性伸縮的真實案例(其配置的最小實例數爲4,單實例閾值100,閾值使用率0.7),其中上半部分是業務每秒的請求數,下半部分是擴縮實例的決策圖,可以看到在成功率100%的情況下,業務完美應對流量高峯。

圖7 彈性伸縮案例

3.2 冷啓動優化

冷啓動是指在函數調用鏈路中包含了資源調度、鏡像/代碼下載、啓動容器、運行時初始化、用戶代碼初始化等環節。當冷啓動完成後,函數實例就緒,後續請求就能直接被函數執行。冷啓動在Serverless領域至關重要,它的耗時決定了彈性伸縮的速度。

所謂“天下武功,無堅不破,唯快不破”,這句話在Serverless領域也同樣受用。試想如果拉起一個實例足夠快,快到毫秒級別,那幾乎所有的函數實例都可以縮容到0,等有流量時,再擴容實例處理請求,這對於存在高低峯流量的業務將極大的節省機器資源成本。當然,理想很豐滿,現實很骨感。做到毫秒級別幾乎不可能。但只要冷啓動時間越來越短,成本自然就會越來越低,另外,極短的冷啓動時間對伸縮時函數的可用性以及穩定性都有莫大的好處。

圖8 冷啓動的各個階段

冷啓動優化是個循序漸進的過程,我們對冷啓動優化主要經歷了三個階段:鏡像啓動優化、資源池優化、核心路徑優化。

圖9 鏡像啓動優化成果

圖10 資源池優化成果

3.3 高可用保障

說到高可用,對於一般的平臺,指的就是平臺自身的高可用,但Nest平臺有所不同,Nest的高可用還包含託管在Nest平臺上的函數。因此,Nest的高可用保障需要從平臺和業務函數兩個方面着手。

3.3.1 平臺高可用

對平臺的高可用,Nest主要從架構層、服務層、監控運營層、業務視角層面都做了全面的保障。

圖11 部署架構

3.3.2 業務高可用

對於業務高可用,Nest主要從服務層、平臺層兩個層面做了相關的保障。

圖12 業務監控

3.4 容器穩定性優化

前文已提到,Serverless與傳統模式在CI/CD流程上是不同的,傳統模式都是事先準備好機器然後部署程序,而Serverless則是根據流量的高低峯實時彈性擴縮容實例。當新實例擴容出來後,會立即處理業務流量。這聽起來貌似沒什麼毛病,但在富容器生態下是存在一些問題的:我們發現剛擴容的機器負載非常高,導致一些業務請求執行失敗,影響業務可用性。

分析後發現主要是因爲容器啓動後,運維工具會進行Agent升級、配置修改等操作,這些操作非常耗CPU。同在一個富容器中,自然就搶佔了函數進程的資源,導致用戶進程不穩定。另外,函數實例的資源配置一般比傳統服務的機器要小很多,這也加劇了該問題的嚴重性。基於此,我們參考業界,聯合容器設施團隊,落地了輕量級容器,將運維的所有Agent放到Sidecar容器中,而業務的進程單獨放到App容器中。採用這種容器的隔離機制,保障業務的穩定性。同時,我們也推動了容器裁剪計劃,去掉一些不必要的Agent。

圖13 輕量級容器

4 完善生態,落實收益

Serverless是個系統工程,在技術上涉及到Kubernetes、容器、操作系統、JVM、運行時等各種技術,在平臺能力上涉及到CI/CD各個流程的方方面面。

爲了給用戶提供極致的開發體驗,我們爲用戶提供了開發工具的支持,如CLI(Command Line Interface)、WebIDE等。爲了解決現有上下游技術產品的交互的問題,我們與公司現有的技術生態做了融合打通,方便開發同學使用。爲了方便下游的集成平臺對接,我們開放了平臺的API,實現Nest賦能各下游平臺。針對容器過重,系統開銷大,導致低頻業務函數自身資源利用率不高的問題,我們支持了函數合併部署,成倍提升資源利用率。

4.1 提供研發工具

開發工具能夠降低平臺的使用成本,幫助開發同學快速的進行CI/CD流程。目前Nest提供了CLI工具,幫助開發同學快速完成創建應用、本地構建、本地測試、Debug、遠程發佈等操作。Nest還提供了WebIDE,支持在線一站式完成代碼的修改、構建、發佈、測試。

4.2 融合技術生態

僅支持這些研發工具還是不夠的,項目推廣使用後,我們很快就發現開發同學對平臺有了新的需求,如無法在Pipeline流水線、線下服務實例編排平臺上完成對函數的操作,這對我們項目的推廣也形成了一些阻礙。因此,我們融合這些公司的成熟技術生態,打通了Pipeline流水線等平臺,融入到現有的上下游技術體系內,解決用戶的後顧之憂。

4.3 開放平臺能力

有很多Nest的下游解決方案平臺,如SSR(Server Side Render)、服務編排平臺等,通過對接Nest的OpenAPI,實現了生產力的進一步解放。例如,不用讓開發同學自己去申請、管理和運維機器資源,就能夠讓用戶非常快速的實現一個SSR項目或者編排程序從0到1的創建、發佈與託管。

Nest除了開放了平臺的API,還對用戶提供了自定義資源池的能力,擁有了該項能力,開發同學可以定製自己的資源池,定製自己的機器環境,甚至可以下沉一些通用的邏輯,實現冷啓動的進一步優化。

4.4 支持合併部署

合併部署指的是將多個函數部署在一個機器實例內。合併部署的背景主要有兩個:

基於這兩個背景,我們考慮支持合併部署,將一些低頻的函數部署到同一個機器實例內,來提升預留實例中業務進程的資源利用率。

在具體實現上,我們參考Kubernetes的設計方案,設計了一套基於Sandbox的函數合併部署體系(每個Sandbox就是一個函數資源),將Pod類比成Kubernetes的Node資源,Sandbox類比成Kubernetes的Pod資源,Nest Sidecar類比成Kubelet。爲了實現Sandbox特有的部署、調度等能力,我們還自定義了一些Kubernetes資源(如SandboxDeployment、SandboxReplicaSet、SandboxEndpoints等)來支持函數動態插拔到具體的Pod實例上。

圖14 合併部署架構

除此之外,在合併部署的形態下,函數之間的隔離性也是不可迴避的問題。爲了儘可能的解決函數(合併在同一個實例中)之間的互相干擾問題,在Runtime的實現上,我們針對Node.js和Java語言的特點採取了不同的策略:Node.js語言的函數使用不同的進程來實現隔離,而Java語言的函數,我們採用類加載隔離。採用這種策略的主要原因是由於Java進程佔用內存空間相較於Node.js進程會大很多。

5 落地場景、收益

目前Nest產品在美團前端Node.js領域非常受歡迎,也是落地最廣泛的技術棧。當前Nest產品在美團前端已實現了規模化落地,幾乎涵蓋了所有業務線,接入了大量的B/C端的核心流量。

5.1 落地場景

具體的落地前端場景有:BFF(Backend For Frontend)、CSR(Client Side Render)/SSR(Server Side Render)、後臺管理平臺場景、定時任務、數據處理等。

5.2 落地收益

Serverless的收益是非常明顯的,尤其在前端領域,大量的業務接入已是最好的說明。具體收益,從以下兩個方面分別來看:

6 未來規劃

作者簡介

招聘信息

美團基礎架構團隊誠招高級、資深技術專家,Base北京、上海。我們致力於建設美團全公司統一的高併發高性能分佈式基礎架構平臺,涵蓋數據庫、分佈式監控、服務治理、高性能通信、消息中間件、基礎存儲、容器化、集羣調度等基礎架構主要的技術領域。歡迎有興趣的同學投送簡歷到:tech@meituan.com。

| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出於分享和交流等非商業目的轉載或使用本文內容,敬請註明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業性轉載或者使用。任何商用行爲,請發送郵件至tech@meituan.com申請授權。

相關文章