HTTPProxy: a CRD with header matching, weighted routing, retries — features Ingress wouldn't get for years. Envoy under the hood.1. The resource model
Ingress puts everything into a single object owned by whoever happens to edit it. Gateway API splits the responsibility along the three personas that actually exist in real clusters: cluster admin, platform team, app team.
Ingress one resource
Host, path, TLS, backend, controller-specifics — all in one place. RBAC has nothing to grip on.
Gateway API three resources
Each layer is a separate API object. Each persona owns one. RBAC works.
2. Same use case, both ways
Route /api/v1 to api-v1, do an 80/20 canary
between api-v2 and api-v2-canary on /api/v2.
Look how the vendor annotations leak into Ingress.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: api annotations: nginx.ingress.kubernetes.io/use-regex: "true" spec: ingressClassName: nginx rules: - http: paths: - path: /api/v1 pathType: Prefix backend: service: name: api-v1 port: { number: 80 } - path: /api/v2 pathType: Prefix backend: service: name: api-v2 port: { number: 80 } --- # second Ingress, just to express "20% canary" apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: api-canary annotations: nginx.ingress.kubernetes.io/canary: "true" nginx.ingress.kubernetes.io/canary-weight: "20" spec: ingressClassName: nginx rules: - http: paths: - path: /api/v2 pathType: Prefix backend: service: name: api-v2-canary port: { number: 80 }
apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: api spec: parentRefs: - name: eg rules: - matches: - path: { type: PathPrefix, value: /api/v1 } backendRefs: - name: api-v1 port: 80 - matches: - path: { type: PathPrefix, value: /api/v2 } backendRefs: - name: api-v2 port: 80 weight: 80 - name: api-v2-canary port: 80 weight: 20 # No annotations. The 80/20 split is a typed, # controller-agnostic field on backendRefs. # Switching from Envoy Gateway to Cilium # or NGINX Gateway Fabric: same YAML.
3. What's actually in the spec
Ingress's spec covers host + path + backend. Everything else used to live
in annotations: blocks that differ per controller. Gateway API
moved the common cases into the spec itself.
| Feature | Ingress | Gateway API |
|---|---|---|
| Path matching | In spec — Prefix / Exact | In spec — PathPrefix / Exact / RegularExpression |
| Header matching | Vendor annotations only | In spec — typed headers: on every match |
| HTTP method matching | Not supported | In spec — method: on match |
| Query-param matching | Not supported | In spec — queryParams: on match |
| Traffic splitting / canary | Vendor annotations — two Ingress objects | In spec — weight: on each backendRef |
| Request mirroring | Vendor-specific where supported | In spec — RequestMirror filter |
| URL rewrite | Vendor annotations only | In spec — URLRewrite filter |
| Header manipulation | Vendor annotations only | In spec — RequestHeaderModifier, ResponseHeaderModifier |
| Protocols | HTTP / HTTPS | HTTP, gRPC, TLS, TCP, UDP (separate Route kinds) |
| Cross-namespace routing | Same namespace only | Explicit allowedRoutes + ReferenceGrant |
| Standardised status | Vendor-specific, often missing | Accepted, Programmed, ResolvedRefs on every resource |
| Persona separation / RBAC | One object → one RBAC verb | Three resource kinds → three RBAC scopes |
| Portability across controllers | Low — annotations are vendor lock-in | High — spec is the contract |
4. Where Envoy fits in the timeline
Envoy-based ingress controllers existed long before Gateway API. Each invented its own custom CRDs because Ingress wasn't expressive enough. Gateway API standardises what they all converged on.
Mapping CRD on top of Envoy. Same motivation: Ingress is too limited.VirtualService CRDs on Envoy. Same pattern again.Gateway, GatewayClass, HTTPRoute reach Standard channel.What's actually running here
The Demo page is served by exactly that stack: a public
Open Telekom Cloud ELB hands TLS-encrypted traffic to Envoy Gateway,
which evaluates eight upstream Routes (six HTTPRoute, one
GRPCRoute, one TCPRoute) and forwards to the
matching pod. On top run Envoy-Gateway extension policies — rate limit,
CORS, Basic Auth, TLS minimum, security headers and a Coraza WAF — all
declarative, no annotations anywhere.