istio 流量管理 - (sunznx) 振翅飞翔
30 July 2019

路由

istio 的路由主要由 3 部分组成:

  • Gateway
  • DestinationRule
  • VirtualService

Gateway 是流量入口
VirtualService 是后端实际要访问的服务
DestinationRule 是对 VirtualService 的分类。在 VritualService 里面通过 VirtualService.spec.http.route.destination.subset 来过滤出需要的 分类(subset)

以 istio 官方文档的 bookinfo 的 reviews 为例

gateway

apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: bookinfo-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - "*"

默认情况下,访问 reviews service 通过 rr 算法,会在 3 个 (v1, v2, v3)service 中随机返回命中的一个。

review-destinationRule

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: reviews
spec:
  host: reviews
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
  - name: v3
    labels:
      version: v3

review-virtualservice

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

通过上面的设置,就能让所有经过 reviews 的流量全走到 v1 这个 subset 里面。修改 review-virtualservice 如下,可以使 http 请求 header 中含有 end-user 为 jason 的流量全走到 subset v2

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: ratings
spec:
  hosts:
  - ratings
  http:
  - match:
    - headers:
        end-user:
          exact: jason
    fault:
      delay:
        percentage:
          value: 100.0
        fixedDelay: 7s
    route:
    - destination:
        host: ratings
        subset: v1
  - route:
    - destination:
        host: ratings
        subset: v1

关于路由更详细的文档 https://istio.io/docs/reference/config/networking/v1alpha3/virtual-service

错误注入

熔断

熔断的实现是通过设置 istio DestinationRule 的 trafficPolicy 来实现的。istio 的文档 httpbin 例子里面提供了示例:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  trafficPolicy:
    connectionPool:
      tcp:
        maxConnections: 1
      http:
        http1MaxPendingRequests: 1
        maxRequestsPerConnection: 1
    outlierDetection:
      consecutiveErrors: 1
      interval: 1s
      baseEjectionTime: 3m
      maxEjectionPercent: 100

connectionPool 表示连接达到 tcp/http connection 达到多少个之后就不再处理请求
outlierDetection 表示当 client 达到什么条件之后,不再处理这个 client 的连接
outlierDetection.interval 表示多久检查一次,算出符合条件的 client
outlierDetection.consecutiveErrors 表示连续达到多少次之后视为,达到条件(达到条件之后,便不再处理这个 client 的连接)
outlierDetection.baseEjectionTime 表示多久之后重新启用已经停用的 client

https://istio.io/docs/reference/config/networking/v1alpha3/destination-rule/

流量复制

设置 v1 v2 两个 http 服务,并设置流量全部走到 v1

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: httpbin-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: httpbin
        version: v1
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
        ports:
        - containerPort: 80

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: httpbin-v2
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: httpbin
        version: v2
    spec:
      containers:
      - image: docker.io/kennethreitz/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
        ports:
        - containerPort: 80

---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
    - httpbin
  http:
  - route:
    - destination:
        host: httpbin
        subset: v1
      weight: 100
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: httpbin
spec:
  host: httpbin
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

开始测试

$ kubectl run busybox --image=busybox --restart=Never --image-pull-policy=IfNotPresent -- sleep 3600
$ kubectl exec -it busybox -c busybox -- wget -q -O - http://httpbin:8000/headers

$ export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
$ kubectl logs -f $V1_POD -c httpbin
[2019-07-30 03:02:23 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2019-07-30 03:02:23 +0000] [1] [INFO] Listening at: http://0.0.0.0:80 (1)
[2019-07-30 03:02:23 +0000] [1] [INFO] Using worker: sync
[2019-07-30 03:02:23 +0000] [8] [INFO] Booting worker with pid: 8
127.0.0.1 - - [30/Jul/2019:03:04:42 +0000] "GET /headers HTTP/1.1" 200 274 "-" "Wget"

$ export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
$ kubectl logs -f $V2_POD -c httpbin
[2019-07-30 03:02:32 +0000] [1] [INFO] Starting gunicorn 19.9.0
[2019-07-30 03:02:32 +0000] [1] [INFO] Listening at: http://0.0.0.0:80 (1)
[2019-07-30 03:02:32 +0000] [1] [INFO] Using worker: sync
[2019-07-30 03:02:32 +0000] [8] [INFO] Booting worker with pid: 8

可以看到流量全走到了 v1 版本

设置 mirror

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  hosts:
    - httpbin
  http:
  - route:
    - destination:
        host: httpbin
        subset: v1
      weight: 100
    mirror:
      host: httpbin
      subset: v2
$ kubectl exec -it busybox -c busybox -- wget -q -O - http://httpbin:8000/headers

再观察 V1_POD  和 V2_POD 的 log 可以看到 V2_POD 里面也有请求