第二十二部分 Istio速率限制

概述

如何使用 Istio 动态的对服务通信进行速率限制。

前提

  • 正确安装Istio
  • 确保启用Istio策略检查
  • 部署 Bookinfo 示例应用
  • 将所有服务的默认版本设置为 v1
kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml

YAML具体内容如下:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: productpage
spec:
  hosts:
  - productpage
  http:
  - route:
    - destination:
        host: productpage
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: reviews
spec:
  hosts:
  - reviews
  http:
  - route:
    - destination:
        host: reviews
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - route:
    - destination:
        host: ratings
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: details
spec:
  hosts:
  - details
  http:
  - route:
    - destination:
        host: details
        subset: v1
---

速率限制

此任务中会根据客户的 IP 地址,针对对目标为 productpage 的流量配置 Istio 的速率限制。客户端使用X-Forwarded-For 请求 Header 存放客户端 IP 地址。此外还会针对用户的登录情况进行有条件的速率限制。为方便起见,可以使用 memquota 适配器完成速率限制。但是在生产系统上就需要提供 Redis 服务,然后配置 redisquota 适配器。 memquota 和 redisquota 适配器都支持 quota template,因此,在两个适配器上启用速率限制的配置是相同的。

速率限制配置分为两部分。

客户端

  • QuotaSpec 定义客户端应该请求的配额名称和大小。
  • QuotaSpecBinding 有条件地将 QuotaSpec 与一个或多个服务相关联。

Mixer 端

  • quota instance 定义了 Mixer 如何选定配额。
  • memquota adapter 包含了 memquota 适配器的具体配置。
  • quota rule 定义何时将配额实例分派给 memquota 适配器。
  • 1.1.2以上版本,运行以下命令以使用 memquota 启用速率限制:
kubectl apply -f samples/bookinfo/policy/mixer-rule-productpage-ratelimit.yaml

YAML内容具体如下:

apiVersion: config.istio.io/v1alpha2
kind: handler
metadata:
  name: quotahandler
  namespace: istio-system
spec:
  compiledAdapter: memquota
  params:
    quotas:
    - name: requestcountquota.instance.istio-system
      maxAmount: 500
      validDuration: 1s
      # The first matching override is applied.
      # A requestcount instance is checked against override dimensions.
      overrides:
      # The following override applies to 'reviews' regardless
      # of the source.
      - dimensions:
          destination: reviews
        maxAmount: 1
        validDuration: 5s
      # The following override applies to 'productpage' when
      # the source is a specific ip address.
      - dimensions:
          destination: productpage
          source: "10.28.11.20"
        maxAmount: 500
        validDuration: 1s
      # The following override applies to 'productpage' regardless
      # of the source.
      - dimensions:
          destination: productpage
        maxAmount: 2
        validDuration: 5s
---
apiVersion: config.istio.io/v1alpha2
kind: instance
metadata:
  name: requestcountquota
  namespace: istio-system
spec:
  compiledTemplate: quota
  params:
    dimensions:
      source: request.headers["x-forwarded-for"] | "unknown"
      destination: destination.labels["app"] | destination.service.name | "unknown"
      destinationVersion: destination.labels["version"] | "unknown"
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpec
metadata:
  name: request-count
  namespace: istio-system
spec:
  rules:
  - quotas:
    - charge: 1
      quota: requestcountquota
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpecBinding
metadata:
  name: request-count
  namespace: istio-system
spec:
  quotaSpecs:
  - name: request-count
    namespace: istio-system
  services:
  - name: productpage
    namespace: default
    #  - service: '*'  # Uncomment this to bind *all* services to request-count
---
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: quota
  namespace: istio-system
spec:
  # quota only applies if you are not logged in.
  # match: match(request.headers["cookie"], "user=*") == false
  actions:
  - handler: quotahandler
    instances:
    - requestcountquota
  • 1.1.2 或更早的版本,请使用下列配置:
kubectl apply -f samples/bookinfo/policy/mixer-rule-productpage-ratelimit-crd.yaml

YAML具体内容如下:

apiVersion: config.istio.io/v1alpha2
kind: memquota
metadata:
  name: handler
  namespace: istio-system
