istio流量控制实战
上次我们聊了istio的流量控制基本的概念,这次我们通过官方给的DEMO和我自己设计的一些场景来进行一些流量控制的试验,主要是为了了解如何应用istio到我们日常的一些场景中。
有以下几个场景:
- 控制访问
- 灰度发布
- 镜像流量
- 熔断
环境配置
我们直接使用官方的DEMO,可以节省很多的时间,下载地址。
下载好后解压出来,先安装对应的Service和Deployment
kubectl label namespace default istio-injection=enabled #开启sidecar自动注入
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml #部署初始应用
kubectl apply -f samples/bookinfo/networking/destination-rule-all.yaml #部署默认规则
应用大概长这样
控制访问
控制访问是istio最基础的功能,它通过Virtual Service和Destination Rule 来控制集群内或集群外对内部挂载服务的访问,那如何对我们刚刚部署好的bookinfo应用进行访问呢?
首先我们需要预先定义一个入口网关:
kubectl apply -f samples/bookinfo/networking/bookinfo-gateway.yaml
cat samples/bookinfo/networking/bookinfo-gateway.yaml
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:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: bookinfo
spec:
hosts:
- "*"
gateways:
- bookinfo-gateway
http:
- match:
- uri:
exact: /productpage
- uri:
prefix: /static
- uri:
exact: /login
- uri:
exact: /logout
- uri:
prefix: /api/v1/products
route:
- destination:
host: productpage
port:
number: 9080
这个网关配置的意思是访问到网关80端口的HTTP请求后缀为productpage、static、login、logout、/api/v1/products都路由到productpage这个服务的9080端口。
我们可以进行确认,先找到istio的网关ip和端口
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].nodePort}')
export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="https")].nodePort}')
export TCP_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="tcp")].nodePort}')
export INGRESS_HOST=$(kubectl get po -l istio=ingressgateway -n istio-system -o jsonpath='{.items[0].status.hostIP}')
浏览器访问INGRESS_HOST:INGRESS_PORT/productpage
就能够看到配置的bookinfo网页了。
灰度发布
讲到灰度发布我们需要先看一下之前配置的DestinationRulekubectl get dr
details details 19h
productpage productpage 19h
ratings ratings 19h
reviews reviews 19h
这次我们会灰度改变reviews,可以查看一下现有的reviews的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
---
其中v1版本没有评星,v2版本评星为黑色,v3版本评星为红色,现在去访问INGRESS_HOST:INGRESS_PORT/productpage
的bookinfo网页,可以看到每次进入都有不同的情况出现,我们先配置到v1无评星版本。
kubectl apply -f samples/bookinfo/networking/virtual-service-all-v1.yaml
配置后再去访问,可以发现,已经锁定在了无评星版本。
预设一个场景,我们要从v1灰度发布到v3版本,先分一半的流量到v3当中
kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-50-v3.yaml
再去进入网页,持续多次刷新的话,会交替出现无评星和红色评星的版本,假定现在版本已经稳定,准备将全部流量切换到v3
kubectl apply -f samples/bookinfo/networking/virtual-service-reviews-v3.yaml
现在就只会出现红色评星版本的了。
清理
kubectl delete -f samples/bookinfo/networking/virtual-service-all-v1.yaml
镜像流量
镜像流量一般被用于给测试服务器打入正式的请求类型,模拟线上环境进行测试。
环境配置
想要试验镜像流量的话我们需要首先部署两个httpbin-v1/v2版本作为被访问的服务,一个sleep服务作为请求者。
#部署httpbin-v1
cat <<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
cat <<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
#部署httpbin-service
kubectl 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
#部署sleep
cat <<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
环境配置好之后,我们首先设置一个Virtual Service和Destination Rule将请求全部转入httpbin_v1。
kubectl apply -f - <<EOF
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
EOF
用sleep服务请求一下http_bin
export SLEEP_POD=$(kubectl get pod -l app=sleep -o jsonpath={.items..metadata.name})
kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl http://httpbin:8000/headers' | python -m json.tool
查看对应的日志
export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
kubectl logs $V1_POD -c httpbin
export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
kubectl logs $V2_POD -c httpbin
可以看到只有v1收到了这一次的请求。
现在我们开启镜像流量配置
kubectl apply -f - <<EOF
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
mirror_percent: 100
EOF
配置中的mirror是开启镜像流量,mirror_percent是镜像比例,再进行一次请求。
kubectl exec -it $SLEEP_POD -c sleep -- sh -c 'curl http://httpbin:8000/headers' | python -m json.tool
清理
kubectl delete virtualservice httpbin
kubectl delete destinationrule httpbin
kubectl delete deploy httpbin-v1 httpbin-v2 sleep
kubectl delete svc httpbin
熔断
环境配置
部署一个http-bin服务,一个fortio服务
kubectl apply -f samples/httpbin/httpbin.yaml
kubectl apply -f samples/httpbin/sample-client/fortio-deploy.yaml
环境部署好之后我们就需要配置一个DestinationRule TrafficPolicy:
kubectl apply -f - <<EOF
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
- maxConnections:最大连接数
- maxRequestsPerConnection:每连接最多请求数
- http1MaxPendingRequests:到达的最多等待请求数
- consecutiveErrors:连续出发多少次50X错误后驱逐出负载均衡池
- Interval:拒绝访问扫描的时间间隔,即在interval(1s)内连续发生1个consecutiveErrors错误,则触发服务熔断,
- baseEjectionTime:最短拒绝访问时长。这个时间主机将保持拒绝访问,且如果决绝访问达到一定的次数。这允许自动增加不健康服务的拒绝访问时间,时间为baseEjectionTime*驱逐次数
- maxEjectionPercent:是服务在负载均衡池中被拒绝访问(被移除)的最大百分比,负载均衡池中最多有多大比例被剔除,默认是10%。 尝试使用fortio请求一下:
export FORTIO_POD=$(kubectl get pods -lapp=fortio -o 'jsonpath={.items[0].metadata.name}')
kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio curl -quiet http://httpbin:8000/get
请求成功
fortio是一个简单的压测工具,我们增加一下流量试试,设置并发数2,请求20次
kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 2 -qps 0 -n 20 -loglevel Warning http://httpbin:8000/get
可以看到,虽然我们设置了maxRequestsPerConnection和maxConnections都为1,但是仍然成功了大部分,这和请求的顺序和时间有关系。
我们加大并发量:
kubectl exec "$FORTIO_POD" -c fortio -- /usr/bin/fortio load -c 3 -qps 0 -n 30 -loglevel Warning http://httpbin:8000/get
在并发量为3的情况下,符合了我们最初的预期,只打入了36.7%的流量。
清理
kubectl delete destinationrule httpbin
kubectl delete deploy httpbin fortio-deploy
kubectl delete svc httpbin fortio