騰訊雲Service Mesh生產實踐及架構演進

  • 2019 年 10 月 11 日
  • 筆記

背景介紹

Service Mesh(服務網格)是一個基礎設施層,讓服務之間的通訊更安全、快速和可靠,是雲原生技術棧的關鍵組建之一。2018 年是 Service Mesh 高歌猛進的一年,Service Mesh 數據面板百花齊放,遍地開花,業界幾乎所有大廠都在推出自己的 Service Mesh 產品。2018 年 Service Mesh 大事件如下:

a.2018 年 7 月 31 日,Istio 1.0 版本發布,標誌著 Istio 可用於生產環境

b.2018 年 9 月 19 日,Conduit,這個史上唯一一個主打 rust 語言的 Mesh,宣布合入到 Linkerd,後續作為 linkerd2.x 版本繼續演進。

c.2018 年 11 月 28 日,Istio 的官配 sidecar,高性能邊緣代理 Envoy,成為了繼 k8s 以及 prometheus 之後,第三個從 CNCF 畢業的項目。

d.2018 年 12 月 5 日,AWS 推出服務網狀網路 App Mesh 公開預覽版,供用戶輕鬆的監視與控制 AWS 上,構成應用程式微服務之間的通訊。

早在 2017 年騰訊雲中間件團隊就選定 Istio 為技術路線,開始 Service Mesh 的相關研發工作,作為騰訊雲 TSF(微服務平台)的無侵入式服務框架的核心實現,並在 18 年初在騰訊廣告平台投入,打磨穩定後陸續開始對外輸出,目前在銀行、電商、零售、汽車等行業都有落地案例。

落地過程並非一帆風順,本文將對騰訊雲 Service Mesh 在生產實踐過程中遇到的典型問題以及解決方案進行總結分享,同時對騰訊雲 Service Mesh 後續重點探索的技術方案進行簡要闡述。

騰訊雲 Service Mesh 核心技術實現

騰訊雲 Service Mesh,遵循 Service Mesh 的理念進行設計,為應用提供服務自動註冊發現、灰度路由、限流、熔斷等服務治理能力,且應用無需對源碼進行侵入式變更即可與該服務框架進行集成。

在實現上,基於業界達到商用標準的開源軟體 Istio、envoy 進行構建。整體架構上,從功能邏輯上分為數據面和控制面:

控制面主要提供配置及控制指令支撐 sidecar 的正常運行,以及對服務運行過程中的相關數據進行採集。數據面主要是提供通訊代理(sidecar)來進行透明的服務調用,支撐正常的業務流程。

接下來,讓我們對騰訊雲 Service Mesh 各關鍵優化點做詳細的描述。

解耦 k8s,擁抱其他計算平台

眾所周知,Istio 強依賴於 Kubernetes ,大部分功能都依託於 Kubernetes 平台進行構建。下面列舉幾個核心的功能:

(1) 服務配置管理:   Istio 配置通過 Kubernetes Crd (custom resources definition) 以及 configmap 進行存取

(2) 服務發現及健康檢查:

Istio 全功能的服務註冊發現能力是基於 Kubernetes 的 PodServices 能力以及 Endpoints 機制實現的,節點健康檢查能力基於 ReadinessProbe 機制實現 (當前社區上面也有基於 Consul 的服務發現機制實現,但是缺失健康檢查機制)。

但實際落地過程中,TSF 的用戶並非全部是 Kubernetes 用戶,例如公司內部的一個業務因歷史遺留問題,不能完全容器化部署,同時存在 VM 和容器環境,架構如下:

從業務架構圖可以看出,業務要求 TSF 可以支援其部署在自研 PAAS 以及 Kubernetes 的容器、虛擬機以及裸金屬的服務都可以通過 Service Mesh 進行相互訪問。

因此,為了實現多平台的部署,必須與 Kubernetes 進行解耦。經過分析發現,脫離 Kubernetes 後,Istio 存在以下三個問題:

