OCTO 2.0:美團基於Service Mesh的服務治理系統詳解

OCTO是美團內部的服務治理平臺,包含服務通信框架、命名服務、服務數據中心和用戶管理平臺等組件,爲公司內全部服務提供了整套的服務治理方案和統一的服務治理體驗。我們在之前的幾篇文章中分別從不同的角度介紹了OCTO平臺的建設情況。包括:

本文將繼續介紹OCTO系統在Service Mesh化演進方面的工作。主要從數據面的角度,詳細介紹各項技術方案的設計思路。

1 整體架構

我們先看看OCTO 2.0的整體架構,如下圖所示:

圖1 整體架構四個組成部分:基礎設施、控制平面、數據平面和運維繫統

基礎設施是指美團現有的服務治理系統 OCTO1.0,包括MNS、KMS(鑑權管理服務)、MCC(配置管理中心)、Rhino(熔斷限流服務)等。這些系統接入到OCTO 2.0的控制平面,避免過多重構引入的不必要成本。

OCTO 2.0控制平面摒棄了社區的Istio,完全自研。數據平面基於開源Envoy改造。運維繫統負責數據面組件的升級、發佈等運維工作。更多的整體選型及原因可參考之前團隊的文章: 《美團下一代服務治理系統 OCTO2.0 的探索與實踐》

對OCTO 2.0中的功能,我們分爲Mesh和服務治理兩個維度,下面我們來重點看下流量劫持、無損重啓、服務路由等幾個常見的問題,並闡述美團是如何結合現有比較完善的服務治理系統來解決這些問題的。

2 Mesh功能

2.1 流量劫持

OCTO 2.0並未採用Istio的原生方案,而是使用iptables對進出POD的流量進行劫持。主要考量了以下兩個因素:

  1. iptables自身存在性能損失大、管控性差的問題:

    • iptables在內核對於包的處理過程中定義了五個“hook point”,每個“hook point”各對應到一組規則鏈,outbond流量將兩次穿越協議棧並且經過這5組規則鏈匹配,在大併發場景下會損失轉發性能。
    • iptables全局生效,不能顯式地禁止相關規則的修改,沒有相關ACL機制,可管控性比較差。
  2. 在美團現有的環境下,使用iptables存在以下幾個問題:

    • HULK容器爲富容器形態,業務進程和其他所有基礎組件都處於同一容器中,這些組件使用了各種各樣的端口,使用iptables容易造成誤攔截。
    • 美團現在存在物理機、虛擬機、容器等多個業務運行場景,基於iptables的流量劫持方案在適配這些場景時複雜度較高。

鑑於上述問題,我們最終採用了Unix Domain Socket直連方式來實現業務進程和OCTO-Proxy之間的流量轉發。

圖2 Unix Domain Socket直連流量轉發

在服務消費者一方,業務進程通過輕量的Mesh SDK和OCTO-Proxy所監聽的UDS地址建立連接。在服務提供者一方,OCTO-Proxy代替業務進程監聽在TCP端口上,業務進程則監聽在指定的UDS地址上。

該方案的優點是Unix Domain Socket相比於iptable劫持擁有更好的性能和更低的運維成本。缺點是需要Mesh SDK的支持。

2.2 服務訂閱

原生Envoy的CDS/EDS請求是全量服務發現模式,即將系統中所有的服務列表都請求到數據面中。由於大規模集羣中服務數量太多,真正所需的只是少數服務,所以需要改造成按需服務發現模式,僅請求需要訪問的後端服務的節點列表。

圖3 服務訂閱流程

在業務進程啓動後,需要通過HTTP的方式向OCTO-Proxy發起訂閱請求。OCTO-Proxy將所請求的後端服務Appkey更新到XDS中,XDS再向控制面請求特定的服務資源。

爲了增加整個過程中的健壯性,降低後續運維成本,我們做了部分適配。例如,OCTO-Proxy的啓動速度有可能慢於業務進程,所以在Mesh SDK中增加了請求的重試邏輯;將Mesh SDK和OCTO-Proxy之間的http請求改造成了同步請求,防止Pilot資源下發延時帶來的問題;Mesh SDK的訂閱信息也會保存在本地文件中,以便OCTO-Proxy熱重啓或者故障重啓時使用。

