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

ServiceMesher2018-11-12 03:31:56

作者:dm03514 譯者:楊傳勝 原文:https://medium.com/dm03514-tech-blog/sre-resiliency-bolt-on-sidecar-rate-limiting-with-envoy-sidecar-5381bd4a1137

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

問題

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

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

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

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

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

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

  1. $ make load-test

  2. echo "GET http://localhost:8080/slow" | vegeta attack -rate=500 -duration=0 | tee results.bin | vegeta report

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

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

Envoy 是什麼?

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

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

Envoy 的 官方文檔 和 MattKlein 的文章提供了一個比我更好的對 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 發出請求。配置完之後的最終狀態如下圖所示:

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

cluster

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

  1. clusters:

  2.  - name: api

  3.    connect_timeout: 1s

  4.    type: strict_dns

  5.    lb_policy: round_robin

  6.    hosts:

  7.    - socket_address:

  8.        address: localhost

  9.        port_value: 8080

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

virtual_host

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

  1. - name: api

  2.  domains:

  3.  - "*"

  4.  routes:

  5.  - match:

  6.      prefix: "/"

  7.    route:

  8.      cluster: api

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

  1. static_resources:

  2.  listeners:

  3.  - name: listener_0

  4.    address:

  5.      socket_address: { address: 0.0.0.0, port_value: 10000}

  6.    filter_chains:

  7.    - filters:

  8.      - name: envoy.http_connection_manager

  9.        config:

  10.          stat_prefix: ingress_http

  11.          codec_type: AUTO

  12.          route_config:

  13.            name: remote_api

  14.            virtual_hosts:

  15.            - name: api

  16.              domains:

  17.              - "*"

  18.              routes:

  19.              - match:

  20.                  prefix: "/"

  21.                route:

  22.                  cluster: api

  23.          http_filters:

  24.          - name: envoy.router

  25.  clusters:

  26.  - name: api

  27.    connect_timeout: 1s

  28.    type: strict_dns

  29.    lb_policy: round_robin

  30.    hosts:

  31.    - socket_address:

  32.        address: localhost

  33.        port_value: 8080

  34. admin:

  35.  access_log_path: "/dev/null"

  36.  address:

  37.    socket_address:

  38.      address: 0.0.0.0

  39.      port_value: 9901

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

  1. $ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=500

  2. echo "GET http://localhost:10000/slow" | vegeta attack -rate=500 -duration=0 | tee results.bin | vegeta report

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

配置並運行全局速率限制服務

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

