Module 6: Traffic Management

To gain advanced traffic control, you must deploy a Waypoint Proxy for the workload’s namespace. The traffic is then routed from the Ztunnel, through the Waypoint Proxy, and finally to the destination workload.

The following section describes some advanced traffic control capabilities:

  • Traffic Splitting: Uses Waypoint Proxy

    • Percentage-based routing (e.g., 90% to V1, 10% to V2).

  • Header-Based Routing: Uses Waypoint Proxy

    • Route traffic based on HTTP headers

  • Traffic Mirroring: Uses Waypoint Proxy

    • Shadowing a percentage of live traffic to a test service.

  • Fault Injection: Uses Waypoint Proxy

    • Injecting artificial delays or HTTP failure codes for resilience testing.

  • L4 Load Balancing: Uses Ztunnel Only

    • Basic connection-level balancing (default behavior).

  • Timeouts/Retries: Uses Waypoint Proxy

    • Configuring application-level request timeouts and retries on failure.

Navigate to the subdirectory: 060-traffic-management

About Traffic Routing

Traffic routing is essential for strategies such as canary releases, A/B testing, and blue-green deployments.

OpenShift Service Mesh provides three core routing techniques:

  • Weighted routing: Distributes traffic across service versions based on percentage allocations, enabling controlled rollouts and gradual migrations.

  • Header-based routing: Routes traffic based on HTTP request headers, supporting user segmentation and feature flags.

  • Path-based routing: Routes traffic based on the request URI path, enabling API versioning and endpoint-specific routing.

Task 1: Create Waypoint proxies

The waypoint proxy is the place where Istio in ambient mode enforces Layer 7 policies. Creating one waypoint per namespace is considered a best practice because it aligns perfectly with Kubernetes’ natural isolation boundary: the namespace.

Create a waypoint for the travel-portal and travel-agency namespace:

apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  labels:
    istio.io/waypoint-for: service
  name: waypoint-travel-agency
  namespace: travel-agency
spec:
  gatewayClassName: istio-waypoint
  listeners:
  - name: mesh
    port: 15008
    protocol: HBONE
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  labels:
    istio.io/waypoint-for: service
  name: waypoint-travel-portal
  namespace: travel-portal
spec:
  gatewayClassName: istio-waypoint
  listeners:
  - name: mesh
    port: 15008
    protocol: HBONE
oc apply -f 01-waypoints-create.yaml

Task 2: Enroll the travel application namespaces to use waypoints

Now enroll the services in the namespaces to use the waypoints for L7 traffic:

apiVersion: v1
kind: Namespace
metadata:
  labels:
    istio-discovery: enabled
    istio.io/dataplane-mode: ambient
    istio.io/use-waypoint:  waypoint-travel-agency
  name: travel-agency
spec: {}
---
apiVersion: v1
kind: Namespace
metadata:
  labels:
    istio-discovery: enabled
    istio.io/dataplane-mode: ambient
    istio.io/use-waypoint: waypoint-travel-portal
  name: travel-portal
spec: {}
oc apply -f 02_1-ns-use-waypoints.yaml

After enrolling the namespace, requests from any pods using the ambient data plane to services in travel-portal and travel-agency will route through the waypoint for L7 processing and policy enforcement.

Confirm that the waypoint proxy is used by all the services in the travel-portal and travel-agency namespaces:

istioctl ztunnel-config svc --namespace ztunnel
travel-agency          cars                      172.30.123.174 waypoint-travel-agency  1/1
travel-agency          discounts                 172.30.67.195  waypoint-travel-agency  1/1
travel-agency          flights                   172.30.28.227  waypoint-travel-agency  1/1
travel-agency          hotels                    172.30.237.136 waypoint-travel-agency  1/1
travel-agency          insurances                172.30.200.242 waypoint-travel-agency  1/1
travel-agency          mysqldb                   172.30.116.248 waypoint-travel-agency  1/1
travel-agency          travels                   172.30.150.117 waypoint-travel-agency  1/1
travel-agency          waypoint-travel-agency    172.30.155.226 None                    1/1
travel-control         control                   172.30.171.212 travel-control-waypoint 1/1
travel-control         travel-control-waypoint   172.30.31.88   None                    1/1
travel-control         travel-gateway-istio      172.30.58.61   travel-control-waypoint 1/1
travel-control-sidecar control                   172.30.234.8   None                    1/1
travel-portal          travels                   172.30.235.132 waypoint-travel-portal  1/1
travel-portal          viaggi                    172.30.125.155 waypoint-travel-portal  1/1
travel-portal          voyages                   172.30.133.148 waypoint-travel-portal  1/1
travel-portal          waypoint-travel-portal    172.30.56.159  None                    1/1

