如何在 Istio 中支援 Dubbo、Thrift、Redis 以及任何七層協議?

騰訊雲原生發表於2021-03-08

趙化冰,騰訊雲高階工程師,Istio Member,ServiceMesher管理委員,Istio 專案貢獻者, Aerika 專案建立者 ,熱衷於開源、網路和雲端計算。目前主要從事服務網格的開源和研發工作。

唐陽,知乎基礎架構工程師。Istio 專案貢獻者,Argo 專案貢獻者,專注於開源,雲原生與微服務。目前負責知乎服務網格的研發工作。

備註:本文根據騰訊雲趙化冰和知乎唐陽在 IstioCon 2021 中的演講 “How to Manage Any Layer-7 Traffic in an Istio Service Mesh?” 整理而成。

大家好,今天我們想和大家分享的主題是如何擴充套件 Istio 以支援任何七層協議?作為雲原生領域中一個人氣非常高的開源專案, Istio 目前已經基本成為了 Service Mesh 的事實標準。騰訊雲上也提供了基於 Istio 進行增強,和 Istio API 完全相容的 Service Mesh 管理服務 TCM(Tencent Cloud Mesh),以幫助我們的使用者以較小的遷移成本和維護代價快速利用到 Service Mesh 提供的流量管理和服務治理能力。今天非常高興能夠有這個機會來和大家一起分享一下我們在此過程中的一些經驗。

Service Mesh 提供了一個對應用透明的基礎設施層,可以解決我們在分散式應用/微服務中遇到的常見挑戰,例如:如何找到服務提供者?如何保證服務之間的通訊安全?如何得知服務之間的呼叫關係?如何進行流量管理如灰度釋出?等等。Service Mesh 的實現方式是伴隨應用部署一個 Sidecar Proxy,該 Sidecar Proxy 會攔截應用的出向和入向流量, 對這些流量進行分析和處理,以達到在不修改應用程式碼的情況下對服務進行流量管理、安全加密,遙測資料收集的目的。為了實現這些服務治理能力,Sidecar Proxy 不只需要在 OSI 網路模型的三、四層上對流量進行處理,更重要的是需要在七層上進行處理。在七層上,Istio 預設只支援了 HTTP 和 gPRC 兩種協議。但我們在微服務中經常還會使用到的其他七層協議,當將這些微服務應用遷移到 Service Mesh 時,我們希望使用一致的方式對所有的這些七層協議進行統一管理,以充分利用 Service Mesh 基礎設施提供的雲原生能力。

在今天的分享中,我將會介紹幾種將 Istio 流量管理能力擴充套件到其他七層協議的方法,並對比分析這幾種方法各自的優缺點。我會介紹如何利用 Aeraki 開源專案來在 Istio 中管理任何七層協議,包括 Dubbo、Thrift、Redis 等。為了讓大家瞭解 Aeraki 是如何工作的,會展示一個採用 Aeraki 實現 Thrift 服務 Traffic Splitting 的例子。來自知乎的唐陽還會為我們展示如何使用 Aeraki 的一些有趣的真實案例。

Service Mesh 中常見的七層協議

如下圖所示,一個典型的微服務應用中通常會使用到這些七層協議:

  • 同步呼叫:不同服務之間會採用 RPC (遠端方法呼叫)進行相互呼叫。常見的 RPC 呼叫協議包括 gRPC,Thrift,Dubbo,HTTP 也可以看做一種 RPC (只支援 GET/SET/POST 這幾種標準方法) 。一些大的公司為了滿足自己特定業務場景的需求,往往還會採用一些私用的 RPC 協議。
  • 非同步訊息:除了 RPC 之外,非同步訊息也是微服務通訊的一種常見模式,包括 Kafka,RabbitMQ,ActiveMQ 等。
  • 各種資料庫和快取系統:例如 Redis, MySQL,MongoDB 等等。

那麼當將這樣一個微服務應用加入到 Service Mesh 以後,我們希望能夠通過 Service Mesh 得到哪些管理能力呢?

理想情況下,我們希望 Service Mesh 能夠管理微服務中用到的所有七層協議的流量,包括 RPC、Messaging、Cache、DB等。例如:

  • 基於請求的負載均衡:可以將來自同一個 TCP 連結的多個獨立的請求分發到不同的後端伺服器,以實現更智慧,更合理的負載均衡。
  • 基於七層 Header 的流量路由:根據七層 Header 中的屬性進行路由,例如根據 Dubbo 請求中的服務名或者 Redis 請求的 Key 進行路由。
  • 對客戶端的請求響應注入延遲或者錯誤,以測試應微服務用的彈性。
  • 提供應用級安全,例如基於 HTTP Header 中的 JWT Token 進行認證,或者對 Redis 伺服器進行認證。
  • 請求層面的遙測資料,包括請求成功率、請求耗時、呼叫跟蹤等等。

