自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Dropbox是如何將接入層從Nginx遷移到Envoy的

開發(fā) 前端
這篇文章里講述了 Dropbox 如何將他們的接入層從 Nginx 遷移到了 Envoy,就兩個(gè)軟件之間基于多個(gè)維度進(jìn)行了對(duì)比,并介紹了他們的遷移流程、遷移后的現(xiàn)狀以及這個(gè)過程中遇到的一些問題。

【編者的話】這篇文章里講述了 Dropbox 如何將他們的接入層從 Nginx 遷移到了 Envoy,就兩個(gè)軟件之間基于多個(gè)維度進(jìn)行了對(duì)比,并介紹了他們的遷移流程、遷移后的現(xiàn)狀以及這個(gè)過程中遇到的一些問題。

在這篇文章里,我們將會(huì)介紹 Dropbox 之前曾經(jīng)使用過的一套基于 Nginx 的流量基礎(chǔ)設(shè)施,它的一些痛點(diǎn),以及遷移到 Envoy 之后帶來的一些好處。我們將會(huì)針對(duì) Nginx 和 Envoy 就多個(gè)軟件工程及運(yùn)維方面進(jìn)行比較。我們還將簡(jiǎn)單地介紹一下我們的遷移流程,遷移后的現(xiàn)狀,以及在這個(gè)過程中遇到的一些問題。

在我們大部分的 Dropbox 流量轉(zhuǎn)到 Envoy 后,我們還必須無縫地將一個(gè)已經(jīng)處理了數(shù)千萬個(gè)建立的連接,每秒數(shù)百萬個(gè)請(qǐng)求,并擁有數(shù)個(gè) TB 帶寬的復(fù)雜系統(tǒng)遷移到上面。這實(shí)際上已經(jīng)讓我們成為了世界上最大的 Envoy 用戶之一。

免責(zé)聲明:盡管我們?cè)噲D保持客觀性,但是本文中有很多對(duì)比是針對(duì) Dropbox 以及我們軟件開發(fā)的方式進(jìn)行的:我們的技術(shù)棧選型是 Bazel,gRPC 和 C++/Golang。

另外請(qǐng)注意,下文提到的 Nginx 指的是其開源版本,而不是具有附加功能的商業(yè)版本。

舊的一套基于 Nginx 的流量基礎(chǔ)設(shè)施

我們的 Nginx 配置絕大部分都是靜態(tài)的,然后通過結(jié)合 Python2、Jinja2 以及 YAML 等渲染。任意一處變動(dòng)都需要一次完整的重新部署才能生效。所有動(dòng)態(tài)的部分,比如 upstream 管理以及 stats exporter,這些是用 Lua 編寫的。更復(fù)雜的邏輯放到了用 Go 編寫的,下面一層的代理層。

Nginx 在我們的環(huán)境里良好運(yùn)行了近十年。但是它已經(jīng)無法再適應(yīng)我們現(xiàn)在的開發(fā)優(yōu)秀實(shí)踐:

  • 我們內(nèi)部和(私有)外部的 API 逐漸從 REST 遷移到 gRPC,這需要各種代理層面的轉(zhuǎn)碼功能支持;
  • Protocol buffer 已經(jīng)成為服務(wù)定義和配置的事實(shí)標(biāo)準(zhǔn);
  • 所有軟件,無論使用哪種語言,均是通過 Bazel 構(gòu)建和測(cè)試;
  • 我們的工程師開始重度參與開源社區(qū)里一些重要的基礎(chǔ)設(shè)施項(xiàng)目。

此外,在運(yùn)維方面,Nginx 的維護(hù)成本非常高:

  • 配置生成邏輯太過靈活,而且拆分到了 YAML、Jinja2 和 Python等多處;
  • 監(jiān)控是一個(gè) Lua 腳本、日志解析以及基于系統(tǒng)的基礎(chǔ)監(jiān)控的組合;
  • 越來越依賴于第三方模塊,而這會(huì)帶來穩(wěn)定性、性能以及后續(xù)升級(jí)成本等多方面的影響;
  • Nginx 的部署及流程管理同其他服務(wù)完全不同。它完全依賴于其他一些系統(tǒng)的配置:syslog、logrotate 等,并沒有和基礎(chǔ)系統(tǒng)完全分離。

正是出于上述種種原因,10 年來我們首次開始尋找 Nginx 的潛在替代產(chǎn)品。

為什么不用 Bandaid?

正如我們上面提到的,在 Dropbox 內(nèi)部,我們嚴(yán)重依賴一個(gè) Golang 實(shí)現(xiàn)的代理(稱為 Bandaid)。之所以它可以和 Dropbox 的整個(gè)基礎(chǔ)設(shè)施很好地集成,原因是在于它可以訪問到我們內(nèi)部 Golang 庫(kù)的廣闊生態(tài):監(jiān)控、服務(wù)發(fā)現(xiàn)、限流等。我們考慮過從 Nginx 遷移到 Bandaid 的方案,但是這里面有一些問題阻礙了我們這樣做:

  • Golang 比 C/C++ 占用更多的資源。資源的低使用率對(duì)于跑在邊緣端的我們至關(guān)重要,因?yàn)槲覀儫o法輕易地“自動(dòng)擴(kuò)容”那里的部署。
  • CPU 開銷主要來自 GC、HTTP 解析器和 TLS ,后者的優(yōu)化程度低于 Nginx/Envoy 使用的 BoringSSL。
  • “每個(gè)請(qǐng)求一個(gè) goroutine”的模型和 GC 開銷大大增加了像我們這樣高連接服務(wù)的內(nèi)存需求。
  • Go 的 TLS 棧不支持 FIPS(譯者注:見 go #11658)。
  • 除了 Dropbox,Bandaid 沒有一個(gè)社區(qū)的支持,這意味著我們只能依靠我們自己來開發(fā)功能。

綜合上述原因,我們決定將所有流量基礎(chǔ)設(shè)施遷移到 Envoy。

全新的基于 Envoy 的流量基礎(chǔ)設(shè)施

讓我們逐一看看開發(fā)和運(yùn)維幾個(gè)主要的維度,從中了解為什么我們認(rèn)為 Envoy 對(duì)我們來說是一個(gè)更好的選擇,以及我們從 Nginx 遷移到 Envoy 之后獲得了哪些收益。

性能

Nginx 的架構(gòu)是一個(gè)事件驅(qū)動(dòng)和多進(jìn)程的模式。它支持 SO_REUSEPORT,EPOLLEXCLUSIVE 以及 Worker 綁核。盡管它是基于事件循環(huán)的,但它并不是完全非阻塞的。這意味著某些操作(例如打開一個(gè)文件或記錄 access/error 日志)可能會(huì)導(dǎo)致事件循環(huán)中止(即使啟用了 aio,aio_write 和線程池),這樣會(huì)使得尾部延遲增加,從而導(dǎo)致旋轉(zhuǎn)磁盤驅(qū)動(dòng)器上出現(xiàn)幾秒鐘的延遲。

Envoy 是一個(gè)類似的事件驅(qū)動(dòng)式的架構(gòu),只是它使用的是線程而不是進(jìn)程。它也同樣支持 SO_REUSEPORT(自帶一個(gè) BPF 過濾器的支持),并且依賴 libevent 實(shí)現(xiàn)事件循環(huán)(換句話說,沒有用到像 EPOLLEXCLUSIVE 這樣的 epoll(2) 功能)。Envoy 在事件循環(huán)里并沒有任何阻塞 IO 的操作。甚至日志記錄也是以非阻塞方式實(shí)現(xiàn)的,因此它不會(huì)出現(xiàn)停頓的現(xiàn)象。

從理論上講,Nginx 和 Envoy 應(yīng)該具有相似的性能特征。但是“希望”不是我們的做事風(fēng)格,因此我們的第一步便是針對(duì)經(jīng)過類似調(diào)整的 Nginx 和 Envoy 設(shè)置運(yùn)行各種工作負(fù)載測(cè)試。

我們的測(cè)試結(jié)果表明,在大多數(shù)測(cè)試工作負(fù)載下,Nginx 和 Envoy 擁有相近的性能表現(xiàn):?jiǎn)蚊敫哒?qǐng)求(RPS),高帶寬以及混合的低延遲/高帶寬的 gRPC 代理。

