Z.S.K.'s Records

Istio学习(VirtualService and DestinationRule流量管理)

经过之前的对Istio的学习,大致明白了istio的机制,而要体会istio对流量的神奇魔法,自然离不开 virtualservice 与 destinationrule这两个对象了.

首先,再次说明下两个istio与kubernetes不同的地方:

  1. 在kuberntes中, 服务与服务之间是通过clusterIP(kube-proxy)进行通信的, 而在istio中,对于网格间的两个服务,得益于xDS机制,通过CDS可直接得到EDS, 因此服务与服务之间不再经过clusterIP,而是直接到达对方的一个端点上(PortIP + Port)
  2. 在istio中,对于网格间的服务需要访问网格外的服务,仍然通过 ClusterIP 进行通信,但有一点需要注意:这里并没有 kube-proxy 的参与!Envoy 自己实现了一套流量转发机制,当你访问 ClusterIP 时,Envoy 就把流量转发到具体的 Pod 上去,不需要借助 kube-proxy 的 iptablesipvs 规则
  3. 对于入口流量管理,您可能会问: 为什么不直接使用 Kubernetes Ingress API ? 原因是 Ingress API 无法表达 Istio 的路由需求。 Ingress 试图在不同的 HTTP 代理之间取一个公共的交集,因此只能支持最基本的 HTTP 路由,最终导致需要将代理的其他高级功能放入到注解(annotation)中,而注解的方式在多个代理之间是不兼容的,无法移植。Istio Gateway 通过将L4-L6配置与L7配置分离的方式克服了 Ingress 的这些缺点。 Gateway 只用于配置L4-L6功能(例如,对外公开的端口,TLS 配置),所有主流的L7代理均以统一的方式实现了这些功能。 然后,通过在 Gateway 上绑定 VirtualService 的方式,可以使用标准的 Istio 规则来控制进入 Gateway 的 HTTP 和 TCP 流量

要理解virtualservice与DestinationRule,先划个重点: 在Istio所提供的基本连接和发现基础上,通过virtualservice,能够将请求路由到Istio网格中的特定服务。每个virtualservice由一组DestinationRule组成,这些DestinationRule使Istio能够将virtualservice的每个给定请求匹配到网格内特定的目标服务(或目标服务子集)

通俗来讲就是,virtualservice定义了服务请求满足什么条件应该转发到哪里,而DestinationRule则定义了这些请求具体的路由规则,比如请求目标服务的什么版本, 负载均衡策略,连接限制等.

以下所有的操作都是基于这个demo

VirtualService

一个简单的virtualservice的yaml文件格式如下.

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: service-b
spec:
hosts:
- service-b #客户端访问服务的地址,扩展为 service-b.default.svc.cluster.local
http:
- route:
- destination: # 目标服务,这里不一定是service-b服务,也就是说, 虽然客户端访问service-b,但不一定就需要转到service-b,转发到其它服务也是支持的.
host: service-b #扩展为 service-b.default.svc.cluster.local
subset: v1 #subset 会在destinationRule中使用

VirtualService 映射的就是 Envoy 中的 Http Route Table

需要注意上面hosts字段对应的为kubernetes的service name, 不过这里进行了省略, 在实际使用时会被扩展为FQDN形式的域名.

DestinationRule

对于上面新建的virtualservice,如果没有对应的destinationrule, 则请求service-b会被返回404(BlackHoleCluster), 因为现在还没有v1版本的service-b,路由不可达

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: service-b
spec:
host: service-b #这里的名字需要跟virtualservice中定义的一致
trafficPolicy:
loadBalancer:
simple: RANDOM
subsets:
- name: v1 #这里的名字需要跟virtualservice中定义的一致
labels:
version: v1
- name: v2
labels:
version: v2

DestinationRule映射到 Envoy 的配置文件中就是 Cluster

这样,virtualservice创建的路由即可达了, subset机制类似kubernetes中给对应的pod打label

上面虽然定义了subset存在v2的版本, 但是在virtualservice中并没有指定v2,因此100%的流量转向v1.

当然virtualservice与destinatiorule结合支持非常多的属性, 比如流量分配、熔断机制、 错误注入等机制.

