All Products
Search
Document Center

Alibaba Cloud Service Mesh:Use traffic mirroring across clusters at the service mesh layer

Last Updated:Mar 11, 2026

Traffic mirroring, also called traffic shadowing, copies live production traffic to a test service in real time without affecting the production environment. Mirrored requests are fire-and-forget: the mesh sends them out of band of the critical request path and discards all responses. Service Mesh (ASM) supports cross-cluster traffic mirroring, so you can validate new service versions, debug issues, or run simulation tests against real production traffic.

In the following setup, Cluster A is the production environment and Cluster B is the test environment. The ingress gateway in Cluster A mirrors incoming traffic to Cluster B, where a test service processes the mirrored requests.

Cross-cluster traffic mirroring architecture

When to use traffic mirroring

Use caseDescription
Pre-release validationMirror production traffic to a new service version and compare results before deploying. Unlike manual testing with sample data, mirrored traffic covers real-world edge cases such as malformed input and malicious payloads.
System migrationMirror traffic from the old system to the new system for a trial run. Verify that the new system handles all production scenarios before cutover.
Live debuggingMirror traffic from a running service to a temporary debugging instance. Reproduce issues with real traffic without affecting users.
Test data isolationRoute mirrored production traffic to a separate test database, keeping production data untouched.
User behavior loggingCapture real user behavior data for recommendation algorithms and user profile analysis by storing mirrored traffic in logs.

How traffic mirroring works

Two behaviors to understand before you configure mirroring:

  • Host header modification: Mirrored requests have -shadow appended to the Host/Authority header. For example, if the original Host is httpbin, the mirrored request Host becomes httpbin-shadow. The host field in the VirtualService mirror section only determines the destination address -- it does not overwrite the original Host header.

  • Mirror percentage: The mirrorPercentage.value field controls what fraction of traffic to mirror. If omitted, 100% of traffic is mirrored.

Sample VirtualService configuration

The following VirtualService routes all traffic to the v1 subset and mirrors it to the v1-mirroring subset:

apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: myapp-traffic-mirroring
spec:
  hosts:
    - myapp
  http:
    - route:
        - destination:
            host: myapp.default.svc.cluster.local
            port:
              number: 8000
            subset: v1
          weight: 100
      mirror:
        host: myapp.default.svc.cluster.local
        port:
          number: 8000
        subset: v1-mirroring

After you apply this configuration, every request to myapp is served by the v1 subset, while a copy of each request is sent to the v1-mirroring subset.

Prerequisites

Before you begin, make sure that you have:

  • Two Kubernetes clusters (Cluster A and Cluster B) with ASM configured

  • kubectl configured to access both clusters

  • Automatic sidecar injection enabled in the target namespaces

Step 1: Deploy the httpbin service in Cluster B

  1. Create a file named httpbin.yaml with the following content:

    Expand to view httpbin.yaml

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: httpbin
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: httpbin
      labels:
        app: httpbin
        service: httpbin
    spec:
      ports:
      - name: http
        port: 8000
        targetPort: 80
      selector:
        app: httpbin
    ---
    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:
          serviceAccountName: httpbin
          containers:
          - image: docker.io/kennethreitz/httpbin
            imagePullPolicy: IfNotPresent
            name: httpbin
            ports:
            - containerPort: 80
  2. Deploy the httpbin v1 application:

    kubectl apply -f httpbin.yaml
  3. Verify the Pod is running:

    kubectl get pods -l app=httpbin

    The expected output is similar to:

    NAME                         READY   STATUS    RESTARTS   AGE
    httpbin-v1-xxxxxxxxx-xxxxx   2/2     Running   0          30s

    A 2/2 in the READY column confirms the sidecar proxy was injected alongside the application container.

Step 2: Configure a routing rule for the ingress gateway in Cluster B

This step exposes the httpbin service through the Cluster B ingress gateway so that mirrored traffic from Cluster A can reach it.

  1. Create a file named httpbin-gateway.yaml with the following content:

    Expand to view httpbin-gateway.yaml

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "*"
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
    spec:
      hosts:
      - "*"
      gateways:
      - httpbin-gateway
      http:
      - match:
        - uri:
            prefix: /headers
        route:
        - destination:
            host: httpbin
            port:
              number: 8000
  2. Apply the Gateway and VirtualService:

    kubectl apply -f httpbin-gateway.yaml
  3. Send a test request to verify the service is reachable through the ingress gateway:

    curl http://<cluster-b-ingress-gateway-ip>/headers

    Replace <cluster-b-ingress-gateway-ip> with the external IP of the ingress gateway in Cluster B.

    The expected output is similar to:

    {
      "headers": {
        "Accept": "*/*",
        "Host": "47.99.XX.XX",
        "User-Agent": "curl/7.79.1",
        "X-Envoy-Attempt-Count": "1",
        "X-Envoy-External-Address": "120.244.XXX.XXX",
        "X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=158e4ef69876550c34d10e3bfbd8d43f5ab481b16ba0e90b4e38a2d53ac****;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
      }
    }

Step 3: Configure traffic mirroring in Cluster A

Deploy the same httpbin application in Cluster A if you have not already done so. Use the same httpbin.yaml from Step 1:

kubectl apply -f httpbin.yaml

Next, create four resources in Cluster A:

  • A ServiceEntry that registers the Cluster B ingress gateway as an external service

  • A DestinationRule that defines the v1 subset for the local httpbin service

  • A Gateway and VirtualService that route traffic to the local httpbin service and mirror it to Cluster B

Create the ServiceEntry

