local ratelimit

local ratelimit是只在Pod或者vm本地生效的,通过envoy的 envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit 实现。

针对service

在开启了istio的namespace中,创建Deployment nginx、Service nginx。

$ kubectl create deployment nginx --image nginx
$ kubectl expose deployment nginx --port 80

此时,从其他Pod中请求 service nginx ,可以一直请求成功,返回nginx的默认页面。

开启local ratelimit。具体规则见 filter-local-ratelimit-svc-1.yaml

$ kubectl apply -f filter-local-ratelimit-svc-1.yaml

该envoy filter会通过workloadSelector作用到符合label的workload上,包括容器Pod和虚拟机的workload entry。在本例中是以容器Pod为示例的。

      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.local_ratelimit
          typed_config:
            "@type": type.googleapis.com/udpa.type.v1.TypedStruct
            type_url: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
            value:
              stat_prefix: http_local_rate_limiter
              token_bucket:
                max_tokens: 1
                tokens_per_fill: 1
                fill_interval: 60s

如上,envoy filter会patch local_ratelimit,限制为一分钟1个请求。

此时,从其他Pod中请求 service nginx,可以发现,一分钟内,只有第一个请求成功,后续的请求都会返回 HTTP/1.1 429 Too Many Requests

$ curl -i nginx.hellobaby
HTTP/1.1 429 Too Many Requests
x-local-rate-limit: true
content-length: 18
content-type: text/plain
date: Wed, 16 Jun 2021 03:52:15 GMT
server: envoy
x-envoy-upstream-service-time: 0

针对pod

上面是通过service nginx来访问服务,接下来通过pod的ip来访问服务。

$ kubectl scale deployment --replicas=2 nginx

此时可以验证,通过单个pod ip访问服务时,两个服务之间的限流是独立的,pod 1限流后,pod 2仍然可以访问,方法是先向pod 1发送请求达到限流,然后立即向pod 2发送请求。

针对特定route

前面2个例子是针对所有的vhosts/routes。envoy filter也可以针对特定的route设置限流规则。

由于限流是在服务提供方(Provider)的入方向进行配置,因此其match规则为:

    - applyTo: HTTP_ROUTE
      match:
        context: SIDECAR_INBOUND
        routeConfiguration:
          vhost:
            name: "inbound|http|80"
            route:
              action: ANY

其中,name为istio proxy 的 inbound route 名称。