总结: DestinationRule 经常和 VirtualService 结合使用,VirtualService 用到的服务子集 subset 在 DestinationRule 上也有定义。同时,在 VirtualService 上定义了一些规则,在 DestinationRule 上也定义了一些规则。那么,DestinationRule 和 VirtualService 都是用于流量治理的,为什么有些定义在 VirtualService 上,有些定义在 DestinationRule 上呢?
VirtualService 是一个虚拟 Service,描述的是满足什么条件的流量被哪个后端处理。可以对于这样一个 Restful 服务,每个路由规则都对应其中 Resource 中的资源匹配表达式。只是在 VirtualService 中,这个匹配条件不仅仅是路径方法的匹配,还是更开放的 Match 条件。而 DestinationRule 描述的是这个请求到达某个后端后怎么去处理,即所谓目标的规则,类似以上 Restful 服务到达的目的后端。
理解了这两个对象的定义,就不难理解其规则上的设计原理,从而理解负载均衡和熔断等策略为什么被定义在 DestinationRule 上。DestinationRule 定义了满足路由规则的流量到达后端后的访问策略。在 istio 中可以配置目标服务的负载均衡策略、连接池大小、异常实例驱逐规则等功能

实操

服务模型如下:

其中, service-b为3个实例, 分为blue,与green两个版本, github地址在这里

流量分配

service-a访问service-b时, 20%的流量转到版本v-blue, 80%的流量转到版本v-green

注意: subset指定的字段需要在pod上存在对应的label, istio不会自动给pod打上标签. 如果所有的pod上都没有该标签, 则路由不可达

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: service-b-20
spec:
hosts:
- service-b
http:
- route:
- destination:
host: service-b
subset: v-blue
weight: 20
- destination:
host: service-b
subset: v-green
weight: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: service-b-20
spec:
host: service-b
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
subsets:
- name: v-blue
labels:
version: v-blue
- name: v-green
labels:
version: v-green

kubernetes apply -f servicea-to-serviceb-20-80.yaml

生成的pod如下, 可以看到,总共6个pod, 其中有3个pod有label为version=1.5.2,这3个pod是无法响应请求的,

只有verion=v-blue或者v-green的可接受请求.

从kiali上看效果可以看到, 流量的分配大致维持在2/8的比例:

超时重试

请求service-b的v-green版本时,出现5xx、connect-failure时进行重试,最大重试5次, 重试超时时间3s,整个请求超时7s

并不是所有的http code值都能够进行重试, 不能说指定200进行重试, 这个是做不到的, 重试的更多参数可参考这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: service-b-timeout
spec:
hosts:
- service-b
http:
- route:
- destination:
host: service-b
subset: v-blue
weight: 20
- destination:
host: service-b
subset: v-green
weight: 80
retries:
attempts: 5
perTryTimeout: 3s
retryOn: 5xx,connect-failure
timeout: 7s
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: service-b-timeout
spec:
host: service-b
subsets:
- name: v-green
labels:
version: v-green
- name: v-blue
labels:
version: v-blue

由于使用的这个例子已经打好的镜像, 不想再重新折腾了, 就不演示模拟5xx的场景了.

错误注入

从 service-a访问 service-b的version: v-green的所有流量请求延迟5秒钟,并把流量的100%返回400错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: service-b-fault
spec:
hosts:
- service-b
http:
- fault:
delay:
fixedDelay: 5s
percentage: #如果未指定,则所有的请求都将 延时5s
value: 100
abort:
httpStatus: 400
percentage: #如果未指定,则所有的请求都将被返回400
value: 100
route:
- destination:
host: service-b
subset: v-green
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: service-b-fault
spec:
host: service-b
subsets:
- name: v-green
labels:
version: v-green

效果:

从请求时间可以看到, 请求被延迟5s之后直接返回了错误fault filter abort,故障注入起到了效果.

需要注意的是: fault字段的故障注入是有先后顺序的, 如果把http错误跟时延顺序对调一下, 则效果又将不同.

请求熔断

熔断机制是作用在DestinationRule上的,这个很好理解, DistinationRule控制着流量转发的具体对象. 一个典型的例子如下:

对于service-b的v-green版本的请求, 只允许最大一个连接

部署以下规则kubectl apply -f servicea-to-serviceb-circuit-breaker.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: service-b-20
spec:
hosts:
- service-b
http:
- route:
- destination:
host: service-b
subset: v-blue
weight: 20
- destination:
host: service-b
subset: v-green
weight: 80
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: service-b-cb
spec:
host: service-b
subsets:
- name: service-b-blue
labels:
version: v-blue
- name: service-b-green
labels:
version: v-green
trafficPolicy:
connectionPool:
http:
http1MaxPendingRequests: 1
maxRequestsPerConnection: 1
tcp:
maxConnections: 1
outlierDetection:
baseEjectionTime: 10s
consecutiveErrors: 1
interval: 10s
maxEjectionPercent: 100