The mirrored traffic targets an external address (the Cluster B ingress gateway). A ServiceEntry tells the mesh how to resolve this address.

  1. Create a file named httpbin-cluster-b.yaml with the following content:

    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
      name: httpbin-cluster-b
    spec:
      hosts:
      - httpbin.mirror.cluster-b
      location: MESH_EXTERNAL
      ports:
      - number: 80    # Port of the ingress gateway in Cluster B
        name: http
        protocol: HTTP
      resolution: STATIC
      endpoints:
      - address: 47.95.XX.XX   # External IP of the ingress gateway in Cluster B

    Replace 47.95.XX.XX with the actual external IP of the ingress gateway in Cluster B.

  2. Apply the ServiceEntry:

    kubectl apply -f httpbin-cluster-b.yaml

Create the DestinationRule

The VirtualService routes traffic to the v1 subset of httpbin. A DestinationRule defines this subset.

  1. Create a file named httpbin-destinationrule.yaml with the following content:

    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: httpbin
    spec:
      host: httpbin
      subsets:
      - name: v1
        labels:
          version: v1
  2. Apply the DestinationRule:

    kubectl apply -f httpbin-destinationrule.yaml

Create the Gateway and VirtualService with mirroring

The VirtualService routes all traffic to the v1 subset of the local httpbin service, while mirroring a percentage of that traffic to Cluster B through the ServiceEntry.

  1. Create a file named httpbin-gateway.yaml with the following content:

    Expand to view httpbin-gateway.yaml

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gateway
    spec:
      selector:
        istio: ingressgateway
      servers:
      - port:
          number: 80
          name: http
          protocol: HTTP
        hosts:
        - "*"
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
    spec:
      gateways:
        - httpbin-gateway
      hosts:
        - '*'
      http:
        - match:
            - uri:
                prefix: /headers
          mirror:
            host: httpbin.mirror.cluster-b    # Resolves to the Cluster B ingress gateway via the ServiceEntry
            port:
              number: 80
          mirrorPercentage:
            value: 50                         # Mirror 50% of traffic; omit this field to mirror 100%
          route:
            - destination:
                host: httpbin
                port:
                  number: 8000
                subset: v1
    Note

    The mirror.host field is used only for destination resolution. The actual Host header of mirrored requests is the original Host with -shadow appended, not httpbin.mirror.cluster-b.

  2. Apply the configuration:

    kubectl apply -f httpbin-gateway.yaml

Step 4: Verify that traffic mirroring works

Check the Envoy configuration

View the Envoy config dump of the ingress gateway Pod in Cluster A to confirm the mirror policy is applied:

"routes": [
  {
    "match": {
      "prefix": "/headers",
      "case_sensitive": true
    },
    "route": {
      "cluster": "outbound|8000|v1|httpbin.default.svc.cluster.local",
      "timeout": "0s",
      "retry_policy": {
        "retry_on": "connect-failure,refused-stream,unavailable,cancelled,retriable-status-codes",
        "num_retries": 2,
        "retry_host_predicate": [
          {
            "name": "envoy.retry_host_predicates.previous_hosts",
            "typed_config": {
              "@type": "type.googleapis.com/envoy.extensions.retry.host.previous_hosts.v3.PreviousHostsPredicate"
            }
          }
        ],
        "host_selection_retry_max_attempts": "5",
        "retriable_status_codes": [503]
      },
      "request_mirror_policies": [
        {
          "cluster": "outbound|80||httpbin.mirror.cluster-b",
          "runtime_fraction": {
            "default_value": {
              "numerator": 500000,
              "denominator": "MILLION"
            }
          },
          "trace_sampled": false
        }
      ]
    }
  }
]

Key fields to verify:

FieldExpected valueMeaning
request_mirror_policies.clusteroutbound|80||httpbin.mirror.cluster-bMirrored traffic is sent to the Cluster B ingress gateway
runtime_fraction.numerator50000050% of traffic is mirrored (500,000 / 1,000,000)

Send test traffic and check logs

The Envoy config dump confirms the configuration, but you also need to verify that mirrored traffic actually reaches Cluster B. Send test requests and check the httpbin Pod logs on both clusters.

  1. Send a request to the ingress gateway in Cluster A:

    curl http://<cluster-a-ingress-gateway-ip>/headers
  2. Check the httpbin Pod logs in Cluster A to confirm the request was served:

    kubectl logs -l app=httpbin -c httpbin

    The expected output shows that Cluster A received and processed the request:

    127.0.0.6 - - [11/Mar/2026 06:00:00] "GET /headers HTTP/1.1" 200 -
  3. Check the httpbin Pod logs in Cluster B to confirm the mirrored request arrived:

    kubectl logs -l app=httpbin -c httpbin

    If mirroring is working, the Cluster B logs show a corresponding GET /headers request entry:

    127.0.0.6 - - [11/Mar/2026 06:00:00] "GET /headers HTTP/1.1" 200 -

    The mirrored request carries a Host header with the -shadow suffix (for example, httpbin-shadow), but the default httpbin access log format does not display Host headers. To inspect the -shadow header, check the Envoy access logs of the sidecar proxy or enable verbose logging.

    Note

    If Cluster B does not show the mirrored request, verify that the ServiceEntry IP address matches the actual external IP of the Cluster B ingress gateway and that the ingress gateway accepts traffic on port 80.

Clean up

To remove the resources created in this tutorial:

On Cluster B:

kubectl delete -f httpbin-gateway.yaml
kubectl delete -f httpbin.yaml

On Cluster A:

kubectl delete -f httpbin-gateway.yaml
kubectl delete -f httpbin-destinationrule.yaml
kubectl delete -f httpbin-cluster-b.yaml
kubectl delete -f httpbin.yaml