spec:
  quotas:
  - name: requestcount.quota.istio-system
    maxAmount: 500
    validDuration: 1s
    # The first matching override is applied.
    # A requestcount instance is checked against override dimensions.
    overrides:
    # The following override applies to 'reviews' regardless
    # of the source.
    - dimensions:
        destination: reviews
      maxAmount: 1
      validDuration: 5s
    # The following override applies to 'productpage' when
    # the source is a specific ip address.
    - dimensions:
        destination: productpage
        source: "10.28.11.20"
      maxAmount: 500
      validDuration: 1s
    # The following override applies to 'productpage' regardless
    # of the source.
    - dimensions:
        destination: productpage
      maxAmount: 2
      validDuration: 5s
---
apiVersion: config.istio.io/v1alpha2
kind: quota
metadata:
  name: requestcount
  namespace: istio-system
spec:
  dimensions:
    source: request.headers["x-forwarded-for"] | "unknown"
    destination: destination.labels["app"] | destination.service.name | "unknown"
    destinationVersion: destination.labels["version"] | "unknown"
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpec
metadata:
  name: request-count
  namespace: istio-system
spec:
  rules:
  - quotas:
    - charge: 1
      quota: requestcount
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpecBinding
metadata:
  name: request-count
  namespace: istio-system
spec:
  quotaSpecs:
  - name: request-count
    namespace: istio-system
  services:
  - name: productpage
    namespace: default
    #  - service: '*'  # Uncomment this to bind *all* services to request-count
---
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: quota
  namespace: istio-system
spec:
  # quota only applies if you are not logged in.
  # match: match(request.headers["cookie"], "user=*") == false
  actions:
  - handler: handler.memquota
    instances:
    - requestcount.quota

如果 destination 的值是 reviews,则限制每 5 秒(validDuration)1 (maxAmount 字段)个请求。如果 destination 是 productpage,每 5 秒 2 个请求。当处理请求时,会按照自顶向下的顺序,选择第一个符合条件的 override 进行速率限制。或者

kubectl apply -f samples/bookinfo/policy/mixer-rule-productpage-redis-quota-rolling-window.yaml

YAML具体内容如下:

apiVersion: config.istio.io/v1alpha2
kind: handler
metadata:
  name: redishandler
  namespace: istio-system
spec:
  compiledAdapter: redisquota
  params:
    redisServerUrl: redis-release-master:6379
    connectionPoolSize: 10
    quotas:
    - name: requestcountquota.instance.istio-system
      maxAmount: 500
      validDuration: 1s
      bucketDuration: 500ms
      rateLimitAlgorithm: ROLLING_WINDOW
      # The first matching override is applied.
      # A requestcount instance is checked against override dimensions.
      overrides:
      # The following override applies to 'reviews' regardless
      # of the source.
      - dimensions:
          destination: reviews
        maxAmount: 1
      # The following override applies to 'productpage' when
      # the source is a specific ip address.
      - dimensions:
          destination: productpage
          source: "10.28.11.20"
        maxAmount: 500
      # The following override applies to 'productpage' regardless
      # of the source.
      - dimensions:
          destination: productpage
        maxAmount: 2
---
apiVersion: config.istio.io/v1alpha2
kind: instance
metadata:
  name: requestcountquota
  namespace: istio-system
spec:
  compiledTemplate: quota
  params:
    dimensions:
      source: request.headers["x-forwarded-for"] | "unknown"
      destination: destination.labels["app"] | destination.workload.name | "unknown"
      destinationVersion: destination.labels["version"] | "unknown"
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpec
metadata:
  name: request-count
  namespace: istio-system
spec:
  rules:
  - quotas:
    - charge: 1
      quota: requestcountquota
---
apiVersion: config.istio.io/v1alpha2
kind: QuotaSpecBinding
metadata:
  name: request-count
  namespace: istio-system
spec:
  quotaSpecs:
  - name: request-count
    namespace: istio-system
  services:
  - name: productpage
    namespace: default
    #  - service: '*'  # Uncomment this to bind *all* services to request-count
---
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: quota
  namespace: istio-system
spec:
  # quota only applies if you are not logged in.
  # match: match(request.headers["cookie"], "session=*") == false
  actions:
  - handler: redishandler
    instances:
    - requestcountquota
---

