F5 2021/01/11

Envoy 雲原生時代下的思考

Envoy 是雲原生時代的明星,其本質是反向代理負載均衡類軟體,領域上歸於應用交付, Envoy 又引發了哪些關於傳統應用交付領域的思考?

首先我們看一下 Envoy 官方是如何介紹 Envoy 的:

ENVOY IS AN OPEN SOURCE EDGE AND SERVICE PROXY, DESIGNED FOR CLOUD-NATIVE APPLICATIONS Envoy 是一個開源的邊緣以及服務代理,為雲原生應用而生。

 

從網站首頁的這一段描述可以清晰的看出官方對 Envoy 的定義,簡單來說就是雲原生時代下東西南北流量的代理。Lfyt 公司是微服務應用架構的先導者,在大量的微服務類文章中我們都可以看到 Lfyt 的身影,在從單體應用大規模轉向微服務架構後,一個嚴重的問題橫更在開發與架構人員面前,一方面 Lyft 的服務採用了多種語言開發,而採用類庫來解決分散式架構下的各種問題需要進行大量的語言適配以及對代碼的侵入,另一方面 Lyft 的業務都是部署在 AWS 上的,大量依賴 AWS 的 ELB 以及 EC2,但是 ELB 以及 AWS 在當時所提供的服務間流量管控、洞察與問題排除都不能滿足 Lyft 的需求,正是基於這樣的背景,Lfyt 於 2015 年 5 月開始了 Envoy 的開發,最早是作為一個邊緣代理進行部署並開始替代 ELB,隨後開始作為 sidecar 方式進行大規模部署。2016 年 9 月 14 日,Lyft 在其 Blog上正式對外宣佈了這一項目:Envoy C++ L7 代理與通信匯流排。一時間 Envoy 得到了大量的關注,Google 等公司開始貢獻到這個項目裡,並在一年後的 2017 年 9 月將專案捐獻給 CNCF。有了 Lyft 這樣一個好媽,又過繼給了 CNCF 這樣一個富爸,再加上同父異母的 Istio 明星兄弟的加持,可以說 Envoy 一時風光無兩,賺足了眼球與開發者的支援,僅一年多點時間便從 CNCF 畢業了。

容器技術助推了企業實踐 DevOps 與進行微服務改造,Kubernetes 容器編排平臺則讓企業能夠更加自信的將更多業務從傳統架構遷移到基於容器的現代基礎架構之上,Kubernetes 解決了容器編排、應用發佈等問題,但是當服務之間的通訊從以前的記憶體之間調用變成了基於 TCP 的網路通信後,網路對應用服務的影響變得更加巨大與不確定,基於傳統的應用架構的維運手段無法適應與解決巨大且複雜的服務間通信洞察、排錯,為了解決這樣的問題,sevice mesh 應用而生,並迅速成為關注的熱。Istio 項目則是此生態中最重要的玩家,Istio 的架構是一個典型的管理平面與資料分離的架構,在資料平面的選擇上是開放的,但是 Istio 預設選擇了 Envoy 作為資料平面。兩大人氣明星強強聯手,讓幾乎同一時期的 linkerd 變得黯然失色。而在這個時間點,NGINX 同樣也曾短暫的進行了 Nginmesh 項目,試圖讓 NGINX 作為 Istio 的資料平面,但最終在 2018 年底放棄了,為什麼會放棄,這個本文後面會提到。

當前除了 Istio 選擇 Envoy 作為資料平面外,以 Envoy 為基礎的專案還有很多,例如 k8s 的多個 Ingress Controller 項目:Gloo, Contour, Ambassador。Istio 自身的 Ingress gateway 與 Egress gateway 同樣選擇的是 Envoy。

下面我將從技術方面來看看為何 Envoy 能夠得到開發社群的如此重視。將從以下幾個方面來總結:

● 技術特徵
● 部署架構
● 軟體架構

 

技術特徵

