Mirroring
Copy live production traffic to a shadow environment or service so that you can try out, analyze, and monitor new software changes before deploying them to production.
About traffic mirroring
When releasing changes to a service, you want to finely control how those changes get exposed to users. This progressive delivery approach to releasing software allows you to reduce the blast radius, especially when changes introduce unintended behaviors. Traffic mirroring, also referred to as traffic shadowing, is one way to observe the impact of new software releases and test out new changes before you roll them out to production. Other approaches to slowly introduce new software include canary releases, A/B testing, or blue-green deployments.
When you turn on traffic shadowing for an app, kgateway makes a copy of all incoming requests. Kgateway still proxies the request to the backing destination along the request path. It also sends a copy of the request asynchronously to another shadow destination. When a response or failure happens, copies are not generated. This way, you can test how traffic is handled by a new release or version of your app with zero production impact. You can also compare the shadowed results against the expected results. You can use this information to decide how to proceed with a canary release.
To observe and analyze shadowed traffic, you can use a tool like Open Diffy. This tool create diff-compares on the responses. You can use this data to verify that the response is correct and to detect API forward/backward compatibility problems.
Before you begin
-
Follow the Get started guide to install kgateway.
-
Follow the Sample app guide to create an API gateway proxy with an HTTP listener and deploy the httpbin sample app.
-
Get the external address of the gateway and save it in an environment variable.
export INGRESS_GW_ADDRESS=$(kubectl get svc -n kgateway-system http -o jsonpath="{.status.loadBalancer.ingress[0]['hostname','ip']}") echo $INGRESS_GW_ADDRESS
kubectl port-forward deployment/http -n kgateway-system 8080:8080
Set up mirroring
-
Edit the httpbin service that you deployed earlier.
kubectl edit service httpbin -n httpbin
-
In the
spec.selector
field, addversion: v1
to make sure that the httpbin service routes requests always to thev1
version of the httpbin app.httpbin service snippet1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
... spec: clusterIP: 172.20.134.236 clusterIPs: - 172.20.134.236 internalTrafficPolicy: Cluster ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - name: http port: 8000 protocol: TCP targetPort: 8080 - name: tcp port: 9000 protocol: TCP targetPort: 9000 selector: app: httpbin version: v1
-
Deploy another version (
v2
) of httpbin. You use this app to receive the mirrored traffic from httpbinv1
.kubectl apply -f- <<EOF apiVersion: v1 kind: Service metadata: name: httpbin2 namespace: httpbin labels: app: httpbin service: httpbin spec: ports: - name: http port: 8000 targetPort: 8080 - name: tcp port: 9000 selector: app: httpbin version: v2 --- apiVersion: apps/v1 kind: Deployment metadata: name: httpbin2 namespace: httpbin spec: replicas: 1 selector: matchLabels: app: httpbin version: v2 template: metadata: labels: app: httpbin version: v2 spec: serviceAccountName: httpbin containers: - image: docker.io/mccutchen/go-httpbin:v2.6.0 imagePullPolicy: IfNotPresent name: httpbin command: [ go-httpbin ] args: - "-port" - "8080" - "-max-duration" - "600s" # override default 10s ports: - containerPort: 8080 # Include curl container for e2e testing, allows sending traffic mediated by the proxy sidecar - name: curl image: curlimages/curl:7.83.1 resources: requests: cpu: "100m" limits: cpu: "200m" imagePullPolicy: IfNotPresent command: - "tail" - "-f" - "/dev/null" - name: hey image: gcr.io/solo-public/docs/hey:0.1.4 imagePullPolicy: IfNotPresent EOF
-
Verify that the httpbin2 pod is up and running.
kubectl get pods -n httpbin
-
Create an HTTPRoute for the httpbin app that mirrors requests along the
mirror.example
domain from the httpbin app to the httpbin2 app.kubectl apply -f- <<EOF apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: httpbin-mirror namespace: httpbin spec: parentRefs: - name: http namespace: kgateway-system hostnames: - mirror.example rules: - matches: - path: type: PathPrefix value: / filters: - type: RequestMirror requestMirror: backendRef: kind: Service name: httpbin2 port: 8000 backendRefs: - name: httpbin port: 8000 EOF
-
Send a few requests to the httpbin app on the
mirror.example
domain. Verify that you get back a 200 HTTP response code.for i in {1..5}; do curl -vik http://$INGRESS_GW_ADDRESS:8080/headers \ -H "host: mirror.example:8080"; done
for i in {1..5}; do curl -vik localhost:8080/headers \ -H "host: mirror.example"; done
-
Get the logs of the httpbin app and verify that you see the requests that you sent.
kubectl logs -l version=v1 -n httpbin
Example output:
time="2025-03-14T19:43:01.1546" status=200 method="GET" uri="/headers" size_bytes=508 duration_ms=0.05 user_agent="curl/8.7.1" client_ip=10.0.8.23 time="2025-03-14T19:43:02.3565" status=200 method="GET" uri="/headers" size_bytes=443 duration_ms=0.06 user_agent="curl/8.7.1" client_ip=10.0.8.23 time="2025-03-14T19:43:03.0178" status=200 method="GET" uri="/headers" size_bytes=508 duration_ms=0.08 user_agent="curl/8.7.1" client_ip=10.0.6.27 time="2025-03-14T19:43:03.3874" status=200 method="GET" uri="/headers" size_bytes=508 duration_ms=0.06 user_agent="curl/8.7.1" client_ip=10.0.8.23 time="2025-03-14T19:43:03.6862" status=200 method="GET" uri="/headers" size_bytes=443 duration_ms=0.07 user_agent="curl/8.7.1" client_ip=10.0.6.27
-
Get the logs of the httpbin2 app and verify that you see the same requests.
kubectl logs -l version=v2 -n httpbin
Example output:
time="2025-03-14T19:43:01.1548" status=200 method="GET" uri="/headers" size_bytes=443 duration_ms=0.17 user_agent="curl/8.7.1" client_ip=10.0.8.23 time="2025-03-14T19:43:02.3565" status=200 method="GET" uri="/headers" size_bytes=508 duration_ms=0.06 user_agent="curl/8.7.1" client_ip=10.0.8.23 time="2025-03-14T19:43:03.0173" status=200 method="GET" uri="/headers" size_bytes=443 duration_ms=0.15 user_agent="curl/8.7.1" client_ip=10.0.6.27 time="2025-03-14T19:43:03.3869" status=200 method="GET" uri="/headers" size_bytes=443 duration_ms=0.16 user_agent="curl/8.7.1" client_ip=10.0.8.23 time="2025-03-14T19:43:03.6858" status=200 method="GET" uri="/headers" size_bytes=508 duration_ms=0.05 user_agent="curl/8.7.1" client_ip=10.0.6.27
Cleanup
You can remove the resources that you created in this guide.kubectl delete service httpbin2 -n httpbin
kubectl delete deployment httpbin2 -n httpbin
kubectl delete httproute httpbin-mirror -n httpbin