Istio(二)通过Istio实现服务治理

环境信息:

组件 版本
Kubernetes 1.15.5
Istio 1.4.2
helm 2.16.1

服务治理

sidecar的注入

前面提到,通过ServiceMesh进行微服务治理和传统的SDK的区别在于,ServiceMesh主要是依靠在应用POD中附加透明代理方式实现。下面介绍下istio是如何实现透明代理注入的。

sidecar的注入方式有两种:
1、手动方式注入
手动注入其实就是修改deployment的pod template添加另外一个sidecar容器

1
istioctl kube-inject -f samples/sleep/sleep.yaml | kubectl apply -f -

2、使用自动方式注入
原理:
Kubernetes 调用时,admissionregistration.k8s.io/v1beta1会进行配置。会在带有 istio-injection=enabled 标签的命名空间中选择 Pod进行sidercar注入
配置sidecar自动注入是使用了mutating webhook admission controller实现的,在Kubernetes1.9+的版本才实现此功能,需要对pod实现此功能需要给对应的namespace打上istio-injection=enabled 的标签

1
kubectl label namespace default istio-injection=enabled

查看是否生效

1
2
3
4
5
6
kubectl get namespace -L istio-injection
NAME STATUS AGE ISTIO-INJECTION
default Active 20h enabled
istio-system Active 19h
kube-public Active 20h
kube-system Active 20h

这样就会在 Pod 创建时触发 Sidecar 的注入过程了。
运行测试容器

1
kubectl apply -f samples/sleep/sleep.yaml

查看POD内是否有两个container

1
2
3
kubectl get pod
NAME READY STATUS RESTARTS AGE
sleep-86cf99dfd6-ffmct 2/2 Running 0 13m

查看POD的详细信息,发现多了个istio-proxy的container及其配置信息

1
kubectl describe pod/sleep-86cf99dfd6-ffmct

需要关闭自动注入功能,只需要将这个标签去掉即可

1
kubectl label namespace default istio-injection-

然后新建的POD就不带sidercar容器了

概念讲解

Gateway: 服务入口流量控制,可与VirtualService绑定然后通过Istio规则来控制流量,早期使用kubernetes的ingress,后因为ingress的功能原因,又独立弄了套istio-Gateway,Istio Gateway 只配置四层到六层的功能,通常也会绑定VirtualServer实现七层转发规则。

VirtualService: 定义转发到后端应用的路由规则,比如流量拆分百分比。

DestinationRule:所定义了经过VirtualService处理之后的流量的访问策略。如定义负载均衡配置、连接池大小以及外部检测(用于在负载均衡池中对不健康主机进行识别和驱逐)配置。

bookinfo应用部署

以官方微服务应用Bookinfo为例演示istio的应用治理能力
bookinfo 应用程序显示的有关书籍的信息,类似于在线书店的单个商品。应用页面上显示的是书籍的描述、书籍详细信息(ISBN,页数等)以及书评。

bookinfo 应用一共包含四个微服务:Productpage、Details、Reviews、Ratings。

