SRE 彈效能力:使用 Envoy 對應用進行速率限制

ServiceMesher發表於2018-11-04

作者:dm03514 

譯者:楊傳勝 

原文:medium.com/dm03514-tec… 

速率限制是緩解級聯故障和防止耗盡共享資源的一種簡單有效的方法。Envoy 是一個功能豐富的代理,可以為任何服務輕鬆新增速率限制的功能。本文將介紹在不更改應用程式本身配置的前提下如何配置 Envoy 來強制對應用進行速率限制。

問題

你是否遇到過資源被大量的請求淹沒或耗盡的情況?你的客戶端是否具有回退重試或速率限制的邏輯?在微服務架構中,不對其使用量進行限制的資源很容易被客戶端發出的大量請求所淹沒。當然可能存在一定數量的客戶端,這些客戶端本身就已經實現了各種重試/回退和速率限制的策略。不對訪問量進行限制的客戶端會耗盡服務端的資源,從而使其他客戶端無法訪問服務,甚至有些客戶端會一直髮起請求,直到使服務完全不可用。

API 的使用進行約束的常用方法是啟用速率限制。與基於 IP 的速率限制或者 web 框架提供的應用級別速率限制不同,Envoy 允許在 HTTP 層實現快速,高效能和可靠的全域性速率限制。

SRE 彈效能力:使用 Envoy 對應用進行速率限制

上圖中左側的 Service Client 代表使用率特別高的客戶端。在執行期間,它可以使負載均衡後端的所有服務例項流量飽和,並使其他更高優先順序的客戶端丟棄其請求。

Envoy 能夠對網路級別的任何服務進行速率限制,而無需對應用程式進行任何修改。此外,由於 Envoy 工作在 7 層,也就是應用程式級別,所以它可以檢查 HTTP 速率資訊並對其進行速率限制。

在本教程中,vegata 負載測試工具用於模擬上述示例中的批處理作業。下圖顯示了請求速率大約為 500次/秒 的穩定狀態。

譯者注:首先克隆 grokking-go 專案,然後進入 bolt-on-out-of-process-rate-limits 目錄

$ make load-test​echo "GET http://localhost:8080/slow" | vegeta attack -rate=500 -duration=0 | tee results.bin | vegeta report複製程式碼

SRE 彈效能力:使用 Envoy 對應用進行速率限制

在模擬後臺作業期間,對 API 資源 /slow 的訪問速率達到了每秒 3500 個請求,影響到了其他端點和客戶端。

SRE 彈效能力:使用 Envoy 對應用進行速率限制

為了解決這個問題,下面的解決方案將使用 Envoy 強制限制請求速率為 500個請求/秒。但首先...

Envoy 是什麼?

Envoy 是一個輕量級代理伺服器,能夠處理任何 TCP/IP/HTTP/GRPC/HTTP2 等協議的連線。它具有高度可配置性,並支援許多不同的外掛。它還使可觀察性成為一等公民。

在 Envoy 橫空出世之前,應用程式級別的重試、延遲注入、速率限制和熔斷都要通過應用程式本身的程式碼邏輯來實現。Envoy 將這些功能從應用程式中剝離出來,並讓運維管理人員能夠配置和啟用這些功能,無需對應用程式本身作任何修改。

Envoy 的 官方文件Matt Klein 的文章提供了一個比我更好的對 Envoy 的介紹:

Envoy 是一款由 Lyft 開源的,使用 C++ 編寫的高效能分散式代理,專為單體服務和應用而設計。它也被作為大型微服務框架 Istio service mesh 的通訊匯流排和通用資料平面。通過借鑑 NGINX、HAProxy、硬體負載均衡器和雲負載均衡器等解決方案,Envoy 作為一個獨立的程式與應用程式一起執行,並通過與平臺無關的方式提供一些高階特性,從而形成一個對應用透明的通訊網格。當基礎設施中的所有服務流量通過 Envoy 網格流動時,通過一致的可觀察性,調整整體效能和新增更多底層特性,一旦發生網路和應用程式故障,能夠很容易定位出問題的根源。

解決方案

所有程式碼和示例都可以在 GitHub 上找到。

下面給出具體的解決方案:

  • 將 Envoy 配置為 API 負載均衡器的前端代理;仍然允許所有流量通過。

  • 配置並執行全域性速率限制服務。

  • 配置 Envoy 使用全域性速率限制服務。

我們需要一種方法來限制同一時間發出的請求數量,以便將 API 負載均衡器與請求達到高峰的客戶端隔離,並確保其他客戶端在執行這些批處理作業(通過 vegeta 來模擬)期間可以繼續訪問 API。為了達到這個目的,我們將 Envoy 代理和批處理客戶端 vegeta 部署在同一臺機器上。

