In sidecar Istio, the routing surface was Gateway +
VirtualService. Ambient flips that to Kubernetes Gateway
API — same job, different shape. HTTPRoute takes over from
VirtualService, and a Gateway resource — with
the right gatewayClassName — declares the proxy itself.
Edge ingress, in-mesh waypoint, or east-west gateway.
The catch: one Gateway kind drives three completely
different deployments. The kind doesn't change — the
gatewayClassName does (istio /
istio-waypoint / istio-eastwest). And what
can attach to each one is different too: full L7 route set at
the edge, every L7 kind plus authZ on a waypoint, and L4-only on the
east-west side. The diagram below is the cheat sheet, with copy-paste
YAML for each shape.
Version note: GatewayClass names track Solo's Istio
distribution (1.26+); upstream Istio uses the same istio
/ istio-waypoint names, and istio-eastwest
is the Solo convention for the east-west class.
Edge · waypoint · east-west — where each kind lives
Read it like this: three columns, three deployments of the
same Envoy binary — and each one is declared with a different
gatewayClassName. Edge ingress takes north-south traffic and
speaks the full L7 route set (HTTPRoute, TLSRoute,
TCPRoute, GRPCRoute). Waypoints sit inside a
workload namespace and handle L7 routing plus authZ
(HTTPRoute, AuthorizationPolicy targetRefs,
RequestAuthentication targetRefs). The east-west gateway is
deliberately dumb — L4 only. It terminates HBONE from a peer cluster, hands
the inner connection to the local ztunnel, and lets the remote
waypoint do all the L7 work.
The Gateway API kinds, by where they live
One card per location. Each one tells you what attaches there and gives you a YAML sample to copy and adapt — the colour stripe matches the column in the diagram above.
🌐 Edge ingress gateway.networking.k8s.io/v1
North-south ingress. Pick gatewayClassName: istio
and istiod spins up an Envoy Deployment +
Service (LoadBalancer by default) — nothing to install
yourself. Every L7 route kind attaches via parentRefs.
Gateway · edge ingress with HTTP + HTTPS listenersistiod creates the Envoy Deployment
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: bookinfo-gateway
namespace: bookinfo
spec:
gatewayClassName: istio
listeners:
- name: http
port: 80
protocol: HTTP
allowedRoutes:
namespaces:
from: Same
- name: https
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: bookinfo-tls
allowedRoutes:
namespaces:
from: Same
HTTPRoute · path-prefix match to a backend ServiceparentRefs → ingress Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: productpage
namespace: bookinfo
spec:
parentRefs:
- name: bookinfo-gateway
kind: Gateway
hostnames:
- bookinfo.example.com
rules:
- matches:
- path:
type: PathPrefix
value: /productpage
backendRefs:
- name: productpage
port: 9080
TLSRoute · SNI-based passthrough for a backendterminate at the workload, not the edge
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: TLSRoute
metadata:
name: api-passthrough
namespace: bookinfo
spec:
parentRefs:
- name: bookinfo-gateway
kind: Gateway
sectionName: https-passthrough
hostnames:
- secure-api.example.com
rules:
- backendRefs:
- name: secure-api
port: 443
🛡️ Waypoint gateway.networking.k8s.io/v1
The in-mesh L7 proxy. This is where HTTP routing, JWT validation, and
L7 authZ actually happen for a Service, namespace, or workload.
Declared with gatewayClassName: istio-waypoint — and the
istio.io/waypoint-for label is what decides its scope.
Get that label wrong and the waypoint quietly does nothing.
Gateway · waypoint for one Serviceistiod creates the waypoint Deployment
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: reviews-waypoint
namespace: bookinfo
labels:
# Scope: service | workload | all
istio.io/waypoint-for: service
spec:
gatewayClassName: istio-waypoint
listeners:
- name: mesh
port: 15008
protocol: HBONE
HTTPRoute · 80/20 canary at the waypointparentRefs → waypoint Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews-split
namespace: bookinfo
spec:
parentRefs:
- name: reviews-waypoint
kind: Gateway
rules:
- matches:
- path:
type: PathPrefix
value: /reviews
backendRefs:
- name: reviews-v1
port: 9080
weight: 80
- name: reviews-v2
port: 9080
weight: 20
GRPCRoute · explicit gRPC method matchingparentRefs → waypoint Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: GRPCRoute
metadata:
name: reviews-grpc
namespace: bookinfo
spec:
parentRefs:
- name: reviews-waypoint
kind: Gateway
rules:
- matches:
- method:
service: reviews.v1.ReviewsService
method: GetReview
backendRefs:
- name: reviews-v1
port: 9080
AuthorizationPolicy · L7 authZ on the waypointtargetRefs → waypoint Gateway
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
name: reviews-read-only
namespace: bookinfo
spec:
# Attach to the waypoint, NOT the workload. This is what makes
# the policy run at L7 instead of being enforced by ztunnel at L4.
targetRefs:
- kind: Gateway
group: gateway.networking.k8s.io
name: reviews-waypoint
action: ALLOW
rules:
- to:
- operation:
methods: ["GET"]
paths: ["/reviews/*"]
🔁 East-west gateway.networking.k8s.io/v1
The bridge between meshes — and intentionally the least clever of the
three. gatewayClassName: istio-eastwest, one HBONE listener
on :15008, done. Don't bother trying to attach an
HTTPRoute here — it won't do anything. East-west is
SNI-routed at L4; the inner connection is decrypted at the remote
ztunnel, and any L7 work lives on the destination
waypoint.
Gateway · east-west gateway on :15008 HBONEistiod-gloo creates the Envoy Deployment + LB Service
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: istio-eastwestgateway
namespace: istio-eastwest
spec:
gatewayClassName: istio-eastwest
listeners:
- name: hbone
port: 15008
protocol: HBONE
tls:
mode: Passthrough
allowedRoutes:
namespaces:
from: All
Why there's no HTTPRoute heredesign note · not YAML
# East-west traffic from another cluster arrives as an HBONE tunnel.
# The east-west gateway terminates TLS, reads the SPIFFE identity and
# the inner destination service from the SNI, and forwards the inner
# connection to the local ztunnel — which then delivers it to the
# real workload (or to that workload's waypoint, if it has one).
#
# So:
# * The gateway does NO HTTP parsing.
# * Routing decisions are SNI-based, made by Envoy automatically
# from the inner CONNECT headers — no HTTPRoute needed.
# * Any L7 policy (HTTPRoute, AuthorizationPolicy with method/path,
# RequestAuthentication) belongs on the DESTINATION waypoint.
#
# If you find yourself wanting to attach an HTTPRoute to an east-west
# Gateway, that's the signal you actually want a waypoint Gateway
# (istio-waypoint) instead.
Attachment cheat sheet — parentRefs vs targetRefs
These two are not the same thing.
Routes (HTTPRoute, GRPCRoute,
TCPRoute, TLSRoute) use
parentRefs — "I'm a routing rule on this Gateway."
Policies (AuthorizationPolicy,
RequestAuthentication) use targetRefs —
"apply this policy to whatever is on the other end of this reference."
Routes attach upward; policies attach sideways.
| Resource | Attaches via | Common targets | Notes |
|---|---|---|---|
HTTPRoute |
parentRefs |
Gateway (ingress · waypoint) | Path / header / method matching, weighted backendRefs, filters. |
GRPCRoute |
parentRefs |
Gateway (ingress · waypoint) | gRPC service + method matching — cleaner than path-matching on HTTPRoute. |
TCPRoute |
parentRefs |
Gateway (ingress only) | Raw TCP forwarding to a backend Service / port. |
TLSRoute |
parentRefs |
Gateway (ingress only) | SNI-based TLS passthrough — terminate at the workload, not the edge. |
AuthorizationPolicy |
targetRefs |
Gateway (waypoint) · Service | L4 rules → enforced by ztunnel; L7 rules require a waypoint target. |
RequestAuthentication |
targetRefs |
Gateway (waypoint) | JWT issuer / audience / JWKS — populates request.auth.*. |
ReferenceGrant |
n/a — grant resource | Route ⇄ backend Service across namespaces | Required when a Route in namespace A references a Service in namespace B. |
GatewayClass |
cluster-scoped | picks the controller | Names a controller (e.g. istio.io/gateway-controller). One per class. |
HTTPRoute vs the legacy VirtualService
For anything new on Ambient, reach for HTTPRoute first.
It's portable, it's what every other Gateway API controller speaks, and
both Solo and upstream Istio ship new features there before
VirtualService. That said — VirtualService
still earns its keep for a few things: advanced fault injection (delay
+ abort with percentage selectors), regex header matching,
percentage-sampled mirroring, and routing to
DestinationRule subsets by name. If you don't need any of
those, use HTTPRoute.
Same 80 / 20 canary split, written both ways. Collapse one to focus on the other.
HTTPRoute · 80 / 20 canarymodern · portable
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: reviews-split
namespace: bookinfo
spec:
parentRefs:
- name: reviews-waypoint
kind: Gateway
rules:
- matches:
- path:
type: PathPrefix
value: /reviews
backendRefs:
- name: reviews-v1
port: 9080
weight: 80
- name: reviews-v2
port: 9080
weight: 20
VirtualService · same split, legacyneeds a DestinationRule for subsets
apiVersion: networking.istio.io/v1
kind: VirtualService
metadata:
name: reviews-split
namespace: bookinfo
spec:
hosts:
- reviews
http:
- match:
- uri:
prefix: /reviews
route:
- destination:
host: reviews
subset: v1
weight: 80
- destination:
host: reviews
subset: v2
weight: 20
---
apiVersion: networking.istio.io/v1
kind: DestinationRule
metadata:
name: reviews
namespace: bookinfo
spec:
host: reviews
subsets:
- name: v1
labels: { version: v1 }
- name: v2
labels: { version: v2 }
CLI — inspect Gateway API resources
🔍 kubectl + istioctl moves day 2
The signal that matters most is the Programmed condition on a Gateway.
If it's True, istiod has built an Envoy config and the
route attachments have resolved. If it's not, start digging there.
kubectl get · list every Gateway API resource in the clusterfast overview
# Everything Gateway-API in every namespace
kubectl get gateway,httproute,grpcroute,tcproute,tlsroute,referencegrant -A
# Just Gateways, with the controller and Programmed condition
kubectl get gateway -A -o wide
# Which GatewayClasses are registered? (controllers Solo / upstream Istio install)
kubectl get gatewayclass
# Expect to see: istio · istio-waypoint · istio-eastwest
kubectl describe gateway · read attached routes + Programmed conditionthe single most useful command
# Look at the Status block at the bottom: it lists every Route that
# attached, every listener's resolved Hostnames, and the Programmed
# condition (True = Envoy config has been generated).
kubectl -n bookinfo describe gateway reviews-waypoint
# Same for routes — the .status.parents[] array shows which Gateway
# each route ended up attaching to.
kubectl -n bookinfo describe httproute reviews-split
istioctl proxy-config · read the live Envoy configingress or waypoint Envoy
# Route table on the ingress Envoy
istioctl proxy-config routes deploy/bookinfo-gateway -n bookinfo
# Same for a waypoint
istioctl proxy-config routes deploy/reviews-waypoint -n bookinfo
# Listeners + clusters, in case the route table is empty
istioctl proxy-config listeners deploy/reviews-waypoint -n bookinfo
istioctl proxy-config clusters deploy/reviews-waypoint -n bookinfo
kubectl get gatewayclass · confirm the controllers are registeredprereq for any Gateway CR
kubectl get gatewayclass -o custom-columns=\
NAME:.metadata.name,\
CONTROLLER:.spec.controllerName,\
ACCEPTED:.status.conditions[?\(@.type==\"Accepted\"\)].status
# Expected:
# NAME CONTROLLER ACCEPTED
# istio istio.io/gateway-controller True
# istio-waypoint istio.io/mesh-controller True
# istio-eastwest istio.io/eastwest-controller True
Full reference table
One row per Gateway API kind, plus the three Ambient GatewayClasses at the bottom. The Where it lives column is the load-bearing one — it tells you which proxy actually serves the resource.
| Resource | Kind | API | What it does | Where it lives |
|---|---|---|---|---|
Gateway |
gateway-api | gateway.networking.k8s.io/v1 |
Declares a proxy + listeners. The proxy is created by istiod based on gatewayClassName. |
edge · waypoint · east-west |
HTTPRoute |
gateway-api | gateway.networking.k8s.io/v1 |
HTTP routing: path / header / method matching, weighted backends, filters, rewrites. | ingress Envoy · waypoint |
GRPCRoute |
gateway-api | gateway.networking.k8s.io/v1 |
gRPC service + method matching. Cleaner than path-matching on HTTPRoute for gRPC. | ingress Envoy · waypoint |
TCPRoute |
gateway-api | gateway.networking.k8s.io/v1alpha2 |
Raw TCP forwarding from a listener to a backend Service. | ingress Envoy |
TLSRoute |
gateway-api | gateway.networking.k8s.io/v1alpha2 |
SNI-based TLS passthrough — terminate at the workload, not the gateway. | ingress Envoy |
ReferenceGrant |
gateway-api | gateway.networking.k8s.io/v1beta1 |
Permits a Route in namespace A to reference a backend Service in namespace B. | istiod (admission) |
GatewayClass |
gateway-api | gateway.networking.k8s.io/v1 |
Cluster-scoped. Names a controller — one row per implementation. | cluster-scoped |
istio |
edge | GatewayClass | Edge ingress class. istiod creates an Envoy Deployment + LB Service per Gateway. |
controller: istio.io/gateway-controller |
istio-waypoint |
waypoint | GatewayClass | In-mesh waypoint class. :15008 HBONE listener; scope via istio.io/waypoint-for. |
controller: istio.io/mesh-controller |
istio-eastwest |
east-west | GatewayClass | East-west gateway class (Solo distribution). Single :15008 HBONE listener, L4 only. |
controller: istio.io/eastwest-controller |
Where to go from here
Read this alongside the
Istio Ambient CRDs visual map — it
covers every CRD istiod watches, and Gateway API is one
column of that bigger map. If you want to know what an east-west
Gateway actually does on the wire once a packet lands on
:15008, jump over to
HBONE east-west.
For the kind definitions themselves, the upstream
Gateway API site
is the source of truth. Solo's GatewayClass names
(istio, istio-waypoint,
istio-eastwest) match upstream Istio for the first two —
the east-west class is the Solo addition.