Productpage: 使用 Python 开发,负责展示书籍的名称和书籍的简介。
Details: 使用 Ruby 开发,负责展示书籍的详细信息。
Reviews: 使用 Java开发,负责显示书评(Reviews分为3个版本分别对应V1、V2、V3对应的显示就是没有星星,黑星星、红星星。
Ratings: 使用 Node.js 开发,负责显示书籍的评星。

部署bookinfo

1
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml

1
2
3
4
5
6
7
8
kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
details-v1 1/1 1 1 13m
productpage-v1 1/1 1 1 13m
ratings-v1 1/1 1 1 13m
reviews-v1 1/1 1 1 13m
reviews-v2 1/1 1 1 13m
reviews-v3 1/1 1 1 13m

通过Istio实现bookinfo服务治理

通过istio-Gateway暴露bookinfo

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
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: bookinfo-gateway
spec:
selector:
istio: ingressgateway # 关联istio-system namespace下istio-ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway #关联Gateway对象
http:
- match:
- uri:
exact: /productpage #精确匹配
- uri:
prefix: /static #前缀匹配
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage.default.svc.cluster.local #对应kubernetes的Service,注意使用服务的短名称时Istio会根据规则所在的命名空间来处理这一名称,而非服务所在的命名空间。
port:
number: 9080
EOF

访问istio-gateway对应的nodeport端口

1
2
3
kubectl get svc/istio-ingressgateway -n istio-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway NodePort 10.43.187.106 <none> 15020:30485/TCP,80:31380/TCP,443:31390/TCP,31400:31400/TCP,15029:31464/TCP,15030:31827/TCP,15031:30522/TCP,15032:31907/TCP,15443:32151/TCP 5h38m

如访问:http://172.16.1.6:31380/productpage,
出现bookinfo页

左侧评Reviews分为3个版本分别对应V1、V2、V3对应的显示就是没有星星,黑星星、红星星。可以通过刷新页面进行切换显示

V1 空星星显示页

V2 黑星星显示页

V3 红星星显示页

通过kiali查看流量拓扑
创建kiali登录的帐号和密码

1
kubectl create secret generic kiali-secret -n istio-system --from-literal "username=admin" --from-literal "passphrase=admin"

动态请求路由

配置路由请求策略将全部访问流量指向reviews v1版本

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
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews.default.svc.cluster.local
subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: reviews
spec:
host: reviews.default.svc.cluster.local
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
- name: v3
labels:
version: v3
EOF

DestinationRule内定义与后端服务的关联,这里的v1、v2、v3对应的label实际上就是对应kubernetes的workload的label

1
2
3
4
5
6
7
8
kubectl get deployment --show-labels
NAME READY UP-TO-DATE AVAILABLE AGE LABELS
ratings-v1 1/1 1 1 23h app=ratings,version=v1
reviews-v1 1/1 1 1 23h app=reviews,version=v1
reviews-v2 1/1 1 1 23h app=reviews,version=v2
reviews-v3 1/1 1 1 23h app=reviews,version=v3

然后在去访问bookinfo页面发现Book Reviews处怎么刷新都还是空白
打开Kiali可以看见绿色代表流量走向的箭头已经指向reviews v1,对应的是

查看kiali查看流量拓扑,流量都指向V1

实现灰度发布

在这里实现两种灰度发布的方式

方式一:通过http的head将请求路由到不同的后端服务上
比如我们这里将用户标识进行请求路由,比如将用户名为jason的请求路由到Reviews service v2的版本上其他用户还是轮询访问

清空之前定义的规则

1
kubectl delete VirtualService/reviews

然后定义新规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- match:
- headers:
end-user:
exact: jason
route:
- destination:
host: reviews
subset: v2
- route:
- destination:
host: reviews
subset: v1
EOF

规则定义在请求头的end-user字段为Jason时将请求转发到Reviews-server v2版本
在UI上通过sign in登录输入用户名jason,不用输入密码刷新页面可以看见
无论怎么刷新都还是黑星星
退出登录或用其他用户名登录访问都还是Reviews-server v1版本(没星星)

方式二:将访问流量分比例,比如百分之80流量访问Reviews-server v1版本,百分之20流量访问Reviews-server v2版本
先将刚才demo的规则清除

1
kubectl delete virtualservice/reviews

定义新的路由规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: reviews
spec:
hosts:
- reviews
http:
- route:
- destination:
host: reviews
subset: v1
weight: 80
- destination:
host: reviews
subset: v2
weight: 20
EOF

不断刷新页面,你会发现Review只会显示没有星星和黑星星,并且黑星星的显示次数比没星星要少很多。
查看kiali看见流量走向Reviews-server v1和v2版本

熔断

在微服务中,服务被拆成一个个服务模块,各个服务模块之间有大量的调用,为了保证不会因为某个服务访问流量过载而影响其他服务导致大规模集群故障,所以通过熔断机制来保证服务的可靠性。

熔断的实现根我们日常生活中电路中的保险开关是一样的,通过熔断器对应用服务进行检测,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。

熔断器配置:在时间窗口内,接口调用超时比率达到一个阈值,会开启熔断。进入熔断状态后,后续对该服务接口的调用会被拒绝,直接进入服务降级模式。

熔断恢复:当经过了对应的时间之后,服务将从熔断状态恢复过来,再次接受调用。

通过istio实现熔断
启动httpbin测试应用
httpbin是一个web服务,我们将它是部署到kubernetes中然后通过fortio模拟请求访问

1
2
kubectl apply -f samples/httpbin/httpbin.yaml

创建断路器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
cat <<EOF | kubectl create -f -
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
EOF

在本步骤中,我们可以理解为Istio的熔断功能主要是通过在链接池中加入上述三个参数:
· MaxConnections定义了到目标主机的 HTTP1/TCP 最大连接数;
· http1MaxPendingRequests定义了针对一个目标的 HTTP 请求的最大排队数量;
· maxRequestsPerConnection定义了对某一后端的请求中,一个连接内能够发出的最大请求数量。如果将这一参数设置为 1 则会禁止 keep alive 特性。

接下来创建一个客户端,用来向后端服务发送请求,观察是否会触发熔断策略。这里要使用一个简单的负载测试客户端,名字叫 fortio。这个客户端可以控制连接数量、并发数以及发送 HTTP 请求的延迟。这里我们会把给客户端也进行 Sidecar 的注入,以此保证 Istio 对网络交互的控制:

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

接下来就可以登入客户端 Pod 并使用 Fortio 工具来调用 httpbin。-curl 参数表明只调用一次

1
FORTIO_POD=$(kubectl get pod | grep fortio | awk '{ print $1 }')

1
kubectl exec -it $FORTIO_POD -c fortio /usr/bin/fortio -- load -curl http://httpbin:8000/get
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
HTTP/1.1 200 OK
server: envoy
date: Sun, 02 Feb 2020 08:49:27 GMT
content-type: application/json
content-length: 371
access-control-allow-origin: *
access-control-allow-credentials: true
x-envoy-upstream-service-time: 3
{
"args": {},
"headers": {
"Content-Length": "0",
"Host": "httpbin:8000",
"User-Agent": "fortio.org/fortio-1.3.1",
"X-B3-Parentspanid": "ea1d6bd8e1ac8621",
"X-B3-Sampled": "0",
"X-B3-Spanid": "cef333d2e32ce6b4",
"X-B3-Traceid": "d89ae2b658f513b3ea1d6bd8e1ac8621"
},
"origin": "127.0.0.1",
"url": "http://httpbin:8000/get"
}

这表明我们创建的客户端已经成功与服务端进行了一次通信。
在kiali上可以看见httpbin连接路由图。请求都是返回正常的状态码为200

验证熔断
在上面的熔断设置中指定了 maxConnections=1 以及 http1MaxPendingRequests=1。这意味着如果超过了一个连接同时发起请求,Istio 就会熔断,阻止后续的请求或连接。我们不妨尝试通过并发2个连接(-c 2)发送20个请求数(-n 20)来看一下结果。

1
kubectl exec -it $FORTIO_POD -c fortio /usr/bin/fortio -- load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get

1
2
3
4
5
6
7
...
Sockets used: 17 (for perfect keepalive, would be 2)
Code 200 : 3 (15.0 %)
Code 503 : 17 (85.0 %)
Response Header Sizes : count 20 avg 34.5 +/- 82.13 min 0 max 230 sum 690
Response Body/Total Sizes : count 20 avg 226.3 +/- 159.6 min 153 max 601 sum 4526
All done 20 calls (plus 0 warmup) 5.768 ms avg, 344.2 qps

基本上所有的请求都发送成功了。明明我们设置的最大连接数是1,而我们模拟了两个并发连接,理论上应该只有一半的请求能成功才对,难道熔断没有成功?这里别忘了我们还设置了http1MaxPendingRequests=1,正如在前文中介绍的,这个参数的功能类似于为最大连接数提供了一级缓存,所以虽然我们的最大连接数是1,但是因为这个参数也为1,所以两个并发连接的请求都可以发送成功。

接下来我们需要修改请求连接数,将连接数提高到3(-c 3),请求数不变

1
kubectl exec -it $FORTIO_POD -c fortio /usr/bin/fortio -- load -c 3 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get

­
这时候可以观察到熔断行为生效了

1
2
3
4
...
Code 200 : 13 (65.0 %)
Code 503 : 7 (35.0 %)

有百分之65的请求通过了,百分之35的请求被拒绝了

重复执行上述测试命令后,进入服务降级模式

1
2
3
...
Sockets used: 20 (for perfect keepalive, would be 3)
Code 503 : 20 (100.0 %)

黑白名单

定义个黑名单,禁止Review-v3访问后端ratings服务,所以就不会显示红星星

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
cat <<EOF | kubectl apply -f -
apiVersion: "config.istio.io/v1alpha2"
kind: handler
metadata:
name: denyreviewsv3handler
spec:
compiledAdapter: denier
params:
status:
code: 7
message: Not allowed
---
apiVersion: "config.istio.io/v1alpha2"
kind: instance
metadata:
name: denyreviewsv3request
spec:
compiledTemplate: checknothing
---
apiVersion: "config.istio.io/v1alpha2"
kind: rule
metadata:
name: denyreviewsv3
spec:
match: destination.labels["app"] == "ratings" && source.labels["app"]=="reviews" && source.labels["version"] == "v3"
actions:
- handler: denyreviewsv3handler
instances: [ denyreviewsv3request ]
EOF

重点关注match: destination.labels["app"] == "ratings" && source.labels["app"]=="reviews" && source.labels["version"] == "v3"
这句配置,定义了源和目标,源为label为version==v3的workload,目标为label为app==reviews的workload。应用后去访问bookinfo发现,不断刷新,访问到v3时你会发现如下图显示。

https://jimmysong.io/istio-handbook/concepts/traffic-management-basic.html
https://www.servicemesher.com/istio-handbook/best-practices/how-to-implement-ingress-gateway.html