根据实际情况替换 rate_limit_algorithm 和 redis_server_url 的值。redisquota Handler 定义了 4 个不同的速率限制模式。缺省限制为每秒 500 个请求,使用 ROLLING_WINDOW 算法进行配额检查,该算法的 bucketDuration 定义为 500 毫秒。还定义了三个 override:

  • 如果 destination 的值是 reviews,maxAmount 设置为 1。
  • 如果 destination 的值是为 productpage,并且来源 IP 为 10.28.11.20,上限设置为 500。
  • 如果 destination 的值是为 productpage,则上限设置为 2。

当处理请求时,会按照自顶向下的顺序,选择第一个符合条件的 override 进行速率限制。

确认已经创建 quota instance:

kubectl -n istio-system get instance requestcountquota -o yaml

quota 模板定义了三个 dimension,在 memquota 或者 redisquota 中可以根据这些定义,对符合属性要求的请求进行覆盖。destination 会在 destination.labels["app"]、destination.service.host 或 "unknown" 中选择第一个非空值。

确认 quota rule 已经创建:

kubectl -n istio-system get rule quota -o yaml

rule 告诉 Mixer,调用 memquota 或者 redisquota Handler,并使用上面创建的 requestcountquota Instance 生成对象传递给 Handler。这个步骤会使用 quota 模板对dimension 进行映射。

确认 QuotaSpec 已经创建:

kubectl -n istio-system get QuotaSpec request-count -o yaml

QuotaSpec 中声明,上面定义的 requestcountquota 的消耗倍数为 1。

确认 QuotaSpecBinding 已经创建:

kubectl -n istio-system get QuotaSpecBinding request-count -o yaml

QuotaSpecBinding 把前面的 QuotaSpec 绑定到需要应用限流的服务上。productpage 被显式的绑定到了 request-count 上。因为 QuotaSpecBinding 所属命名空间和这些服务是不一致的,所以这里必须定义每个服务的 namespace。如果去掉最后一行的注释标志,service: '*' 会把所有的服务绑定到 QuotaSpec 上,第一行就无效了。

在浏览器中刷新 productpage 页面。

request-count 配额适用于 productpage ,每 5 秒允许 2 个请求。如果你不断刷新页面,你会看到 RESOURCE_EXHAUSTED:Quota is exhausted for: requestcount。

有条件的速率限制

在上面的例子里,我们有效的把 productpage 的速率限制为每客户端 IP 2 rps。如果我们想要放弃对已登录用户的速率限制。在 bookinfo 的例子中,我们使用 session= Cookie 来标识已登录用户。在真实场景中,可能会使用 JWT token 来完成这一目的。

可以为 quota rule 添加一个基于 cookie 的匹配条件:

kubectl -n istio-system edit rules quota
...
  match: match(request.headers["cookie"], "session=*") == false
...

memquota 或者 redisquota 适配器仅在 session= Cookie 不存在的情况下才会调用。这样一来,已登录用户就不受限制了。已登录用户不受速率限制。

用 jason 的身份登录,重复刷新 productpage。应该不会出现任何问题。

未登录用户受到速率限制。从 jason 的身份登出,重复刷新 productpage,会看到 RESOURCE_EXHAUSTED:Quota is exhausted for: requestcount。

理解速率限制

在前面的例子中演示了 Mixer 根据条件对请求实施速率限制的过程。

每个有名称的 Quota 实例,例如前面的 requestcount,都代表了一套计数器。这一个集合就是所有 Quota dimensions 的笛卡尔积定义的。如果上一个 expiration 区间内的请求数量超过了 maxAmount,Mixer 就会返回 RESOURCE_EXHAUSTED 信息给 Proxy。Proxy 则返回 HTTP 429 给调用方。

memquota 适配器使用一个为亚秒级分辨率的滑动窗口来实现速率限制。

redisquota 适配器能够通过配置来选择使用 ROLLING_WINDOW 或者 FIXED_WINDOW 算法来进行速率限制。

适配器配置中的 maxAmount 设置了关联到 Quota 实例中的所有计数器的缺省限制。如果所有 override 条目都无法匹配到一个请求,就只能使用 maxAmount 限制了。memquota/redisquota 会选择适合请求的第一条 override。override 条目无需定义所有 quota dimension,例如例子中的 0.2 qps 条目在 4 条 quota dimensions 中只选用了三条。

如果要把上面的策略应用到某个命名空间而非整个 Istio 网格,可以把所有 istio-system 替换成为给定的命名空间。

 

 

你可能感兴趣的:(istio)