Gateway API Conformance and Portability

“Gateway API is portable” is true in a narrower sense than it first appears, and the difference between the broad reading and the narrow one is where migration projects lose a month. Portability is defined by a conformance programme with named feature sets and published reports, and a configuration is portable exactly to the extent that it uses features every candidate implementation has passed. This page covers how the levels are defined, how to read a conformance report, and how to keep the portable and non-portable parts of your configuration separate on purpose.

Prerequisite concepts

This assumes familiarity with the resource model in Kubernetes Ingress and Gateway API, and it complements gateway selection criteria, which covers the non-API dimensions — operational burden, extensibility model, team skills — that a conformance report deliberately says nothing about.

Three levels, and what each one promises

What each level actually guarantees Core features are mandatory and behave identically across every conformant implementation. Extended features are optional but, where supported, must behave as specified. Implementation-specific extensions carry no portability guarantee at all and are usually expressed as separate policy resources rather than as route fields. Core — mandatory path and header matching, weighted backends, request header modifier, redirects portable: yes — every conformant implementation passes the same tests Extended — optional request mirroring, response header modifier, timeouts, regular-expression paths portable: only among implementations that report the feature as supported Implementation-specific rate limiting, authentication, retries, WAF, custom filters portable: no — by design, and usually a separate policy resource A route using only the top band moves between implementations unchanged. Most real routes do not.

The third band is not a failure of the specification. Rate limiting and authentication differ so much between products that a single portable schema would either be a lowest common denominator or an unimplementable superset. Keeping them out of the route types is what let the route types stabilise.

Reading a conformance report

Every implementation that claims conformance publishes a machine-readable report per release, listing the profiles it ran and the features it supports.

# excerpt from a published conformance report
apiVersion: gateway.networking.k8s.io/v1
kind: ConformanceReport
implementation:
  organization: example-org
  project: example-gateway
  version: v1.4.2
gatewayAPIVersion: v1.2.1
profiles:
  - name: HTTP
    core:   { result: success, statistics: { passed: 42, failed: 0, skipped: 0 } }
    extended:
      result: success
      supportedFeatures:
        - HTTPRouteRequestTimeout
        - HTTPRouteMethodMatching
        - HTTPRouteResponseHeaderModification
      unsupportedFeatures:
        - HTTPRouteRequestMirroring        # <- the one your canary plan needed
  - name: GRPC
    core:   { result: success }

Three things to check, in order. First, gatewayAPIVersion — a report against an older API version says nothing about fields added since. Second, the unsupportedFeatures list, which is the actionable part and is easier to read than the passed count. Third, whether the profile you care about was run at all: an implementation may be fully conformant for HTTP and not implement GRPCRoute.

Keeping the portable part portable

The practical technique is directory separation, enforced in review. Routes that use only Core and agreed Extended features live in one place; policy attachments live in another; and the split is visible in every pull request.

Make the lock-in visible in the directory tree Routes, Gateways and reference grants live in directories that any conformant implementation can consume. Policy resources live under a directory named for the implementation that defines them, so a reviewer can see at a glance which files would survive a change of gateway and which would be rewritten. portable — moves unchanged routes/orders-route.yaml routes/payments-route.yaml gateways/edge-gateway.yaml routes/reference-grants.yaml implementation-specific — rewritten policies/envoy-gateway/orders-ratelimit.yaml policies/envoy-gateway/orders-retry.yaml policies/envoy-gateway/oidc.yaml one subdirectory per implementation The ratio between the two boxes is your real migration estimate — far more useful than a feature checklist, and it is a number you can watch grow over time in code review.
k8s/
  routes/                 # portable: HTTPRoute, GRPCRoute, ReferenceGrant
    orders-route.yaml
    payments-route.yaml
  gateways/               # portable: Gateway, listeners, TLS refs
    edge-gateway.yaml
  policies/               # NOT portable: one subdirectory per implementation
    envoy-gateway/
      orders-ratelimit.yaml
      orders-retry.yaml

Running the upstream conformance suite against your own cluster is the other half, and it is worth doing even when you have no plans to switch implementations: it catches the case where a controller upgrade quietly drops support for an Extended feature you depend on.

# run the upstream suite against the class you actually use
go test ./conformance -run TestConformance \
  -args --gateway-class=envoy-gateway \
        --supported-features=HTTPRouteRequestTimeout,HTTPRouteMethodMatching

Decision matrix

Situation What to do
Choosing between two implementations diff their unsupportedFeatures lists for the profiles you use
A feature you need is Extended confirm support in the report, then pin the version
A feature you need is implementation-specific accept the lock-in explicitly, and isolate it in policies/
Planning a future migration budget for rewriting policies/ entirely, not for editing it
Upgrading a controller re-run conformance before rolling it to production

Gotchas and failure signals

An unsupported field may be accepted and ignored rather than rejected, depending on the implementation. The route reports Accepted: True and the behaviour is simply absent — a mirror that never mirrors, a timeout that never fires. Verify behaviour, not status, for anything above Core.

Conformance is per version, not per product. A report for v1.4.2 tells you nothing about v1.5.0, and Extended support has been dropped between minor releases before.

Policy attachment points differ even where the policy is equivalent. One implementation attaches rate limiting to the route, another to the backend, a third to the Gateway. The effective scope differs accordingly, so a “same” policy can apply to more traffic than it did before.

A “supports Gateway API” claim in marketing material is not a report. Ask for the ConformanceReport for the specific release; if there is not one, treat every feature above Core as unverified.

The dangerous failure is the one that reports success An implementation that rejects an unsupported field gives an immediate error at apply time. One that accepts and ignores it reports the route as Accepted while the behaviour is simply missing, so a request mirror never mirrors and nobody finds out until an incident review asks where the shadow traffic went. route uses an unsupported field rejected at admission an error at apply time — annoying, and honest accepted and ignored status says Accepted, behaviour is absent test the behaviour, not the status

Validation

  • The conformance report for your exact controller version is on file
  • Every Extended feature in use appears in that report’s supportedFeatures
  • Portable routes and implementation-specific policies live in separate directories
  • The upstream conformance suite runs in CI against your GatewayClass
  • Behaviour of each Extended feature is asserted by a test, not assumed from status

FAQ

What does Core conformance actually guarantee?

That every conformant implementation passed the same upstream test suite for those features, so path and header matching, weighted backends, the standard header modifier and redirects behave identically. A route that uses only Core features can be moved between conformant implementations unchanged. That is a real guarantee, and it is narrower than most teams assume when they hear the word portable.

Why are rate limiting and authentication not in the specification?

Because the products differ too much for one schema to describe them without becoming either a lowest common denominator or an unimplementable superset. Leaving them out as policy attachments is what allowed the route types to stabilise. The trade is explicit: those parts of your configuration are locked to an implementation and should be stored separately so that lock-in is visible.

How do I check whether an implementation supports a feature?

Read the ConformanceReport published for the exact release you run. The unsupportedFeatures list per profile is the actionable part, and it is more useful than the pass count. Also check gatewayAPIVersion — a report against an older API version says nothing about fields added since — and confirm the profile you need, such as GRPC, was run at all.

What happens if I use a field an implementation does not support?

Behaviour varies: some implementations reject the resource, others accept it and silently ignore the field. The second case is the dangerous one, because the route reports Accepted true while the mirror never mirrors or the timeout never fires. For anything above Core, assert the behaviour with a test rather than trusting the status block.


Parent: Kubernetes Ingress & Gateway API