$ istioctl pc route nginx-f89759699-khbdr --name "inbound|80||"
inbound|80||                                                  *                                        /*
inbound|80||                                                  *                                        /*
$ istioctl pc cluster nginx-f89759699-khbdr  --direction=inbound
SERVICE FQDN     PORT     SUBSET     DIRECTION     TYPE             DESTINATION RULE
                 80       -          inbound       ORIGINAL_DST
$ istioctl pc route nginx-f89759699-5zv98 --name "inbound|80||" -o yaml

具体查看其route表项如下。其中route中的 cluster: inbound|80|| 为inbound方向的cluster。不过这里很奇怪,为什么有2条一模一样的route记录呢?

- name: inbound|80||
  validateClusters: false
  virtualHosts:
  - domains:
    - '*'
    name: inbound|http|80
    routes:
    - decorator:
        operation: nginx.hellobaby.svc.cluster.local:80/*
      match:
        prefix: /
      name: default
      route:
        cluster: inbound|80||
        maxStreamDuration:
          grpcTimeoutHeaderMax: 0s
          maxStreamDuration: 0s
        timeout: 0s
...

具体envoy filter配置见 filter-local-ratelimit-svc-2.yaml

上述配置生效后,查看pod的名为 “inbound 80   ” 的proxy-config route nginx-f89759699-khbdr 规则,可以看到增加了 typedPerFilterConfig 部分,这是我们配置的envoy filter中的patch部分。可以看到已经生效了。此时从其他pod中去请求80端口的服务,可以观察到限流生效。
- name: inbound|80||
  validateClusters: false
  virtualHosts:
  - domains:
    - '*'
    name: inbound|http|80
    routes:
    - decorator:
        operation: nginx.hellobaby.svc.cluster.local:80/*
      match:
        prefix: /
      name: default
      route:
        cluster: inbound|80||
        maxStreamDuration:
          grpcTimeoutHeaderMax: 0s
          maxStreamDuration: 0s
        timeout: 0s
      typedPerFilterConfig:
        envoy.filters.http.local_ratelimit:
          '@type': type.googleapis.com/udpa.type.v1.TypedStruct
          typeUrl: type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit
          value:
            filter_enabled:
              default_value:
                numerator: 100
              runtime_key: local_rate_limit_enabled
            filter_enforced:
              default_value:
                numerator: 100
              runtime_key: local_rate_limit_enforced
            response_headers_to_add:
            - append: false
              header:
                key: x-local-rate-limit
                value: "true"
            stat_prefix: http_local_rate_limiter
            token_bucket:
              fill_interval: 60s
              max_tokens: 1
              tokens_per_fill: 1
...

针对url路径/方法/源IP

envoy filter可以通过descriptor,设置针对url路径、请求方法、源IP的限流策略。

具体参考 pathmethodremote-address

Ref1: config-route-v3-ratelimit-action Ref2: Using rate limit descriptors for local rate limiting

global ratelimit

global ratelimit的特点是,其限流器是一个公共的gRPC服务,使用同一个限流器的pods,都受这个限流器的限制,并且互相之间是共享一个限流QPS的。

下面通过对gateway配置global限流器,来演示global ratelimit的应用。

部署限流器

首先需要部署一个限流服务。这里使用的是istio示例里指定的ratelimit

相关配置文件为 ratelimit-config.yaml ratelimit.yaml

服务部署到 namespace istio-system ,部署后会有一个service ratelimit ,其gRPC端口为8081,这个即为请求的限流API。

配置gateway限流规则

相关配置文件为 filter-ratelimit.yamlfilter-ratelimit-svc.yaml

注意 filter-ratelimit-svc.yaml 的配置与官网不同,目前(1.10版本)的文档是有问题的,其指定的vhost.name使用了通配符,实际上是不支持的。上面的示例中删除了这部分,这样可以实现vhost.name通配。

也可以将其改为 httpbin.example.com:80,但这样就只为host为 httpbin.example.com 的请求生效了。

      match:
        context: GATEWAY
        routeConfiguration:
          vhost:
            name: "*:80"
            route:
              action: ANY

部署服务

使用httpbin作为测试服务,配置文件为 httpbin.yaml

部署gateway

配置文件为 gateways.yaml。设置其hosts为 httpbin.example.com

部署virtual service

配置文件为 virtualservices.yaml。设置其hosts为 httpbin.example.com,关联gateway httpbin-gateway。

验证ratelimit生效

客户端请求 curl -i -H "Host: httpbin.example.com" http://{ingress gateway external ip}/status/200,可以观察到,一分钟内,第一个请求返回的是200,后续的请求均返回 429 Too Many Requests

这样可以验证global ratelimit已经生效,但无法验证其是global的。

验证global ratelimit生效

将ingress gateway扩容为2副本。

  1. 客户端请求 curl -i -H "Host: httpbin.example.com" http://{ingress gateway pod 1 ip}/status/200,可以观察到,第一个请求返回的是200,后续的请求均返回 429 Too Many Requests。;
  2. 在一分钟内,客户端请求 curl -i -H "Host: httpbin.example.com" http://{ingress gateway pod 2 ip}/status/200,可以观察到,请求均返回 429 Too Many Requests

这个现象与local ratelimit是不一样的,验证了global属性。

[root@debian-77dc9c5f4f-nscvz /]# curl -i -H "Host: httpbin.example.com" 10.244.2.104:8080/status/200
HTTP/1.1 200 OK
server: envoy
date: Wed, 16 Jun 2021 11:49:10 GMT
content-type: text/html; charset=utf-8
access-control-allow-origin: *
access-control-allow-credentials: true
content-length: 0
x-envoy-upstream-service-time: 5

[root@debian-77dc9c5f4f-nscvz /]# curl -i -H "Host: httpbin.example.com" 10.244.6.71:8080/status/200
HTTP/1.1 429 Too Many Requests
x-envoy-ratelimited: true
date: Wed, 16 Jun 2021 11:49:13 GMT
server: envoy
content-length: 0
x-envoy-upstream-service-time: 2

Ref: