限流是一种限制发送到服务端的请求数量的机制,Envoy使用令牌桶算法实现本地限流。本文介绍如何在ASM中配置本地限流。

前提条件

  • 已创建ASM实例,且ASM实例符合以下要求:
    • 如果您使用的是ASM商业版(专业版),需要版本≥v1.14.3。关于升级ASM实例的具体操作,请参见升级ASM实例
    • 如果您使用的是ASM标准版,仅支持Istio原生方式配置本地限流功能,且需要版本≥v1.9。不同Istio版本需参考相应版本文档,关于最新的Istio版本配置本地限流功能的具体操作,请参见Enabling Rate Limits using Envoy
  • 已创建Kubernetes托管版集群。具体操作,请参见创建Kubernetes托管版集群
  • 已添加集群到ASM实例。具体操作,请参见添加集群到ASM实例
  • 已为Kubernetes集群中的default命名空间开启自动注入。具体操作,请参见多种方式灵活开启自动注入

什么是限流?

限流的概念

限流是一种限制发送到服务端的请求数量的机制。它指定客户端在给定时间段内可以向服务端发送的最大请求数,通常表示为一段时间内的请求数,例如每分钟300个请求或每秒10个请求等。限流的目的是防止服务因来自同一客户端IP或来自任何客户端的全局请求而过载。

以每分钟300个请求的限流机制为例,向服务发送301个请求,限流器将拒绝第301个请求。同时,限流器会返回一个429 HTTP状态码(Too Many Requests),并在请求到达服务之前进行拒绝。

限流的方式

Envoy代理实现限流主要有以下两种方式:

方式说明
全局(或分布式)限流
  • 允许跨多个服务对请求进行请求速率限制,这种方式基于集群中所有服务共享集中的限流服务实现。通常这种全局限流服务需要一个外部组件,例如Redis数据库等。
  • 全局限流的典型用例是当多个请求端向较少数量的服务端发送请求时,在这种情况下,多个客户请求端可能会压垮服务端,而全局限流可以帮助防止级联故障。例如,您可以在入口网关配置全局限流,并控制进入网格的请求总数。一旦这些请求在网格内时,您可以使用本地限流来管理发送到特定服务的请求数量。
本地限流
  • 基于每个Envoy进程配置,即每个注入了Envoy代理的Pod。相比全局限流来说,本地限流的配置更简单,不需要额外的组件。本地限流的优先级高于全局限流,当同时使用本地速率限制器和全局速率限制器时,首先应用本地速率限制器进行限制,如果未达到本地速率限制,则应用全局速率限制器进行限制。示例场景如下:
    • 假设本地限流对特定客户端IP的请求限制为每分钟50个请求,全局请求限制数为每分钟60个请求。客户端发送超过50个请求,则本地限流将拒绝该请求,即使全局限流尚未达到限制。
    • 假设本地限流对特定客户端IP的请求限制为每分钟50个请求,全局请求限制数为每分钟40个请求。客户端发送超过40个请求,虽然未达到本地限流,但是已达到全局限流,因此该请求被拒绝。
  • 本地限流如果有多个副本,则每个副本都有各自的速率限制器,也就是说如果您在一个副本上被限流,在另一个副本上可能不会被限流。

本地限流的工作原理

Envoy使用令牌桶算法实现本地限流。令牌桶算法是一种限制发送到服务端的请求数量的方法,基于一定数量的令牌桶。存储桶以恒定的速率不断填充令牌,当向服务发送请求时,会从存储桶中删除一个令牌。如果存储桶为空,则请求将被拒绝。通常需要指定以下内容:
  • 桶被填充的速率(填充间隔)。
  • 每个填充间隔添加到桶中的令牌数。

默认情况下,当速率限制器拒绝请求,并设置x-envoy-ratelimited响应标头时,Envoy会发送状态码为429的HTTP响应。您也可以将速率限制器配置为返回自定义HTTP状态代码,并配置其他响应标头。

