# istio 三日谈之二 路由规则

路由控制是 istio 的最常用功能了，经过前面的准备，我们已经基本可以进行这些内容的尝试了。

注意下面的路由规则都忽略了对来源的过滤，会显得比较呆板或者说没用，但是在加入过滤条件之后，就完全不可同日而语了。具体的过滤规则的写法可以参考官方文档或者 istio 中的 bookinfo 实例。

## 创建 frontend-v2

为了展示路由分配的能力，我们首先创建一个名为`frontend-v2`的`Deployment`，这个`deploy`跟之前的 v1 共享同一个 PVC，也就是共享同一套页面。利用环境变量来控制输出。

```yaml
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: frontend-v2
spec:
  replicas: 1
  template:
    metadata:
      labels:
        name: frontend
        version: "2"
    spec:
      containers:
      - name: php
        image: 10.211.55.86:5000/php:7.1.7-apache
        ports:
        - containerPort: 80
          protocol: TCP
        volumeMounts:
        - name: wwwroot
          mountPath: /var/www/html
        env:
        - name: "SERVICE_VERSION"
          value: "2"
      volumes:
      - name: wwwroot
        persistentVolumeClaim:
          claimName: frontend-v1
```

同前面的 v1 一样，这个`Deployment`也需要使用`istioctl kube-inject`进行注入，最后使用`kubectl apply -f` 运行。

## 流量分配

### 首先创建如下路由规则

**default.yaml**

```yaml
type: route-rule
name: frontend-default
spec:
  destination: svc-frontend.default.svc.cluster.local
  precedence: 1
  route:
  - tags:
      version: "2"
    weight: 100
```

* destination：必须是服务的 fqdn
    
* precedence：整数，优先级，越大越先
    
* route：数组
    
    * tag：**Pod** 的标签选择器。
        
    * weigh: 整数，权重，分配到当前路由的比率。
        

创建之后，使用`istioctl create -f default.yaml`，创建这一规则。接下来我们使用`kubectl exec -it`进入 tool pod 进行测试：

```plaintext
$ curl svc-frontend/index.php

-----------------------------
From: frontend-797054967-r12m5
Version: 1
```

返回内容表明这一服务调用的是最初的 v1 版本的 frontend。

接下来修改 default.yaml 的`version "1"`为`version "2"`，然后用`istioctl replace -f default.yaml`更新路由规则。再次验证：

```plaintext
$ curl svc-frontend/index.php

-----------------------------
From: frontend-v2-90739004-xpmrn
Version: 2
```

这里可以看到，这一响应来自于 v2 版本的 Pod，并且返回的版本号也是 2。

然后我们再次修改路由规则，测试一下路由分配：

```yaml
type: route-rule
name: frontend-default
spec:
  destination: svc-frontend.default.svc.cluster.local
  precedence: 1
  route:
  - tags:
      version: "2"
    weight: 10
  - tags:
      version: "1"
    weight: 90
```

根据上面的路由规则，对 svc-frontend 这一服务的访问，应该有 10% 流量分给版本 2，其余的 90% 分配给了版本 1。我们在 tool pod 中使用如下脚本测试这一分配：

```bash
#!/bin/bash
for i in {1..100}
do
  curl -s svc-frontend/index.php | grep Version
done
```

执行效果：

```plaintext
$  ./curl.batch.sh  | grep 2 | wc -l
10
```

可以看到，完全符合之前我们的路由设置。

## 超时策略

为了保障服务质量，我们有时会要求对某些服务的返回时间进行限制

前面提到，我们生成了一个`delay.php`，用于进行延时测试，文件内容如下：

```php
<?php
header("Content-type: text/plain");
sleep(4);
echo "\n-----------------------------\n";
echo "\nFrom: ".gethostname()."\n";
echo "Version: ".$_ENV['SERVICE_VERSION']."\n";
```

正常执行`time curl -s svc-frontend/delay.php`，会返回如下结果：

