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}]
RequestRedirectanswers the client itself (scheme, hostname, port, path, statusCode 301/302/303/307/308) - the usual http -> https rule on the port-80 listener.ResponseHeaderModifierchanges what goes back (add a header, dropServer).RequestMirroris how you test a new version on real traffic with zero user impact: v2 sees every request, users only see v1's answers. Envoy-based data planes add-shadowto the Host header of the copy. Watch the cost: v2 writes to the same database as v1 unless you stop it.URLRewritereplaces a prefix or the whole path, nothing more: capture groups (/$2) do not exist here - the main thing ingress2gateway cannot translate.
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:
- predict which rule of which route serves a request
- rewrite a prefix, set headers, redirect, and mirror traffic to a new version
- put a timeout on one route
- route gRPC with a GRPCRoute and fix UnsupportedProtocol