OnCallReady

Lesson 34.20 · Kubernetes: Ingress, Gateway API & Service Mesh · 9 min read

HTTPRoute traffic management: matches, filters, timeouts, mirrors

In plain words

Think of a post room with sorting rules on the wall: letters with a red stamp go to the director, letters for "accounts" go to finance after the department name is crossed out and replaced with a desk number, and everything else is split between two clerks. The most specific rule wins, wherever it is on the wall.

An HTTPRoute is that wall of rules. Each rule has matches (path, headers, method, query), filters that change the request or response (add a header, redirect, rewrite the path, mirror a copy elsewhere), backendRefs with optional weights, and timeouts. GRPCRoute does the same for gRPC, matching on service and method instead of paths.

One route, many rules

An HTTPRoute is a list of rules; each rule has matches (when), filters (what to change) and backendRefs (where). 16.26 used weights and a header match. Here is the rest, as it behaves on any conformant implementation.

What you need to know already: the first HTTPRoute and its canary rule (16.26-16.27), HTTP headers, redirects and status codes (9.21-9.22), the previous lesson's listeners and status.

Which rule wins

When several rules (even in different routes on the same Gateway) match a request, the spec defines the order:

1. an Exact path match beats a prefix
2. the longest prefix
3. a match with a method
4. the most header matches
5. the most query param matches
6. the oldest route (creationTimestamp), then alphabetical namespace/name

So two teams can both claim shop.lab/ and shop.lab/api - the more specific match wins, and the tie-break is predictable. (With ingress-nginx, the admission webhook simply refused the second one, 16.21.)

Filters

rules:
- matches:
  - path: {type: PathPrefix, value: /old-api}
  filters:
  - type: URLRewrite                  # what rewrite-target did, without regexes
    urlRewrite:
      path: {type: ReplacePrefixMatch, replacePrefixMatch: /api}
  - type: RequestHeaderModifier
    requestHeaderModifier:
      set: [{name: X-Env, value: prod}]
      remove: [X-Debug]
  backendRefs: [{name: api, port: 80}]
- matches:
  - path: {type: PathPrefix, value: /search}
  filters:
  - type: RequestMirror               # a copy to v2, its answer thrown away
    requestMirror:
      backendRef: {name: search-v2, port: 80}
  backendRefs: [{name: search-v1, port: 80}]

Timeouts

- matches:
  - path: {type: PathPrefix, value: /reports}
  timeouts:
    request: 15s          # the whole request, as the client experiences it
    backendRequest: 10s   # one try to the backend (equal or less than request)
  backendRefs: [{name: reports, port: 80}]

Past the timeout the gateway answers 504. Unlike ingress-nginx's annotation, this is a typed field every conformant implementation reads the same way. Retries are still experimental in Gateway API; implementations offer them through their own policy objects meanwhile.

GRPCRoute

gRPC runs over HTTP/2. A GRPCRoute matches on the gRPC service and method instead of paths:

apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata: {name: stock, namespace: shop}
spec:
  parentRefs: [{name: edge, namespace: gw-edge}]
  hostnames: [grpc.shop.lab]
  rules:
  - matches:
    - method: {service: stock.v1.Stock, method: Get}
    backendRefs: [{name: grpc-stock, port: 9090}]

The backend must be reachable over HTTP/2 without TLS (h2c). Implementations learn that from the Service port's appProtocol: kubernetes.io/h2c; without it the route reports ResolvedRefs False, UnsupportedProtocol - one of the classic gRPC-through-a-gateway surprises.

What you can now do:

Why it helps

This is where app teams spend their time with Gateway API: canaries by weight, a beta header that routes testers to v2, a legacy path rewritten for a new service, a timeout so a slow endpoint fails fast instead of hanging. All of it is typed fields instead of annotation strings, so it is portable between implementations and validated by the API server.

The precedence rule is the part that surprises people: rules are not evaluated top to bottom. An exact path beats a prefix, a longer prefix beats a shorter one, and more header matches beat fewer. Knowing that explains most "why did this request go there?" questions.

Commands in this lesson

kubectl

FAQ

Does the order of rules matter?

Not for which rule wins: Gateway API defines precedence by specificity (exact path, then the longest prefix, then method, then the most header matches, then query parameters). Order only breaks exact ties. This differs from Istio VirtualServices, where the first matching rule wins.

What is the difference between timeouts.request and timeouts.backendRequest?

request is the total time the Gateway gives the whole request, including any retries; backendRequest is the time for one attempt to the backend. If the request timeout passes, the client gets a 504 from the Gateway.

Is a mirrored request safe to send?

The client never sees the mirror's answer, but the mirror receives a real request. If that backend writes to a shared database or sends emails, mirroring doubles the side effects. Mirror read-only paths, or point the copy at an isolated environment.

URLRewrite or RequestRedirect?

URLRewrite changes the path or host on the way to the backend; the client does not notice. RequestRedirect answers the client with a 301 or 302 and a new Location, so the client sends a new request. Use a rewrite to hide a backend's path layout, a redirect when the public URL itself should change.

Why does my GRPCRoute show UnsupportedProtocol?

The implementation needs to know the backend speaks HTTP/2. Set appProtocol: kubernetes.io/h2c (plain-text HTTP/2) on the Service port, or the TLS variant if the backend uses TLS, and the route resolves. The route's ResolvedRefs condition turns True once it can use the backend.

In an interview Mid

How would you send 10% of traffic and all testers to a new version using Gateway API?

One HTTPRoute with two rules. The first rule matches a header, for example x-canary: "on", and has a single backendRef to the v2 Service, so testers always reach v2. The second rule has no matches and two backendRefs, v1 with weight 90 and v2 with weight 10. Precedence picks the header rule for testers because it is more specific, whatever the order. I would add a timeouts.request so a slow v2 fails fast, watch v2's error rate, then move the weights step by step.

Also asked: How does Gateway API decide which rule matches a request? · What filters can an HTTPRoute rule apply? · What is the difference between rewriting a URL and redirecting it?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.