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

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

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

httpbin v1的deployment:

 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
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的deployment:

 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
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的k8s service:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
kubectl apply -f - <<EOF
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

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

 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
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
  name: httpbin-gateway
spec:
  selector:
    istio: ingressgateway # use istio default controller
  servers:
  - port:
      number: 80
      name: http
      protocol: HTTP
    hosts:
    - httpbin.example.com
    tls:
      httpsRedirect: true
  - port:
      number: 443
      name: https
      protocol: HTTPS
    tls:
      mode: SIMPLE
      credentialName: bookinfo-credential # must be the same as secret
    hosts:
    - httpbin.example.com
EOF

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

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

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
curl https://httpbin.example.com/headers
{
  "headers": {
    "Accept": "*/*",
    "Host": "httpbin.example.com",
    "User-Agent": "curl/7.64.1",
    "X-B3-Parentspanid": "f11310681322428c",
    "X-B3-Sampled": "1",
    "X-B3-Spanid": "d7d9533be9694d50",
    "X-B3-Traceid": "7fad3179541ed266f11310681322428c",
    "X-Envoy-Attempt-Count": "1",
    "X-Envoy-Internal": "true",
    "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"
  }
}

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

1
2
3
4
5
6
7
8
9
export V1_POD=$(kubectl get pod -l app=httpbin,version=v1 -o jsonpath={.items..metadata.name})
kubectl logs "$V1_POD" -c httpbin

127.0.0.6 - - [27/Jul/2021:14:05:19 +0000] "GET /headers HTTP/1.1" 200 599 "-" "curl/7.64.1"

export V2_POD=$(kubectl get pod -l app=httpbin,version=v2 -o jsonpath={.items..metadata.name})
kubectl logs "$V2_POD" -c httpbin

无访问日志打印

镜像流量到v2版本

接下来改变httpbin虚拟服务中的路由规则,配置镜像100%的流量到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
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: httpbin
spec:
  gateways:
    - httpbin-gateway
  hosts:
    - httpbin.example.com
  http:
  - route:
    - destination:
        host: httpbin
        port:
          number: 8000
        subset: v1
      weight: 100
    mirror:
      host: httpbin
      subset: v2
    mirrorPercentage:
      value: 100.0
EOF

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

参考