istio从入门到放弃系列(二)——istio流量控制实战

1,720 阅读5分钟

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