(1)Pilot/Mixer 的遠程動態配置能力不可用(只能用本地配置)

(2)Pilot 無法獲取服務節點健康資訊

(3) 無法通過 Istioctl(Istio 小工具)進行服務註冊 / 反註冊以及寫配置能力針對這 3 個問題,TSF 團隊對 Istio 的能力進行了擴展和增強,增強後的架構如下:

下表更詳細的描述了存在的問題、解決方案以及所得到的目的,同時 TSF 團隊實現了 Istio 對 Consul 的完整適配。

經過改造後,Service Mesh 成功與 Kubernetes 平台解耦,組網變得更加簡潔,通過 REST API 可以對數據面進行全方位的控制,可從容適配任何的底層部署環境,對於私有雲客戶可以提供更好的體驗。

服務定址模式的演進

解決了跨平台部署問題後,第二個面臨的問題就是服務的定址互通問題。

Istio 下的應用使用 FQDN(fully qualified domain name)進行相互調用,基於 FQDN 的定址依賴 DNS 伺服器,Istio 官方對 DNS 伺服器的說明如下:

Istio 的官方 demo(https://Istio.io/docs/examples/bookinfo/)中,Reviews 與 Ratings 之間的完整的服務調用會經過以下過程:

從圖上可以看出,Reviews 和 Ratings 的互通,kube-dns 主要實現 2 個功能:

(1) 應用程式的 DNS 請求被 kube-dns 接管

(2)kube-dns 可以將服務名解析成可被 iptables 接管的虛擬 IP(clusterIP)

在私有雲的實際交付中,客戶的生產環境不一定包含 Kubernetes 或者 kube-dns,我們需要另外尋找一種機制來實現上面的兩個功能。

在 DNS 選型中,有集中式和分散式兩種方案,分別如下:

集中式 DNS:代表有 ConsulDNS, CoreDNS 等,通過內置機制或者插件的方式,實現與服務註冊中心進行數據同步。其架構組網如下,

kube-dns 也屬於集中式 DNS 的一種,集中式 DNS 存在以下問題:組網中額外增加一套 DNS 集群,並且一旦 DNS Server 集群不可服務,所有數據面節點在 DNS 快取失效後都無法工作,因此需要為 DNS Server 考慮高可用甚至容災等一系列後續需求,會導致後期運維成本增加。

分散式 DNS:就是將服務 DNS 的能力下沉到數據平面中,其架構組網如下:

分散式 DNS 運行在數據面節點上,DNS 無單點故障,無需考慮集群容災等要素,只需要有機制可以在其 down 掉後重新拉起即可。但是,由於其與業務進程運行在同一節點,因此其資源佔用率必須控制得足夠低,才不會對業務進程產生影響。

綜合考慮,最終選用了分散式 DNS 的方案,最開始團隊採用獨立進程作為 DNS Server 的方案,如下圖

該方案新增監聽在 127.0.0.1:53 上的 mesh-dns 進程,該進程實時從 Pilot 同步服務列表。Mesh-dns 在節點啟動時將 127.0.0.1 寫入到 /etc/resolv.conf 首行中,同時接管 /etc/resolv.conf 的其他 nameserver。這樣,當 app 發起 DNS 查詢時,DNS 請求首先會到達 mesh-dns,遇到匹配服務名的查詢則直接返回,而當遇到不是針對服務名的 DNS 查詢時,就把 DNS 請求轉發給其他 nameserver 進行處理。

該方案看起來簡單可行,但是經測試驗證後發現存在以下問題:

(1)resolv.conf 修改時間差問題:該方案需要對 /etc/resolv.conf 進行修改,在 linux 環境,域名解析機制是通過 glibc 提供的。而 glibc 2.26 之前的版本有個 BUG,導致假如在進程啟動後,對 resolv.conf 就行修改,則該修改無法被該進程感知,直到進程重啟。而由於在容器部署的場景中,mesh-dns 和應用分別部署在同一個 POD 的不同容器中,容器的啟動是相互獨立的,所以無法保證對 resolv.conf 的修改一定在應用啟動前。即使改成通過 InitContainer 進行修改,當容器異常重啟後,resolv.conf 也同樣會被還原導致服務不可用。

(2) 埠監聽衝突問題:由於 mesh-dns 必須監聽 53 埠,假如客戶節點環境已經安裝了 dnsmasq 等同樣需要佔用 53 的進程,則可能會出現埠衝突導致啟動失敗。

(3)nameserver 選擇策略問題:假如存在多個 nameserver,部分作業系統,默認會使用 rotate(隨機選取一個作為首選查詢的 nameserver)作為 nameserver 的選擇策略。此時會出現一定概率下會選不到 127.0.0.1 的 nameserver,從而導致服務域名解釋失敗。

針對上述問題,對方案進行了進一步的優化,優化後的方案如下圖:

mesh-dns 不再監聽 53 埠,而是監聽在 5353 埠(可配置),啟動時無需修改 resolv.conf。通過增加 iptables 規則,將所有發往 nameserver 的流量導入到 mesh-dns,從而解決了上文中的「埠監聽衝突」以及「nameserver 選擇策略」的問題。

mesh-dns 通過 inotify 監聽 /etc/resolv.conf,可以隨時獲取環境中 dns 配置的更改,從而解決了上文中的「resolv.conf 修改時間差」的問題。

與非 Service Mesh 服務的互通

現實總是複雜的,前面解決 mesh 服務之間相互訪問的問題,如何解決用戶 Service Mesh 應用和其他非 Mesh 應用的相互訪問呢? 用戶內部有不同技術棧,一部分服務基於 service  mesh 進行實現服務,另外一部分服務基於 spring cloud 框架進行實現。同時,客戶的微服務組網中,存在大量第三方服務如支付網關、分散式存儲、設備等,微服務需要與這些第三方服務也存在交互。用戶期望支援的架構如下圖所示:

這個架構中,最大的挑戰在於涉及了兩個不同的微服務框架之間的互通。但是,這兩個微服務框架從架構模式、概念模型、功能邏輯上,都存在較大的差異。唯一相通的點,就是他們都是微服務框架,可以將應用的能力通過服務的形式提供出來,給消費者調用,消費者實際上並不感知服務的具體實現。

基於這個共通點,為了使得不同框架開發的服務能夠正常工作,TSF 團隊做了大量的開發工作,將兩個微服務框架,從部署模式、服務及功能模型上進行了拉通,主要包括如下幾點:

(1) 服務模型的互通:基於統一的服務元數據模型,針對 pilot registry 及 spring cloud registry 的服務註冊發現機制進行拉通

(2) 服務 API 的互通:基於標準 API 模型(OpenAPI v3),針對兩邊框架的 API 級別服務治理能力進行拉通

(3) 服務路由能力互通:基於標準權重演算法以及標籤模型,針對 pilot virtual-service 以及 spring cloud ribbon 能力進行拉通。

(4) 服務限流能力互通:基於標準令牌桶架構和模型,以及條件匹配規則,對 mixer 及 spring cloud ratelimiter 能力進行拉通。

代理單節點多服務

用戶的需求是多種多樣的,在交付過程中存在如下多服務場景:

(1) 客戶機器資源不足,且沒有做容器化,因此需要把多個服務部署到一個節點上。

(2) 客戶的傳統應用使用 OSGI(一種 Java 模組化技術)實現,一個進程中包含多個服務,監聽在同一個埠。

為了支援多服務場景,簡化用戶的使用流程,TSF 提供了服務描述文件,可支援多服務場景,服務配置文件與 Kubernetes 標準格式一致:

pilot-agent 會根據服務配置,按照–的格式將配置中 services 註冊成多個獨立的服務實例。

在 OutBound 服務路由時,可以通過 LDS->RDS->CDS->EDS 的方式進行路由,和獨立部署的服務沒有區別:

然而,在 InBound 服務路由過程中,通過開源 Istio 生成的 listener 會遇到一些坑。

對於多服務監聽同一埠的場景,開源 Istio 在生成 inbound 的時候,會將同 IP+Port 的其中一個服務給 reject 掉

因此,生成的 LDS 中,只有其中一個服務的相關路由資訊:

這樣一來,普通消息投遞,不會有什麼問題(目標端點資訊是一致的),但是假如需要與 mixer 結合,做 api 鑒權或者限流等操作,則會出現兩個服務的 mixer_attribute 互相混淆的情況,導致功能不可用。

為了解決這個問題,團隊分析了 envoy 的 filter_chain_match 能力(https://www.envoyproxy.io/docs/envoy/v1.8.0/api-v2/api/v2/listener/listener.proto.html?highlight=filter_chain_match#envoy-api-msg-listener-filterchainmatch),對 pilot 進行改造,擴展了 listener 能力,通過 server_name 來分流數據包到不同的 filter 中。

最終生成的 LDS 如下:

經過這樣的改造,同一埠上,不同的服務的 filter 配置不再衝突,兩個服務的 mixer_attribute 也能相互隔離,順利支援同埠多服務的場景。

二進位協議的支援

在當前業界的開源 Service Mesh 產品中,主打的協議都是標準協議(HTTP1/2, GRPC),標準協議都有一個特點,那就是協議頭中包含了目的端相關的所有資訊,Service Mesh 會根據這些資訊進行路由。如下表所示:

對於其他二進位協議,則分為 2 大類:

第一種是協議中帶有目標端資訊的二進位協議,如 thrift,dubbo 等;

第二種是協議中不帶有目標端資訊的二進位協議,這種就比較多了,一般常見於私有雲中的各種私有通訊協議。

開源 Istio 中,對於二進位協議的支援,僅僅局限於四層的埠轉發,一般用於集成外部服務(mysql, mongodb 等),典型場景是對不同入口的流量做轉發,如下圖所示:

單純的四層轉發,無法滿足複雜的微服務路由的需求。當前 TSF 交付的客戶中,多個客戶都提出了需要支援私有協議路由的需求,因此,針對這個問題,TSF 團隊提供了兩種解決方案。

(1) 用戶將私有協議轉換成 GRPC 協議,接入到 Service Mesh

由於 GRPC 的 Data Frame 本身傳輸的就可以是 TCP 協議,因此用戶可以直接把自己的二進位協議通過 GRPC 的 bytes 類型編碼,然後通過 Data Frame 傳輸過來.

該方案適用於本身有一定的技術積累,願意去做 GRPC 改造的用戶

(2) 根據用戶定義的協議頭描述文件,進行私有協議七層路由

中間件團隊對 envoy 的 filter 進行了擴展,用戶提供一個 protobuf 格式的描述文件,指定協議頭的欄位順序,proxy 根據描述文件的定義,進行消息頭的接收及解析,然後根據解析後的消息頭內容,進行七層路由和轉發。

該方案適用於自身帶有目標端資訊的二進位協議,可以讓私有協議的用戶無需任何的改造,即可接入 Service Mesh。

總   結

騰訊雲 Service Mesh 當前通過 TSF 平台在持續交付中,上文主要針對落地過程中遇到的典型功能性需求及技術方案演進進行了總結介紹,除此之外,中間件團隊在 Service Mesh 性能方面也有很多優化和探索,主要包括減少 envoy 和 mixer 之間的網路交互、優化數據包在 envoy 節點內部從內核態到用戶態的拷貝次數、envoy 到 envoy 之間數據的轉發性能等,後續將針對性能優化進行專項分享。


作者簡介

單家駿,來自騰訊公司。騰訊雲高級工程師,負責騰訊雲中間件 paas 以及 servicemesh 的研發與架構,關注云原生與中間件技術。熱愛開源、崇尚技術,希望能夠使用技術使軟體的應用變得簡單、高效和美好。