浅谈Service Mesh体系中的Envoy
摘要: 提到Envoy就不得不提Service Mesh,說到Service Mesh就一定要談及微服務了,那么我們就先放下Envoy,簡單了解下微服務、Service Mesh以及Envoy在Service Mesh中處于一個什么樣的角色。
背景
最近因工作原因開始了解Service Mesh與Envoy,為系統性梳理所學內容,因此沉淀了此文檔,但由于所知有限,如文檔中有描述不當之處,希望不吝賜教。
提到Envoy就不得不提Service Mesh,說到Service Mesh就一定要談及微服務了,那么我們就先放下Envoy,簡單了解下微服務、Service Mesh以及Envoy在Service Mesh中處于一個什么樣的角色。
過去幾年間,架構領域最火的方向非微服務莫屬,那么微服務架構到底為我們帶來了什么樣的好處呢?下面通過一張圖說明架構的演進,如下:
伴隨著業務規模的變大,微服務的好處顯而易見,例如它本身所具備的可擴展性、易維護性、故障和資源隔離性等諸多特性使得產品的生產研發效率大大提高,同時,基于微服務架構設計,研發人員可以構建出原生對于“云”具備超高友好度的系統,讓產品的持續集成與發布變得更為便捷。
然而沒有所謂的銀彈,微服務帶來很多好處的同時也引入了很多問題。在云原生模型里,一個應用可以由數百個服務組成,每個服務可能有數千個實例,每個實例的狀態可能持續的發生變化,此時,服務間的通信不僅異常復雜,而且都是運行時的行為,管理好服務間通信對于保證端到端的性能與可靠性來說無疑成為重中之重。在Service Mesh沒有出現之前,微服務框架之間的通訊大多采用SDK方案,但該方式短板也非常明顯,例如對業務有侵入性、無法做到SDK升級對業務透明等。
基于以上種種復雜原因催生了服務間通訊層的出現,這個層即不應該與應用程序的代碼耦合,又能捕獲到底層環境的動態變化并作出適當的調整,避免業務出現單點故障;同時也可以讓開發者只關注自身業務,將應用云化后帶來的諸多問題以不侵入業務代碼的方式提供給開發者。
上述所說的這個服務間通訊層就是Service Mesh(國內通常翻譯為服務網格),它可以提供安全、快速、可靠的服務間通訊。如果用一句話來解釋什么是Service Mesh,可以將其比作微服務間的TCP/IP層,負責服務之間的調用、限流、熔斷和監控等。
讀到這里大家一定仍然存在這樣的疑惑,Service Mesh到底是什么呢?這是一個全新的東西嗎?它的演進過程是什么樣的呢?下面使用一張圖來說明其演進過程,如下:
從上圖可以看到最初的Service Mesh始于一個網絡代理,在2016年1月業界第一個開源項目Linkerd發布,同年9 月 29 日的 SF Microservices 大會上,“Service Mesh”這個詞匯第一次在公開場合被使用,隨后Envoy也發布了自己的開源版本,但此時的Service Mesh更多停留在Sidecar層面,并沒有清晰的Sidecar管理面,因此屬于Service Mesh的第一代。此時雖然Service Mesh尚不成熟,但一個初具雛形的服務間通訊層已然出現,如下圖:
隨后Google聯合IBM、Lyft發起了Istio項目,從架構層面明確了數據平面、控制平面,并通過集中式的控制平面概念進一步強化了Service Mesh的價值,再加上巨頭背書的緣故,因此Service Mesh、Istio概念迅速火爆起來。此時已然進入到了第二代的Service Mesh,控制平面的概念及作用被大家認可并接受,而更重要的一點是至此已經形成了一個完整意義上的SDN服務通訊層。此時的Service Mesh架構如下圖:
至此Service Mesh的背景信息基本介紹完畢,接下來開始進入正題說說Envoy相關的內容。其在完整的Service Mesh體系中處于一個什么位置呢?繼續看圖:
Envoy是Istio中的Sidecar官方標配,是一個面向服務架構的高性能網絡代理,由C++語言實現,擁有強大的定制化能力,通過其提供的Filter機制基本可以對請求轉發過程中超過50%的流程做定制化,在性能方面由于其實現參考了Nginx,也處于主流水平,當然還有很多特性,在這里就不做一一介紹了。
從一份配置了解Envoy主流程
任何軟件架構設計,其核心都是圍繞數據展開的,基本上如何定義數據結構就決定了其流程的走向,剩下的不外乎加上一些設計手法,抽離出變與不變的部分,不變的部分最終會轉化為程序的主流程,基本固化,變的部分盡量保證擁有良好的擴展性、易維護性,最終會轉化為主流程中各個抽象的流程節點。
對于Envoy也不例外,作為一個網絡代理程序,其核心職責就是完成請求的轉發,在轉發的過程中人們又希望可以對其做一定程度的微處理,例如附加一個Header屬性等,否則就沒必要使用代理程序了。那么Envoy是如何運作的呢?它是如何定義其數據結構,并圍繞該數據結構設計軟件架構、程序流程,又是如何抽象出變得部分,保證高擴展性呢?
帶著這些疑問,試想Envoy作為一個高度可定制化的程序,其定制化的載體必然是配置信息,那么我們下面就試著從Envoy的一份配置來解讀其架構設計與程序流程。
在查看其配置前,我們不妨先腦補一下網絡代理程序的流程,比如作為一個代理,首先要能獲取請求流量,通常是采用監聽端口的方式實現,其次拿到請求數據后需要對其做些微處理,例如附加Header頭或校驗某個Header字段內容等,這里針對來源數據的層次不同,就可以分為L3L4L7,然后將請求轉發出去,轉發這里又可以衍生出如果后端是一個集群,需要從中挑選出一臺機器,如何挑選又涉及到負載均衡等。腦補下來大致流程應該就是這個樣子,接下來我們看看Envoy是如何組織其配置信息的。
Envoy配置的簡單配置信息如下:
關鍵字段說明:
Listener: 服務(程序)監聽者。就是真正干活的。 Envoy 會暴露一個或者多個listener監聽downstream的請求。
Filter: 過濾器。在 Envoy 中指的是一些“可插拔”和可組合的邏輯處理層。是 Envoy 核心邏輯處理單元。
Route_config: 路由規則配置,即請求路由到后端那個集群(cluster)。
Cluster: 服務提供方集群。Envoy 通過服務發現定位集群成員并獲取服務。具體請求到哪個集群成員是由負載均衡策略決定。通過健康檢查服務來對集群成員服務狀態進行檢查。
根據上面我們腦補的流程,配合上這份配置的話,Envoy大致處理流程如下圖:
Envoy內部對請求的處理流程其實跟我們上面腦補的流程大致相同,即對請求的處理流程基本是不變的,而對于變化的部分,即對請求數據的微處理,全部抽象為Filter,例如對請求的讀寫是ReadFilter、WriteFilter,對HTTP請求數據的編解碼是StreamEncoderFilter、StreamDecoderFilter,對TCP的處理是TcpProxyFilter,其繼承自ReadFilter,對HTTP的處理是ConnectionManager,其也是繼承自ReadFilter等等,各個Filter最終會組織成一個FilterChain,在收到請求后首先走FilterChain,其次路由到指定集群并做負載均衡獲取一個目標地址,然后轉發出去。
淺談Envoy架構
聊完了基本流程后,本節會試著分析其架構設計,希望從其架構設計中獲得一些益處。
先賣個關子,在本節開始之前我們不妨先思考一個有趣的問題:Envoy本身采用C++開發的,普遍認可C++程序執行性能會更好,那么延伸下來可以想到Envoy的設計目標似乎是在追求高性能,那么真是如此嗎?
在探究Envoy架構設計之前,我們先來看看Envoy自身是怎么描述其設計目標的,如下:
Envoy并不是很慢(我們已經花了相當長的時間來優化關鍵路徑)?;谀K化編碼,易于測試,而不是性能最優。我們的觀點是,在其他語言或者運行效率低很多的系統中,部署和使用Envoy能夠帶來很好的運行效率。
非常有意思的表述,Envoy并沒有把追求極致性能作為目標,那么其架構設計會弱化性能這塊嗎?
目前業內公認代理程序性能最好的是Nginx,其采用了per thread one eventloop模型,這種架構被業內普遍借鑒,那么Envoy呢?我們先看看下面的架構圖:
看到里面Worker的工作方式是不是很熟悉,會不會有一點點困惑呢?呵呵,沒錯,Envoy也采用了類Nginx的架構,方式是:多線程 + 非阻塞 + 異步IO(Libevent),雖然Envoy沒有把極致性能作為目標,但不等于沒有追求,只不過是相對于擴展性而言級別稍微低一點而已。
Envoy的另一特點是支持配置信息的熱更新,其功能由XDS模塊完成,XDS是個統稱,具體包括ADS(Aggregated Discovery Service)、SDS(Service Discovery Service)、EDS(Endpoint Discovery Service)、CDS(Cluster Discovery Service)、RDS(Route Discovery Service)、LDS(Listener Discovery Service)。XDS模塊功能是向Istio的Pilot獲取動態配置信息,拉取配置方式分為V1與V2版本,V1采用HTTP,V2采用gRPC。
Envoy還支持熱重啟,即重啟時可以做到無縫銜接,其基本實現原理是:
Envoy同樣也支持Lua編寫的Filter,不過與Nginx一樣,都是工作在HTTP層,具體實現原理都一樣,不做贅述了。
到此為止我們看完了上面的架構圖,如果你對其內部實現也有興趣的話,可以看看下面的內部實現類圖:
其內部實現為了靈活性,做了很多抽象封裝,但基本上可以拆分為幾個大的功能模塊,具體如上圖,不再贅述。
Envoy性能談
軟件的世界從來就不存在什么銀彈,雖然ServiceMesh優勢很明顯,甚至被尊稱為服務間的通訊層,但不可否認的是ServiceMesh的到來確實對應用的性能帶來了損耗,可以從兩個方面看待此問題:
本節主要談論Envoy在性能方面的努力及社區在性能方面呼聲較高的一些內容。
Envoy作為Sidecar其提供的核心功能可以簡單總結為以下三點:
從上述三點中我們試著分析下性能優化的關鍵點,其中第1、3點是與業務基本無關的,屬于通用型功能,而第2點的性能是與業務復雜度呈現相關性的,比如請求校驗規則的多與少、遙測數據的采集精細度、數據統計的維度多樣性等,因此最有可能提升Sidecar性能的點就是對請求的攔截與Sidecar之間通訊協議的高效性。
針對請求的攔截,目前常規的做法是使用iptables,在部署Sidecar時配置好iptables的攔截規則,當請求來臨后iptables會從規則表中從上至下順序查找匹配規則,如果沒遇到匹配的規則,就一條一條往下執行,如果遇到匹配的規則,那就執行本規則并根據本規則的動作(accept, reject, log等),決定下一步執行的情況。為了更直觀的展示iptables的執行過程,請看下圖:
了解iptables的基本流程后,不難發現其性能瓶頸主要是兩點:
既然知道了iptables的缺陷,那么優化手段不外乎從這兩點下手,而Linux社區與Envoy社區也正在計劃對此做優化,具體如下:
為什么規避Linux正常協議處理過程中內核態與用戶態的轉換如此重要呢?就以對我們最直觀的內存拷貝為例,正常情況下,一個網絡數據包從網卡到應用程序需要經過如下的過程:數據從網卡通過 DMA 等方式傳到內核開辟的緩沖區,然后從內核空間拷貝到用戶態空間,在 Linux 內核協議棧中,這個耗時操作甚至占到了數據包整個處理流程的 57.1%。為了更直觀的對內存拷貝消耗有所了解,畫了一張簡圖,如下:
簡說DPDK
DPDK全稱Intel Data Plane Development Kit,是Intel提供的數據平面開發工具集,為Intel Architecture(IA)處理器架構下用戶空間高效的數據包處理提供庫函數和驅動的支持,它不同于Linux系統以通用性設計為目的,而是專注于網絡應用中數據包的高性能處理,它將數據包處理、內存管理、處理器調度等任務轉移到用戶空間完成,而內核僅僅負責部分控制指令的處理。這樣就解決了處理數據包時的系統中斷、上下文切換、系統調用、系統調度等問題。
VPP是the vector packet processor的簡稱,是一套基于DPDK的網絡幀處理解決方案,是一個可擴展框架,提供開箱即用的交換機/路由器功能。是Linux基金會下開源項目FD.io的一個子項目,由思科貢獻的開源版本,目前是FD.io的最核心的項目。
整個DPDK還是非常復雜的,通過一兩篇文章很難說清楚,且本文重點也不在DPDK,因此下面只簡單介紹下其基本原理,讓我們大致清楚為什么Envoy引入VPP后可以大幅提升請求處理轉發效率。
為了說清楚DPDK是如何大幅提升了數據包的處理性能,我們先看一下普通的數據包在Linux中的收發過程,如下圖:
通過上面兩張圖我們可以大致清楚數據包的一個完整的收發過程,可以看到整個處理鏈路還是比較長的,且需要在內核態與用戶態之間做內存拷貝、上下文切換、軟硬件中斷等。雖然Linux設計初衷是以通用性為目的的,但隨著Linux在服務器市場的廣泛應用,其原有的網絡數據包處理方式已很難跟上人們對高性能網絡數據處理能力的訴求。在這種背景下DPDK應運而生,其利用UIO技術,在Driver層直接將數據包導入到用戶態進程,繞過了Linux協議棧,接下來由用戶進程完成所有后續處理,再通過Driver將數據發送出去。原有內核態與用戶態之間的內存拷貝采用mmap將用戶內存映射到內核,如此就規避了內存拷貝、上下文切換、系統調用等問題,然后再利用大頁內存、CPU親和性、無鎖隊列、基于輪詢的驅動模式、多核調度充分壓榨機器性能,從而實現高效率的數據包處理。說了這么多,接下來我們看下在DPDK中數據包的收發過程,如下圖:
通過對比得知,DPDK攔截中斷,不觸發后續中斷流程,并繞過內核協議棧,通過UIO(Userspace I/O)技術將網卡收到的報文拷貝到應用層處理,報文不再經過內核協議棧。減少了中斷,DPDK的包全部在用戶空間使用內存池管理,內核空間與用戶空間的內存交互不用進行拷貝,只做控制權轉移,減少報文拷貝過程,提高報文的轉發效率。
DPDK能夠繞過內核協議棧,本質上是得益于 UIO 技術,UIO技術也不是DPDK創立的,是內核提供的一種運行在用戶空間的I/O技術,Linux系統中一般的驅動設備都是運行在內核空間,在用戶空間用的程序調用即可,UIO則是將驅動的很少一部分運行在內核空間,絕大多數功能在用戶空間實現,通過 UIO 能夠攔截中斷,并重設中斷回調行為,從而繞過內核協議棧后續的處理流程。
那么UIO是如何攔截中斷的呢?我們先看看作為一個設備驅動的兩個主要職責:
UIO的實現機制其實是對用戶空間暴露文件接口,比如當注冊一個 UIO 設備 uioX,就會出現文件 /dev/uioX,對該文件的讀寫就是對設備內存的讀寫。除此之外,對設備的控制還可以通過 /sys/class/uio 下的各個文件的讀寫來完成。UIO架構及流程圖如下,不再贅述。
說完了DPDK,那么Cilium又是如何提高報文轉發效率呢?既然Cilium 是基于 eBPF 和 XDP 實現的,而XDP歸根結底也是利用eBPF為Linux內核提供高性能、可編程的網絡數據路徑框架,既然核心是eBPF,那么我們先了解下eBPF是什么。
簡說eBPF與XDP
eBPF(extended Berkeley Packet Filter)起源于BPF,它提供了內核的數據包過濾機制。Linux 3.15 開始引入 eBPF。其擴充了 BPF 的功能,豐富了指令集。它在內核提供了一個虛擬機,用戶態將過濾規則以虛擬機指令的形式傳遞到內核,由內核根據這些指令來過濾網絡數據包。直白地講就是我們可以讓內核按照我們的規則來對數據包進行處理,包括未進入協議棧之前的處理哦,有沒有瞬間覺得eBPF很牛逼,既然都這么強大了,有沒有什么最佳實踐或者應用呢?請看下圖:
我們可以看到XDP本身就是一個eBPF的最佳實踐,由于其他內容跟本文檔討論內容無關,不再展開。作為eBPF是如何工作以提供強大的能力呢?請看下圖:
首先是將用戶的.c文件編譯后自動生成eBPF 字節碼文件,也就是一堆的指令集合,其次通過系統調用將字節碼注入到內核,然后內核驗證合法性,通過校驗后使用JIT將其run起來,用戶程序與run起來的eBPF程序使用內核提供的標準Maps做數據交換。
與DPDK的內存全部在用戶空間來避免內存拷貝、上下文切換、系統調用等不同,eBPF都是在內核空間執行的。但兩者的核心都是通過避免數據包在內核態與用戶態之間的往復來提升轉發效率。
說完了eBPF,接下來該XDP粉墨登場了。XDP(eXpress Data Path)為Linux內核提供了高性能、可編程的網絡數據路徑。由于網絡包在還未進入網絡協議棧之前就處理,它給Linux網絡帶來了巨大的性能提升(性能比DPDK還要高)。
XDP在Linux內核4.8中引入,在數據包到達協議棧、分配sk_buff之前攔截,不同于DPDK的是XDP是作為內核功能的一部分,是與內核協同工作的。其基本處理流程如下圖:
XDP同樣將用戶程序編譯后生成eBPF字節碼文件,注入內核執行包過濾。XDP包過濾是在數據包進入內核協議棧之前,如果判斷數據包不需進一步處理可直接在內核態轉發數據包,如果判斷TX設備來不及處理會直接丟包,如果判斷數據包需再處理則轉給協議棧。
而為什么會有XDP比DPDK更高效的結論呢?也許通過下面這張圖你可以自己找到答案。
作為數據報文處理的新貴,其帶來的性能優勢是不言而喻,但XDP真的那么完美嗎?答案一定是否定的,其缺點有二:
聊了那么多關于eBPF與XDP的內容,其在業界存在最佳實踐嗎?是的,目前facebook開源的katran項目,使用的正是這兩項技術,據稱其從IPVS轉到eBPF后,使其性能提高了10倍。Linux社區中有人用XDP編寫的一個簡單的入口防火墻就可以輕松實現每秒處理1100萬個數據包的性能。
簡說QUIC協議
說完了如何高效的轉發請求,接下來我們聊聊Sidecar之間如何高效的通訊。
提到通訊那就一定要提及通訊協議了,作為我們耳熟能詳的兩大基本通訊協議TCP與UDP的優缺點這里就不再贅述了,那么我們是否能整合TCP與UDP兩者的優點呢,這樣既保證了TCP的可靠與安全性,又兼具UDP的速度與效率,不可否認的是往往正是出于人們對美好事物的向往,才持續不斷的推動我們前進的腳本。QUIC在這種期許下誕生,旨在創建幾乎等同于TCP的獨立連接,但有著低延遲,并對類似SPDY的多路復用流協議有更好的支持。
QUIC協議本身就內置TLS棧,實現自己的傳輸加密層,而沒有使用現有的TLS 1.2。同時QUIC還包含了部分HTTP/2的實現,因此QUIC的地位看起來是這樣的:
QUIC協議的誕生就是為了降低網絡延遲,開創性的使用了UDP協議作為底層傳輸協議,通過多種方式減少了網絡延遲。因此帶來了性能的極大提升,且具體的提升效果在Google旗下的YouTube已經驗證。
既然QUIC協議相比現有其他的協議更具優勢 ,那是否也可以將其應用到Envoy中呢?Envoy社區正在推動官方重構其架構的目的之一就是為了QUIC,最終目的是希望使用QUIC作為Sidecar之間的通訊協議。
試想一下如果Envoy應用了上述技術,性能會有怎樣的提升呢?這個就留給各位看官自行腦補吧。
讀到這里不知各位是否會產生這樣的疑問,目前作為ServiceMesh中數據面板的Sidecar有好幾個,為什么只有Envoy社區在性能方面呼聲最高呢?這就牽扯到一個老掉牙的話題了,因為Envoy是C系語言編寫的,在應用OS特性時有著先天優勢。
雜談
上節內容提到目前有與Envoy同類的幾個程序,包括Linkerd、Conduit、NginMesh,下面就以個人所知簡單描述下各自的特點,僅供諸位參考。
就個人而言,其實挺希望Conduit的壯大,正如其設計初衷說的那樣:輕量化,相比Istio這種重部署模式來講,非常適合小規模業務的快速上手,且Conduit與Linkerd系出同門,足以保證其設計理念的先進性,雖然Buoyant公司宣稱Conduit與Linkerd的目標不同,但細想下來未嘗Buoyant公司沒有存在一絲不甘,希望推出一個完整的Service Mesh方案來顛覆Istio一家獨大的局面,奪回Service Mesh開創者的殊榮。
下面是各自的特性簡述。
NginMesh:
NginMesh給人的感覺更多的像是做了一個Istio的橋接器,只負責把Istio的配置信息翻譯成Nginx所知的,通過重啟Nginx的方式應用配置。給我的感覺僅僅是為了搭上ServiceMesh的順風車而臨時推出的一個方案。
Linkerd:
作為“Service Mesh”概念的締造者、布道者,最終卻在Service Mesh的大潮中,被由Google、IBM、Lft聯手打造的Istio + Envoy打敗,不得不感嘆巨頭的強大與初創公司的弱小與艱辛,由衷的希望看到Conduit的崛起,逆殺Istio。話說這是不是典型的弱者心態啊,哈哈。
Conduit:
原文鏈接
轉載于:https://juejin.im/post/5b471a0ce51d4519256281d2
總結
以上是生活随笔為你收集整理的浅谈Service Mesh体系中的Envoy的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: iOS 模型数组去重复
- 下一篇: Redis的特性以及优势(附官网)