Challenge ·Medium

Shadow Deployment with Gateway API Request Mirroring

A new version of the checkout API is deployed but receives no traffic. Mirror live requests to it so it runs under real load, and read its Pod logs to prove the copies arrive while clients keep getting the stable answer.

Scenario

The checkout API runs in the checkout namespace. A new version sits beside the stable one and receives nothing:

DeploymentVersionRole
checkout-stablev1serves production traffic
checkout-shadowv2new version, a rewritten pricing engine

Both listen on port 8080, and both print one line per request they handle:

07:41:12 checkout-stable v1 handled GET /api/checkout id=- host=checkout.acme-retail.io from=10.42.0.9

A Gateway named edge-gateway is already provisioned on 192.168.1.240, listening on port 80 for checkout.acme-retail.io, and /etc/hosts on cplane-01 resolves that name. No HTTPRoute exists yet, so the domain answers nothing so far.


Task

The team wants v2 exercised under real production load before it ever answers a customer. That is a shadow deployment: every live request is duplicated to checkout-shadow, and the copy's response is discarded.

  1. Create an HTTPRoute named checkout-route in the checkout namespace, attached to edge-gateway and serving checkout.acme-retail.io.
  2. Route /api/checkout to checkout-stable on port 8080.
  3. Send a copy of every one of those requests to checkout-shadow on port 8080. A request to the domain is then answered by v1 and logged by both Pods.
  4. Send ten tagged requests to the domain:
    for i in $(seq 1 10); do
      curl -s -H "X-Request-Id: shadow-$i" \
        http://checkout.acme-retail.io/api/checkout
    done
    
  5. Save the shadow Pod logs to /home/laborant/shadow.logs:
    kubectl logs deployment/checkout-shadow -n checkout > /home/laborant/shadow.logs
    

    The file has to show all ten requests, shadow-1 through shadow-10, handled by v2 even though no client ever saw a v2 response.
One request answered by v1 and copied to v2, whose response is discarded

The client is always answered by v1. v2 receives an identical copy and its response goes nowhere.

Important

checkout-shadow must not become a second backend. If a client can ever receive a v2 answer, this is a canary release rather than a shadow deployment, and v2 is no longer free to fail.


Hint 1 | Where duplication lives in the API

Duplicating traffic is not a second backend and not a weight. It is a filter, and filters attach to a rule beside its backendRefs.

kubectl explain httproute.spec.rules.filters
kubectl explain httproute.spec.rules.filters.requestMirror

The list of filter types is short. One of them describes sending a copy elsewhere.

Hint 2 | The shape of a rule

A rule carries what to match, what to do with the request, and where to send it:

rules:
- matches:
  - path:
      type: PathPrefix
      value: <path>
  filters:
  - type: <filter-type>
    <filterField>:
      backendRef:
        name: <service-name>
        port: <service-port>
  backendRefs:
  - name: <service-name>
    port: <service-port>

The copied destination sits inside the filter. The real destination stays in backendRefs.

See: HTTPRoute, Request mirroring

Hint 3 | Reading the result

A working mirror looks like nothing changed from the client side, so check the route first and the logs second:

kubectl describe httproute checkout-route -n checkout

Accepted: True and ResolvedRefs: True mean the route attached and every Service it names, including the mirrored one, resolved. A mirror pointing at a Service that does not exist fails here.

Then send a few tagged requests and grep both log streams for the same id:

kubectl logs deployment/checkout-shadow -n checkout --tail=50 | grep 'id=shadow-'

The same id in both Pods is the proof that one client request became two.


Test Cases