● 介面化與 API
● 動態性
● 擴展性
● 可觀測性
● 現代性

 

介面化與 API

當我第一次打開 Envoy 的配置時候,我的第一感覺是,天啊,這樣一個產品使用者該怎麼配置和使用。先來直觀的感受下,在一個並不複雜的實驗環境下,一個 Envoy 的實際設定檔行數竟然達到了 20000 行。

 

儘管這是 Istio 環境下的動態配置,雖然還有方式去優化使得實際配置量減少,或者說當完全使用靜態配置方式進行配置的時候我們不會做如此大量的配置,但是當我們看到以下實際的配置結構輸出就會感覺到對於這樣一個軟體,如果以普通方式進行配置與維護顯然是不切實際的,其配置完全 json 結構化,並擁有大量的描述性配置,相對於 NGINX 等這些反向代理軟體來說,其配置結構實在是過於複雜。

   

 (複雜的配置結構)

顯然,Envoy 的設計天生就不是為手工而設,因此 Envoy 設計了大量的 xDS 協定介面,需要使用者自行設計一個 xDS 的服務端實現對所有配置處理,Envoy 支援 gRPC 或者 REST 與服務端進行通信從而更新自身的配置。xDS 是 Envoy DS(discover service)協議的統稱,具體可分為 Listener DS(LDS), Route DS (RDS), Cluster DS (CDS), Endpoint DS (EDS), 此外還有 Secret DS,為了保證配置一致性的聚合 DS-ADS 等,更多的 xDS 可查看這裡。這些介面用於自動化產生各種具體不同的配置物件。可以看出,這是一個高度動態性的運行時配置,要想用好它則必須開發一個具有足夠能力的 server 端,顯然這不是傳統反向代理軟體的設計思維。

  (圖片來自 https://gist.github.com/nikhilsuvarna/bd0aa0ef01880270c13d145c61a4af22)

 

動態性

正如前面所述,Envoy 的配置高度依賴介面自動化產生各種配置,這些配置是可以進行 Runtime 修改而無需 reload 檔,在現代應用架構中,一個服務端點的生命週期都變得更短,其運行的不確定性或彈性都變得更大,所以能夠對配置進行 runtime 修改而無需重新 reload 設定檔這個能力在現代應用架構中顯得尤其珍貴,這正是 Istio 選擇 Envoy 作為資料平面的一個重要考慮。Envoy 同時還具備熱重啟能力,這使得在升級或必須進行重啟的時候變得更加優雅,已有連接能夠得到更多的保護。

在 Istio 場景下,Envoy 的容器裡運行兩個進程,一個叫 pilot-agent,一個是 envoy-proxy 本身,pilot-agent 負責管理與啟動 Envoy,並產生一個位於 /etc/istio/proxy/ 下的 envoy-rev0.json 初始設定檔,這個檔裡定義了 Envoy 應該如何與 pilot server 進行通信以獲取配置,利用該設定檔最終啟動 Enovy 進程。但是 Envoy 最終運行的配置並不僅僅是 envoy-rev0.json 裡的內容,它包含上文所說的通過 xDS 協定發現的所有動態配置。

在下圖的 envoy 整體配置 dump 中可以看到包含了 bootstrap 的內容以及其它靜態以及動態配置:

 (Envoy 配置結構)

 

結合下圖可以看出基本的 Envoy 配置結構及其邏輯,無論是入口 listener(類似 F5 的 VS 以及部分 profile 配置,NGINX 的 listener 以及部分 Server 段落配置)還是路由控制邏輯(類似 F5 LTM policy,NGINX 的各種 Locations 匹配等),還是 Clusters(類似 F5 pool, NGINX 的 upstream)、Endpoints(類似 F5 pool member,NGINX 的 upstream 裡的 server),乃至 SSL 證書完全可以通過介面從服務側自動化的發現過來。

(圖片來自 https://gist.github.com/nikhilsuvarna/bd0aa0ef01880270c13d145c61a4af22)

 

擴展性

Envoy 的配置中可以看到大量的 filter,這些都是其擴展性的表現,Envoy 學習了 F5 以及 NGINX 的架構,大量使用外掛程式式,使得開發者可以更加容易的開發。從 listener 開始就支援使用 filter,支援開發者開發 L3,L4,L7 的外掛程式從而實現對協議擴展與更多控制。

在實際中,企業在 C++ 的開發儲備方面可能遠不如 JavaScript 等這樣的語言多,因此 Envoy 還支持 Lua 以及 Webassembly 擴展, 這一方面使得無需經常重新編譯二進位並重啟,另一方面降低了企業外掛程式開發難度,讓企業可以使用更多相容 Webassembly 的語言進行外掛程式編寫,然後編譯為 Webassenmbly 機器碼實現高效的運行。目前來說 Envoy 以及 Istio 利用 Webassembly 做擴展還在早期階段,走向成熟還需一段時間。

(圖片來自 https://www.servicemesher.com/istio-handbook/concepts/envoy.html)

 

從上面的圖可以看出,這樣的請求處理結構非常的接近於 F5 TMOS 系統的設計思想,也在一定程度上與 NGINX 類似。連接、請求在不同的協議層面與階段對應不同的處理元件,而這些元件本身是可擴展的、可程式設計的,進而實現對資料流程的靈活程式設計控制。

 

可觀測性

說 Envoy 生來具備雲原生的特質,其中一大特點就是對可觀測性的重視,可以看到可觀測的三大元件:logs,metrics,tracing 默認都被 Envoy 所支持。

Envoy 容許使用者以靈活的方式在靈活的位置定義靈活的日誌格式,這些變化可以通過動態配置下發從而實現立即生效,並容許定義對日誌的採樣等。在 Metrics 則提供了能夠與 Prometheus 進行集成的諸多指標,值得一提的是 Envoy 容許 filter 本身來擴充這些指標,例如在限流或者驗證等 filter 中容許外掛程式本身定義屬於自己的指標從而幫助用戶更好的使用和量化外掛程式的運行狀態。在 Tracing 方面 Envoy 支持向 zipkin,jaeger,datadog,lightStep 等協力廠商集成,Envoy 能夠生產統一的請求 ID 並在整個網路結構中保持傳播,同時也支持外部的 x-client-trace-id,從而實現對微服務之間關係拓撲的描述。

 

Envoy 生成的每個 span 包含以下資料:

● 通過設置 - -service-cluster 的原始服務集群資訊。

● 請求的開始時間和持續時間。

● 通過設置 - -service-node 的原始主機資訊。

● 通過 x-envoy-downstream-service-cluster 標頭設置的下游集群。

● HTTP 請求 URL,方法,協定和使用者代理。

● 通過 custom_tags 設置的其他自訂標籤。

● 上游集群名稱和地址。

● HTTP 回應狀態碼。

● GRPC 回應狀態和消息(如果可用)。

● HTTP 狀態為 5xx 或 GRPC 狀態不是 “OK” 時的錯誤標記。

● 跟蹤特定於系統的中繼資料。

 

現代性

其實,說 Envoy 具有現代性顯然是正確的廢話,Envoy 天生為現代應用架構而生,這裡主要是想從幾個我們最容易能夠感受到的方面來說明一下。首先是其特殊的結構設計,在 Envoy 裡它支援利用 iptables 截取流量並做透明處理,其本身能夠利用 getsockopt () 實現對 NAT 條目中原始目的資訊的提取,並在 listener 監聽上容許在從被跳轉的埠 listener 中跳躍到實際能匹配原始目的資訊的非綁定型 listener,儘管從反向代理角度看這就有點像 F5 的 VS 內部跳轉,NGINX 的 subrequest,但是其最大的特點和能力在於對連接的透明性,這在 Pod sidecar 模式的部署中顯得尤其重要,具體原理可參考這裡。

對於現代應用最愛的灰度發佈,流量鏡像,斷路器,全局限流等等功能,其在配置上也非常的簡潔,這一點儘管 F5/NGINX 等軟體也能完成類似的工作,但在原生性上以及配置的難易程度上 Envoy 具有更大優勢。

現代性的另一個表現就是對協定的支援,看看以下支援的協定,熟悉應用交付、反向代理軟體的同學可能會情不自禁的表示讚歎,而這些協議的支援更從另一方面表現了 Envoy 作為更加面向開發者和 SRE 的一個特質。

● gRPC
● HTTP2
● MongoDB
● DynamoDB
● Redis
● Postgres
● Kafka
● Dubbo
● Thrift
● ZooKeeper
● RockeMQ

 

部署架構

在瞭解完 Envoy 的技術特徵後,再來從部署架構角度看 Enovy。

完整 Sidecar 模型部署,這是 Envoy 最大的部署特徵,services 之間的通信完全轉化為 Envoy 代理之間的通信,從而實現將諸多非業務功能從服務代碼中移出到外部代理元件,Envoy 負責網路通信控制與流量的可觀測。也可以部署為簡化的 sidecar,其僅充當 service 入站方向的代理,無需額外的流量操縱,這個結構在我對外闡述的基於 NGINX 實現業務可觀測性中所使用。

 (圖片來自 https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request#requ...)

 

Hub 型,這與 NGINX 的 MRA 中的 Router-mesh 型理念相同,所有服務使用一個集中的 Envoy 進行通信,這種部署結構一般適用於中小型服務,可通過與服務註冊的適配將服務流量導向到 Envoy。

(圖片來自 https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request#requ...)

 

Envoy 也可以作為 Ingress edge 閘道或 Egress 閘道,在這種場景下一般 Envoy 多用於 Ingress controller 或 API 閘道,可以看到很多的此類實現喜歡使用 Envoy 作為底層,例如 Gloo, Ambassador 等。

 (圖片來自 https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request#requ...)

 

下面這個部署結構應該是大家比較熟悉的,Envoy 作為一個 Edge 閘道,並同時部署額外一層微服務閘道(或代理平臺層)。

 

(圖片來自 https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request#requ...)

 

最後,這是將所有形態的 Envoy 部署集中到了一起,這種架構可能會在服務從傳統架構向微服務架構遷移過程的中間形態。

(圖片來自 https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request#requ...)

 

最後,來看一下 Istio 裡是如何使用 Envoy 的。

(圖片來自網路)

 

總結來看,由於 Envoy 的跨平臺性,使其具有和 NGINX 一樣的靈活部署結構,但是實際上部署結構往往與最終的配置實現機制有著強關係,軟體的能力能否適應在此結構下的靈活與簡單的配置實現是最終考驗。客觀的講,在這方面 Envoy 更具有優勢。

 

軟體架構

Envoy 採用了單進程多執行序的設計結構,主執行緒負責配置更新,進程信號處理等。請求則是由多個 worker 執行序來處理,為了簡化與避免處理複雜,一個連接始終由一個執行序,這樣可儘量減少執行緒間的資料共用而引發的一些鎖操作。Envoy 盡可能的避免執行序之間的狀態共用,為此設計了 Thread Local Store 機制。在日誌的寫入上,實際上是 worker 執行序寫入到記憶體緩存,最後再由檔刷新執行序來負責寫入到磁片,這樣可以在一定程度上提高效率。整體上來說,Envoy 在設計時候還是比較偏重於簡化複雜性,並強調靈活性,因此與 NGINX 不同它並沒有把對性能的追求放在第一位,這一點在 Envoy 的相關官方Blog裡可以得到驗證。

(圖片來自 https://blog.envoyproxy.io/envoy-threading-model-a8d44b922310

 

與 NGINX 類似,Envoy 整體是非同步非阻塞的設計,採用的是事件驅動方式。每個執行序都負責每一個 listener,可以採用 SO_REUSEPORT 也可以共用 socket,NGINX 也有類似的機制。

(圖片來自https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request#requ...

 

當請求由 listener 監聽並開始處理後,根據配置連接將會被後續的 L3、4、7 等多個 filter 進行處理。

圖片取自 jimmysong.io

 

NGINX 創新與引領

在瞭解完 Envoy 的技術特性及其架構後,我們再回到此文的原點。Envoy 從出生就帶著現代應用架構的基因,是不是說對於 NGINX/F5 等這些前浪就落伍了呢。

記得 NGINX 的作者 Igor 在 F5 中國 520 大會上曾這樣對大家介紹 NGINX 為何如此成功。他說,他沒有想到會如此成功,究其原因是他在正確的時間點開發了一個正確的軟體。我們知道,在 2003 年左右那個時期,還談不上什麼分散式架構、微服務,那時候主要要解決的是單機性能問題,正是基於這樣的背景,NGINX 無論從架構設計還是代碼品質都嚴格苛求於性能。在功能上,NGINX 本來是一款 Web Server 軟體,L7 反向代理則是其能力的延伸,而 L4 代理能力的增加則更晚,鑒於這樣的背景,從現代應用架構角度來看,確實有一些能力是較難覆蓋的。同樣,Envoy 誕生和發展于現代應用架構時代,正如 Envoy 自我闡述,其參考了大量已有的軟硬體反向代理、負載均衡產品,從上面的技術分析中也可以看出 Envoy 有很多 NGINX 以及 F5 架構理念,可以說 Envoy 從成熟的反向代理產品中吸取了諸多精華,並在設計時候充分考慮現代應用架構的需求,它也是一個在正確的時間的一個正確軟體。

微服務架構下,很多問題都變成了如何控制服務之間的通訊與流量洞察,這是典型的應用交付領域,作為這個領域的領導者,一方面需積極擁抱和適應新時代的應用架構,一方面需要創新並繼續引領新的方向。歷史上這個領域發生了兩次技術革新,第一次是在 2006 年左右,當時一度關於「負載均衡已死」 話題被炒爆,實質是當時市場開始發生變換,大家不再滿足於簡單的負載均衡,需求衍生到應用安全、網路優化、應用優化、接入控制、流量控制等更多複雜的場景,應用交付概念開始被提出,可以說在 2006 年前,市場的主要概念和技術方向是以四層交換機為核心理念的負載均衡技術,大部分玩家是傳統網路廠商,思維與概念都是以網路交換為基礎,而 F5 就像一個奇怪的傢伙,產品設計思想完全在另一個維度之上,自 2004 年就開始發佈的 TMOS V9 作業系統自此開始引領市場,此後 10 年,無人超越。第二次技術革新發生在 2016 年左右,受雲、微服務的影響,軟體化、輕量化變為市場主流,同時 Devops 思想意味著使用者角色發生了變化,傳統的面向網路運維人員的設計開始變得難以滿足市場需求。以 F5 為主導的這個領域在市場上也發生了新的變化,Gartner 不再對應用交付領域發佈魔力象限分析,轉而形成以 Guide 方式的指導。

現代應用架構飛速發展,大量應用開始微服務化,但從業務存取的整體鏈條來看,Enovy 還不能解決所有問題,例如應用安全防護,複雜的企業協議,以及不同的組織關係導致的不同需求。可以看到以 F5/NGINX 為代表的應用交付產品在 DevOps 大潮下也開始積極的實現產品融入,F5 發佈了完整的自動化工具鏈,從產品的 bootstrap 到網路配置、到應用服務配置,到最後的監控遙測都已經形成了完整的介面,並採用聲明式介面來將產品管理提升到更高角色人群與管理系統中。NGINX 同樣構建了自身的 API 以及 Controller 平面,對外提供聲明式 API 介面,開發者可以更好的利用介面融入自身的控制平面。這些變化都是為了讓開發者或者 SRE 能夠更好的使用 F5/NGINX, 詳細可以參考我的《從傳統 ADC 邁向 Cloud Native ADC》系列文章。

 

F5 在收購 NGINX 與 Shape 之後,提出了新的願景,將充分利用可廣泛觸達的資料平面能力,藉助 AI 進一步挖掘資料潛能説明使用者更好的掌握和瞭解應用行為、性能,為業務運營提出參考,並回饋到元件配置與運行管理,從而形成閉環。

現代應用交付依然不能缺少一個重要場景,那就是應用安全,儘管 Istio 等產品在安全通信,身份,策略方面做了不少的嘗試,但是應用安全本身則比較缺乏,F5 作為 WAF 安全領域的領導廠商,通過將安全能力轉移到 NGINX 上形成了新的 NGINX APP Protect,利用其跨平臺的能力幫助使用者更好的管理微服務場景下的應用安全能力,幫助企業更好的落地 DevSecOps。

如果將 Envoy 的那些技術特徵與 F5 進行對比的話,我們可以看到 F5 一定程度上欠缺在擴展性與現代性上,F5 具有較好的程式設計控制能力,但是相對於更大的外掛程式開發來說是不足的,這和現代性往往可以聯繫到一起看,比如想針對某個很新的協議做一個類似 Envoy 的複雜 7 層 filter 是無法實現的,儘管 iRule 或者 iRuleLX 可以一定程度上做一些事情。然而無論怎樣,最終 F5 的產品形態本身決定了 F5 的 BIGIP 是無法完全跨平臺的,因為它無法以容器來運行。值得期待的是,這樣的形態限制將會被 F5 下一代 TMOS 系統打破。

Service Mesh 是當前熱門的技術方向,F5 基於 Istio 打造了企業級的 Aspen Mesh 服務網格產品,説明企業更好、更容易的部署和使用 Istio。Aspen mesh 團隊成員進入僅有 7 個位置的的 Istio Technical Oversight Committee,負責 Istio 的 RFCs/Designs/APIs 等方面的重要職責。儘管 Istio 在 service mesh 領域擁有絕對的生態與熱度,但這並不表示 Istio 是唯一的選擇,在很多時候客戶可能希望採用一種更加簡潔的 Service Mesh 去實現大部分所需功能而不是去部署一整套複雜的 Istio 方案,基於 NGINX 元件打造的 NGINX Service Mesh (NSM) 將為用戶帶來新的選擇,一個更加簡單易用的 Service Mesh 產品,這是我們在文章最開始提到 NGINX 終止 Nginmesh 的原因。

技術發展是一個必然的過程,2006 年從傳統的負載均衡技術演變為應用交付,除了負載均衡之外,引入安全、存取控制、接入控制、流量控制等諸多方面。2016 年左右,這個領域再次發生新的技術變革,大量新生代反向代理開源軟體的出現對傳統應用交付類產品產生了一次新的衝擊,積極適應與改變並創新是制勝的關鍵。Envoy 作為新代表有著優秀的能力,但它也不能解決所有問題,Envoy 擁有更陡峭的學習曲線以及更高開發成本與維護成本,對於企業來說應根據實際情況選擇合適的解決方案與產品來解決架構中的不同問題,避免追趕潮流而讓自己陷入陷阱。

F5 則更加需要讓開發人員瞭解 TMOS 系統所擁有的巨大潛能(特別是下一代產品在架構以及形態上的顛覆),瞭解其優秀全代理架構以及可以在任意層面進行的程式設計控制, 讓開發者、SRE 以 F5 TMOS 作為一種能力平臺和中介軟體進行開發,更好的利用 F5 自身已經擁有的應用交付能力來快速實現自身需求。