# Istio 的频率限制

本来这个应该是作为第三天“零散功能点”介绍的，结果目标规则部分遇到一个 bug 一直没得到修正，就拖
着了——然后后来发现自己这个想法挺无知的——零散的功能点非常多，非常大，而且文档非常弱，很难搞，只好
逐个介绍了。

## 简介

调用频率限制这个功能其实也是比较常见的东西了。这里就不多做介绍了。下面简单粗暴的介绍一下测试要完
成的目标。

测试中我们将使用两个服务，服务叫 workload，客户端叫 sleep，workload 服务正常返回群众喜闻乐见
的 “Hello World”，而 sleep 仅用来做 Shell 方便测试。

测试过程将为 workload 建立规则：

- 对于任意访问，十秒钟之内仅能访问两次；
- 对于来自 sleep 的访问，十秒钟之内仅能访问一次

> 下面所有内容都在 default 命名空间进行

## 建立测试环境

编辑 workload.yaml 以及 sleep.yaml 之后，
我们使用如下命令运行(这两部分源码，只有些乌七八糟的标签和命名有点问题，其他很普通。
见最后页)

    istioctl kube-inject -f workload.yaml | kubectl apply -f -
    istioctl kube-inject -f sleep.yaml | kubectl apply -f -

`kubectl get pods` 确定 Pod 成功启动，也可以使用 curl 测试 NodePort，会得到 40x 的错误
页面。

### 建立规则

- quota.yaml

这部分内容规则来自：`https://istio.io/docs/reference/config/mixer/template/quota.html`

这个对象用于设置检测服务来源、目标的标准维度，下面我们设置的是利用服务名称来检测。

> 这部分可能存在 bug，官方介绍的 source 定义是 `source.labels["app"] | source.service | "unknown"`
> 我的理解是说首先获取服务 Pod 的 app 标签内容，如果没有，再获取服务名称。但是后面我们会看到，
> 删除`source.labels["app"]`的定义之后，最终生效的依旧是 app 标签，这也是为什么给 workload 加上这么多奇怪标签的原因。

~~~yaml
apiVersion: config.istio.io/v1alpha2
kind: quota
metadata:
  name: requestcount
spec:
  dimensions:
    source: source.service | "unknown"
    sourceVersion: source.labels["version"] | "unknown"
    destination:  destination.service | "unknown"
~~~

- handler.yaml

这一对象的定义来自：`https://istio.io/docs/reference/config/mixer/adapters/memquota.html`

下面的代码对应我们之前的目的，任意访问都是每 10 秒钟两次，而特定源和目标的访问则是每 10 秒钟一
次。

其中 quotas 部分，name 字段的来源是上面的 quota.yaml

> 这个不知道是不是 bug，必须写上半个 fqdn。

dimentions 字段则需要使用 quota.yaml 定义的维度。这里的维度可以比 quota.yaml 中定义的
维度宽松（也就是少，例如定义三个，只使用其中的两个），可以理解，这也符合匹配的规则。

~~~yaml
apiVersion: config.istio.io/v1alpha2
kind: memquota
metadata:
  name: handler
spec:
  quotas:
  - name: requestcount.quota.default
    maxAmount: 2
    validDuration: 10s
    overrides:
    - dimensions:
        destination: workload-pod.default.svc.cluster.local
        source: sleep.default.svc.cluster.local
      maxAmount: 1
      validDuration: 10s
~~~

- rule.yaml

这部分的定义参考：`https://istio.io/docs/reference/config/mixer/policy-and-telemetry-rules.html#istio.mixer.v1.config.Rule`

这个对象的作用就是，对于符合筛选条件的服务调用，使用对应的 handler 进行处理。

这里的 handler 和 requestcount 两个字段的内容，也需要是`实例名称.对象类型`的方式。

~~~yaml
apiVersion: config.istio.io/v1alpha2
kind: rule
metadata:
  name: quota
spec:
  actions:
  - handler: handler.memquota
    instances:
    - requestcount.quota
~~~

## 测试运行

### 集群外

使用 curl 重复访问 NodePort 三次，会发现第三次出现：`RESOURCE_EXHAUSTED:Quota is exhausted for: RequestCount#`

这说明我们设置的通用规则生效了。

### 集群内

在 sleep pod 中同样执行 curl 三次，发现第二次就开始出现超限说明。

上面两种情况，在超过时间窗口限制之后，都会自动恢复。

## 幕后

定义 quota 对象，在系统中会展现为一系列的计数器，计数器的维度就是quota 中的维度的笛卡尔积。如果在 validDuration 的时间窗口过期之前调用次数超过了 maxAmount 规定，Mixer 就会返回 RESOURCE_EXHAUSTED 给 Envoy，Envoy 则会反馈 429 代码给调用方。

## 结论

文档不完善的开源系统。。坑真大啊！

## 源码

### Workload 的 Deployment 和 Service：

~~~yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: workload-app
    version: http200
  name: workload-200
spec:
  replicas: 1
  selector:
    matchLabels:
      app: workload-pod
      version: http200
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: workload-pod
        version: http200
    spec:
      containers:
      - image: php:7.0-apache
        imagePullPolicy: IfNotPresent
        name: http200
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
      dnsPolicy: ClusterFirst
      restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: workload-service
    version: http200
  name: workload-200
spec:
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 80
    nodePort: 30200
  selector:
    app: workload-pod
    version: http200
  sessionAffinity: None
  type: NodePort
~~~

### Sleep 的定义

~~~yaml
apiVersion: v1
kind: Service
metadata:
  name: sleep
  labels:
    app: sleep
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: sleep
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sleep
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: sleep
    spec:
      containers:
      - name: sleep
        image: tutum/curl
        command: ["/bin/sleep","infinity"]
        imagePullPolicy: IfNotPresent
~~~