Task 3: Enable tracing for the Waypoint proxies

In order to see traces, we have to enable tracing for these two waypoints:

apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
  name: telemetry-waypoint-travel-agency
  namespace: travel-agency
spec:
  targetRefs:
  - kind: Gateway
    name: waypoint-travel-agency
    group: gateway.networking.k8s.io
  tracing:
  - providers:
    - name: "otel"
    randomSamplingPercentage: 50
---
apiVersion: telemetry.istio.io/v1
kind: Telemetry
metadata:
  name: telemetry-waypoint-travel-portal
  namespace: travel-portal
spec:
  targetRefs:
  - kind: Gateway
    name: waypoint-travel-portal
    group: gateway.networking.k8s.io
  tracing:
  - providers:
    - name: "otel"
    randomSamplingPercentage: 50
oc apply -f 02_2-enable-tracing.yaml

Task 4: Traffic Splitting

In this section we want to split traffic accross two versions of a service. This is useful for A/B tests and canary rollouts.

Step 1: Create a second version of the cars service deployment:

oc apply -f 05-cars-app-v2.yaml

Step 2: Create Kubernetes services for version v1 and v2:

oc apply -f 06-cars-services.yaml

Step 3: Apply a traffic splitting policy:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: cars-traffic-split
  namespace: travel-agency
spec:
  parentRefs:
  - group: ""
    kind: Service
    name: cars
    port: 8000
  rules:
  - backendRefs:
    - name: cars-v1
      port: 8000
      weight: 70
    - name: cars-v2
      port: 8000
      weight: 30
oc apply -f 07-cars-traffic-split.yaml

Step 4: Observe the traffic splitting in Kiali

Traffic Splitting in Kiali

Clean up:

oc delete -f 07-cars-traffic-split.yaml

Task 5: Traffic Redirect

We can use a HTTPRoute to redirect traffic. In this scenario we redirect travel requests for Madrid going to the travels API service, to Amsterdam.

First make a request for Madrid:

oc exec $(oc get pod -l app=cars -n travel-agency -o jsonpath='{.items[0].metadata.name}') -n travel-agency -- curl -sSv travels.travel-agency.svc.cluster.local:8000/travels/Madrid

Next apply the Redirect policy:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: travels-madrid-redirect-route
  namespace: travel-agency
spec:
  parentRefs:
  - group: ""
    name: travels
    kind: Service
    namespace: travel-agency
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /travels/Madrid
      filters:
        - type: RequestRedirect
          requestRedirect:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /travels/Amsterdam
    - backendRefs:
      - name: travels
        port: 8000
oc apply -f 08-travel-redirect.yaml

Now make a request for Madrid again and notice that you got a Redirect:

oc exec $(oc get pod -l app=cars -n travel-agency -o jsonpath='{.items[0].metadata.name}') -n travel-agency -- curl -sSv travels.travel-agency.svc.cluster.local:8000/travels/Madrid
* About to connect() to travels.travel-agency.svc.cluster.local port 8000 (#0)
*   Trying 172.30.150.117...
* Connected to travels.travel-agency.svc.cluster.local (172.30.150.117) port 8000 (#0)
> GET /travels/Madrid HTTP/1.1
> User-Agent: curl/7.29.0
> Host: travels.travel-agency.svc.cluster.local:8000
> Accept: */*
>
< HTTP/1.1 302 Found
< location: http://travels.travel-agency.svc.cluster.local:8000/travels/Amsterdam
< date: Thu, 15 Jan 2026 12:50:52 GMT
< server: istio-envoy
< x-envoy-decorator-operation: travel-agency~travels.travel-agency.svc.cluster.local:8000/*
< content-length: 0

Clean up:

oc delete -f 08-travel-redirect.yaml

Task 6: Traffic Rewrite

Instead of redirecting the client to the new URL, it would be more appropriate to use a rewrite policy in this case.

Create the rewrite policy:

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: travels-madrid-rewrite-route
  namespace: travel-agency
spec:
  parentRefs:
  - group: ""
    name: travels
    kind: Service
    namespace: travel-agency
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /travels/Madrid
      filters:
        - type: URLRewrite
          urlRewrite:
            path:
              type: ReplacePrefixMatch
              replacePrefixMatch: /travels/Amsterdam
      backendRefs:
      - name: travels
        port: 8000
oc apply -f 09-travel-rewrite.yaml

Now send a request for Madrid again, and you’ll get back the data for Amsterdam:

oc exec $(oc get pod -l app=cars -n travel-agency -o jsonpath='{.items[0].metadata.name}') -n travel-agency -- curl -sSv travels.travel-agency.svc.cluster.local:8000/travels/Madrid
* About to connect() to travels.travel-agency.svc.cluster.local port 8000 (#0)
*   Trying 172.30.150.117...
* Connected to travels.travel-agency.svc.cluster.local (172.30.150.117) port 8000 (#0)
> GET /travels/Madrid HTTP/1.1
> User-Agent: curl/7.29.0
> Host: travels.travel-agency.svc.cluster.local:8000
> Accept: */*
>
{"city":"Amsterdam","coordinates":null,"createdAt":"2026-01-15T13:11:31Z","status":"Valid","flights":[{"airline":"Red Airlines","price":1001},{"airline":"Blue Airlines","price":351},{"airline":"Green Airlines","price":301}],"hotels":[{"hotel":"Grand Hotel Amsterdam","price":505},{"hotel":"Little Amsterdam Hotel","price":82}],"cars":[{"carModel":"Sports Car","price":1005},{"carModel":"Economy Car","price":302}],"insurances":[{"company":"Yellow Insurances","price":308},{"company":"Blue Insurances","price":57}]}
< HTTP/1.1 200 OK
< content-type: application/json
< date: Thu, 15 Jan 2026 13:11:31 GMT
< content-length: 515
< x-envoy-upstream-service-time: 170
< server: istio-envoy
< x-envoy-decorator-operation: travels.travel-agency.svc.cluster.local:8000/*

Clean up:

oc delete -f 09-travel-rewrite.yaml

Task 7: Traffic Mirroring

Traffic mirroring (also called shadowing) in Istio means: A copy of real production traffic is sent to another service, without affecting the original request or response. The client still talks only to the primary service. The mirrored service receives the same request, but its response is ignored.

Client
  |
  |----> Primary service (real response used)
  |
  |----> Mirrored service (copy, response discarded)

No extra latency is added to the client path, because Istio does not wait for the mirror.

Typical use cases:

  • Testing a new version in production

  • Performance Benchmarking

  • Migration of services (runtimes, databases, switching cloud providers)

  • API contract validation

In our scenario we have already two versions of the cars service deployed in the travel-agency namespace. Make sure you have cleaned up the traffic splitting policy.

The following policy will mirror 100% of the traffic going to the cars service to the cars-v2 service.

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: traffic-mirroring-cars-v2
  namespace: travel-agency
spec:
  parentRefs:
  - group: ""
    kind: Service
    name: cars
    port: 8000
  rules:
  - filters:
    - type: RequestMirror
      requestMirror:
        percentage: 100
        backendRef:
          name: cars-v2
          port: 8000
    backendRefs:
    - name: cars
      port: 8000

Create the mirroring policy:

oc apply -f 010-traffic-mirroring.yaml

Now check the logs of the cars-v2 pods. You should see incoming requests:

oc logs --follow -n travel-agency deployment/cars-v2

In Kiali you won’t see incoming traffic, but outgoing traffic for the cars-v2 service.

Traffic Mirroring in Kiali

Clean up:

oc delete -f 010-traffic-mirroring.yaml

Task 8: Scaling of Waypoint Proxies

Waypoints run as standalone workloads, so they can be scaled like any other OpenShift resource.

We use a Horizontal Pod Autoscaler for the travel portal waypoint Deployment and specify replicas between 2 and 5:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: waypoint-travel-portal-hpa
  namespace: travel-portal
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: waypoint-travel-portal
  minReplicas: 2
  maxReplicas: 5
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 50
oc apply -f 03-waypoint-hpa.yaml

Task 9: Pod Disruption Budget for Waypoint Proxies

We can further increase the availability of waypoints by using PodDisruptionBudgets. This PodDisruptionBudget (PDB) is there to protect the waypoint proxy from being taken completely down by voluntary disruptions. Your waypoint is critical because it is the L7 enforcement point.

With this PDB, OpenShift must always keep at least one waypoint pod running.

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: waypoint-travel-portal-pdb
  namespace: travel-portal
spec:
  minAvailable: 1
  selector:
    matchLabels:
      gateway.networking.k8s.io/gateway-name: waypoint-travel-portal
oc apply -f 04-waypoint-pdb.yaml
Congratulations! You have completed Module 6: Traffic Management in Ambient Mode.