要實現以上這些流量管理和服務治理能力,Service Mesh 需要分析和處理 TCP 資料包中的七層協議的 Header。即 Service Mesh 必須具有七層協議的管理能力,而不只是在 TCP 層面上進行處理。

然而在 Istio 中,對於除了 HTTP 和 gRPC 之外的協議,我們只能在 OSI 三到六層對這些協議進行處理。這意味著我們只能基於三層的 IP 地址,四層的 TCP 埠或者六層的 SNI(Server Name Indication)對這些協議進行路由。只能收集到 TCP 層面的指標,例如 TCP 收發包數量或者開啟/關閉的 TCP 連結數量。只能採用 mTLS 進行鏈路層面的認證和許可權控制。換而言之,對於這些協議,我們依然需要在應用程式碼中處理流量控制、可觀測性、安全認證這些本應該由 Service Mesh 基礎設施來統一處理的共性問題。這違背了我們將微服務遷移到 Service Mesh 的初衷:將微服務通訊和治理的共性問題從應用程式碼下沉到 Service Mesh 基礎設施層。

如何擴充套件 Istio 的協議管理能力?

如果我們希望能夠在 Istio 中管理這些七層協議,我們應該如何實現呢?假設我們有一個 BookInfo 微服務,但該微服務採用了一種稱為 AwesomeRPC 的協議而不是 HTTP 來實現服務間的遠端呼叫。

我們來看一下如何才能夠在 Istio 中實現 AwesomeRPC 協議的流量管理,例如根據請求 header 中的 user name 欄位將來自 ProductPage 的請求路由到不同版本的 Reviews 中,以實現一個灰度釋出的場景。

我們想到的最顯而易見的方式就是直接修改 Istio 程式碼。首先我們需要在 Istio 的 VirtualService CRD 中支援 AwesomeRPC 協議。增強後的 VirtualService CRD 如下圖中最左的規則配置所示。 AwesomeRPC 和 HTTP 路由的語義類似,都是根據 Header 中某些屬性的值進行路由。因此我們只需要將 HTTP 協議型別改為 AwesomeRPC,可以直接採用 VirtualService 中的 HTTPRoute 結構來表示 AwesomeRPC 的路由規則。然後我們需要在 Pilot 程式碼中根據 AwesomeRPC 的服務定義和 VirtualService 定義的路由規則生成 Envoy 所需的真實配置,並通過 xDS 下發給資料面的 Envoy。當然,以上的前提是我們已經通過 Envoy 的 Filter 擴充套件機制編寫了 AwesomeRPC 的 Filter 外掛,實現 AwesomeRPC 的編解碼,Header 解析,動態路由等資料面所需的功能。

採用這種方式,在 Envoy Filter 已經實現了的情況下,在控制面增加一個新的七層協議的過程是相對比較簡單的。但是由於我們修改了 Istio 的原始碼,因此需要自己維護一個 Istio 的私有分支,這導致了額外的維護代價,並且很難跟上 Istio 快速的迭代步伐。

如果不希望維護自己的 Istio 程式碼分支,一種可行的替代方式是採用 Istio EnvoyFilter CRD:EnvoyFilter 是 Istio 提供的一種靈活強大的配置機制。我們可以使用 EnvoyFilter為 Pilot 生成的預設 Envoy 配置打一個補丁,新增、修改或者刪除預設 Envoy 配置中的部分內容,以按我們的要求修改 Envoy 在 Istio Service Mesh 中的預設行為。

如下圖所示,由於 Pilot 並不理解 AwesomeRPC 協議,對於 Pilot 來說, AwesomeRPC 服務只是一個 TCP 服務。在 Pilot 生成的預設配置中,AwesomeRPC 服務對應的 Outbound Listener 的 FilterChain 中採用了一個 TCP Proxy 來處理其流量。我們在 EnvoyFilter 的 Match 部分中選中該 TCP Proxy,並在 Operation 部分將其替換為一個配置了 Traffic Splitting 規則的 AwesomeRPC Filter。Pilot 會根據 EnvoyFilter 修改其生成的預設 Envoy 配置,然後下發到資料面的 Envoy 上。這樣我們就通過 EnvoyFilter 在 Istio 中實現了對 AwesomeRPC 協議的支援。

