通过配置ASMHeaderPropagation实现宽松模式的流量泳道

本文介绍Baggage作为链路透传的请求头时,如何通过虚拟服务以及ASMHeaderPropagation等流量规则资源的配置实现宽松模式的流量泳道和流量降级。

前提条件

功能介绍

Baggage是OpenTelemetry推出的一种标准化机制,旨在实现分布式系统调用链路中跨进程传递上下文信息。它通过在HTTP头部增加名为“Baggage”的字段实现,字段值为键值对格式,可传递租户ID、追踪ID、安全凭证等上下文数据,支持链路追踪、日志关联等功能而无需修改代码。例如:

baggage: userId=alice,serverNode=DF%2028,isProduction=false

基于透传的Baggage上下文信息,服务网格ASM可以利用ASMHeaderPropagation资源帮助您在服务调用链路上透传任意请求头、并基于该请求头实现宽松模式的流量泳道。有关宽松模式的流量泳道,请参见流量泳道概述

步骤一:配置服务透传Baggage上下文

本节主要展示如何通过OpenTelemetry Operator自动插装的方法,为Kubernetes集群中的服务添加Baggage透传能力。

  1. 部署OpenTelemetry Operator。

    1. 通过kubectl连接到ASM实例添加的Kubernetes集群。执行以下命令,创建opentelemetry-operator-system命名空间。

      kubectl create namespace opentelemetry-operator-system
    2. 执行以下命令,使用Helm在opentelemetry-operator-system命名空间下安装OpenTelemetry Operator。(关于Helm安装步骤,请参见安装Helm

      helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
      helm install  \
          --namespace=opentelemetry-operator-system \
          --version=0.46.0 \
          --set admissionWebhooks.certManager.enabled=false \
          --set admissionWebhooks.certManager.autoGenerateCert=true \
          --set manager.image.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/opentelemetry-operator" \
          --set manager.image.tag="0.92.1" \
          --set kubeRBACProxy.image.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/kube-rbac-proxy" \
          --set kubeRBACProxy.image.tag="v0.13.1" \
          --set manager.collectorImage.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/opentelemetry-collector" \
          --set manager.collectorImage.tag="0.97.0" \
          --set manager.opampBridgeImage.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/operator-opamp-bridge" \
          --set manager.opampBridgeImage.tag="0.97.0" \
          --set manager.targetAllocatorImage.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/target-allocator" \
          --set manager.targetAllocatorImage.tag="0.97.0" \
          --set manager.autoInstrumentationImage.java.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-java" \
          --set manager.autoInstrumentationImage.java.tag="1.32.1" \
          --set manager.autoInstrumentationImage.nodejs.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-nodejs" \
          --set manager.autoInstrumentationImage.nodejs.tag="0.49.1" \
          --set manager.autoInstrumentationImage.python.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-python" \
          --set manager.autoInstrumentationImage.python.tag="0.44b0" \
          --set manager.autoInstrumentationImage.dotnet.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/autoinstrumentation-dotnet" \
          --set manager.autoInstrumentationImage.dotnet.tag="1.2.0" \
          --set manager.autoInstrumentationImage.go.repository="registry-cn-hangzhou.ack.aliyuncs.com/acs/opentelemetry-go-instrumentation" \
          --set manager.autoInstrumentationImage.go.tag="v0.10.1.alpha-2-aliyun" \
          opentelemetry-operator open-telemetry/opentelemetry-operator
    3. 执行以下命令,检查opentelemetry-operator是否正常运行。

      kubectl get pod -n opentelemetry-operator-system

      预期输出:

      NAME                                      READY   STATUS    RESTARTS   AGE
      opentelemetry-operator-854fb558b5-pvllj   2/2     Running   0          1m
  2. 配置自动插装(auto-instrumentation)。

    1. 使用以下内容,创建instrumentation.yaml文件。

      apiVersion: opentelemetry.io/v1alpha1
      kind: Instrumentation
      metadata:
        name: demo-instrumentation
      spec:
        propagators:
          - baggage
        sampler:
          type: parentbased_traceidratio
          argument: "1"
    2. 执行以下命令,在default命名空间下声明自动插装。

      kubectl apply -f instrumentation.yaml
      说明

      对于OpenTelemetry框架来说,其最佳实践还包括部署OpenTelemetry Collector以收集可观测数据。由于本文主要演示OpenTelemetry自动插装实现的Baggage链路透传,因此没有包含部署OpenTelemetry Collector的步骤。有关服务网格ASM如何通过OpenTelemetry上报链路追踪数据,请参考见将链路追踪数据采集到阿里云可观测链路OpenTelemetry版

步骤二:部署示例服务

  1. 为default命名空间启用Sidecar网格代理自动注入。具体操作,请参见管理全局命名空间

    说明

    关于自动注入的更多信息,请参见配置Sidecar注入策略

  2. 使用以下内容,创建mock.yaml文件。

    展开查看mock.yaml内容

    apiVersion: v1
    kind: Service
    metadata:
      name: mocka
      labels:
        app: mocka
        service: mocka
    spec:
      ports:
      - port: 8000
        name: http
      selector:
        app: mocka
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mocka-v1
      labels:
        app: mocka
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mocka
          version: v1
          ASM_TRAFFIC_TAG: v1
      template:
        metadata:
          labels:
            app: mocka
            version: v1
            ASM_TRAFFIC_TAG: v1
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v1
            - name: app
              value: mocka
            - name: upstream_url
              value: "http://mockb:8000/"
            ports:
            - containerPort: 8000
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: mockb
      labels:
        app: mockb
        service: mockb
    spec:
      ports:
      - port: 8000
        name: http
      selector:
        app: mockb
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockb-v1
      labels:
        app: mockb
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockb
          version: v1
          ASM_TRAFFIC_TAG: v1
      template:
        metadata:
          labels:
            app: mockb
            version: v1
            ASM_TRAFFIC_TAG: v1
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v1
            - name: app
              value: mockb
            - name: upstream_url
              value: "http://mockc:8000/"
            ports:
            - containerPort: 8000
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: mockc
      labels:
        app: mockc
        service: mockc
    spec:
      ports:
      - port: 8000
        name: http
      selector:
        app: mockc
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockc-v1
      labels:
        app: mockc
        version: v1
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockc
          version: v1
          ASM_TRAFFIC_TAG: v1
      template:
        metadata:
          labels:
            app: mockc
            version: v1
            ASM_TRAFFIC_TAG: v1
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v1
            - name: app
              value: mockc
            ports:
            - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mocka-v2
      labels:
        app: mocka
        version: v2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mocka
          version: v2
          ASM_TRAFFIC_TAG: v2
      template:
        metadata:
          labels:
            app: mocka
            version: v2
            ASM_TRAFFIC_TAG: v2
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v2
            - name: app
              value: mocka
            - name: upstream_url
              value: "http://mockb:8000/"
            ports:
            - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockb-v2
      labels:
        app: mockb
        version: v2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockb
          version: v2
          ASM_TRAFFIC_TAG: v2
      template:
        metadata:
          labels:
            app: mockb
            version: v2
            ASM_TRAFFIC_TAG: v2
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v2
            - name: app
              value: mockb
            - name: upstream_url
              value: "http://mockc:8000/"
            ports:
            - containerPort: 8000
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockc-v2
      labels:
        app: mockc
        version: v2
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockc
          version: v2
          ASM_TRAFFIC_TAG: v2
      template:
        metadata:
          labels:
            app: mockc
            version: v2
            ASM_TRAFFIC_TAG: v2
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v2
            - name: app
              value: mockc
            ports:
            - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mocka-v3
      labels:
        app: mocka
        version: v3
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mocka
          version: v3
          ASM_TRAFFIC_TAG: v3
      template:
        metadata:
          labels:
            app: mocka
            version: v3
            ASM_TRAFFIC_TAG: v3
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v3
            - name: app
              value: mocka
            - name: upstream_url
              value: "http://mockb:8000/"
            ports:
            - containerPort: 8000
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockb-v3
      labels:
        app: mockb
        version: v3
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockb
          version: v3
          ASM_TRAFFIC_TAG: v3
      template:
        metadata:
          labels:
            app: mockb
            version: v3
            ASM_TRAFFIC_TAG: v3
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v3
            - name: app
              value: mockb
            - name: upstream_url
              value: "http://mockc:8000/"
            ports:
            - containerPort: 8000
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: mockc-v3
      labels:
        app: mockc
        version: v3
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: mockc
          version: v3
          ASM_TRAFFIC_TAG: v3
      template:
        metadata:
          labels:
            app: mockc
            version: v3
            ASM_TRAFFIC_TAG: v3
          annotations:
            instrumentation.opentelemetry.io/inject-java: "true"
            instrumentation.opentelemetry.io/container-names: "default"
        spec:
          containers:
          - name: default
            image: registry-cn-hangzhou.ack.aliyuncs.com/acs/asm-mock:v0.1-java
            imagePullPolicy: IfNotPresent
            env:
            - name: version
              value: v3
            - name: app
              value: mockc
            ports:
            - containerPort: 8000

    对于每个实例服务Pod,都加入了instrumentation.opentelemetry.io/inject-java: "true"instrumentation.opentelemetry.io/container-names: "default"两个注解,以声明该实例服务使用Java语言实现,并要求OpenTelemetry Operator对名称为default的容器进行自动插装。

  3. 执行以下命令,部署实例服务。

    kubectl apply -f mock.yaml

    基于OpenTelemetry自动插装机制,部署的服务Pod将自动具有在调用链路中传递Baggage的能力。

步骤三:创建流量规则实现宽松模式流量泳道

  1. 创建DestinationRule流量规则。

    1. 使用以下内容,创建dr-mock.yaml文件。

      展开查看YAML内容

      apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      metadata:
        name: dr-mock-default-mocka
      spec:
        host: mocka.default.svc.cluster.local
        subsets:
          - labels:
              version: v1
            name: v1
          - labels:
              version: v2
            name: v2
          - labels:
              version: v3
            name: v3
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      metadata:
        name: dr-mock-default-mockb
      spec:
        host: mockb.default.svc.cluster.local
        subsets:
          - labels:
              version: v1
            name: v1
          - labels:
              version: v3
            name: v3
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: DestinationRule
      metadata:
        name: dr-mock-default-mockc
      spec:
        host: mockc.default.svc.cluster.local
        subsets:
          - labels:
              version: v1
            name: v1
          - labels:
              version: v2
            name: v2

      此文件表示将mocka、mockb、mockc三个服务按照Pod的version标签分为v1、v2、v3三个版本子集,其中mocka有v1、v2、v3三个版本,mockb有v1、v3两个版本,mockc有v1、v2两个版本。

    2. 通过kubectl连接ASM实例,执行以下命令,创建DestinationRule流量规则。

      kuebctl apply -f dr-mock.yaml
  1. 创建ASMHeaderPropagation流量规则。服务已经实现了Baggage透传能力的前提下,可以通过ASMHeaderPropagation流量规则来指定利用Baggage在链路上透传自定义请求头。

    1. 使用以下内容,创建propagation.yaml文件。以下文件表示利用Baggage中的上下文信息,在调用链路中透传名为version的请求头。

      apiVersion: istio.alibabacloud.com/v1beta1
      kind: ASMHeaderPropagation
      metadata:
        name: version-propagation
      spec:
        headers:
          - version
    2. 通过kubectl连接ASM实例,执行以下命令, 创建ASMHeaderPropagation流量规则。

      kuebctl apply -f propagation.yaml
  1. 创建VirtualService流量规则。

    1. 使用以下内容,创建vs-mock.yaml文件。

      展开查看YAML内容

      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: vs-mock-default-mocka
      spec:
        hosts:
          - mocka.default.svc.cluster.local
        http:
          - match:
              - headers:
                  version:
                    exact: v1
            route:
              - destination:
                  host: mocka.default.svc.cluster.local
                  subset: v1
                fallback:
                  target:
                    host: mocka.default.svc.cluster.local
                    subset: v1
          - match:
              - headers:
                  version:
                    exact: v2
            route:
              - destination:
                  host: mocka.default.svc.cluster.local
                  subset: v2
                fallback:
                  target:
                    host: mocka.default.svc.cluster.local
                    subset: v1
          - match:
              - headers:
                  version:
                    exact: v3
            route:
              - destination:
                  host: mocka.default.svc.cluster.local
                  subset: v3
                fallback:
                  target:
                    host: mocka.default.svc.cluster.local
                    subset: v1
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: vs-mock-default-mockb
      spec:
        hosts:
          - mockb.default.svc.cluster.local
        http:
          - match:
              - headers:
                  version:
                    exact: v1
            route:
              - destination:
                  host: mockb.default.svc.cluster.local
                  subset: v1
                fallback:
                  target:
                    host: mockb.default.svc.cluster.local
                    subset: v1
          - match:
              - headers:
                  version:
                    exact: v2
            route:
              - destination:
                  host: mockb.default.svc.cluster.local
                  subset: v2
                fallback:
                  target:
                    host: mockb.default.svc.cluster.local
                    subset: v1
          - match:
              - headers:
                  version:
                    exact: v3
            route:
              - destination:
                  host: mockb.default.svc.cluster.local
                  subset: v3
                fallback:
                  target:
                    host: mockb.default.svc.cluster.local
                    subset: v1
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: vs-mock-default-mockc
      spec:
        hosts:
          - mockc.default.svc.cluster.local
        http:
          - match:
              - headers:
                  version:
                    exact: v1
            route:
              - destination:
                  host: mockc.default.svc.cluster.local
                  subset: v1
                fallback:
                  target:
                    host: mockc.default.svc.cluster.local
                    subset: v1
          - match:
              - headers:
                  version:
                    exact: v2
            route:
              - destination:
                  host: mockc.default.svc.cluster.local
                  subset: v2
                fallback:
                  target:
                    host: mockc.default.svc.cluster.local
                    subset: v1
          - match:
              - headers:
                  version:
                    exact: v3
            route:
              - destination:
                  host: mockc.default.svc.cluster.local
                  subset: v3
                fallback:
                  target:
                    host: mockc.default.svc.cluster.local
                    subset: v1
      

      此文件为mocka→mockb→mockc的服务调用链路创建流量泳道路由规则。具体来说,是通过匹配调用链路中透传的version请求头内容、将请求转发到对应的版本(例如,带有version: v2请求头的请求都发送到v2版本的服务)。同时,该流量规则也指定了流量回退规则:当调用链路中的某个服务的对应版本不存在时,统一将请求回退到服务的v1版本。

    2. 通过kubectl连接ASM实例,执行以下命令,创建VirtualService流量规则。

      kubectl apply -f vs-mock.yaml
  2. 创建网关引流规则。

    1. 使用以下内容,创建gw-mock.yaml文件。

      展开查看YAML内容

      apiVersion: networking.istio.io/v1beta1
      kind: Gateway
      metadata:
        name: mockgw
        namespace: default
      spec:
        selector:
          istio: ingressgateway
        servers:
          - hosts:
              - '*'
            port:
              name: http
              number: 80
              protocol: HTTP
      ---
      apiVersion: networking.istio.io/v1beta1
      kind: VirtualService
      metadata:
        name: swimlane-ingress-vs-weighted-mock
        namespace: default
      spec:
        gateways:
          - default/mockgw
        hosts:
          - '*'
        http:
          - name: mock-weighted
            route:
              - destination:
                  host: mocka.default.svc.cluster.local
                  subset: v1
                headers:
                  request:
                    set:
                      version: v1
                weight: 40
              - destination:
                  host: mocka.default.svc.cluster.local
                  subset: v2
                headers:
                  request:
                    set:
                      version: v2
                weight: 30
              - destination:
                  host: mocka.default.svc.cluster.local
                  subset: v3
                headers:
                  request:
                    set:
                      version: v3
                weight: 30

      此文件表示为mocka→mockb→mockc的服务调用链路创建网关引流规则。引流方式则为权重引流,对于发送到ASM网关的流量,以4:3:3的比例分别转发到mocka服务的v1、v2、v3版本。当网关向mocka服务转发请求时,会根据转发的目标版本为请求添加version请求头,保证调用链路中存在泳道对应的版本信息。

    2. 通过kubectl连接ASM实例,执行以下命令,创建网关引流规则。

      kubectl apply -f gw-mock.yaml

步骤四:验证流量泳道是否生效

  1. 获取ASM网关的公网IP。具体操作,请参见步骤二:获取ASM网关地址

  2. 执行以下命令,设置环境变量。xxx.xxx.xxx.xxx为上一步获取的IP。

    export ASM_GATEWAY_IP=xxx.xxx.xxx.xxx
  3. 验证全灰度链路功能是否生效。

    1. 执行以下命令,查看v1泳道的访问效果。

      for i in {1..100};  do curl http://${ASM_GATEWAY_IP} ;  echo ''; sleep 1; done;

      预期输出:

      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v2, ip: 192.168.1.28)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v2, ip: 192.168.1.1)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v2, ip: 192.168.1.28)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v2, ip: 192.168.1.1)
      -> mocka(version: v2, ip: 192.168.1.28)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v2, ip: 192.168.1.1)
      -> mocka(version: v2, ip: 192.168.1.28)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v2, ip: 192.168.1.1)
      -> mocka(version: v2, ip: 192.168.1.28)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v2, ip: 192.168.1.1)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v2, ip: 192.168.1.28)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v2, ip: 192.168.1.1)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v2, ip: 192.168.1.28)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v2, ip: 192.168.1.1)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v1, ip: 192.168.1.27)-> mockb(version: v1, ip: 192.168.1.30)-> mockc(version: v1, ip: 192.168.1.14)
      -> mocka(version: v3, ip: 192.168.1.26)-> mockb(version: v3, ip: 192.168.1.29)-> mockc(version: v1, ip: 192.168.1.14)

      可以看到,流量将以约4:3:3的比例发送到服务调用链路的v1、v2、v3三个版本,并由v1作为基线版本,当调用链路中不存在某个服务的特定版本时,将会调用该服务的v1版本。