关于trafficPolicy的更多参数说明, 可参考这里

为了测试效果,需要安装一个测试工具, fortio,这个工具在istio官方的httpbin目录下也存在, 可直接部署

1
kubectl apply -f samples/httpbin/sample-client/fortio-deploy.yaml

效果:

先测试一个连接的情况:

kubectl exec -it fortio-deploy-7cb865f87f-vkkhz -c fortio /usr/bin/fortio -- load -c 1 -qps 0 -n 2 -loglevel Warning http://10.244.0.97:8088/api/v1/greeting

一个连接的情况,发送的两个请求都正常返回

测试三个连接的情况:

kubectl exec -it fortio-deploy-7cb865f87f-vkkhz -c fortio /usr/bin/fortio -- load -c 3 -qps 0 -n 30 -loglevel Warning http://10.244.0.97:8088/api/v1/greeting

上图中显示code -1为100%, service-a有如下红框标出的报错, 说明限制的连接数起到了熔断的作用.

service-a前三条有正常日志,是因为service-a调用的链路里除了调用service-b,还调用了service-c, service-c这条跟没有限制.

当然还有一些如HTTP Redirect,HTTP Rewrite等操作都能在Istio实现, 但一般来讲都会选择在入口处实现, 具体的可参考这里

错误

在使用kiali的过程中, 或多或少会遇到istio的warning提示, 官方的kiali的错误说明列表

More than one Virtual Service for same host

出这个warning提示不影响istio的功能,原因在于存在多个virtualservice作用在service-b这个host上, 虽然Istio将合并配置,但istio建议不要在多个虚拟服务定义中定义相同的部分

这里是因为存在另一个virtualservice,host一列中定义了”*“, 自然包含了service-b这个host,因此重合了

More than one Gateway for the same host port combination

这个warning提示原因同上, 也因存在多个gateway作用于同一个hosts.

通过上面两个提示可以看出, 只要存在一个gateway或者virtualservice,使用了 hosts为”*“的定义,就能出现这个报错.

mTLS settings of a non-local Destination Rule are overridden

原因在于不存在mtls配置

可在spec指定即可.

1
2
3
4
5
spec:
host: service-b
trafficPolicy:
tls:
mode: SIMPLE

ingressgateway 使用 NodePort暴露服务.

如果在测试的时候使用的nodeport的方式暴露服务(不是loadbalance), 在gateway/virtualservice需要自定义域名的时候, 比如官方的bookinfo的例子,如果想通过bookinfo.local:30732/productpage访问 productpage服务, hosts字段必须为”*“, 写bookinfo.local是行不通的,参考官方说明

这是因为当使用浏览器访问bookinfo.local:30732/productpage时(/etc/hosts中指定了域名解析),通过浏览器的debug可以看到请求中的host为Host: bookinfo.local:30732, 到达gateway时发现host中的域名(没有端口)跟请求中的域名比不对,导致404,同时, gateway中不支持域名:端口的形式,会报错, 因此, 这种情况下只能使用”*“

当然,在生产环境中不太可能使用Nodeport的形式暴露服务.同时也会有DNS解析服务, 因此也不太可能有上述问题.我当时测试时没想通耽误了一些时间,因此记录一下.

只定义gateway, 没有virtualservice

最后, virtualservice还可以与gateway结合使用, 通过gateway向外暴露istio服务网格内的服务, 但当只定义了gateway而没有virtualservice时,请求会被转发到blackhole, 返回404, Istio-Gsteway

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$  istioctl proxy-config route $INGRESS_POD -o json  -n istio-system
[
{
"name": "http.80",
"virtualHosts": [
{
"name": "blackhole:80",
"domains": [
"*"
],
"routes": [
{
"match": {
"prefix": "/"
},
"directResponse": {
"status": 404
},
"perFilterConfig": {
"mixer": {}
}
}
]
}
],
"validateClusters": false
}
]

参考文章:

转载请注明出处https://izsk.me

Z.S.K. wechat
Scan Me To Read on Phone
I know you won't do this,but what if you did?