下面我們來看一個採用 Thrift 協議的真實案例。Thrift 是 Apache 基金會下一個輕量級、支援多語言的開源 RPC 框架。Envoy 中已經支援 Thrift,但 Istio 中只對 Thrift 提供了有限的支援,並不能實現 Traffic Splitting 等高階流量管理功能。如果我們希望在 Istio 中提供下圖中右下角所示 Thrif 服務的 Traffic Splitting 流量控制,我們可以通過 EnvoyFilter 來實現。

(本示例相關原始碼可以從 https://github.com/aeraki-framework/thrift-envoyfilter-example 下載)

首先,我們需要建立一個圖中左邊所示的 EnvoyFilter 來處理客戶端的出向流量,該 EnvoyFilter 的 Match 條件選中了 $(thrift-sample-server-vip)_9090 這個 Outbound Listener 中 的 tcp_proxy,在 Patch 部分將其替換為一個 thrift_proxy。在該 thrift_proxy 中,我們按照 Traffic Splitting 的要求為其配置了相應的路由:將 30% 的流量路由到 Server v1版本,70% 的流量路由到 Server v2 版本。我們也需要為 Thrift Server 端建立一個如圖右上所示的 EnvoyFilter 來處理伺服器端的入向流量。相比客戶端的 EnvoyFilter 而言,伺服器端的 EnvoyFilter 配置要簡單一些,因此我們不需要在伺服器端配置任何路由規則,只需要將 tcp_proxy 替換為 thrift_proxy 即可。這個 thrift_proxy 雖然沒有路由規則,但提供了大量七層的服務通訊和治理能力,包括請求層面的負載均衡、產生請求層面的 Metrics 資料等。

從上面的介紹和示例可以看到, EnvoyFilter CRD 好比是 Istio 中的一把瑞士軍刀,可以對 Pilot 生成的 Envoy 配置進行非常靈活的定製,以達到對七層協議進行管理的目的。但是 EnvoyFilter 也帶來了一些難以處理的問題:

  • EnvoyFilter 將 Envoy 的底層實現細節直接暴露給了運維人員:運維人員必須非常瞭解 Envoy 的配置細節,而這些配置細節往往和 Envoy Filter 內部的實現機制緊密相關,例如 Filter 的名稱和 Filter 內部的配置格式等。這導致建立 EnvoyFilter 成為了一種和程式碼細節高度耦合的工作,難以直接交付給運維人員。更為合理的方式則應該是採用一種面向使用者的高階配置語言來遮蔽這些實現細節,例如 Istio 中的 VirtualService 和 DestinationRule。
  • EnvoyFilter 中的匹配條件依賴於 Pilot 生成的 Envoy 配置中的結構組成和元素命名,例如 Listener 的名稱,FilterChain 的構成等。而這些結構和命名在不同的 Istio 版本之間可能發生變化,導致原本能夠正常工作的 EnvoyFilter 在新版本中出現問題。
  • EnvoyFilter 中的匹配條件還依賴於一些和特定 K8s 叢集相關的內容,例如 Service Cluster IP,這意味著一個 EnvoyFilter 不能用於多個不同叢集中的相同服務。當 Service 被重建時,由於 Cluster IP 會發生變化,相應的 EnvoyFilter 也必須進行改動,修改 Match 條件中的 Cluster IP。
  • 我們需要為每個 Service 建立相應的 EnvoyFilter,當 Mesh 中管理的服務較多時,手動建立成百上千的 EnvoyFilter 的工作是非常繁瑣而且及易出錯的。
  • 對 Istio 而言,EnvoyFilter 中的 Patch 部分基本上是一個黑盒,因此 Istio 只能對 EnvoyFilter 的正確性進行非常有限的驗證。這導致 EnvoyFilter 的除錯非常困難,當 Envoy 未能按照你的設想工作時,你很難知道到底是 EnvoyFilter 的什麼地方出現了問題。

由於上述的種種問題,我們可以看到,雖然可以使用 EnvoyFilter 來在 Istio 中實現七層協議的管理,但是在一個生產系統,特別是一箇中大型的 Service Mesh 中管理和維護這些 EnvoyFilter 是非常困難的。

Aeraki:在 Istio 中管理任何七層協議

由於難以手動對 EnvoyFilter 進行管理和維護 ,我們建立了Aeraki (發音:[Air-rah-ki])專案來自動化這個流程。Aeraki 是希臘語中“微風”的意思,我們希望 Aeraki 這股微風能幫助 Istio 在雲原生的旅程中航行得更遠。

Aeraki 的基本工作原理如下圖所示:Aeraki 從 Istio 中拉取服務資料,根據 ServiceEntry 和 Aeraki 流量規則生成 Envoy 配置,並採用 EnvoyFilter 將生成的配置推送到 Istio 中。簡而言之,你可以把 Aeraki 看做 Istio 中管理的七層協議的 Operator

相比於直接修改 Istio 程式碼和採用 EnvoyFilter 這兩種擴充套件 Istio 流量管理能力的方式,採用 Aeraki 為我們帶來了以下的好處:

  • 不需要修改 Istio 程式碼,因此節省了單獨維護一個 Istio 的私有程式碼分支的額外工作量,可以快速跟隨 Istio 的版本迭代進行升級。
  • Aeraki 作為一個獨立元件部署在 Mesh 的控制面,可以很方便地作為一個外掛和 Istio 進行整合,對 Istio 的流量管理能力進行擴充套件。
  • 協議相關的預設配置由 Aeraki 自動生成,並且這些配置可以根據 Istio 版本和 K8s 叢集相關資訊自動進行調整。節約了大量 EnvoyFilter 的手動建立和維護工作。
  • Aeraki 在 Envoy 配置之上進行了抽象,提供了一層面向使用者的配置 CRD 來對這些七層協議進行管理。這些高階 CRD 隱藏了 Envoy 的配置細節,遮蔽了不同 Istio 版本生成的預設 Envoy 配置的差異,對於運維非常友好。對於 Thrift 和 Dubbo 這樣的 RPC 協議,由於其語義和 HTTP 類似,Aeraki 直接採用了 Istio VirtualService 和 DestinationRule;對於非 RPC 協議,Aeraki 則定義了一些新的 CRD 來進行管理,例如 RedisService 和 RedisDestination。我們後面將進一步介紹如何使用這些配置 CRD 來定製規則,例如實現 Traffic Splitting。

和 Istio 類似,Aeraki 也採用了埠名稱來識別協議型別。埠取名需要遵循 “tcp-七層協議名-xxx” 的命名規則。例如,一個 Thrift 服務應取名為 “tcp-thrift-service”。需要注意的是,我們必須保留埠名中的“tcp-”字首,因為對於 Istio 而言,這是一個 TCP 協議的服務。Aeraki 則會根據埠名中的七層協議來生成相應的 Envoy 配置,並替換 Istio 預設生成的 tcp_proxy。

我們來看看如何採用 Aeraki 來實現上面 Thrift 服務的 Traffic Splitting 用例。首先我們需要在 Thrift Service 定義的 Port 命名中宣告該 Service 的七層協議型別:“tcp-thrift-hello-server”,然後建立一個 VirtualService 將 Thrift 請求按照指定比例路由到不同的服務版本中。Aeraki 將根據服務定義和 VirtualService 生成所需的 Envoy 配置,並通過 EnvoyFilter 傳送給 Istio。

可以看到,相對於手動建立 EnvoyFilter,採用 Aeraki 來管理 Thrift 要簡單得多。如果不需要特殊的流量規則,則會更簡單,只需要按照命名規範在 Port 名稱中宣告 Thrift 協議即可,Aeraki 會生成所需的 Envoy 配置,無需任何額外的工作。

想自己試試 Aeraki 的 Thrift、Dubbo、Redis 服務管理能力?非常簡單,只需在一個連線到 K8s 叢集的命令列終端上執行下面兩行程式碼,就可以安裝一個帶有 Aeraki 外掛的 Istio 叢集以及相應的 Demo 程式,歡迎大家嘗試!

`git clone https:``//github``.com``/aeraki-framework/aeraki``.git``aeraki``/demo/install-demo``.sh`

也可以訪問 Aeraki 的線上 Demo,檢視從 Thrift、Dubbo、Redis 等服務收集到的監控指標皮膚:http://aeraki.zhaohuabing.com:3000/d/pgz7wp-Gz/aeraki-demo?orgId=1&refresh=10s&kiosk

使用 Aeraki 增強 Service Mesh

下面我們來看一下使用 Aeraki 的七層協議管理能力來增強 Service Mesh 的一些案例。

遮蔽開發/生產環境的差異

我們在開發、測試和生產環境中通常需要訪問不同的後端資源,例如需要連線到不同的 Redis 快取或者不同的 mySQL 資料庫。一般來說,我們需要修改隨應用程式釋出的配置檔案中的後端資源地址,以達到在不同環境中切換後端資源的目的。通過 Aeraki 的幫助,我們可以用 Service Mesh 來遮蔽不同後端資源的配置差異,使得應用程式可以用相同的方式訪問不同環境中的後端資源。

如下圖所示,我們在 Dev、Staging 和 Prod 三個環境中都需要訪問 Redis 服務,這三個 Redis 服務有不同的 IP 地址和訪問密碼,部署方式也可能不同:在開發環境中,為了節約資源和簡化部署,我們可能使用單個 Redis 例項;在測試和生產環境中,我們會使用 Redis 叢集來保證 Redis 服務的高可用和擴充套件性,我們也可能直接使用雲服務商提供的 Redis 託管服務。當在這三個環境中進行切換時,我們需要配置不同的 IP 地址和訪問密碼,如果 Redis 部署的方式不同,我們甚至可能需要修改客戶端程式碼來切換 Redis 單例項模式和叢集模式,這極大影響了我們開發、測試和上線的效率。

通過 Aeraki 提供的 RedisService 和 RedisDestination CRD,我們可以遮蔽這些不同 Redis 服務提供者之間的差異,允許客戶端以統一的方式訪問後端的 Redis 服務。

在採用 Aeraki 之前,我們在不同的環境中需要配置不同的 IP 地址和 Redis 訪問密碼。採用 Aeraki 之後,在客戶端可以採用相同的程式碼和配置,通過修改 Aeraki CRD 來切換不同環境中的 Redis 配置,大大減少在不同環境之間進行切換的成本。即使 Redis 從單例項改為了 Redis 叢集,客戶端也可以採用相同的方式進行訪問。

採用流量映象進行對比測試

有一些資料庫或者資料庫代理採用相同的網路協議。例如 TiDB、Oceanbase、Aurora、Kingshard等都相容 MySQL 協議;Twemproxy、Codis、Tendis、Pika等都採用了 Redis 協議。由於業務需求,我們有時需要從一個實現遷移到另一個實現上。在遷移之前,我們需要進行對比測試,以對比不同實現的效能、功能及相容性。

例如下面的場景:我們最初只用了一個單例項 Redis 來做快取,隨著線上業務的不斷擴充套件,該 Redis 例項已經出現了訪問瓶頸,我們希望切換為採用 Twemproxy 來對 Redis 進行水平擴充套件。通過採用 Aeraki 來將線上的 Redis 流量映象到 Twemproxy 測試環境,我們可以採用真實的業務資料對 Twemproxy 進行充分的測試,以評估其對線上業務的影響。

採用全流量故障注入測試系統彈性

Istio 可以實現 HTTP 和 gRPC 的故障注入,但這還不夠。在一個分散式系統中,應用服務、資料庫、快取、訊息系統等都可能由於網路或者其他原因出現不可用的情況。採用 Aeraki,我們可以對系統中的所有這些可能的故障點進行完整的模擬,以測試系統的彈性,保證我們的系統在一部分出現問題後可以自愈或者通過降級保證系統基本可用,而不至於整個系統崩潰。

小結

Service Mesh 中有大量的七層協議流量,包括 RPC、Database、Cache、Messaging 等型別的七層協議,但 Istio 只提供了 HTTP 和 gRPC 的七層管理能力,對其他七層協議的支援非常有限。Aerkai 開源專案通過非侵入的方式為 Istio 提供了任意七層協議的支援能力,並提供了面向使用者的高階配置 CRD,可以很方便地對這些協議的流量進行管理,實現灰度釋出等高階流量管理能力。目前 Aeraki 已經支援了 Thrift、Dubbo、Redis、Kafka、Zookeeper,並即將支援更多的協議。Aeraki 的定位是做成一個非侵入式 Istio 功能增強工具集,除了協議擴充套件之外,還會關注解決在 Istio 使用過程中遇到的其他常見問題,包括效率優化、配置簡化、第三方服務發現接入、功能擴充套件等。如果您希望瞭解更多關於 Aeraki 的內容,歡迎訪問 Github 主頁 https://github.com/aeraki-framework/aeraki

**招聘資訊

騰訊雲 Service Mesh 團隊正在火熱招聘中,Base 成都、北京、深圳或者西安,要求候選者熟悉 Kubernetes/Istio/Envoy。歡迎大家傳送簡歷到 huabingzhao@tencent.com 或者微信聯絡 zhao_huabing。

參考連結:

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!

相關文章