可以說,進(jìn)行一個(gè)良好的性能測(cè)試非常困難。Nginx 有提供一個(gè)用于性能測(cè)試的準(zhǔn)則,但是這些準(zhǔn)則尚未形成規(guī)范。Envoy 也有提供一個(gè)基準(zhǔn)測(cè)試指南,甚至在 envoy-perf 項(xiàng)目下也提供了一些工具,但遺憾的是后者似乎不怎么維護(hù)了。

我們轉(zhuǎn)而使用內(nèi)部的測(cè)試工具。之所以稱其為“綠巨人(hulk)”,是因?yàn)樗诜鬯槲覀兊姆?wù)方面享有盛譽(yù)。

這么說吧,我們發(fā)現(xiàn)測(cè)試結(jié)果里有幾處顯著的差異:

  • Nginx 表現(xiàn)出更高的長(zhǎng)尾延遲。這主要是在遭遇繁重的 I/O 時(shí)事件循環(huán)停滯所致,尤其是與 SO_REUSEPORT 一起使用的情況,因?yàn)樵谶@種情況下,它可以代表當(dāng)前被阻塞的 Worker 程序繼續(xù)接受連接請(qǐng)求;
  • 在沒有開啟統(tǒng)計(jì)數(shù)據(jù)收集的情況下,Nginx 的功能和 Envoy 相當(dāng),但是我們的 Lua 統(tǒng)計(jì)數(shù)據(jù)收集模塊在高 RPS 測(cè)試中拖慢了 Nginx 3 倍??紤]到我們對(duì) lua_shared_dict 的依賴,它在 Nginx 的各個(gè) worker 之間是以一個(gè)互斥鎖進(jìn)行同步,性能方面的損失也是意料之中的情況。

我們也知道統(tǒng)計(jì)數(shù)據(jù)收集模塊效率很低下。我們有考慮過在用戶空間里實(shí)現(xiàn)一個(gè)類似于 FreeBSD 的 counter(9) 的功能:綁定 CPU 核心,為每個(gè) worker 分配一個(gè)無鎖的計(jì)數(shù)器以及一個(gè)取值的例程,該例程會(huì)循環(huán)遍歷所有 worker 并聚合匯總他們各自的統(tǒng)計(jì)信息。但是我們最終放棄了這個(gè)想法,因?yàn)槿绻覀兿胍O(jiān)控 Nginx 內(nèi)部狀態(tài)(比如所有錯(cuò)誤情況),那就意味著我們要維護(hù)一個(gè)巨大的補(bǔ)丁程序,這將使得后續(xù)升級(jí)變成一個(gè)真正的地獄。

由于 Envoy 不會(huì)受到這兩個(gè)問題的困擾,因此在遷移到 Envoy 之后,我們可以釋放多達(dá) 60% 的服務(wù)器資源(之前被 Nginx 獨(dú)占)。

可監(jiān)察性

可監(jiān)察性是任何產(chǎn)品的最基本的運(yùn)維需求,尤其是對(duì)于像代理這樣的基礎(chǔ)設(shè)施而言。在遷移期間,這一點(diǎn)尤為關(guān)鍵,這樣一來任何問題都可以被監(jiān)控系統(tǒng)檢測(cè)到,而不用等到沮喪的用戶報(bào)障時(shí)才發(fā)現(xiàn)。

非商業(yè)版本的 Nginx 自帶一個(gè)“stub status”模塊,提供了 7 個(gè)統(tǒng)計(jì)信息: 

  1. Active connections: 291  
  2. server accepts handled requests 
  3. 16630948 16630948 31070465  
  4. Reading: 6 Writing: 179 Waiting: 106 

