> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Mirroring

> A step by step demo showing how to configure Istio traffic mirroring to duplicate requests from a primary service to a mirror target for testing and telemetry.

This guide demonstrates Istio Service Mesh traffic mirroring. Traffic mirroring (also called shadowing) sends a duplicate of client requests to a mirror target while the primary target serves the actual response. It’s useful for testing, telemetry, and validating new revisions without impacting live traffic.

Prerequisites:

* An Istio-enabled cluster with sidecar injection enabled for the namespace you use.
* kubectl configured to communicate with the cluster.

Links and references:

* [Istio Service Mesh course](https://learn.kodekloud.com/user/courses/istio-service-mesh)
* [Kubernetes Documentation](https://kubernetes.io/docs/)

## 1) Confirm Istio sidecar injection is enabled for the namespace

Check the namespace labels to ensure `istio-injection=enabled` is present:

```shell theme={null}
root@controlplane ~ ➜  kubectl get ns --show-labels
NAME               STATUS   AGE     LABELS
default            Active   2m38s   istio-injection=enabled,kubernetes.io/metadata.name=default
istio-system       Active   2m6s    kubernetes.io/metadata.name=istio-system
kube-node-lease    Active   2m38s   kubernetes.io/metadata.name=kube-node-lease
kube-public        Active   2m38s   kubernetes.io/metadata.name=kube-public
kube-system        Active   2m38s   kubernetes.io/metadata.name=kube-system

root@controlplane ~ ➜
```

## 2) Create echo-server deployments (v1 and v2)

We deploy two revisions of the same app. Note the shared `app: echo-server` label and distinct `version` labels — these `version` labels become DestinationRule subsets that Istio will use.

```yaml theme={null}
# echo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-server-v1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo-server
      version: v1
  template:
    metadata:
      labels:
        app: echo-server
        version: v1
    spec:
      containers:
      - name: echo
        image: ealen/echo-server
        ports:
        - containerPort: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo-server-v2
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo-server
      version: v2
  template:
    metadata:
      labels:
        app: echo-server
        version: v2
    spec:
      containers:
      - name: echo
        image: ealen/echo-server
        ports:
        - containerPort: 80
```

Apply and validate the deployments:

```shell theme={null}
root@controlplane ~ ➜ kubectl apply -f echo-deployment.yaml
deployment.apps/echo-server-v1 created
deployment.apps/echo-server-v2 created

root@controlplane ~ ➜ kubectl get pods --show-labels
NAME                                     READY   STATUS    RESTARTS   AGE   LABELS
echo-server-v1-59ff75d58-t4dq6           1/2     Running   0          7s    app=echo-server,pod-template-hash=59ff75d58,security.istio.io/tlsMode=istio,service.istio.io/canonical-name=echo-server,service.istio.io/canonical-revision=v1,version=v1
echo-server-v2-5698db4f99-lm5ss          2/2     Running   0          7s    app=echo-server,pod-template-hash=5698db4f99,security.istio.io/tlsMode=istio,service.istio.io/canonical-name=echo-server,service.istio.io/canonical-revision=v2,version=v2
```

## 3) Create a ClusterIP Service that selects both revisions

A single Service selects pods by the shared `app: echo-server` label so client traffic can hit either revision.

```yaml theme={null}
# echo-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: echo-server
  labels:
    app: echo-server
spec:
  ports:
  - port: 80
    name: http
  selector:
    app: echo-server
```

Apply the Service and confirm:

```shell theme={null}
root@controlplane ~ ➜ kubectl apply -f echo-svc.yaml
service/echo-server created

root@controlplane ~ ➜ kubectl get svc --show-labels
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE   LABELS
echo-server  ClusterIP   10.96.123.45   <none>        80/TCP    10s   app=echo-server
```

## 4) Inspect pod logs to see baseline behavior

Open two terminals (or tmux panes). Tail v1 logs:

```shell theme={null}
root@controlplane ~ ➜ kubectl logs -f echo-server-v1-59ff75d58-t4dq6
Listening on port 80.
{"name":"echo-server","hostname":"echo-server-v1-59ff75d58-t4dq6","msg":"[GET] - /","time":"2025-04-11T16:26:41.095Z",...}
{"name":"echo-server","hostname":"echo-server-v1-59ff75d58-t4dq6","msg":"[GET] - /","time":"2025-04-11T16:26:42.074Z",...}
```

Tail v2 logs:

```shell theme={null}
root@controlplane ~ ➜ kubectl logs -f echo-server-v2-5698db4f99-lm5ss
Listening on port 80.
{"name":"echo-server","hostname":"echo-server-v2-5698db4f99-lm5ss","msg":"[GET] - /","time":"2025-04-11T16:26:23.173Z",...}
{"name":"echo-server","hostname":"echo-server-v2-5698db4f99-lm5ss","msg":"[GET] - /","time":"2025-04-11T16:26:36.848Z",...}
```

## 5) From a test pod, call the service to see which pod responds

If you don’t yet have a test pod, create one (for example, a busybox or alpine pod with curl). Exec into it and call the ClusterIP service. The echo-server JSON includes a HOSTNAME field — extract it to determine which pod served the response:

```shell theme={null}
root@controlplane ~ ➜ kubectl exec -ti test -- /bin/bash
root@test:/# curl -s http://echo-server | grep -o '"HOSTNAME":"[^"]*"' | sed 's/"HOSTNAME":"\(.*\)"/HOSTNAME: \1/'
HOSTNAME: echo-server-v2-5698db4f99-lm5ss
root@test:/# curl -s http://echo-server | grep -o '"HOSTNAME":"[^"]*"' | sed 's/"HOSTNAME":"\(.*\)"/HOSTNAME: \1/'
HOSTNAME: echo-server-v2-5698db4f99-lm5ss
root@test:/# curl -s http://echo-server | grep -o '"HOSTNAME":"[^"]*"' | sed 's/"HOSTNAME":"\(.*\)"/HOSTNAME: \1/'
HOSTNAME: echo-server-v1-59ff75d58-t4dq6
root@test:/# exit
```

You’ll see requests appear in the corresponding pod logs shown earlier.

## 6) Define DestinationRule subsets for v1 and v2

Create a DestinationRule that exposes `v1` and `v2` subsets for the VirtualService to reference:

```yaml theme={null}
# dr.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: echo-server
spec:
  host: echo-server
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2
```

Apply and confirm:

```shell theme={null}
root@controlplane ~ ➜ kubectl apply -f dr.yaml
destinationrule.networking.istio.io/echo-server created

root@controlplane ~ ➜ kubectl get destinationrules.networking.istio.io
NAME        HOST         AGE
echo-server echo-server  3s
```

## 7) Create a VirtualService that routes to v1 and mirrors to v2

The VirtualService below routes 100% of client traffic to subset `v1` and mirrors 100% of the requests to subset `v2`. Mirrored requests are sent in parallel to the mirror target; the mirror’s responses are discarded by the proxy.

```yaml theme={null}
# vs-mirror.yaml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: echo-server
spec:
  hosts:
  - echo-server
  http:
  - route:
    - destination:
        host: echo-server
        subset: v1
      weight: 100
    mirror:
      host: echo-server
      subset: v2
    mirrorPercentage:
      value: 100.0
```

Apply the VirtualService:

```shell theme={null}
root@controlplane ~ ➜ kubectl apply -f vs-mirror.yaml
virtualservice.networking.istio.io/echo-server created
```

## 8) Test mirroring behavior

From the test pod, invoke the service again. All client responses should now come from `v1`, while identical mirrored requests are visible in `v2` logs:

```shell theme={null}
root@controlplane ~ ➜ kubectl exec -ti test -- /bin/bash
root@test:/# curl -s http://echo-server | grep -o '"HOSTNAME":"[^"]*"' | sed 's/"HOSTNAME":"\(.*\)"/HOSTNAME: \1/'
HOSTNAME: echo-server-v1-59ff75d58-t4dq6
root@test:/# curl -s http://echo-server | grep -o '"HOSTNAME":"[^"]*"' | sed 's/"HOSTNAME":"\(.*\)"/HOSTNAME: \1/'
HOSTNAME: echo-server-v1-59ff75d58-t4dq6
root@test:/# curl -s http://echo-server | grep -o '"HOSTNAME":"[^"]*"' | sed 's/"HOSTNAME":"\(.*\)"/HOSTNAME: \1/'
HOSTNAME: echo-server-v1-59ff75d58-t4dq6
root@test:/# exit
```

Check the pod logs you left open — you’ll see matching request entries in both v1 and v2 logs. v1 serves the client; v2 receives mirrored traffic.

<Callout icon="lightbulb" color="#1CB2FE">
  Traffic mirroring is a safe way to validate a new revision or collect telemetry. Mirrored requests are sent to the mirror target, but the proxy ignores mirror responses — client behavior is unaffected.
</Callout>

<Callout icon="warning" color="#FF6B6B">
  If examples reference Gateway API CRDs (common in some docs), ensure the Gateway CRDs are installed before applying manifests that rely on them. Example command to install a Gateway API release if CRDs are missing:

  ```shell theme={null}
  kubectl get crd gateways.gateway.networking.k8s.io &> /dev/null || \
  kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.2.1/standard/gateway.networking.k8s.io_crd.yaml
  ```
</Callout>

## Quick reference — key resources demonstrated

| Resource            | Purpose                                                              | Example                            |
| ------------------- | -------------------------------------------------------------------- | ---------------------------------- |
| Deployment          | Two revisions with `version` labels used as subsets                  | `echo-server-v1`, `echo-server-v2` |
| Service (ClusterIP) | Selects pods by shared `app` label so traffic reaches both revisions | `echo-server`                      |
| DestinationRule     | Defines `v1` and `v2` subsets that VirtualService references         | `dr.yaml`                          |
| VirtualService      | Routes client traffic to `v1` and mirrors to `v2`                    | `vs-mirror.yaml`                   |

This completes the mirroring demonstration. Try adjusting weights or mirror percentages to experiment with partial mirroring and collect telemetry from mirrored revisions without altering client-facing behavior.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/istio-certified-associate/module/f3f5ca4b-b8d6-4788-9553-9ed765709933/lesson/a0ca9fa0-e282-411f-9894-5fab1d4cdfbb" />

  <Card title="Practice Lab" icon="flask-conical" cta="Learn more" href="https://learn.kodekloud.com/user/courses/istio-certified-associate/module/f3f5ca4b-b8d6-4788-9553-9ed765709933/lesson/4ca5ae83-9cbc-4f15-a7ef-d977caa61cdd" />
</CardGroup>
