说明
- istio版本号
1.4.2
- k8s集群版本
v1.14.8
- istio在
1.4
提供了基于istioctl命令直接部署的功能,这里使用istioctl部署istio。- 自带配置校验、和丰富的自定义配置选项
- API版本号还在Alpha阶段,
install.istio.io/v1alpha2
,请自行判断是否适用 - 部署要求
- 至少得有Kubernetes集群
- Istio-1.4版本在
1.13
、1.14
、1.15
的k8s集群上是做过测试通过的 - 最新的
1.16
没在官方文档里注明,应该也是可以用的。官方说明在此
- 这里通过官方示例熟悉一下istio的ServiceMesh特性
流量管理 Traffic Management
说明
- 流量管理包含了
Ingress
和Egress
,这里单独拎出来介绍,避免章节篇幅太长 - 下面继续
请求路由 Request Routing
部署VirtualService
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
文件内容如下
1 | apiVersion: networking.istio.io/v1alpha3 |
检查VirtualService
1 | kubectl -n default get virtualservice.networking.istio.io |
输出示例
1 | NAME GATEWAYS HOSTS AGE |
检查DestinationRule
1 | kubectl -n default get destinationrules.networking.istio.io |
输出示例
1 | NAME HOST AGE |
测试路由配置
通过浏览器打开
http://${GATEWAY_URL}/productpage
会发现无论刷新多少次页面都只能访问到
reviewers-v1
的页面。即所有流量都被路由到了
reviewers:v1
配置新的VirtualService
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-test-v2.yaml |
文件内容如下
1 | apiVersion: networking.istio.io/v1alpha3 |
测试新的配置
- 不登陆,返回的页面依旧是
reviews-v1
- 点击右上角的
sign in
,登录之后,返回的页面是reviews-v2
- UserName填入
jason
- Password不填或者随便填
- UserName填入
- 登录其他用户,返回的页面是
reviews-v1
技术总结
通过配置
VirtualService
将请求的数据流修改如下
productpage
→reviews:v2
→ratings
(only for userjason
)productpage
→reviews:v1
(for everyone else)
清理现场
1 | kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
HTTP故障注入 HTTP Fault Injection
环境准备
- 部署VirtualService
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
- 上面两个YAML文件会将请求的数据流变成这样(这一步跟流量管理一样)
productpage
→reviews:v2
→ratings
(only for userjason
)productpage
→reviews:v1
(for everyone else)
注入HTTP延迟错误
实验设计
- 为
jason
用户访问reviews-v2
和rateings
注入7s延迟 reviews-v2
服务,代码里设置了10s的连接超时时间- 理论上,注入7s延迟不会导致
reviews-v2
超时出错
创建故障注入规则
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-delay.yaml |
文件内容如下
1 | apiVersion: networking.istio.io/v1alpha3 |
测试故障注入
- 浏览器打开
http://${GATEWAY_URL}/productpage
- 按
F12
打开开发者工具,切换到network
标签 - 登录
jason
用户,等待7s之后可以看到页面返回以下内容
1 | Error fetching product reviews! |
- 在开发者工具里面能看到请求
productpage
的时间是6s - 登录其他用户或者不登陆,刷新页面并不会引入延迟
技术总结
reviews-v2
服务,代码里面设置10s超时,延时7s并不会引发报错productpage
和ratings
服务之间的超时时间是3s,重试次数为1,因此只能容忍3s+3s总共6s的延迟,这里设置7s,导致超时出错- 这样就可以通过延迟注入,来检查服务间调用的超时时间是否合理
清理现场
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
注入HTTP状态码错误
实验设计
- 为
jason
用户引入HTTP状态码错误
创建故障注入规则
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-ratings-test-abort.yaml |
文件内容如下
1 | apiVersion: networking.istio.io/v1alpha3 |
测试故障注入
- 浏览器打开
http://${GATEWAY_URL}/productpage
- 按
F12
打开开发者工具,切换到network
标签 - 登录
jason
用户,可以看到- 页面很快就加载完毕
ratings
服务不可用,提示Ratings service is currently unavailable
- 登录其他用户或者不登陆,刷新页面并不会看到此报错信息
技术总结
- HTTP状态码注入只影响
jason
用户,由于没有引入延迟错误,所以是立刻返回HTTP 500
- 在浏览器开发者工具的
network
标签里,看不到请求有HTTP500
状态码 - 查看
reviews-v2
服务的日志,可以看到日志,证明是在reviews-v2
和ratings
之间注入故障的
1 | Error: unable to contact http://ratings:9080/ratings got status of 500 |
清理现场
1 | kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
HTTP流量切换 HTTP Traffic Shifting
环境准备
- 部署bookinfo应用
- 配置默认的DestinationRule
配置所有流量走v1服务
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
配置权重路由
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml |
文件内容如下
1 | apiVersion: networking.istio.io/v1alpha3 |
验证流量切换
- 浏览器打开
http://${GATEWAY_URL}/productpage
- 不断按
ctrl+F5
刷新页面,可以看到页面不断在reviews-v1
和reviews-v3
之间切换
技术总结
- 在所有流量都走v1的情况下,通过权重路由的方式,将部分流量切换到了新版本
- 通过流量比例的调整,达到灰度更新的目的
清理现场
1 | kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
TCP流量切换 TCP Traffic Shifting
环境准备
部署多版本
TCP-echo
服务1
kubectl apply -f samples/tcp-echo/tcp-echo-services.yaml
检查部署情况
1
kubectl -n default get pod -l app=tcp-echo
输出示例
1
2
3
4NAME READY STATUS RESTARTS AGE
tcp-echo-5ff7fd7996-ds72s 2/2 Running 0 40m
tcp-echo-v1-b559f69bd-5mbj8 2/2 Running 0 3m48s
tcp-echo-v2-547bb5f4d5-trlh9 2/2 Running 0 3m48s
配置默认TCP流量路由到v1版本
这里把TCP流量路由到
v1
版本
1 | kubectl apply -f samples/tcp-echo/tcp-echo-all-v1.yaml |
文件内容如下
1 | apiVersion: networking.istio.io/v1alpha3 |
测试TCP路由
- 获取
IngressGateway
访问方式,这里的istio-ingressGateway
使用NodePort
1 | export INGRESS_HOST=$(kubectl get pod -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}') |
- 访问
TCP-echo
1 | for i in {1..10};do /bin/bash -c "(date ; sleep 1) | nc $INGRESS_HOST $INGRESS_PORT";done |
输出示例
1 | one Mon Dec 23 10:13:54 CST 2019 |
可以看到输出的信息是以one
开头的,证明访问的是v1的服务
配置新的TCP流量路由
把20%的流量路由到
tcp-echo:v2
1 | kubectl apply -f samples/tcp-echo/tcp-echo-20-v2.yaml |
文件内容如下
1 | apiVersion: networking.istio.io/v1alpha3 |
测试新的TCP路由
1 | for i in {1..10};do /bin/bash -c "(date ; sleep 1) | nc $INGRESS_HOST $INGRESS_PORT";done |
输出示例
1 | one Mon Dec 23 10:19:14 CST 2019 |
技术总结
istio-ingressGateway
通过VirtualService
定义的流量转发权重分配流量- Kubernetes的Service是根据Pod数量,轮询分配TCP流量,无法灵活调配流量比例
- V
irtualService
流量分配比例独立于Kubernetes的Pod副本数量,因此可以独立控制两个版本的数量。
清理现场
1 | kubectl delete -f samples/tcp-echo/tcp-echo-all-v1.yaml |
请求超时 Request Timeout
环境准备
- 部署带有默认
DestionationRules
的bookinfo
- 配置所有流量走v1服务
1 | kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
配置请求延迟
通过配置HTTP路由里面的delay
字段,可以人为地把请求延迟。
- 把请求路由到
reviews-v2
1 | kubectl apply -f - <<EOF |
- 给
ratings
添加2s延迟,让istio-proxy
接到请求时延迟2s再处理
1 | kubectl apply -f - <<EOF |
测试请求延迟
用浏览器打开
http://${GATEWAY_URL}/productpage
等待2s之后页面才加载完毕,
reviews
显示正常
配置请求超时
- 现在给
reviews
服务添加0.5秒的超时
1 | kubectl apply -f - <<EOF |
测试请求超时
- 用浏览器打开
http://${GATEWAY_URL}/productpage
- 等待0.5s之后页面加载完毕,
reviews
服务提示unavailable
技术总结
请求延迟是在
ratings
服务引入的,也就是reviews
服务的upstream
reviews
服务配置了超时时间后,在访问ratings
,超过0.5s之后,ratings
还没返回数据,直接就报unavailable
请求流程如下
productpage
→reviews:v2(timeout=0.5s)
→ratings(delay=2s)
这里的延迟和超时,是由
istio-proxy
控制,程序无法感知除了在istio路由规则中配置注入超时时间,程序可以在
HTTP Header
里添加x-envoy-upstream-rq-timeout-ms
来覆盖掉istio路由规则里配置的超时时间
清理现场
1 | kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml |
熔断 Circuit Breaking
环境准备
- 部署
httpbin
1 | kubectl apply -f samples/httpbin/httpbin.yaml |
- 检查
httpbin
1 | kubectl get pod -l app=httpbin |
配置熔断
- 为
httpbin
创建带有熔断配置的DestinationRule,这里超过1个以上的并发请求会触发熔断maxConnections
限制对后端服务发起的HTTP/1.1
连接数,如果超过了这个限制,就会开启熔断。http1MaxPendingRequests: 1
最大HTTP请求pending数maxRequestsPerConnection: 1
在任何给定时间内限制对后端服务发起的HTTP/2
请求数,如果超过了这个限制,就会开启熔断。
1 | kubectl apply -f - <<EOF |
创建客户端程序
- 部署客户端
1 | kubectl apply -f samples/httpbin/sample-client/fortio-deploy.yaml |
- 查看客户端日志
这里登录到客户端Pod终端使用fortio工具调用httpbin
1 | FORTIO_POD=$(kubectl get pod | grep fortio | awk '{ print $1 }') |
输出示例
1 | HTTP/1.1 200 OK |
触发熔断
上面配置的熔断触发条件,只要超过1个并发请求就会触发熔断,这里演示一下
在客户端并发2个连接并发送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
8
9
10
11
12
13
14
15
16
17
18
19
20
2108:09:41 I logger.go:97> Log level is now 3 Warning (was 2 Info)
Fortio 1.3.1 running at 0 queries per second, 12->12 procs, for 20 calls: http://httpbin:8000/get
Starting at max qps with 2 thread(s) [gomax 12] for exactly 20 calls (10 per thread + 0)
08:09:41 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
Ended after 16.699105ms : 20 calls. qps=1197.7
Aggregated Function Time : count 20 avg 0.0016200426 +/- 0.0003949 min 0.00039727 max 0.002612624 sum 0.032400851
range, mid point, percentile, count
= 0.00039727 <= 0.001 , 0.000698635 , 5.00, 1
0.001 <= 0.002 , 0.0015 , 90.00, 17
0.002 <= 0.00261262 , 0.00230631 , 100.00, 2
target 50% 0.00152941
target 75% 0.00182353
target 90% 0.002
target 99% 0.00255136
target 99.9% 0.0026065
Sockets used: 3 (for perfect keepalive, would be 2)
Code 200 : 19 (95.0 %)
Code 503 : 1 (5.0 %)
Response Header Sizes : count 20 avg 218.5 +/- 50.13 min 0 max 230 sum 4370
Response Body/Total Sizes : count 20 avg 583 +/- 78.46 min 241 max 601 sum 11660
All done 20 calls (plus 0 warmup) 1.620 ms avg, 1197.7 qps在客户端并发3个连接并发送30个请求
1
kubectl exec -it $FORTIO_POD -c fortio /usr/bin/fortio -- load -c 3 -qps 0 -n 30 -loglevel Warning 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
24
25
26
27
28
29
30
3110:25:52 I logger.go:97> Log level is now 3 Warning (was 2 Info)
Fortio 1.3.1 running at 0 queries per second, 12->12 procs, for 30 calls: http://httpbin:8000/get
Starting at max qps with 3 thread(s) [gomax 12] for exactly 30 calls (10 per thread + 0)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
10:25:52 W http_client.go:679> Parsed non ok code 503 (HTTP/1.1 503)
Ended after 29.802582ms : 30 calls. qps=1006.6
Aggregated Function Time : count 30 avg 0.0022023969 +/- 0.001246 min 0.000148903 max 0.00409224 sum 0.066071906
range, mid point, percentile, count
= 0.000148903 <= 0.001 , 0.000574451 , 26.67, 8
0.001 <= 0.002 , 0.0015 , 30.00, 1
0.002 <= 0.003 , 0.0025 , 73.33, 13
0.003 <= 0.004 , 0.0035 , 96.67, 7
0.004 <= 0.00409224 , 0.00404612 , 100.00, 1
target 50% 0.00246154
target 75% 0.00307143
target 90% 0.00371429
target 99% 0.00406457
target 99.9% 0.00408947
Sockets used: 11 (for perfect keepalive, would be 3)
Code 200 : 21 (70.0 %)
Code 503 : 9 (30.0 %)
Response Header Sizes : count 30 avg 161 +/- 105.4 min 0 max 230 sum 4830
Response Body/Total Sizes : count 30 avg 493 +/- 165 min 241 max 601 sum 14790
All done 30 calls (plus 0 warmup) 2.202 ms avg, 1006.6 qps
查看istio-proxy状态
1 | kubectl exec $FORTIO_POD -c istio-proxy -- pilot-agent request GET stats | grep httpbin | grep upstream_rq_pending |
输出示例
1 | cluster.outbound|8000||httpbin.default.svc.cluster.local.upstream_rq_pending_active: 0 |
其中upstream_rq_pending_overflow
为溢出连接池熔断和失败的总请求,它的值是21,说明有 21 次调用触发了熔断
技术总结
流量镜像 Traffic Mirror
环境准备
部署
httpbin-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
25cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-v1
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v1
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
EOF部署
httpbin-v2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin-v2
spec:
replicas: 1
selector:
matchLabels:
app: httpbin
version: v2
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
EOF配置Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15kubectl create -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
app: httpbin
spec:
ports:
- name: http
port: 8000
targetPort: 80
selector:
app: httpbin
EOF创建curl服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: sleep
spec:
replicas: 1
selector:
matchLabels:
app: sleep
template:
metadata:
labels:
app: sleep
spec:
containers:
- name: sleep
image: tutum/curl
command: ["/bin/sleep","infinity"]
imagePullPolicy: IfNotPresent
EOF
配置默认路由
将所有流量路由到httpbin-v1
服务
1 | kubectl apply -f - <<EOF |
测试默认路由
1 | export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name}) |
输出示例
1 | { |
检查日志
检查v1和v2两个版本的Pod日志,只会在v1上看到访问日志
- v1的日志
1 | export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name}) |
输出示例
1 | 127.0.0.1 - - [30/Dec/2019:05:27:31 +0000] "GET /headers HTTP/1.1" 200 303 "-" "curl/7.35.0" |
- v2的日志
1 | export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name}) |
输出示例
1 | <none> |
配置镜像流量到v2
这里把所有流量路由到v1,并且把流量镜像到v2,镜像流量的比例是100%。
当流量被镜像的时候,被镜像请求流量会在HTTP Header里面添加
-shadow
mirror_persent
可以控制镜像流量的比例,如果不定义则所有流量会被镜像
1 | kubectl apply -f - <<EOF |
测试镜像流量
1 | kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl http://httpbin:8000/headers' | python -m json.tool |
验证镜像流量
查看v1和v2两个Pod的日志,可以发现会同时收到请求
- v1的日志
1 | 127.0.0.1 - - [30/Dec/2019:05:33:18 +0000] "GET /headers HTTP/1.1" 200 303 "-" "curl/7.35.0" |
- v2的日志
1 | 127.0.0.1 - - [30/Dec/2019:05:33:18 +0000] "GET /headers HTTP/1.1" 200 303 "-" "curl/7.35.0" |
网络抓包
为了验证流量是真的被镜像了,通过tcpdump命令抓包
- 另起一个终端,运行tcpdump命令
1 | export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name}) |
- 继续发起访问
1 | kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl http://httpbin:8000/headers' | python -m json.tool |
- 在tcpdump的输出中可以看到,确实会往
v2 POD
发送了HTTP Header为Host: httpbin-shadow
的请求
1 | tcpdump: verbose output suppressed, use -v or -vv for full protocol decode |
清理现场
1 | kubectl delete virtualservice httpbin |