通過將 Envoy 作為 Sidecar 與批處理客戶端一起執行,在請求達到負載均衡之前就可以對請求進行速率限制。使用 Envoy 是一個很明智的選擇,因為它具有高度可配置性,高效能,並且可以很好地處理 HTTP 請求之間的平衡。

將 Envoy 配置為 API 負載均衡器的前端代理

第一步是將 Envoy 配置為處於批處理作業客戶端和 API 負載均衡器之間,客戶端向 API 發起的所有請求都會首先經過 Envoy 的處理。首先需要讓 Envoy 知道如何連線 API,然後再更新批處理作業的配置,使該客戶端向 Envoy 發出請起,而不是直接向 API 發出請求。配置完之後的最終狀態如下圖所示:

SRE 彈效能力:使用 Envoy 對應用進行速率限制

此步驟僅通過 Envoy 來對 API 流量進行路由,尚未對應用進行速率限制。為了達到限速的目的,我們還需要做一些額外的配置:

cluster

Cluster 表示 Envoy 連線到的一組邏輯上相似的上游主機(在本示例中表示 API 負載均衡器)。Cluster 的配置非常簡單:

clusters:  - name: api    connect_timeout: 1s    type: strict_dns    lb_policy: round_robin    hosts:    - socket_address:        address: localhost        port_value: 8080複製程式碼

在本示例中,我們執行了一個監聽在 localhost:8080 上的 fakapi 來模擬上圖中的負載均衡器。通過 Envoy 向 API 發出的任何請求都會被髮送到 localhost:8080

virtual_host

virtual_host 部分的配置用來確保所有請求都會路由到上面定義的 API 叢集。

- name: api  domains:  - "*"  routes:  - match:      prefix: "/"    route:      cluster: api複製程式碼

其餘的配置檔案用來確定 Envoy 本身監聽在哪個地址以及 Envoy 與其他服務之間的連線規則。

static_resources:  listeners:  - name: listener_0    address:      socket_address: { address: 0.0.0.0, port_value: 10000}    filter_chains:    - filters:      - name: envoy.http_connection_manager        config:          stat_prefix: ingress_http          codec_type: AUTO          route_config:            name: remote_api            virtual_hosts:            - name: api              domains:              - "*"              routes:              - match:                  prefix: "/"                route:                  cluster: api​          http_filters:          - name: envoy.router​  clusters:  - name: api    connect_timeout: 1s    type: strict_dns    lb_policy: round_robin    hosts:    - socket_address:        address: localhost        port_value: 8080​admin:  access_log_path: "/dev/null"  address:    socket_address:      address: 0.0.0.0      port_value: 9901複製程式碼

更新負載測試工具的引數,直接訪問本地的 Envoy 代理,通過儀表板可以觀察到 Envoy 正在接收流量。下圖的 Envoy 儀表板來自 Grafana 官方儀表板倉庫Lyft 也提供了一份 Envoy 儀表板)。

$ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=500​echo "GET http://localhost:10000/slow" | vegeta attack -rate=500 -duration=0 | tee results.bin | vegeta report複製程式碼

SRE 彈效能力:使用 Envoy 對應用進行速率限制

SRE 彈效能力:使用 Envoy 對應用進行速率限制

SRE 彈效能力:使用 Envoy 對應用進行速率限制

上圖顯示 Envoy 現在正在接收客戶端傳送給 API 的所有請求,並將它們傳送到上游的負載均衡器!

配置並執行全域性速率限制服務

此步驟將配置執行 Lyft 開源的全域性 速率限制 服務。執行該服務非常簡單,只需要克隆它的程式碼倉庫,修改一部分配置檔案,然後通過 docker-compose 啟動就行了。

SRE 彈效能力:使用 Envoy 對應用進行速率限制

首先克隆 Ratelimit 程式碼倉庫並修改配置檔案,更新 domain 欄位以及 descriptor 欄位的 keyvalue

$ cat examples/ratelimit/config/config.yaml複製程式碼
---domain: apisdescriptors:  - key: generic_key    value: default    rate_limit:      unit: second      requests_per_unit: 500複製程式碼

接下來使用docker-compose 的配置檔案(docker-compose.yml)來啟動全域性速率限制服務(詳細步驟請參考 README):

$ docker-compose down && docker-compose up複製程式碼

配置 Envoy 使用全域性速率限制服務

最後一步是配置 Envoy 使用全域性速率限制服務,以強制執行速率限制並降低對 API 的請求速率。配置生效後,Envoy 將會檢查每個傳入連線的速率限制,並根據上面的配置過濾掉一部分請求(限制最多 500 個請求/秒)。

