Local RateLimit

Local RateLimit #

Global RateLimitとの違い

Local RateLimitとGlobal RateLimitの違いは守るスコープの違いにあります。 Global RateLimitはUpstream側のシステムを守るため、RateLimitのマイクロサービス間でリクエスト数を共有するためのストア(redisなど)を外側に持っています。 それに対し、Local RateLimitはRateLimitを提供するProxyだけがリクエスト数を保持でいればよいためインメモリーで実装することができます。

Kubernetes上におけるLocal RateLimitの設置候補

Local RateLimitを実施する候補は2つあります。

  1. istio-proxy(Envoy)のLocal Ratelimit機能を利用する
  2. nginxをistio-proxyとAppの間に立たせ、nginxのRateLimitを利用する

Local RateLimitの概略

envoy と nginx の Rate Limit アルゴリズムの違い #

envoy と nginx では Rate Limit のアルゴリズムが異なります。 ゆえに、バースト性のあるトラフィックに対する制限が異なり、どちらからの乗り換えに対しても検証なしで乗り換えすることはできません。

Proxy ServerRate Limit Algorithm
nginxLeaky Bucket
envoyToken Bucket

envoyとnginxの設定例 #

例えばmyappというアプリケーションに対して10 rpsの Rate Limit の制限をかけ、バースト時のリクエストは50 rpsまで受け付けるようにした場合次のように記述できます。

EnvoyののLocal Rate Limit設定例 #

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: filter-local-ratelimit-myapp
spec:
  workloadSelector:
    labels:
      app: myapp
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: SIDECAR_INBOUND
        listener:
          filterChain:
            filter:
              name: envoy.filters.network.http_connection_manager
      patch:
        operation: INSERT_BEFORE
            value:
              stat_prefix: http_local_rate_limiter
              # Local Ratelimitのパラメーター
              token_bucket:
                max_tokens: 50
                tokens_per_fill: 10
                fill_interval: 1s
              filter_enabled:
                runtime_key: local_rate_limit_enabled
                default_value:
                  numerator: 100
                  denominator: HUNDRED
              filter_enforced:
                runtime_key: local_rate_limit_enforced
                default_value:
                  numerator: 100
                  denominator: HUNDRED

nginxのRate Limit設定例 #


http {
  limit_req_zone myapp_zone zone=myapp_zone:1m rate=10r/s;
  server {
    location /myapp {
      limit_req zone=myapp_zone burst=50 delay=10;
      limit_req_status 429;
      # 省略 ...
    }
  }
}

どちらを選ぶか #

これらを選択するにあたりバースト時の挙動を確認する必要があります。 例えば、前述の設定で 1 つの Pod に対して 70rps 来た場合、envoy と nginx は次の挙動をします。

proxy挙動
envoymax_tokens: 50まで消費し、50rps が App まで到達する。fill_intervalが 10 で指定されているためそれ以降のリクエストは token が回復するまで10rpsを維持する。max_tokens: 50から溢れた 20rps は 429 を返す。
nginxburst=50で指定したリクエスト数まで一度 nginx で受け付け、delay=10で指定した 10 req 分だけ App まで到達する。残りの 40req はキューイングされて FIFO で処理される。burst=50から溢れた 20rps は 429 を返す。

つまり、envoy で Local RateLimit を敷いた場合はmax_tokensで指定したリクエストはたしかに受け付けますが、それをキューせずに Upstream の App にリクエストを流します。この流量をアプリケーション側が処理することが可能であればenvoyの Rate Limit を採用することができます。これが逆に処理できない場合は App のコンテナが処理しきれずに 503 を返します。

したがって、バースト耐性を獲得しつつ、移行というスケジュールが決まった範疇で選択できるのはnginxを利用した Local Rate Limit になります。istio-proxyに加えてnginxもproxyとして挟まりスループットが若干悪くなりますが、BFFを構成するサーバーの応答速度と比較して十分に小さいため許容することにしました。

今後どうするか #

Podを構成する要素としてProxyが2段構えになっているのは多少格好は悪いですが、うまく機能しています。 ただし、後述しますが、envoyやnginxのRateLimitでは負荷の上昇を防げないパターン問題点もあります。 アクセス傾向やPodのMetricsなどを総合的に鑑みてRate Limitの構成と設定値を決めていく必要があります。