2.3 無損熱重啓

2.3.1 流量損失場景

如何在基礎組件升級過程中提供持續、不間斷的服務,做到業務流量無損及業務無感知,是所有基礎組件面臨的一大難題。這個問題對於OCTO-Proxy這種處於流量核心鏈路上的組件更加重要。社區原生的Envoy自身已經支持了熱重啓功能但並不徹底,在某些場景下還是不能做到完全的流量無損。

下圖分別在短連接和長連接兩種場景下來說明OCTO-Proxy熱重啓時的流量損耗問題。

圖4 代理升級過程中流量損耗

對於短連接,所有新的連接會在新的OCTO-Proxy上創建,舊OCTO-Proxy上已有的連接在響應到來後主動斷開。舊OCTO-Proxy的所有短連接逐漸斷開,這就是“Drain”(排空)的過程。連接排空之後,舊OCTO-Proxy主動退出,新的OCTO-Proxy繼續工作。整個過程中的流量,完全無損。

對於長連接方式,SDK和舊OCTO-Proxy維持一條長連接不斷開,並持續使用該連接發送請求。舊OCTO-Proxy進程最終退出時,該連接被動斷開,此時可能尚有部分響應未返回,導致Client端請求超時。因此,Envoy的熱重啓對長連接場景的支持並不完美。

爲了支持基礎組件在升級過程中提供不間斷的服務,業界目前主要使用的是滾動發佈(Rolling Update)的方式:服務器分批停止服務,執行更新,然後重新將其投入使用,直到集羣中所有實例都更新爲新版本。在此過程中會主動摘掉業務流量,保證升級過程中業務流量不丟失。

圖5 滾動升級

美團內部因爲要兼容物理機、虛擬機、容器,業界雲原生使用K8s滾動升級的方式不太適用,所以在現有環境下,如何保證服務治理系統的高可用性以及業務的高可靠性是一個非常棘手的事情。

2.3.2 適配方案

當前方案是把業務服務分爲兩種角色,即對外提供服務的Server端角色和對外發起請求的Client端角色,針對兩種不同的角色採用不同的熱更新支持。

Client端OCTO-Proxy熱更新:老的OCTO-Proxy在進入熱重啓狀態後,對後續“新請求”直接返回含“熱重啓”標誌的響應協議,Client SDK在收到含“熱重啓”標誌的響應協議時,應主動切換新連接並請求重試(以避免當前Client SDK持有的舊連接由於舊OCTO-Proxy熱重啓流程完成後被主動關閉的問題)。這裏需要注意,Client SDK需要“妥善”處理舊連接所在鏈路上遺留的所有“應答”協議。

圖6 客戶端SDK/代理相互配合進行熱升級

通過這種Client SDK和OCTO-Proxy間的交互配合,可以支持Client端在OCTO-Proxy升級過程中保證流量的安全性。

Server端OCTO-Proxy熱更新:Server側OCTO-Proxy在熱重啓開始後,即會主動向Client側OCTO-Proxy發送ProxyRestart消息,通知Client側OCTO-Proxy主動切換新連接,避免當前Client側OCTO-Proxy持有的舊連接由於Server側舊OCTO-Proxy熱重啓流程完成後被強制關閉的問題。Client端OCTO-Proxy在收到“主動切換新連接”的請求後,應即時從可用連接池中移除,並“妥善”處理舊連接所在鏈路上遺留的所有“應答”協議(保守起見可標記連接不可用,並維持一段時間,比如OCTO-Proxy默認drainTime)。

圖7 服務端反向主動通知客戶端重連

2.4 數據面運維

2.4.1 LEGO運維方案

雲原生環境中,Envoy運行在標準的K8S Pod中,通常會獨立出一個Sidecar容器。可以使用K8s提供的能力來完成對Envoy Sidecar容器的管理,例如容器注入、健康檢查、滾動升級、資源限制等。

美團內部所使用的容器運行時模式爲“單容器模式”,即一個Pod內只包含一個容器(不考慮Pause容器)。由於業務進程以及所有的基礎組件都運行在一個容器中,所以只能採用進程粒度的管理措施,無法做到容器粒度的管理。我們通過自研的LEGO平臺解決了OCTO-Proxy的運維問題。

圖8 LEGO數據面管理系統

我們對LEGO-Agent做了定製化改造,增加了對OCTO-Proxy的熱重啓過程的支持。另外,LEGO-Agent還負責對OCTO-Proxy的健康檢查、故障狀態重啓、監控信息上報和版本發佈等。相對於原生K8S的容器重啓方式,進程粒度重啓會有更快的速度。

LEGO系統的另一個優點是,可以同時支持容器、虛擬機和物理機等多種運行時環境。

2.4.2 雲原生運維方案

目前,我們也正在探索OCTO-Proxy雲原生的運維方案,在此場景下最大的區別就是從進程粒度運維轉變爲了容器粒度運維,與業務應用做到了更好的解耦,同時可享受不可變基礎設施等理念帶來的紅利,但是此場景帶來的一個問題就是如何管理容器粒度的熱重啓。

爲此,我們通過自研的Operator對OCTO-Proxy容器進行全生命週期的運維管理,並且期望通過Operator對OCTO-Proxy進行熱重啓,具體流程如下:

圖9 期望的OCTO-Proxy容器熱重啓流程

但是在實施過程中這個方案是有問題的,原因是K8s在底層設計上是不支持對運行中的Pod進行容器的增刪操作,如果需要實現該方案,將需要對K8s底層組件進行定製化修改,這樣將帶來一定風險以及與社區的不兼容性。爲了保證與社區的兼容性,我們對此熱重啓方案進行改造,最終使用雙容器駐留熱重啓方案:

圖10 雙容器駐留熱重啓方案

  1. 首先,我們在啓動Pod時,給OCTO-Proxy不再只分配一個容器,而是分配兩個容器,其中一個容器爲正常運行狀態,另一個容器爲standby狀態。
  2. 當需要對OCTO-Proxy進行熱重啓升級時,我們修改standby容器的鏡像爲最新OCTO-Proxy的鏡像,此時開始熱重啓流程。
  3. 當熱重啓流程結束後,新容器進入正常工作狀態,而舊容器進入等待狀態,最終,我們將舊容器的鏡像修改爲standby鏡像,結束整個熱重啓流程。

該方案利用一開始就駐留雙容器在Pod內,避免了K8s對運行中Pod不能增刪容器的限制,實現了在Pod中對OCTO-Proxy進行容器粒度的熱重啓。

3 未來規劃

目前接入OCTO2.0系統的服務數量數千,天粒度流量數十億。隨着OCTO2.0在公司的不斷推廣,越來越多的服務開始接入,對我們的穩定性和運維能力提出了更高的要求。如何更細緻的瞭解線上OCTO-Proxy以及所稱在的業務系統的健康狀況、如何在出現故障時能更及時的監測到並快速恢復是我們最近OCTO2.0系統建設的重點。除此之外,未來OCTO2.0的規劃重點還包括:

  • 運維、發佈和穩定性建設朝雲原生化方向探索。
  • 支持公司內部Http服務的Mesh化,降低對中心化網關的依賴。
  • 全鏈路mTLS支持。

作者簡介

舒超、世朋、來俊,均來自美團基礎架構部基礎開發組,從事OCTO2.0的開發工作。

團隊簡介

美團基礎技術部-中間件研發中心-基礎開發組,致力於研發公司級、業界領先的基礎架構組件,研發範圍涵蓋分佈式框架、命名服務、Service Mesh等技術領域。歡迎感興趣的同學發送簡歷至:[email protected]

閱讀美團技術團隊更多技術文章合集

前端 | 算法 | 後端 | 數據 | 安全 | 運維 | iOS | Android | 測試

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章