流量镜像,也叫称为影子流量,是指将实时流量的副本发送到镜像服务。镜像流量发生在主服务的关键请求路径之外。 本节将测试使用istio流量管理中的流量镜像功能。会在k8s的default命名空间内部署httpbin服务的v1和v2两个版本,首先把流量全部路由到httpbin:v1,然后执行规则将一部分流量镜像到v2版本。

部署服务并配置默认路由到v1版本

因为default命名空间在前面部署bookinfo应用时已经开启了istio sidecar的自动注入功能,所以这里直接部署httpbin应用v1和v2的两个版本。

httpbin v1的deployment:

 1cat <<EOF | kubectl create -f -
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: httpbin-v1
 6spec:
 7  replicas: 1
 8  selector:
 9    matchLabels:
10      app: httpbin
11      version: v1
12  template:
13    metadata:
14      labels:
15        app: httpbin
16        version: v1
17    spec:
18      containers:
19      - image: docker.io/kennethreitz/httpbin
20        imagePullPolicy: IfNotPresent
21        name: httpbin
22        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
23        ports:
24        - containerPort: 80
25EOF

httpbin v2的deployment:

 1cat <<EOF | kubectl create -f -
 2apiVersion: apps/v1
 3kind: Deployment
 4metadata:
 5  name: httpbin-v2
 6spec:
 7  replicas: 1
 8  selector:
 9    matchLabels:
10      app: httpbin
11      version: v2
12  template:
13    metadata:
14      labels:
15        app: httpbin
16        version: v2
17    spec:
18      containers:
19      - image: docker.io/kennethreitz/httpbin
20        imagePullPolicy: IfNotPresent
21        name: httpbin
22        command: ["gunicorn", "--access-logfile", "-", "-b", "0.0.0.0:80", "httpbin:app"]
23        ports:
24        - containerPort: 80
25EOF

httpbin的k8s service:

 1kubectl create -f - <<EOF
 2apiVersion: v1
 3kind: Service
 4metadata:
 5  name: httpbin
 6  labels:
 7    app: httpbin
 8spec:
 9  ports:
10  - name: http
11    port: 8000
12    targetPort: 80
13  selector:
14    app: httpbin
15EOF

创建httpbin的目标规则,并配置好v1和v2两个服务子集:

 1kubectl apply -f - <<EOF
 2apiVersion: networking.istio.io/v1alpha3
 3kind: DestinationRule
 4metadata:
 5  name: httpbin
 6spec:
 7  host: httpbin
 8  subsets:
 9  - name: v1
10    labels:
11      version: v1
12  - name: v2
13    labels:
14      version: v2
15EOF

为了能从集群外部访问httpbin,给它创建一个Ingress Gateway:

 1kubectl apply -f - <<EOF
 2apiVersion: networking.istio.io/v1alpha3
 3kind: Gateway
 4metadata:
 5  name: httpbin-gateway
 6spec:
 7  selector:
 8    istio: ingressgateway # use istio default controller
 9  servers:
10  - port:
11      number: 80
12      name: http
13      protocol: HTTP
14    hosts:
15    - httpbin.example.com
16    tls:
17      httpsRedirect: true
18  - port:
19      number: 443
20      name: https
21      protocol: HTTPS
22    tls:
23      mode: SIMPLE
24      credentialName: bookinfo-credential # must be the same as secret
25    hosts:
26    - httpbin.example.com
27EOF

这里将使用二级域名httpbin.example.com作为入口,因为前面为bookinfo应用创建Gateway时创建好的证书bookinfo-credential里是*.example.com的通配证书,这里直接使用这个证书bookinfo-credential。

接下来为httpbin服务创建虚拟服务,并与上面创建的gateway关联,创建好入口路由规则:

 1kubectl apply -f - <<EOF
 2apiVersion: networking.istio.io/v1alpha3
 3kind: VirtualService
 4metadata:
 5  name: httpbin
 6spec:
 7  gateways:
 8    - httpbin-gateway
 9  hosts:
10    - httpbin.example.com
11  http:
12  - route:
13    - destination:
14        host: httpbin
15        port:
16          number: 8000
17        subset: v1
18      weight: 100
19EOF

此时从入口域名httpbin.example.com访问httpbin服务时,所有的流量都将被路由到httpbin:v1

在集群外部访问https://httpbin.example.com/headers,正常返回信息:

 1curl https://httpbin.example.com/headers
 2{
 3  "headers": {
 4    "Accept": "*/*",
 5    "Host": "httpbin.example.com",
 6    "User-Agent": "curl/7.64.1",
 7    "X-B3-Parentspanid": "f11310681322428c",
 8    "X-B3-Sampled": "1",
 9    "X-B3-Spanid": "d7d9533be9694d50",
10    "X-B3-Traceid": "7fad3179541ed266f11310681322428c",
11    "X-Envoy-Attempt-Count": "1",
12    "X-Envoy-Internal": "true",
13    "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/default;Hash=6bbc6b64389016a1ed1dcf2219cfb8e626021e6e1d54205e586441ac15b2b7f5;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
14  }
15}

此时在k8s中分别查看httbin服务v1和v2两个版本pod的日志,发现v1有访问日志,v2没有访问日志,说明上面配置生效,目前是所有的流量都被配置路由到了httpbin v1上。

1export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
2kubectl logs "$V1_POD" -c httpbin
3
4127.0.0.6 - - [27/Jul/2021:14:05:19 +0000] "GET /headers HTTP/1.1" 200 599 "-" "curl/7.64.1"
5
6export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
7kubectl logs "$V2_POD" -c httpbin
8
9无访问日志打印

镜像流量到v2版本

接下来改变httpbin虚拟服务中的路由规则,配置镜像100%的流量到v2版本:

 1kubectl apply -f - <<EOF
 2apiVersion: networking.istio.io/v1alpha3
 3kind: VirtualService
 4metadata:
 5  name: httpbin
 6spec:
 7  gateways:
 8    - httpbin-gateway
 9  hosts:
10    - httpbin.example.com
11  http:
12  - route:
13    - destination:
14        host: httpbin
15        port:
16          number: 8000
17        subset: v1
18      weight: 100
19    mirror:
20      host: httpbin
21      subset: v2
22    mirrorPercentage:
23      value: 100.0
24EOF

现在这个是虚拟服务的路由规则是将100%的流量路由到httpbin:v1,同时将100%的相同流量镜像到httpbin:v2。 需要注意,当流量被镜像时,请求将发送到镜像服务,并在headers中的Host/Authority属性值上追加-shadow。例如cluster-1变为cluster-1-shadow, 另外注意镜像的流量是即发即弃的,就是说镜像请求的响应会被丢弃。

此时在集群外部访问https://httpbin.example.com/headers,在k8s中分别查看httbin服务v1和v2两个版本pod的日志,发现两个版本的pod都有访问日志。

总结

流量镜像(Traffic Mirroring),也称为影子流量(Traffic Shadowing), 是一种强大的、无风险的测试应用版本的方法,它将实时流量的副本发送给被镜像的服务。 使用流量镜像,可以搭建一个与原环境类似的环境以进行验收测试,从而提前发现问题。 由于镜像流量存在于主服务关键请求路径带外,终端用户在测试全过程不会受到影响。另外在使用流量镜像之前,必须确认镜像服务连接的数据状态存储(例如数据库等)是独立的,避免污染关键请求路径上的状态存储。

参考