此外,使用速率限制器有以下两个重要概念:
  • 启用速率限制器:表示正在对速率限制器进行配置,但速率限制器并未应用于请求。
  • 执行速率限制器:表示对请求应用或执行速率限制器。

将这两个值表示为传入请求的百分比,例如可以设置为10%的请求启用速率限制器,并为5%的请求强制执行。按照这种方式,可以逐步推出限流,并在对所有请求强制执行之前对其进行测试。

配置本地限流

步骤一:部署示例服务

  1. 使用以下内容,创建httpbin.yaml
    ##################################################################################################
    # httpbin Service示例。
    ##################################################################################################
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: httpbin
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: httpbin
      labels:
        app: httpbin
        service: httpbin
    spec:
      ports:
      - name: http
        port: 8000
        targetPort: 80
      selector:
        app: httpbin
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: httpbin
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: httpbin
          version: v1
      template:
        metadata:
          labels:
            app: httpbin
            version: v1
        spec:
          serviceAccountName: httpbin
          containers:
          - image: docker.io/kennethreitz/httpbin
            imagePullPolicy: IfNotPresent
            name: httpbin
            ports:
            - containerPort: 80
  2. 执行以下命令,创建httpbin应用。
    kubectl apply -f httpbin.yaml -n default
  3. 使用以下内容,创建sleep.yaml
    ##################################################################################################
    # Sleep Service示例。
    ##################################################################################################
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: sleep
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: sleep
      labels:
        app: sleep
        service: sleep
    spec:
      ports:
      - port: 80
        name: http
      selector:
        app: sleep
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: sleep
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: sleep
      template:
        metadata:
          labels:
            app: sleep
        spec:
          terminationGracePeriodSeconds: 0
          serviceAccountName: sleep
          containers:
          - name: sleep
            image: curlimages/curl
            command: ["/bin/sleep", "infinity"]
            imagePullPolicy: IfNotPresent
            volumeMounts:
            - mountPath: /etc/sleep/tls
              name: secret-volume
          volumes:
          - name: secret-volume
            secret:
              secretName: sleep-secret
              optional: true
    ---
  4. 执行以下命令,创建sleep应用。
    kubectl apply -f sleep.yaml -n default
  5. 进入sleep Pod,执行以下命令,向httpbin服务发送多个请求。
    while true; do curl http://httpbin:8000/headers; done

步骤二:定义与执行限流策略