```plaintext
-----------------------------
From: frontend-797054967-r12m5
Version: 1

real    0m4.025s
user    0m0.005s
sys     0m0.004s
```

我们在这里加入一个策略，两秒超时：

```yaml
type: route-rule
name: front-timeout
spec:
  destination: svc-frontend.default.svc.cluster.local
  precedence: 9
  route:
  - tags:
      version: "1"
  httpReqTimeout:
    simpleTimeout:
      timeout: 2s
```

再次测试，则会返回超时的结果：

```plaintext
upstream request timeout
real    0m2.015s
user    0m0.006s
sys     0m0.003s
```

## 重试策略

在服务超时的时候，我们可能会希望请求自动重试。

这一测试要求保留前面的超时策略，以便形成失败结果：

策略文件如下：

```yaml
type: route-rule
name: front-timeout
spec:
  destination: svc-frontend.default.svc.cluster.local
  precedence: 9
  route:
  - tags:
      version: "1"
  httpReqRetries:
    simpleRetry:
      attempts: 3
      perTryTimeout: 2s
```

这里表示每次尝试的超时时间是两秒，重试三次。使用 curl 测试：

```plaintext
$ time curl -s svc-frontend/delay.php
upstream request timeout
real    0m8.136s
user    0m0.005s
sys     0m0.006s
```

时间为 8 秒，相当于四次超时的时间。

## Rewrite

和反向代理的情况类似，有时我们需要对进入服务的 URL 进行重写，下面的路由策略会将 `/front` 替换为 `/` 进行访问：

```yaml
type: route-rule
name: front-timeout
spec:
  destination: svc-frontend.default.svc.cluster.local
  precedence: 9
  match:
    httpHeaders:
      uri:
        prefix: "/front/"
  rewrite:
    uri: "/"
  route:
  - tags:
      version: "1"
```

注意这里对`uri`的操作，是使用 rewrite 中的 uri 替换 match 中的 uri，二者的对应关系是强制的。

在 tool 里面进行测试访问：

```plaintext
$ curl http://svc-frontend/front/index.php
-----------------------------

From: frontend-797054967-r12m5
Version: 1
```

## 故障注入

微服务测试过程中，能够自动生成一些错误，无疑是个很有帮助的事情。目前 istio 支持两种故障的模拟：时延和中断

## 时延

下面的规则会为每个对该服务的请求都生成 7 秒的时延：

```yaml
type: route-rule
name: fdelay
spec:
  destination: svc-frontend.default.svc.cluster.local
  precedence: 9
  route:
  - tags:
      version: "1"
  httpFault:
    delay:
      percent: 100
      fixedDelay: 7s
```

* percent: 产生时延的比例
    
* fixedDelay: 时间
    

使用 curl 验证，可以看到的确多出了 7 秒的处理时间。

```plaintext
$ time curl -s http://svc-frontend/index.php
-----------------------------

From: frontend-797054967-r12m5
Version: 1

real    0m7.028s
user    0m0.007s
sys     0m0.003s
```

## 中断

这一功能可以模拟服务中断的情景，下面的 yaml 定义了该服务 100% 会返回 403 错误。

```yaml
type: route-rule
name: fdelay
spec:
  destination: svc-frontend.default.svc.cluster.local
  precedence: 9
  route:
  - tags:
      version: "1"
  httpFault:
    abort:
      percent: 100
      httpStatus: 403
```

curl 的验证结果如下：

```plaintext
$  curl -v svc-frontend/error.php
* Connected to svc-frontend (10.100.186.68) port 80 (#0)
> GET /error.php HTTP/1.1
> User-Agent: curl/7.38.0
> Host: svc-frontend
> Accept: */*
>
< HTTP/1.1 403 Forbidden
< date: Fri, 28 Jul 2017 01:27:52 GMT
* Server envoy is not blacklisted
< server: envoy
< content-length: 0
<
* Connection #0 to host svc-frontend left intact
```