這當(dāng)然是不夠的,因此我們添加了一個(gè)簡(jiǎn)單的 log_by_lua 處理程序,該處理程序根據(jù) Lua 中可用的 HTTP header 和變量添加每個(gè)請(qǐng)求的統(tǒng)計(jì)信息:狀態(tài)代碼,大小,緩存命中率等。以下是一個(gè)簡(jiǎn)單的吐出統(tǒng)計(jì)數(shù)據(jù)的函數(shù)的例子: 

  1. function _M.cache_hit_stats(stat) 
  2. if _var.upstream_cache_status then 
  3.     if _var.upstream_cache_status == "HIT" then 
  4.         stat:add("upstream_cache_hit"
  5.     else 
  6.         stat:add("upstream_cache_miss"
  7.     end 
  8. end 
  9. end 

除了每個(gè)請(qǐng)求的 Lua 統(tǒng)計(jì)信息外,我們還有一個(gè)非常脆弱的 error.log 解析器,它負(fù)責(zé) upstream,http,Lua 和 TLS 的錯(cuò)誤分類。

在這些之上,我們有一個(gè)單獨(dú)的 exporter ,用于收集 Nginx 的內(nèi)部狀態(tài):自上次 reload 的時(shí)間,worker 數(shù)量,RSS/VMS 大小,TLS 證書使用期限等。

典型的 Envoy 配置為我們提供了數(shù)千種不同的指標(biāo)(采用 Prometheus 格式),用于描述代理流量和服務(wù)器的內(nèi)部狀態(tài): 

  1. $ curl -s http://localhost:3990/stats/prometheus | wc -l  
  2. 14819 

這里面囊括了五花八門的來自不同地方匯總的大量統(tǒng)計(jì)信息:

  • 每個(gè)集群/每個(gè) upstream /每個(gè) vhost 的 HTTP 統(tǒng)計(jì)信息,包括連接池信息和各種時(shí)序直方圖;
  • 每個(gè)監(jiān)聽器的 TCP/HTTP/TLS 下游的連接統(tǒng)計(jì)信息;
  • 從基本版本信息和正常運(yùn)行時(shí)間到內(nèi)存分配統(tǒng)計(jì)以及棄用功能使用情況計(jì)數(shù),各種內(nèi)部/運(yùn)行時(shí)狀態(tài)的統(tǒng)計(jì)信息。

Envoy 的管理接口真心贊。它不僅通過 /certs,/clusters 和 /config_dump 端點(diǎn)提供額外的結(jié)構(gòu)化的統(tǒng)計(jì)信息,而且還提供了非常重要的運(yùn)維功能:

  • 通過 /logging 提供即時(shí)更改錯(cuò)誤日志配置的能力。這讓我們能夠在幾分鐘內(nèi)排查相當(dāng)模糊的問題;
  • /cpuprofiler,/heapprofiler,/contention 在不可避免的性能排障期間肯定會(huì)有用武之地;
  • /runtime_modify 端點(diǎn)允許我們更改配置參數(shù)集而不用推送新的配置,而這些配置可用于功能開關(guān)等。

除了統(tǒng)計(jì)數(shù)據(jù)外,Envoy 還支持可插拔的 tracing 實(shí)現(xiàn)。這不僅對(duì)擁有多個(gè)負(fù)載均衡層的接入層團(tuán)隊(duì)有用,對(duì)于希望從邊緣到應(yīng)用服務(wù)器端到端跟蹤請(qǐng)求延遲的應(yīng)用程序開發(fā)人員也很有用。

從技術(shù)上講,Nginx 也支持通過第三方的 OpenTracing 集成進(jìn)行跟蹤,但是該功能開發(fā)尚不成熟。

最后,同等重要的是,Envoy 能夠通過 gRPC 流式傳輸 access 日志。這減輕了我們接入層團(tuán)隊(duì)不得不支持打通 syslog 到 hive 的負(fù)擔(dān)。此外,在 Dropbox 生產(chǎn)中啟動(dòng)通用 gRPC 服務(wù)比添加自定義的 TCP/UDP 監(jiān)聽器更容易(也更安全!)。

和其他所有操作類似,Envoy 里面 access 日志的配置是通過 gRPC 管理服務(wù),即訪問日志服務(wù)(ALS)設(shè)置。管理服務(wù)是將 Envoy 數(shù)據(jù)平面與生產(chǎn)中的各種服務(wù)集成的標(biāo)準(zhǔn)方式。這也是我們下一節(jié)要介紹的內(nèi)容。

集成

Nginx 提供的集成方案,一個(gè)最佳描述便是 “Unix-ish”。配置是非常靜態(tài)的。而且它嚴(yán)重依賴于文件(例如配置文件本身,TLS 證書和 Ticket 、白名單/黑名單等)以及一些知名的行業(yè)協(xié)議(記錄日志到 syslog 以及通過 HTTP 發(fā)送認(rèn)證子請(qǐng)求)。對(duì)于小型部署而言,這樣做的簡(jiǎn)單性和向后兼容性是一件好事,因?yàn)槲覀兛梢酝ㄟ^編寫幾個(gè) Shell 腳本輕松實(shí)現(xiàn) Nginx 的自動(dòng)化。但是隨著系統(tǒng)規(guī)模的擴(kuò)大,可測(cè)試性和標(biāo)準(zhǔn)化變得更加重要。

Envoy 對(duì)于是否應(yīng)該將接入層數(shù)據(jù)平面和它的控制平面,以及因此也即是與其他基礎(chǔ)設(shè)施集成在一起,持更加堅(jiān)定的態(tài)度。它通過提供一個(gè)穩(wěn)定的 API(通常稱為 xDS ),鼓勵(lì)用戶使用 protobuf 和gRPC。Envoy 通過查詢一個(gè)或多個(gè)這樣的 xDS 服務(wù)來發(fā)現(xiàn)其動(dòng)態(tài)資源。

如今,xDS API 的發(fā)展已然超越 Envoy:通用數(shù)據(jù)平面 API(UDPA)的宏偉目標(biāo)是“成為事實(shí)上的 L4/L7 負(fù)載均衡器標(biāo)準(zhǔn)”。

根據(jù)我們的經(jīng)驗(yàn),這一雄心壯志是靠譜的。我們已經(jīng)在內(nèi)部負(fù)載測(cè)試中使用了開放請(qǐng)求成本匯總(ORCA),并且正在考慮將 UDPA 用于非 Envoy 的負(fù)載均衡,例如我們基于 Katran 的 eBPF/XDP 4層負(fù)載均衡代理。

這對(duì)于 Dropbox 尤其適合,Dropbox 的所有服務(wù)都已經(jīng)實(shí)現(xiàn)了在內(nèi)部通過基于 gRPC 的 API 進(jìn)行交互。我們已經(jīng)實(shí)現(xiàn)了自主版本的 xDS 控制平面,它將 Envoy 同我們的配置管理、服務(wù)發(fā)現(xiàn)、私密信息管理以及路由信息集成在一起。

以下是一些可用的 xDS 服務(wù),它們的 Nginx 替代品以及我們?nèi)绾问褂盟鼈兊囊恍┦纠?/p>

  • 如上所述,訪問日志服務(wù)(ALS)讓我們可以動(dòng)態(tài)地配置訪問日志目標(biāo),編碼和格式。試想一下 Nginx 的 log_format 和 access_log 的動(dòng)態(tài)版本;
  • 端點(diǎn)發(fā)現(xiàn)服務(wù)(EDS)提供相關(guān)集群成員的信息。這類似于 Nginx 配置中動(dòng)態(tài)更新 upstream 里的 server 條目列表(例如,對(duì)于 Lua 來說便是 balancer_by_lua_block)。在這里,我們將其代理到內(nèi)部的服務(wù)發(fā)現(xiàn);
  • 私密信息發(fā)現(xiàn)服務(wù)(SDS)提供了各種與 TLS 相關(guān)的信息,這些信息將涵蓋各種 ssl_ * 指令(以及相應(yīng)的ssl_*_by_lua_block)。我們將這個(gè)接口調(diào)整為適用于我們場(chǎng)景的私密信息分發(fā)服務(wù);
  • 運(yùn)行時(shí)發(fā)現(xiàn)服務(wù)(RTDS)提供了運(yùn)行時(shí)標(biāo)志。我們?cè)?Nginx 中實(shí)現(xiàn)這一功能的方式很不可靠,它是通過在 Lua 里檢查各種文件是否存在來實(shí)現(xiàn)的。這種方法很快會(huì)在各個(gè)服務(wù)器之間變得不一致。 Envoy 的默認(rèn)實(shí)現(xiàn)也是基于文件系統(tǒng)的,但是我們將 RTDS xDS API 指向了我們的分布式配置存儲(chǔ)。這樣一來,我們便可以一次控制整個(gè)集群(通過帶有類似 sysctl 界面的工具),并且在不同服務(wù)器之間不會(huì)出現(xiàn)意外不一致的情況;
  • 路由發(fā)現(xiàn)服務(wù)(RDS)將路由映射到虛擬主機(jī),并允許對(duì) HTTP 頭部和過濾器進(jìn)行額外的配置。用 Nginx 術(shù)語來說,它們類似于帶有 set_header/proxy_set_header 和 proxy_pass 的動(dòng)態(tài) location 配置。在更底下的代理層,我們直接在我們的服務(wù)定義配置中自動(dòng)生成這些配置;

關(guān)于 Envoy 如何與現(xiàn)有生產(chǎn)系統(tǒng)集成的示例,這里有一個(gè)將 Envoy 與自定義服務(wù)發(fā)現(xiàn)集成的一個(gè)經(jīng)典例子。還有一些開源的 Envoy 控制面板實(shí)現(xiàn),例如 Istio 和更少?gòu)?fù)雜度的 go-control-plane。

我們自己的 Envoy 控制平面實(shí)現(xiàn)了越來越多的 xDS API。它在生產(chǎn)中以普通的 gRPC 服務(wù)的形式部署,并充當(dāng)我們基礎(chǔ)設(shè)施構(gòu)建塊的一個(gè)適配器。它通過一組通用的 Golang 庫(kù)來實(shí)現(xiàn)與內(nèi)部服務(wù)進(jìn)行對(duì)話,并通過穩(wěn)定的 xDS API 向 Envoy 公開它們。整個(gè)過程不涉及任何文件系統(tǒng)調(diào)用,信號(hào)量,cron,logrotate,syslog,日志解析器等。

配置

Nginx 在配置方面具有簡(jiǎn)單易讀這樣一項(xiàng)不可否認(rèn)的優(yōu)勢(shì)。但是,隨著配置變得越來越復(fù)雜,并且配置開始變成是代碼生成式的,這項(xiàng)優(yōu)勢(shì)就沒了。

正如上面所提到的,我們的 Nginx 配置是通過 Python2,Jinja2 和 YAML 的混合生成的。 你們中的一些人可能已經(jīng)在 erb,pug,Text::Template 甚至 m4 里面看到或甚至編寫了這樣類似的變體: 

  1. {% for server in servers %} 
  2. server { 
  3. {% for error_page in server.error_pages %} 
  4. error_page {{ error_page.statuses|join(' ') }} {{ error_page.file }}; 
  5. {% endfor %} 
  6. ... 
  7. {% for route in service.routes %} 
  8. {% if route.regex or route.prefix or route.exact_path %} 
  9. location {% if route.regex %}~ {{route.regex}}{% 
  10.         elif route.exact_path %}= {{ route.exact_path }}{% 
  11.         else %}{{ route.prefix }}{% endif %} { 
  12.     {% if route.brotli_level %} 
  13.     brotli on
  14.     brotli_comp_level {{ route.brotli_level }}; 
  15.     {% endif %} 
  16.     ... 

我們的 Nginx 配置的生成方式有一個(gè)大問題:配置生成中涉及的所有語言都允許代入 和/或 邏輯。YAML 有 anchor,Jinja2 有 loop/ifs/macroses,然后,Python 當(dāng)然是圖靈完備的。如果沒有一個(gè)干凈的數(shù)據(jù)模型,復(fù)雜性會(huì)迅速擴(kuò)散到這三者。

這個(gè)問題自然是可以解決的,但是有兩個(gè)基本問題:

  • 這里沒有關(guān)于配置格式的聲明性描述。如果我們想通過編程的方式生成和驗(yàn)證配置,那么需要自己重新造輪子;
  • 語法上有效的配置,從 C 代碼的角度來看仍然可能是無效的。例如,某些與緩沖區(qū)相關(guān)的變量具有值限制,對(duì)齊限制以及與其他變量的相互依賴性。為了從語義上驗(yàn)證配置,我們需要運(yùn)行 nginx -t 來校驗(yàn)。

另一方面,Envoy 自帶一套用于配置的統(tǒng)一數(shù)據(jù)模型:它的所有配置都放到 protobuffer 中定義。這不僅解決了數(shù)據(jù)建模問題,而且還將輸入的信息添加到了配置值里。鑒于 protobuf 是Dropbox 生產(chǎn)環(huán)境里的頭等公民,并且是描述/配置服務(wù)的通用方式,因此,集成就變得更加簡(jiǎn)單了。

我們針對(duì) Envoy 設(shè)計(jì)的新的配置生成器是基于 protobuf 和 Python3 實(shí)現(xiàn)的。所有數(shù)據(jù)建模均在原始文件中完成,而所有邏輯均在 Python 中執(zhí)行。下面是一個(gè)例子: 

  1. from dropbox.proto.envoy.extensions.filters.http.gzip.v3.gzip_pb2 import Gzip 
  2. from dropbox.proto.envoy.extensions.filters.http.compressor.v3.compressor_pb2 import Compressor 
  3.  
  4. def default_gzip_config( 
  5. compression_level: Gzip.CompressionLevel.Enum = Gzip.CompressionLevel.DEFAULT
  6. ) -> Gzip: 
  7.     return Gzip( 
  8.         # Envoy's default is 6 (Z_DEFAULT_COMPRESSION). 
  9.         compression_level=compression_level, 
  10.         # Envoy's default is 4k (12 bits). Nginx uses 32k (MAX_WBITS, 15 bits). 
  11.         window_bits=UInt32Value(value=12), 
  12.         # Envoy's default is 5. Nginx uses 8 (MAX_MEM_LEVEL - 1). 
  13.         memory_level=UInt32Value(value=5), 
  14.         compressor=Compressor( 
  15.             content_length=UInt32Value(value=1024), 
  16.             remove_accept_encoding_header=True
  17.             content_type=default_compressible_mime_types(), 
  18.         ), 
  19.     ) 

注意上述代碼里的 Python3 類型注解!結(jié)合 mypy-protobuf protoc 插件,它們可以在 config 生成器里提供端到端的輸入。如果你用的 IDE 支持自動(dòng)檢查的話,它將會(huì)立即高亮顯示輸入信息不匹配。

在某些情況下,經(jīng)過類型檢查的 protobuf 在邏輯上仍然可能是無效的。在上面的示例中,gzip window_bits 只能取 9 到 15 之間的值。這類限制可以在 protoc-gen-validate protoc 插件的幫助下輕松完成定義:

  1. google.protobuf.UInt32Value window_bits = 9 [(validate.rules).uint32 = {lte: 15 gte: 9}]; 

最后,使用官方定義的配置模型的一個(gè)潛在好處是,它有機(jī)地引領(lǐng)了文檔與配置定義并排配置。 以下是一個(gè) gzip.proto 的示例: 

  1. // Value from 1 to 9 that controls the amount of internal memory used by zlib. Higher values.            
  2. // use more memory, but are faster and produce better compression results. The default value is 5.             
  3. google.protobuf.UInt32Value memory_level = 1 [(validate.rules).uint32 = {lte: 9 gte: 1 

可擴(kuò)展性

要想讓 Nginx 提供超出標(biāo)準(zhǔn)配置所提供的功能范疇之外的特性的話,通常需要編寫一個(gè) C 模塊。 Nginx 的開發(fā)指南對(duì)于可用的構(gòu)建塊提供了詳盡的介紹。這也就是說,這種方式是相對(duì)重量級(jí)的。實(shí)際上,要想安全地編寫一個(gè) Nginx 模塊的話,需要一位相當(dāng)資深的軟件工程師。

關(guān)于可供模塊開發(fā)人員選用的基礎(chǔ)設(shè)施的話,他們可以期待一些像哈希表、隊(duì)列、rb樹這樣的基礎(chǔ)容器,(非RAII)內(nèi)存管理、以及可用于所有請(qǐng)求處理階段的鉤子。另外,還有一些外部庫(kù),比如 pcre、zlib、openssl,當(dāng)然,還有l(wèi)ibc。

為了提供更輕量級(jí)的功能擴(kuò)展,Nginx 提供了 Perl 和 JavaScript 的接口。可悲的是,它們所提供的功能都相當(dāng)有限,絕大部分都局限于請(qǐng)求處理的內(nèi)容階段。

社區(qū)采納的最常用的擴(kuò)展方式是基于第三方的 lua-nginx 模塊和各種 Openresty 類庫(kù)。這一方案幾乎可以外掛到請(qǐng)求處理的任意階段。我們使用 log_by_lua 進(jìn)行統(tǒng)計(jì)信息的收集,然后使用 balancer_by_lua 進(jìn)行動(dòng)態(tài)地后端再配置。

從理論上講,Nginx 提供了使用 C++ 開發(fā)模塊的能力。然而實(shí)際上,對(duì)于所有原語,它都缺少適當(dāng)?shù)?C++ 接口/包裝器,這樣就顯得不太值得了。盡管如此,仍有一些社區(qū)對(duì)此進(jìn)行嘗試。這些還遠(yuǎn)遠(yuǎn)沒有達(dá)到生產(chǎn)就緒。

Envoy 的主要擴(kuò)展機(jī)制是通過 C++ 插件。這個(gè)流程在文檔方面不如 Nginx,但是它更為簡(jiǎn)單。部分原因是:

  • 整潔且擁有良好注釋的界面。C++ 類充當(dāng)自然的擴(kuò)展和文檔點(diǎn)。比如,看看 HTTP 過濾接口。
  • C++14 語言和標(biāo)準(zhǔn)庫(kù)。從 template 和 lambda 函數(shù)等這些基本的語言功能,到類型安全的容器和算法。通常,編寫現(xiàn)代 C++14 與使用 Golang 并沒有多大區(qū)別,或者有些人甚至?xí)f Python。
  • C++14 及其標(biāo)準(zhǔn)庫(kù)以外的功能。由 abseil 庫(kù)提供的,其中包括來自較新版本的 C++ 標(biāo)準(zhǔn)的直接替換,內(nèi)置了靜態(tài)的死鎖檢測(cè)和 debug 支持的互斥鎖,以及額外的/更有效的容器,等等。

具體地,這里有一份 HTTP 過濾模塊的經(jīng)典例子。

通過簡(jiǎn)單地實(shí)現(xiàn) Envoy stats 接口,我們僅用200行代碼就可以將 Envoy 與 Vortex2(我們的監(jiān)視框架)集成在一起。

Envoy 還通過 moonjit 添加了對(duì) Lua 的支持,moonjit 是一個(gè)對(duì) Lua 5.2 做了諸多改進(jìn)支持的 LuaJIT fork。與 Nginx 的第三方 Lua 集成相比,它所提供的功能和開放的鉤子要少得多。由于在開發(fā)、測(cè)試和解釋后代碼的排障等諸多方面存在的額外復(fù)雜性成本,這使得 Lua 對(duì) Envoy 的吸引力大大降低。專門從事 Lua 開發(fā)的公司可能會(huì)不同意這一觀點(diǎn),但是在我們的案例中,我們決定避免使用它,而只是將 C++ 用于 Envoy 的可​​擴(kuò)展性。

Envoy 與其他 Web 服務(wù)器的區(qū)別就在于它提供了對(duì) WebAssembly(WASM)的新興支持 —— 一種快速的,可移植且安全的擴(kuò)展機(jī)制。WASM 不能直接使用,但是可以作為任何通用編程語言的編譯目標(biāo)。Envoy 實(shí)現(xiàn)了一個(gè)適用于代理的 WebAssembly 規(guī)范(還包括相關(guān)的 Rust 和 C++ SDK),該規(guī)范描述了 WASM 代碼和通用的 L4/L7 代理之間的邊界。代理和擴(kuò)展之間代碼的分隔提供了一個(gè)安全的沙箱,而 WASM 低級(jí)凝練的二進(jìn)制格式又為之提供了接近原生的效率。在此之上,在 Envoy 里,proxy-wasm 擴(kuò)展是和 xDS 集成在一起的。這樣便可以進(jìn)行動(dòng)態(tài)地更新,甚至可以進(jìn)行潛在的 A/B 測(cè)試。

在 KubeCon’s 19 上,《通過 WebAssembly 擴(kuò)展 Envoy》這個(gè)演講很好地概述了 Envoy 里集成的 WASM 及其潛在用途。它還暗示已經(jīng)達(dá)到了原生 C++ 代碼性能水平的 60-70%。

使用 WASM,服務(wù)提供方可以安全有效地在其邊緣環(huán)境運(yùn)行客戶的代碼??蛻臬@益的則是可移植性:他們的擴(kuò)展可以在實(shí)現(xiàn) proxy-wasm ABI 的任何云上運(yùn)行。此外,它允許用戶使用任意一種語言,只要它可以編譯為 WebAssembly。這使得他們能夠安全有效地使用更廣泛的非 C++ 類庫(kù)。

Istio 正在向 WebAssembly 的開發(fā)傾注大量資源:他們已經(jīng)有了基于 WASM 的遙測(cè)擴(kuò)展的實(shí)驗(yàn)版本以及用于共享擴(kuò)展的 WebAssemblyHub 社區(qū)。

當(dāng)前,Dropbox 并未用到 WebAssembly。但是,等到 proxy-wasm 的 GO SDK 可用時(shí),這一情況也許會(huì)發(fā)生變化。

構(gòu)建和測(cè)試

默認(rèn)情況下,Nginx 使用的是一套自定義的基于 shell 的配置系統(tǒng)和基于 make 的構(gòu)建系統(tǒng)構(gòu)建的。這是簡(jiǎn)單而優(yōu)雅的,但是將其集成到 Bazel 構(gòu)建的 monorepo 的話需要花費(fèi)大量的精力才能獲得增量、分布式、封閉和可重現(xiàn)構(gòu)建這些所有優(yōu)點(diǎn)。

Google 開源了他們用 Bazel 構(gòu)建的 Nginx 版本,該版本由 Nginx,BoringSSL,PCRE,ZLIB 和 Brotli 庫(kù)/模塊組成。

在測(cè)試方面,Nginx 在一個(gè)單獨(dú)的倉(cāng)庫(kù)里有一組 Perl 驅(qū)動(dòng)的集成測(cè)試,沒有任何單元測(cè)試。

鑒于我們對(duì) Lua 的大量使用以及缺乏內(nèi)置的單元測(cè)試框架,我們求助于使用模擬配置和基于 Python 的簡(jiǎn)單測(cè)試驅(qū)動(dòng)程序進(jìn)行測(cè)試: 

  1. class ProtocolCountersTest(NginxTestCase): 
  2. @classmethod 
  3. def setUpClass(cls): 
  4.     super(ProtocolCountersTest, cls).setUpClass() 
  5.     cls.nginx_a = cls.add_nginx( 
  6.         nginx_CONFIG_PATH, endpoint=["in"], upstream=["out"], 
  7.     ) 
  8.     cls.start_nginxes() 
  9.  
  10. @assert_delta(lambda d: d == 0, get_stat("request_protocol_http2")) 
  11. @assert_delta(lambda d: d == 1, get_stat("request_protocol_http1")) 
  12. def test_http(self): 
  13.     r = requests.get(self.nginx_a.endpoint["in"].url("/")) 
  14.     assert r.status_code == requests.codes.ok 

最重要的是,我們通過預(yù)處理所有生成的配置(例如用 127/8 替換所有 IP 地址,切換到自簽名 TLS 證書等)并在結(jié)果上運(yùn)行 nginx -c 來驗(yàn)證所有語法的語法正確性。

在 Envoy 方面,其主流的構(gòu)建系統(tǒng)已經(jīng)是 Bazel。因此,將它和我們的 monorepo 集成起來并不繁瑣:Bazel 支持輕松添加外部依賴項(xiàng)。

我們還使用 copybara 腳本來同步 Envoy 和 udpa 的 protobuf。當(dāng)你需要進(jìn)行簡(jiǎn)單的轉(zhuǎn)換而無需永遠(yuǎn)維護(hù)大型補(bǔ)丁集時(shí),Copybara 十分方便。

通過 Envoy,我們可以靈活地選擇使用一組預(yù)先編寫好的 mock 來進(jìn)行單元測(cè)試(基于 gtest/gmock ),也可以選擇使用 Envoy 的集成測(cè)試框架,或者同時(shí)使用兩者。至此,針對(duì)每一項(xiàng)細(xì)小的改動(dòng),我們不再需要依靠緩慢的端到端集成測(cè)試了。

gtest 是 Chromium 和 LLVM 等項(xiàng)目所采用的一套相當(dāng)有名的單元測(cè)試框架。如果你想進(jìn)一步了解 googletest,這里有兩份不錯(cuò)的介紹資料:googletest 和 googlemock。

開源的 Envoy 開發(fā)要求每次更改達(dá)到 100% 的單元測(cè)試覆蓋率。它會(huì)通過 Azure CI 流水線針對(duì)每個(gè) PR 自動(dòng)觸發(fā)測(cè)試。

使用 google/becnhmark 對(duì)性能敏感的代碼進(jìn)行微基準(zhǔn)測(cè)試也是一種常見做法: 

  1. $ bazel run --compilation_mode=opt test/common/upstream:load_balancer_benchmark -- --benchmark_filter=".*LeastRequestLoadBalancerChooseHost.*" 
  2. BM_LeastRequestLoadBalancerChooseHost/100/1/1000000          848 ms          449 ms            2 mean_hits=10k relative_stddev_hits=0.0102051 stddev_hits=102.051 
  3. ... 

改用 Envoy 之后,我們開始完全依賴單元測(cè)試來進(jìn)行內(nèi)部模塊開發(fā): 

  1. TEST_F(CourierClientIdFilterTest, IdentityParsing) { 
  2. struct TestCase { 
  3. std::vector<std::string> uris; 
  4. Identity expected; 
  5. }; 
  6. std::vector<TestCase> tests = { 
  7. {{"spiffe://prod.dropbox.com/service/foo"}, {"spiffe://prod.dropbox.com/service/foo""foo"}}, 
  8. {{"spiffe://prod.dropbox.com/user/boo"}, {"spiffe://prod.dropbox.com/user/boo""user.boo"}}, 
  9. {{"spiffe://prod.dropbox.com/host/strange"}, {"spiffe://prod.dropbox.com/host/strange""host.strange"}}, 
  10. {{"spiffe://corp.dropbox.com/user/bad-prefix"}, {""""}}, 
  11. }; 
  12. for (auto& test : tests) { 
  13. EXPECT_CALL(*ssl_, uriSanPeerCertificate()).WillOnce(testing::Return(test.uris)); 
  14. EXPECT_EQ(GetIdentity(ssl_), test.expected); 
  15. }  

實(shí)施亞秒級(jí)的往返測(cè)試在生產(chǎn)力方面會(huì)帶來復(fù)合效果。它讓我們能夠把更多精力放在增加測(cè)試覆蓋范圍。由于可以在單元測(cè)試和集成測(cè)試之間進(jìn)行自由選擇,這使得我們能夠在 Envoy 測(cè)試的覆蓋范圍、速度和成本之間獲得一個(gè)平衡。

學(xué)習(xí)上手 Bazel 對(duì)我們開發(fā)人員來說是一次絕佳的經(jīng)歷。它的學(xué)習(xí)曲線非常陡峭,而且前期需要付出大量的投資,但是它卻有很高的回報(bào):增量式構(gòu)建,遠(yuǎn)程緩存,分布式構(gòu)建/測(cè)試等。

Bazel 很少討論的好處之一是,它讓我們能夠查詢甚至擴(kuò)展依賴圖譜。依賴關(guān)系圖的編程接口,以及跨語言的通用構(gòu)建系統(tǒng),這是一項(xiàng)非常強(qiáng)大的功能。它可以用作代碼提示器,代碼生成,漏洞跟蹤以及部署系統(tǒng)等這類服務(wù)的基礎(chǔ)構(gòu)建塊。

安全性

Nginx 的代碼面非常小,它的外部依賴很少。通常,對(duì)生成的二進(jìn)制文件來說只能看到 3 個(gè)外部依賴項(xiàng):zlib(或者更快的變體之一),一個(gè) TLS 庫(kù)以及 PCRE。Nginx 自研實(shí)現(xiàn)了所有相關(guān)的協(xié)議解析器、事件庫(kù),甚至還重新實(shí)現(xiàn)了某些 libc 函數(shù)。

在某些情況下,Nginx 被認(rèn)為非常安全,以至于它被用作 OpenBSD 里默認(rèn)的 Web 服務(wù)器。后來,兩個(gè)開發(fā)社區(qū)發(fā)生了沖突,也因此有了 httpd。您可以在 BSDCon《介紹OpenBSD的新 httpd 》中了解此舉背后的動(dòng)機(jī)。

這種極簡(jiǎn)主義在實(shí)踐中得到了回報(bào)。Nginx 在11年多的時(shí)間里僅報(bào)告了30個(gè)安全漏洞。

另一方面,Envoy 的代碼量更大,尤其是考慮到 C++ 代碼比用于 Nginx 的基本 C 代碼更加密集時(shí),這一點(diǎn)更為明顯。它還包含來自外部依賴項(xiàng)的數(shù)百萬行代碼。從事件通知到協(xié)議解析器的所有內(nèi)容都推給了第三方庫(kù)。這會(huì)增加攻擊面并造成最終產(chǎn)出的可執(zhí)行文件愈加膨脹。

為了解決這個(gè)問題,Envoy 高度依賴現(xiàn)代安全實(shí)踐。它使用 AddressSanitizer,ThreadSanitizer 和 MemorySanitizer。它的開發(fā)人員甚至超綱地采用了模糊測(cè)試。

任何對(duì)于全球IT基礎(chǔ)架構(gòu)至關(guān)重要的開源項(xiàng)目都可以接入到 OSS-Fuzz,這是一個(gè)用于自動(dòng)化模糊測(cè)試的免費(fèi)平臺(tái)。要了解更多信息,請(qǐng)參閱“OSS-Fuzz/架構(gòu)”。

實(shí)際上,盡管如此,所有這些預(yù)防措施都不能完全抵消增加的代碼所留下的痕跡。結(jié)果便是,在過去的兩年里,Envoy 已經(jīng)發(fā)布了 22 條安全公告。

Envoy 的《安全發(fā)布策略》一文對(duì)此進(jìn)行了詳細(xì)描述,針對(duì)某些指定的漏洞還有詳細(xì)的檢查報(bào)告。Envoy 還是 Google 漏洞獎(jiǎng)勵(lì)計(jì)劃(VRP)的成員之一。VRP 向所有安全研究人員開放,根據(jù)它們?cè)O(shè)計(jì)的規(guī)則,對(duì)發(fā)現(xiàn)和報(bào)告的漏洞提供獎(jiǎng)勵(lì)。

為了應(yīng)對(duì)不斷增長(zhǎng)的漏洞風(fēng)險(xiǎn),我們使用了來自上游發(fā)行版供應(yīng)商 Ubuntu 和 Debian 的最佳可執(zhí)行文件來強(qiáng)化安全措施。我們?yōu)樗羞吘壄h(huán)境曝光的可執(zhí)行文件定義了特殊加固后的構(gòu)建配置文件。它包括 ASLR,堆棧保護(hù)器以及符號(hào)表的加固: 

  1. build:hardened --force_pic 
  2. build:hardened --copt=-fstack-clash-protection 
  3. build:hardened --copt=-fstack-protector-strong 
  4. build:hardened --linkopt=-Wl,-z,relro,-z,now 

在絕大多數(shù)環(huán)境里,fork 的 Web 服務(wù)器(如 Nginx)在堆棧保護(hù)器方面會(huì)存在問題。由于主進(jìn)程和工作進(jìn)程共享同一套堆棧來進(jìn)行灰度,而在灰度驗(yàn)證失敗時(shí),工作進(jìn)程會(huì)被殺死,因此,我們可以通過大約 1000 次嘗試逐位對(duì)灰度的實(shí)例進(jìn)行暴力破解。使用線程作為并發(fā)原語的 Envoy 不受此攻擊的影響。

我們還希望盡可能的給第三方依賴的安全性加固。我們?cè)?FIPS 模式下使用 BoringSSL,該模式包括啟動(dòng)自檢和可執(zhí)行文件的完整性檢查。我們還考慮在某些邊緣環(huán)境的灰度服務(wù)器上運(yùn)行啟用 ASAN 的可執(zhí)行文件。

功能性

這是帖子中最主觀的部分,請(qǐng)做好準(zhǔn)備。

Nginx 最初是設(shè)計(jì)成一個(gè) Web 服務(wù)器,致力于以最少的資源消耗來提供靜態(tài)文件服務(wù)。它的功能性在這里是最重要的:靜態(tài)文件服務(wù),緩存(包括驚群保護(hù))以及 range 緩存。

但是,作為代理的話,Nginx 缺乏現(xiàn)代基礎(chǔ)架構(gòu)所需的功能。它的后端沒有 HTTP/2。盡管可以代理 gRPC 服務(wù),但是它不支持連接多路復(fù)用。而且不支持 gRPC 轉(zhuǎn)碼。最重要的是,Nginx的“核心開放(open-core)”模型限制了可以納入開源版本代理服務(wù)的功能集。結(jié)果便是,某些重要功能(如統(tǒng)計(jì)信息)在它的“社區(qū)”版本里是不可用的。

相比之下,Envoy 已經(jīng)演變?yōu)橐粋€(gè) ingress/egress 代理,經(jīng)常用于以 gRPC 為主要負(fù)載的環(huán)境。盡管它的 Web 服務(wù)功能還是相當(dāng)初級(jí)的:沒有文件服務(wù),而且緩存功能仍然在開發(fā)中,brotli或者預(yù)壓縮這些功能也都不支持。針對(duì)這些場(chǎng)景,我們?nèi)匀惶峁┝艘粋€(gè)小的 fallback Nginx 實(shí)例,Envoy 會(huì)把它當(dāng)成上游集群。

等到 Envoy 的 HTTP 緩存功能生產(chǎn)就緒時(shí),我們便可以將大多數(shù)靜態(tài)服務(wù)用例遷移過去,使用 S3 取代文件系統(tǒng)作為長(zhǎng)期存儲(chǔ)。

Envoy 還提供了許多 gRPC 相關(guān)能力的原生支持:

  • gRPC 代理。這是一項(xiàng)基本功能,允許我們?cè)趹?yīng)用程序里端到端地使用 gRPC(比如 Dropbox 桌面客戶端)
  • HTTP/2 到后端。這項(xiàng)功能使得我們可以大大減少流量層之間的 TCP 連接數(shù),從而減少內(nèi)存消耗和 keepalive 的流量。
  • gRPC -> HTTP 的橋接(+反向代理)這些讓我們能夠使用現(xiàn)代的 gRPC 棧對(duì)外暴露舊版的 HTTP/1 應(yīng)用程序服務(wù)。
  • gRPC-WEB。這項(xiàng)功能讓我們即使在中間層(防火墻,IDS等)尚不支持 HTTP/2 的環(huán)境里也可以端對(duì)端地使用gRPC。
  • gRPC JSON 轉(zhuǎn)碼器。這讓我們能夠?qū)⑺腥胝玖髁?包括 Dropbox 公共 API )從 REST 轉(zhuǎn)到 gRPC。

此外,Envoy 還可以用作一個(gè)出站代理。我們通過它統(tǒng)一了另外幾個(gè)用例場(chǎng)景:

  • Egress 代理:自從 Envoy 添加了對(duì) HTTP CONNECT 方法的支持以后,它便可以用作 Squid 代理的替代產(chǎn)品。我們已經(jīng)開始用 Envoy 替換出站的 Squid 實(shí)例。這樣做不僅極大地提高了可見性,而且還通過統(tǒng)一使用通用的數(shù)據(jù)平面和可觀察性技術(shù)棧省掉了一攬子運(yùn)維方面的糟心事(不再需要為了統(tǒng)計(jì)信息而去解析日志)
  • 第三方軟件服務(wù)發(fā)現(xiàn):我們并沒有使用 Envoy 來構(gòu)建服務(wù)網(wǎng)格,取而代之的是,我們依靠的是軟件里的 Courier gRPC 庫(kù)。但是,當(dāng)遇到需要一次性花費(fèi)最小的精力將開源服務(wù)與我們的服務(wù)發(fā)現(xiàn)連接的場(chǎng)景時(shí),我們實(shí)際還是選擇了 Envoy。比如,Envoy 在我們的分析棧里用作服務(wù)發(fā)現(xiàn)的輔助工具。Hadoop 可以動(dòng)態(tài)地發(fā)現(xiàn)它的 name 及 journal 節(jié)點(diǎn)。 Superset 可以發(fā)現(xiàn)airflow,presto 以及 hive 的后端。Grafana 可以發(fā)現(xiàn)它的 MySQL 數(shù)據(jù)庫(kù)。

社區(qū)

Nginx 的開發(fā)是相當(dāng)中心化的。它的絕大部分開發(fā)活動(dòng)都是核心團(tuán)隊(duì)內(nèi)部進(jìn)行的。nginx-devel 郵件列表上有一些外部活動(dòng),然后官方 Bug 跟蹤上偶爾有一些與開發(fā)相關(guān)的討論。

FreeNode 上有一個(gè) #nginx 頻道。我們可以隨時(shí)加入到里面進(jìn)行更多互動(dòng)性質(zhì)的社區(qū)對(duì)話。

Envoy 的開發(fā)則是開放和去中心化的:通過 GitHub issue/pull request,郵件列表和社區(qū)會(huì)議進(jìn)行協(xié)同。

Slack 上的社區(qū)活動(dòng)也很活躍。你可以在這里收到邀請(qǐng)。

開發(fā)風(fēng)格和工程社區(qū)這些方面很難量化定性,因此,我們不妨一起來看一個(gè)開發(fā) HTTP/3 的特定例子。

F5 最近發(fā)表了 Nginx QUIC 和 HTTP/3 實(shí)現(xiàn)。這塊代碼是干凈的,沒有任何外部依賴。但是開發(fā)過程本身并不透明。在此之前的半年,Cloudflare 提出了自己的 Nginx HTTP/3 實(shí)現(xiàn)。結(jié)果便是,該社區(qū)現(xiàn)在擁有兩套單獨(dú)的用于 Nginx 的 HTTP/3 實(shí)驗(yàn)版本。

再來看看 Envoy ,HTTP/3 的實(shí)現(xiàn)這塊也正在開發(fā)中,它是基于 chromium 的 “quiche”(QUIC,HTTP等)庫(kù)。該項(xiàng)目的進(jìn)展可以通過 這個(gè) GitHub issue 跟蹤。在補(bǔ)丁開發(fā)完成前,設(shè)計(jì)文檔便已經(jīng)對(duì)外公布了。剩下的工作里,想要從社區(qū)參與中獲益的部分會(huì)帶有 “help wanted” 標(biāo)簽。

如你所見,后者顯然更加開放透明,而且非常鼓勵(lì)協(xié)作開發(fā)。對(duì)我們來說,這意味著我們能夠從上游獲得大量的 Envoy 相關(guān)的大大小小的變動(dòng) —— 包括從運(yùn)維改進(jìn)和性能調(diào)優(yōu)到新的 gRPC 轉(zhuǎn)碼功能以及負(fù)載均衡功能的一些變動(dòng)。

遷移后的現(xiàn)狀

我們已經(jīng)讓 Nginx 和 Envoy 并排運(yùn)行了半年多,然后通過 DNS 逐步將流量從一個(gè)切換到另一個(gè)。到目前為止,我們已經(jīng)成功將許多各式各樣的工作負(fù)載遷移到了 Envoy:

  • Ingress 高吞吐量服務(wù)。Dropbox 桌面客戶端的所有文件數(shù)據(jù)都是通過 Envoy 背后端到端 的 gRPC 提供服務(wù)。改用 Envoy 后由于在邊緣環(huán)境的連接復(fù)用效果更好,我們還略微提高了用戶的性能。
  • Ingress 高RPS服務(wù)。這是 Dropbox 桌面客戶端的所有文件元數(shù)據(jù)。同樣地,我們獲得了端到端的 gRPC 帶來的好處,而且去掉了連接池,這意味著我們不受每個(gè)連接一次請(qǐng)求的限制。
  • 通知和遙測(cè)服務(wù)。這里是用來處理所有實(shí)時(shí)通知的服務(wù),因此這些服務(wù)器一般會(huì)有數(shù)百萬個(gè) HTTP 連接(每個(gè)活躍的客戶端會(huì)有一個(gè)連接)。如今我們不再需要使用昂貴的長(zhǎng)輪詢方式來實(shí)現(xiàn)通知服務(wù),取而代之的是,我們可以通過 streaming gRPC 來實(shí)現(xiàn)。
  • 兼有高吞吐量/高RPS的混合服務(wù)。API 流量(元數(shù)據(jù)和數(shù)據(jù)本身)。這使得我們開始考慮一些公共的 gRPC API。我們甚至可以直接在邊緣環(huán)境對(duì)現(xiàn)有的基于 REST 的 API 進(jìn)行轉(zhuǎn)碼。
  • Egress 高吞吐量代理。在我們的使用場(chǎng)景,Dropbox 和 AWS 的通信主要是 S3 這塊。最終我們會(huì)在生產(chǎn)網(wǎng)絡(luò)里下線所有的 Squid 代理,然后只留下一個(gè) L4/L7 的數(shù)據(jù)平面。

遷移的最后一件事便是 www.dropbox.com 自己。在完成遷移后,我們便可以開始停用我們?cè)谶吘壄h(huán)境部署的 Nginx 服務(wù)。一個(gè)時(shí)代即將結(jié)束。

我們遇到的一些問題

當(dāng)然,整個(gè)遷移的過程并非完美無瑕。不過至少?zèng)]有導(dǎo)致任何明顯的中斷。整個(gè)遷移過程中最困難的部分是我們的 API 服務(wù)這塊。大量不同的設(shè)備通過我們的公共 API 和 Dropbox 進(jìn)行通信——從 curl/wget 驅(qū)動(dòng)的 Shell 腳本,以及具有自定義 HTTP/1.0 堆棧的嵌入式設(shè)備,到每個(gè)可能訪問到這里的 HTTP 庫(kù)。 Nginx 是經(jīng)過實(shí)踐檢驗(yàn)的行業(yè)事實(shí)標(biāo)準(zhǔn)??梢岳斫?,大多數(shù)庫(kù)都隱式地依賴于它的某些行為。除了我們的 api 用戶在依賴的 Nginx 行為這塊,在轉(zhuǎn)到 Envoy 后遇到了一些行為不一致的地方以外,我們?cè)谑褂?Envoy 和它提供的類庫(kù)的過程中還發(fā)現(xiàn)了許多 bug。在社區(qū)的幫助下,所有這些問題都很快得到了解決并且反饋到了上游。

下面只是其中一些“異常的”/沒有出現(xiàn)在 RFC 里的行為的摘要:

  • 合并 URL 路徑里的斜杠。URL 規(guī)范化及斜杠合并在 Web 代理里是一項(xiàng)非常常見的功能。Nginx 默認(rèn)啟用了斜杠歸一和斜線合并,但是 Envoy 不支持后者。我們向上游提交了一個(gè)補(bǔ)丁,添加了該項(xiàng)功能,并允許用戶通過merge_slashes 選項(xiàng)選擇性地加入。
  • 虛擬主機(jī)名字里的端口。Nginx 允許接收兩種形式的 Host header:要么是 example.com,又或者是 example.com:port。我們有幾個(gè)曾經(jīng)依賴于這一行為的 API 用戶。首先,我們通過在配置里復(fù)制虛擬主機(jī)(帶和不帶端口)來解決此問題,但是后來我們又在 Envoy 端添加了一個(gè)忽略匹配端口的選項(xiàng):strip_matching_host_port。
  • 傳輸編碼區(qū)分大小寫。出于某種不知名的原因,一小部分子集的 API 客戶端使用了 Transfer-Encoding:Chunked(請(qǐng)注意大寫的“C”)header。這在技術(shù)上是可行的,因?yàn)?RFC7230 聲明了 Transfer-Encoding/TE header 不區(qū)分大小寫。修復(fù)辦法也很簡(jiǎn)單,然后也已經(jīng)提交到了上游的 Envoy 。
  • 一個(gè)請(qǐng)求里同時(shí)帶有 [Content-Length](https://github.com/envoyproxy/envoy/issues/11398) 和 [Transfer-Encoding:chunked](https://github.com/envoyproxy/envoy/issues/11398) 這兩個(gè) header。有些請(qǐng)求在 Nginx 下面是好的,但是遷移到 Envoy 以后就炸了。 RFC7230 有點(diǎn)棘手,通常大家會(huì)覺得 Web 服務(wù)器的確應(yīng)該在處理這些請(qǐng)求時(shí)報(bào)錯(cuò),因?yàn)樗鼈兒芸赡苁?ldquo;走私的(smuggled)”。另一方面,可能又會(huì)有人說,代理應(yīng)該去掉 Content-Length header 然后再轉(zhuǎn)發(fā)該請(qǐng)求。對(duì)此,我們的做法是:我們擴(kuò)展了 http-parse 然后讓使用該庫(kù)的用戶來自主選擇是否要支持這類請(qǐng)求,然后目前我們正在努力為 Envoy 自身引入這項(xiàng)功能的支持。

另外,值得一提的是,我們還遇到了一些常見的配置問題:

  • 熔斷器配置錯(cuò)誤。根據(jù)我們的經(jīng)驗(yàn),如果入站代理采用 Envoy 的話,尤其是工作在 HTTP/1 以及 HTTP/2 這樣的混合環(huán)境里,那么熔斷器的配置不當(dāng)可能會(huì)在流量高峰或后端中斷期間導(dǎo)致意外停機(jī)。如果不使用 Envoy 作為 mesh 代理,不妨考慮放松這塊的配置。值得一提的是,在默認(rèn)情況下,Envoy 的熔斷器限制非常嚴(yán)格 ———— 特別要注意這一點(diǎn)!
  • 緩沖區(qū)。 Nginx 允許將磁盤用作請(qǐng)求的緩沖區(qū)。這在用戶采用的是無法理解 chunked 傳輸編碼的傳統(tǒng)的 HTTP/1.0 后端的情況下很有用。Nginx 可以將它們先緩沖到磁盤上然后轉(zhuǎn)換成帶有 Content-Length 的請(qǐng)求。Envoy 有一個(gè) Buffer 過濾器,但是由于無法將數(shù)據(jù)保存到磁盤,能夠緩沖多少內(nèi)容受限于我們機(jī)器的內(nèi)存大小。

下一步

  • HTTP/3 的黃金時(shí)代即將來臨。幾個(gè)主流瀏覽器均已加入了對(duì) HTTP 3.0 的支持(目前由一個(gè)參數(shù)或者命令行選項(xiàng)來控制是否開啟)。Envoy 也已經(jīng)在進(jìn)行實(shí)驗(yàn)性的支持。升級(jí) Linux 內(nèi)核支持 UDP 加速后,我們會(huì)在我們的邊緣環(huán)境上進(jìn)行試驗(yàn)。
  • 內(nèi)部基于 xDS 的負(fù)載均衡以及異常值檢測(cè)。目前,我們正在考慮使用負(fù)載上報(bào)服務(wù)(LRS)和端點(diǎn)發(fā)現(xiàn)服務(wù)(EDS)的組合作為構(gòu)建模塊,以便為 Envoy 和 gRPC 創(chuàng)建一套通用的后備型、負(fù)載感知式的負(fù)載均衡。
  • 基于 WASM 的 Envoy 擴(kuò)展。等到 Golang 的 proxy-wasm SDK 可用時(shí),我們便可以開始在 Go 里面編寫 Envoy 的擴(kuò)展,這將使我們能夠訪問各種內(nèi)部的 Golang 庫(kù)。
  • 替換 Bandaid。將所有的 Dropbox 代理層統(tǒng)一成一個(gè)單個(gè)的數(shù)據(jù)平面聽上去很有吸引力。為此,我們需要將許多 Bandaid 功能(尤其是在負(fù)載均衡方面)遷移到 Envoy。這還有很長(zhǎng)的路要走,但這是我們目前的計(jì)劃。
  • Envoy 移動(dòng)端。最終,我們想看看能否將 Envoy 用于移動(dòng)端的應(yīng)用程序。站在接入層的角度,在所有的移動(dòng)端平臺(tái)上支持一套具有統(tǒng)一的監(jiān)控和現(xiàn)代化功能(HTTP/3,gRPC,TLS1.3 等)的單個(gè)服務(wù)棧非常耀眼。 

 

責(zé)任編輯:未麗燕 來源: Dockone.io
相關(guān)推薦

2020-01-13 15:22:42

ERP云平臺(tái)遷移

2017-10-16 00:17:56

云計(jì)算信息管理遷移

2021-01-28 09:00:00

SQL數(shù)據(jù)庫(kù)NoSQL

2023-08-23 09:00:00

區(qū)塊鏈以太坊

2012-08-24 09:07:25

IBMdW

2012-10-29 09:27:16

2015-03-20 13:40:17

2010-07-20 09:48:33

2016-10-26 16:44:44

WatchfinderAWS云計(jì)算

2011-09-07 09:30:57

服務(wù)器虛擬機(jī)

2021-07-13 09:45:48

CentOSAlmaLinux命令

2011-06-24 10:10:35

SVN

2016-11-11 00:00:16

MySQLOracle數(shù)據(jù)

2011-08-09 14:27:16

WindowsServ服務(wù)器ADDS

2020-01-06 12:50:50

Windows 7遷移Windows 10

2009-04-16 17:23:37

OracleBasicFileSecureFile

2010-09-29 11:06:21

活動(dòng)目錄OpenLDAP

2013-04-27 10:37:35

Python

2022-03-04 18:14:26

CentOSLinux

2022-08-29 14:14:22

云計(jì)算云遷移數(shù)據(jù)分析
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)