SRE 彈效能力:使用 Envoy 對應用進行速率限制

開啟了速率限制的 Envoy 配置檔案如下所示:

static_resources:  listeners:  - name: listener_0    address:      socket_address: { address: 0.0.0.0, port_value: 10000}    filter_chains:    - filters:      - name: envoy.http_connection_manager        config:          use_remote_address: true          stat_prefix: ingress_http          codec_type: AUTO          route_config:            name: remote_api            virtual_hosts:            - name: api              domains:              - "*"              routes:              - match:                  prefix: "/"                route:                  cluster: api              rate_limits:                - stage: 0                  actions:                    - {generic_key: {descriptor_value: "default"}}​          http_filters:          - name: envoy.rate_limit            config:              domain: apis              stage: 0​          - name: envoy.router​  clusters:  - name: api    connect_timeout: 1s    type: strict_dns    lb_policy: round_robin    hosts:    - socket_address:        address: localhost        port_value: 8080​  - name: rate_limit_cluster    type: strict_dns    connect_timeout: 0.25s    lb_policy: round_robin    http2_protocol_options: {}    hosts:    - socket_address:        address: localhost        port_value: 8081​rate_limit_service:    grpc_service:        envoy_grpc:            cluster_name: rate_limit_cluster        timeout: 0.25s​admin:  access_log_path: "/dev/null"  address:    socket_address:      address: 0.0.0.0      port_value: 9901複製程式碼

然後,我們以 1000個請求/秒 的速率(速率限制的2倍)執行負載測試工具:

$ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=1000​echo "GET http://localhost:10000/slow" | vegeta attack -rate=1000 -duration=0 | tee results.bin | vegeta report複製程式碼

可以檢視一下 ratelimiter 服務的日誌,日誌中顯示了它接收的請求和它進行速率限制檢查的過程:

msg="cache key: apis_generic_key_default_1540829538 current: 35"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 34"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 33"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 31"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 32"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 42"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="starting get limit lookup"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 46"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"複製程式碼

如果速率限制功能無法生效,可以參考該 issue 中的討論。

執行一段時間後,停止負載測試列印出測試報告,可以看到其中 1/2 的請求被 Envoy 限制了,被限制的請求的狀態碼為 429 !!!

$ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=1000​echo "GET http://localhost:10000/slow" | vegeta attack -rate=1000 -duration=0 | tee results.bin | vegeta reportRequests      [total, rate]            128093, 1000.02Duration      [total, attack, wait]    2m8.102168403s, 2m8.090470728s, 11.697675msLatencies     [mean, 50, 95, 99, max]  10.294365ms, 11.553135ms, 33.428287ms, 52.678127ms, 177.709494msBytes In      [total, mean]            1207354, 9.43Bytes Out     [total, mean]            0, 0.00Success       [ratio]                  52.69%Status Codes  [code:count]             200:67494  429:60599Error Set:429 Too Many Requests複製程式碼

通過 Envoy 暴露的速率限制指標(envoy_cluster_ratelimit_over_limit)或(4xx 響應)的速率來繪製儀表板,可以看到相應的視覺化圖表:

SRE 彈效能力:使用 Envoy 對應用進行速率限制

通過視覺化 API 服務實際看到的請求數量,可以證明請求速率在 500個請求/秒 上下波動,這正是我們所期望的!

SRE 彈效能力:使用 Envoy 對應用進行速率限制

再檢視一下 Envoy 傳出的 API 連線,可以看到傳出請求速率也在 500個請求/秒 上下波動!

SRE 彈效能力:使用 Envoy 對應用進行速率限制

實驗成功!

總結

希望通過本文的講解能讓你明白配置 Envoy 以減輕貪婪客戶端對 API 資源的消耗是多麼簡單。我發現這種模式非常有用,因為彈效能力是為應用開發更多功能的基礎。在 Envoy 橫空出世之前,應用程式級別的重試、延遲注入、速率限制和熔斷都要通過應用程式本身的程式碼邏輯來實現。Envoy 將這些功能從應用程式中剝離出來,並讓運維管理人員能夠配置和啟用這些功能,無需對應用程式本身作任何修改。Envoy 完全顛覆了我們對服務彈效能力的認知,希望你讀這篇文章時能和我寫這篇文章時一樣興奮!

參考資料

ServiceMesher社群資訊

微信群:聯絡我入群

社群官網:www.servicemesher.com

Slack:servicemesher.slack.com 需要邀請才能加入

Twitter: twitter.com/servicemesh…

GitHub:github.com/

更多Service Mesh諮詢請掃碼關注微信公眾號ServiceMesher。

SRE 彈效能力:使用 Envoy 對應用進行速率限制


相關文章