当速率限制器拒绝请求时,您可以使用系统默认的响应信息或自定义响应信息。

  1. 登录ASM控制台
  2. 在左侧导航栏,选择服务网格 > 网格管理
  3. 网格管理页面,找到待配置的实例,单击实例的名称或在操作列中单击管理
  4. 在网格详情页面左侧导航栏,选择流量管理中心 > 本地限流
  5. 本地限流页面,单击新建,配置命名空间default,选择任意场景模板,按需选择以下YAML进行配置。
    • 默认响应信息:
      apiVersion: istio.alibabacloud.com/v1beta1
      kind: ASMLocalRateLimiter
      metadata:
        name: httpbin
        namespace: default
      spec:
        workloadSelector:
          labels:
            app: httpbin
        configs:
          - match:
              vhost:
                name: "*"
                port: 8000
                route:
                  header_match:
                  - name: ":path"
                    prefix_match: "/"
            limit:
               fill_interval:
                  seconds: 60
               quota: 10
    • 自定义响应信息:
      apiVersion: istio.alibabacloud.com/v1beta1
      kind: ASMLocalRateLimiter
      metadata:
        name: httpbin
        namespace: default
      spec:
        workloadSelector:
          labels:
            app: httpbin
        configs:
          - match:
              vhost:
                name: "*"
                port: 8000
                route:
                  header_match:
                  - name: ":path"
                    prefix_match: "/"
            limit:
               fill_interval:
                  seconds: 60
               quota: 10
               custom_response_body: '{"custom": "custom message", "message": "Your request be limited" }'
               response_header_to_add:
                 x-rate-limited: 'TOO_MANY_REQUESTS'
                 x-local-rate-limit: 'enabled'
  6. 执行以下命令,发送10个请求。
    curl -v httpbin:8000/headers
    预期输出:
    • 默认响应信息:
      *   Trying 192.168.250.89:8000...
      * Connected to httpbin (192.168.250.89) port 8000 (#0)
      > GET /headers HTTP/1.1
      > Host: httpbin:8000
      > User-Agent: curl/7.85.0-DEV
      > Accept: */*
      >
      * Mark bundle as not supporting multiuse
      < HTTP/1.1 429 Too Many Requests
      < x-local-rate-limit: true
      < content-length: 18
      < content-type: text/plain
      < date: Tue, 27 Sep 2022 07:42:08 GMT
      < server: envoy
      < x-envoy-upstream-service-time: 0
      由预期输出得到,返回x-local-rate-limit响应头和HTTP 429的状态码。
    • 自定义响应信息:
      *   Trying 192.168.250.89:8000...
      * Connected to httpbin (192.168.250.89) port 8000 (#0)
      > GET /headers HTTP/1.1
      > Host: httpbin:8000
      > User-Agent: curl/7.85.0-DEV
      > Accept: */*
      >
      * Mark bundle as not supporting multiuse
      < HTTP/1.1 429 Too Many Requests
      < x-local-rate-limit: enabled
      < x-rate-limited: TOO_MANY_REQUESTS
      < content-length: 67
      < content-type: text/plain
      < date: Tue, 27 Sep 2022 11:45:45 GMT
      < server: envoy
      < x-envoy-upstream-service-time: 0
      <
      * Connection #0 to host httpbin left intact
      {"custom": "custom message", "message": "Your request be limited" }
      由预期输出得到,返回自定义的响应头和HTTP状态码。

限流相关指标

Envoy中的限流功能会产生以下指标:
Metric描述
<stat_prefix>.http_local_rate_limit.enabled触发限流的请求总数。
<stat_prefix>.http_local_rate_limit.ok来自令牌桶的限流响应总数。
<stat_prefix>.http_local_rate_limit.rate_limited没有令牌的响应总数(但不一定强制执行)。
<stat_prefix>.http_local_rate_limit.enforced收到限流响应的总数(例如返回429)。
以上指标以<stat_prefix>.http_local_rate_limit为前缀,其中<stat_prefix>是指在stat_prefix字段中配置的值(例如http_local_rate_limiter)。

在Prometheus中查看限流相关指标

  1. 执行以下命令,启用Envoy收集统计信息。
    在Deployment YAML中的spec.template.metadata下添加annotations,来启用Envoy收集统计信息。
    kubectl patch deployment httpbin --type merge -p '{"spec":{"template":{"metadata":{"annotations":{"proxy.istio.io/config":"proxyStatsMatcher:\n  inclusionRegexps:\n  - \".*http_local_rate_limit.*\""}}}}}'
  2. Pod自动重启后,向httpbin服务发送多个请求。
    curl -v httpbin:8000/headers
    预期输出:
    envoy_http_local_rate_limiter_http_local_rate_limit_enabled{} 37
    
    envoy_http_local_rate_limiter_http_local_rate_limit_enforced{} 17
    
    envoy_http_local_rate_limiter_http_local_rate_limit_ok{} 20
    
    envoy_http_local_rate_limiter_http_local_rate_limit_rate_limited{} 17
  3. 通过自建Prometheus监控或ARMS控制台查看限流相关指标。
    使用ARMS控制台查看限流相关指标的具体操作如下:
    1. 登录ARMS控制台,在左侧导航栏选择Prometheus监控 > Prometheus实例列表
    2. Prometheus监控页面单击目标实例,在左侧导航栏单击大盘列表,然后单击目标大盘。
    3. 在左侧导航栏单击Explore图标,查看相关指标。
      指标示例如下:限流指标