首先克隆 Ratelimit 代碼倉庫並修改配置文件,更新 domain 字段以及 descriptor 字段的 keyvalue

  1. $ cat examples/ratelimit/config/config.yaml

  1. ---

  2. domain: apis

  3. descriptors:

  4.  - key: generic_key

  5.    value: default

  6.    rate_limit:

  7.      unit: second

  8.      requests_per_unit: 500

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

  1. $ docker-compose down && docker-compose up

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

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

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

  1. static_resources:

  2.  listeners:

  3.  - name: listener_0

  4.    address:

  5.      socket_address: { address: 0.0.0.0, port_value: 10000}

  6.    filter_chains:

  7.    - filters:

  8.      - name: envoy.http_connection_manager

  9.        config:

  10.          use_remote_address: true

  11.          stat_prefix: ingress_http

  12.          codec_type: AUTO

  13.          route_config:

  14.            name: remote_api

  15.            virtual_hosts:

  16.            - name: api

  17.              domains:

  18.              - "*"

  19.              routes:

  20.              - match:

  21.                  prefix: "/"

  22.                route:

  23.                  cluster: api

  24.              rate_limits:

  25.                - stage: 0

  26.                  actions:

  27.                    - {generic_key: {descriptor_value: "default"}}

  28.          http_filters:

  29.          - name: envoy.rate_limit

  30.            config:

  31.              domain: apis

  32.              stage: 0

  33.          - name: envoy.router

  34.  clusters:

  35.  - name: api

  36.    connect_timeout: 1s

  37.    type: strict_dns

  38.    lb_policy: round_robin

  39.    hosts:

  40.    - socket_address:

  41.        address: localhost

  42.        port_value: 8080

  43.  - name: rate_limit_cluster

  44.    type: strict_dns

  45.    connect_timeout: 0.25s

  46.    lb_policy: round_robin

  47.    http2_protocol_options: {}

  48.    hosts:

  49.    - socket_address:

  50.        address: localhost

  51.        port_value: 8081

  52. rate_limit_service:

  53.    grpc_service:

  54.        envoy_grpc:

  55.            cluster_name: rate_limit_cluster

  56.        timeout: 0.25s

  57. admin:

  58.  access_log_path: "/dev/null"

  59.  address:

  60.    socket_address:

  61.      address: 0.0.0.0

  62.      port_value: 9901

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

  1. $ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=1000

  2. echo "GET http://localhost:10000/slow" | vegeta attack -rate=1000 -duration=0 | tee results.bin | vegeta report

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

  1. msg="cache key: apis_generic_key_default_1540829538 current: 35"

  2. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 34"

  3. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 33"

  4. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 31"

  5. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 32"

  6. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 42"

  7. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="starting get limit lookup"

  8. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="cache key: apis_generic_key_default_1540829538 current: 46"

  9. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"

  10. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"

  11. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"

  12. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"

  13. ratelimit_1        | time="2018-10-29T16:12:18Z" level=debug msg="looking up key: generic_key_default"

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

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

  1. $ make load-test LOAD_TEST_TARGET=http://localhost:10000 LOAD_TEST_RATE=1000

  2. echo "GET http://localhost:10000/slow" | vegeta attack -rate=1000 -duration=0 | tee results.bin | vegeta report

  3. Requests      [total, rate]            128093, 1000.02

  4. Duration      [total, attack, wait]    2m8.102168403s, 2m8.090470728s, 11.697675ms

  5. Latencies     [mean, 50, 95, 99, max]  10.294365ms, 11.553135ms, 33.428287ms, 52.678127ms, 177.709494ms

  6. Bytes In      [total, mean]            1207354, 9.43

  7. Bytes Out     [total, mean]            0, 0.00

  8. Success       [ratio]                  52.69%

  9. Status Codes  [code:count]             200:67494  429:60599

  10. Error Set:

  11. 429 Too Many Requests

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

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

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

實驗成功!

總結

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

參考資料

  • https://www.datawire.io/envoyproxy/getting-started-lyft-envoy-microservices-resilience/

  • https://www.envoyproxy.io/docs/envoy/latest/

  • https://blog.turbinelabs.io/deploying-envoy-as-a-front-proxy-5b7e0a453f65

  • http://blog.christianposta.com/microservices/00-microservices-patterns-with-envoy-proxy-series/

  • https://blog.envoyproxy.io/introduction-to-modern-network-load-balancing-and-proxying-a57f6ff80236

  • https://eng.lyft.com/announcing-ratelimit-c2e8f3182555

  • https://github.com/dm03514/grokking-go/compare/blog/bolt-on-rate-limits?expand=1

相關閱讀

  • 速率限制系列part4—為Ambassador API網關設計速率限制服務

  • 速率限制系列part3—基於Ambassador API網關實現Java速率限制服務

  • 速率限制系列part2—作用於API網關的速率限制

  • 速率限制系列part1—分佈式系統的一個實用工具

  • 理解 Istio Service Mesh 中 Envoy 代理 Sidecar 注入及流量劫持

Istio

IBM Istio

111 Istio

118 週四晚8點Istio系列第二講:上手Istio: 基本概念,安裝並使用istio進行微服務流量管控

1115 Istio

1122 Envoy

1129 使Istio

126 Istio mixer -

1213 Istio

1220 Istio使Serverless knative

IBMIstio

點擊【閱讀原文】跳轉到網站上瀏覽可以查看文中的鏈接。

  • SOFAMesh(https://github.com/alipay/sofa-mesh)基於Istio的大規模服務網格解決方案

  • SOFAMosn(https://github.com/alipay/sofa-mosn)使用Go語言開發的高性能Sidecar代理

合作社區

參與社區

以下是參與ServiceMesher社區的方式,最簡單的方式是聯繫我!

  • 加入微信交流群:關注本微信公眾號後訪問主頁右下角有獲取聯繫方式按鈕,添加好友時請註明姓名-公司

  • 社區網址:http://www.servicemesher.com

  • Slack:https://servicemesher.slack.com (需要邀請才能加入)

  • GitHub:https://github.com/servicemesher

  • Istio中文文檔進度追蹤:https://github.com/servicemesher/istio-official-translation

  • Twitter: https://twitter.com/servicemesher

  • 提供文章線索與投稿:https://github.com/servicemesher/trans


閱讀原文

TAGS: