灰度发布


流量示例:

17473315071243_upload.png

根据权重将流量划分到v1和v2上:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: forecast-route
  namespace: weather
spec:
  hosts:
  - forecast
  http:
  - route:
    - destination:
        host: forecast  
        subset: v1      
      weight: 0 # 根据权重分配流量
    - destination:
        host: forecast  
        subset: v2      
      weight: 100
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: forecast-dr
spec:
  host: forecast
  subsets:
  - name: v1
    labels:
      version: v1 # 根据不同的标签来选着服务
  - name: v2
    labels:
      version: v2
---
# 对应v1、v2的服务
apiVersion: apps/v1
kind: Deployment
metadata:
  name: forecast-v2
  labels:
    app: forecast
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: forecast
      version: v2
  template:
    metadata:
      labels:
        app: forecast
        version: v2
    spec:
      containers:
      - name: forecast
        image: istioweather/forecast:v2
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3002
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: forecast-v1
  labels:
    app: forecast
    version: v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: forecast
      version: v1
  template:
    metadata:
      labels:
        app: forecast
        version: v1
    spec:
      containers:
      - name: forecast
        image: istioweather/forecast:v2
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3002


根据浏览器路由到不同服务

vs文件:

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: forecast-route
  namespace: weather
spec:
  hosts:
  - forecast
  http:
  - match:
    - headers:
        User-Agent: # 区分不同的浏览器
          regex: .*(Chrome/([\d.]+)).* # Chrome浏览器到v2
    route:
    - destination:
        host: forecast
        subset: v2
  - route: # 其余浏览器流量都到v1
    - destination:
        host: forecast
        subset: v1

dr文件:

apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: forecast-dr
spec:
  host: forecast
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: forecast-v2
  labels:
    app: forecast
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: forecast
      version: v2
  template:
    metadata:
      labels:
        app: forecast
        version: v2
    spec:
      containers:
      - name: forecast
        image: istioweather/forecast:v2
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3002


根据不同客户端分配流量


安卓端 v1、v2 流量各 50% ,其余客户端流量均给到 v1 版本。

apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: frontend-route
  namespace: weather
spec:
  hosts:
  - "*"
  gateways:
  - istio-system/weather-gateway
  http:
  - match: # 安卓端的流量v1、v2各50% 
    - headers:
        User-Agent:
          regex: .*((Android)).*  
    route:
    - destination:
        host: frontend
        subset: v1   
      weight: 50
    - destination:
        host: frontend
        subset: v2
      weight: 50
  - route: # 其余客户端流量都给到 v1
    - destination:
        host: frontend
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: frontend-dr
spec:
  host: frontend
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend-v2
  labels:
    app: frontend
    version: v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
      version: v2
  template:
    metadata:
      labels:
        app: frontend
        version: v2
    spec:
      containers:
      - name: frontend
        image: istioweather/frontend:v2
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 3000


根据用户名路由流量


apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: frontend-route
spec:
  hosts:
  - "*"
  gateways:
  - istio-system/weather-gateway
  http:
  - match: # 如果用户名匹配则路由到v2
    - headers:
        cookie:
          regex: ^(.*?;)?(user=tester)(;.*)?$
    route:
    - destination:
        host: frontend
        subset: v2
  - route: # 其余流量路由到 v1
    - destination:
        host: frontend
        subset: v1
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: frontend-dr
spec:
  host: frontend
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2



集群流量策略


apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: demoapp
spec:
  host: demoapp
  trafficPolicy: # 全局流量策略
    loadBalancer:
      simple: LEAST_CONN # 调度算法
  subsets:
  - name: v10
    labels:
      version: v1.0
    trafficPolicy: # 子集流量策略
      loadBalancer:
        consistentHash:
          httpHeaderName: X-User # 根据标头值调度给同一个Pod
  - name: v11
    labels:
      version: v1.1
---
# 用到的vs
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: demoapp
spec:
  hosts:
  - demoapp
  http:
  - name: canary
    match:
    - uri:
        prefix: /canary
    rewrite:
      uri: /
    route:
    - destination:
        host: demoapp
        subset: v11
  - name: default
    route:
    - destination:
        host: demoapp
        subset: v10

测试:相同的x-user的值会调度给相同的Pod,是Pod不是dm。

root@client # curl -H "X-User: user19" demoapp:8080
iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.6, ServerName: demoappv10-5c497c6f7c-k6vpw, ServerIP: 10.244.5.206!
root@client # curl -H "X-User: user19" demoapp:8080
iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.6, ServerName: demoappv10-5c497c6f7c-k6vpw, ServerIP: 10.244.5.206!
root@client # 
root@client # 
root@client # curl -H "X-User: user20" demoapp:8080
iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.6, ServerName: demoappv10-5c497c6f7c-76gs7, ServerIP: 10.244.3.207!
root@client # curl -H "X-User: user20" demoapp:8080
iKubernetes demoapp v1.0 !! ClientIP: 127.0.0.6, ServerName: demoappv10-5c497c6f7c-76gs7, ServerIP: 10.244.3.207!


断路器:将不健康的服务弹出去

apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: demoapp
spec:
  host: demoapp
  trafficPolicy: # 这里定义在了全局
    loadBalancer:
      simple: RANDOM
    connectionPool: # 连接池
      tcp:
        maxConnections: 100
        connectTimeout: 30ms
        tcpKeepalive:
          time: 7200s
          interval: 75s
      http:
        http2MaxRequests: 1000
        maxRequestsPerConnection: 10
    outlierDetection:
      maxEjectionPercent: 50 # 最大弹出比例
      consecutive5xxErrors: 5 # 连续5次出现5xx错误就弹出主机
      interval: 10s # 每多长时间检测一次
      baseEjectionTime: 1m # 服务被弹出的时长
      minHealthPercent: 40 # 健康主机小于40%禁用弹出
  subsets:
  - name: v10
    labels:
      version: v1.0
  - name: v11
    labels:
      version: v1.1

服务故障测试:通过下面方法将服务的返回码改成5xx,模拟某个Pod或服务出现了故障。

curl -XPOST -d 'livez=FAIT' PodIP:port/livez

外部请求测试:连续请求5次失败之后就会将该Pod提出,一分钟后再加进来,在检测出5次错误再提出,两分钟后再加进来,再检测到5次错误再提出,三分钟后再加进来......。

~]# while true; do curl demoapp.ops.net/livez; sleep 0.$RANDOM; done
Proxying value: FAIT - Took 27 milliseconds.
Proxying value: OK - Took 22 milliseconds.
Proxying value: OK - Took 12 milliseconds.
Proxying value: OK - Took 30 milliseconds.
Proxying value: FAIT - Took 13 milliseconds.
Proxying value: FAIT - Took 25 milliseconds.

服务恢复正常测试:服务正常后就会加入到正常服务列表中。

curl -XPOST -d 'livez=OK' PodIP:port/livez


灰度发布示例


基础灰度发布示例:

apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
  name: grayscale
  namespace: default
spec:
  selector:
    istio: ingressgateway
  servers:
  - hosts:
    - grayscale.ops.net
    port:
      name: http
      number: 80
      protocol: HTTP
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: grayscale
  namespace: default
spec:
  gateways:
  - grayscale
  hosts:
  - grayscale.ops.net
  http:
  - match:
    - uri:
        prefix: /
    route:
    - destination:
        host: grayscale
        subset: v1
        port:
          number: 80
      weight: 100 # 根据权重分配流量
    - destination:
        host: grayscale
        subset: v2
        port:
          number: 80
      weight: 0
---
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: grayscale
spec:
  host: grayscale
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: grayscale
  name: grayscale-v1
  namespace: default
spec:
  selector:
    matchLabels:
      app: grayscale
      version: v1
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: grayscale
        version: v1
    spec:
      containers:
      - image: m.daocloud.io/docker.io/kong/httpbin
        imagePullPolicy: IfNotPresent
        name: httpbin
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: grayscale
  name: grayscale-v2
  namespace: default
spec:
  selector:
    matchLabels:
      app: grayscale
      version: v2
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: grayscale
        version: v2
    spec:
      containers:
      - image: temperature-app:latest
        imagePullPolicy: IfNotPresent
        name: temperature
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: grayscale
  name: grayscale
  namespace: default
spec:
  ports:
    port: 80
    protocol: TCP
    targetPort: 80
  selector: # 这里不要选着版本
    app: grayscale
  type: ClusterIP