Gateway API
GatewayClass, Gateway and HTTPRoute (gateway.networking.k8s.io/v1), listeners and allowedRoutes, path, header and method matching, filters, weighted traffic splitting, ReferenceGrant and migrating from Ingress.
Exam tasks: 3.4 (use the Gateway API to manage ingress traffic), with 3.5 when migrating from Ingress
The decision: which Gateway listener should accept this traffic, which namespaces may attach routes to it, and how should the HTTPRoute match and split requests across backends?
Three resources, three owners
| Resource | Scope | Says |
|---|---|---|
| GatewayClass | Cluster | "Gateways of this class are run by controller X" (spec.controllerName). Usually installed with the implementation |
| Gateway | Namespace | "Listen on these ports and hostnames, with this TLS, and accept routes from these namespaces" |
| HTTPRoute | Namespace | "For these hostnames, send requests matching these rules to these Services" |
- Gateway API is CRDs plus a controller, not part of core Kubernetes. Without an implementation (Envoy Gateway, Istio, Cilium, NGINX Gateway Fabric, Contour, Traefik and others) the objects are accepted and nothing listens.
- GatewayClass, Gateway, HTTPRoute and GRPCRoute are
gateway.networking.k8s.io/v1in the Standard channel. ReferenceGrant, TLSRoute and ListenerSet reachedv1in Gateway API 1.5, and TCPRoute and UDPRoute in 1.6.
k get crd | grep gateway.networking.k8s.io # are the CRDs installed?
k get gatewayclass # ACCEPTED should be True
# install Standard channel CRDs (version from the task or the project's releases page)
k apply --server-side -f https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.6.2/standard-install.yamlThe Gateway
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: gw-system
spec:
gatewayClassName: envoy # must match an existing GatewayClass
listeners:
- name: http
protocol: HTTP
port: 80
hostname: "*.orbit.test"
allowedRoutes:
namespaces:
from: Selector # Same (default) | All | Selector
selector:
matchLabels:
gateway-access: public
- name: https
protocol: HTTPS
port: 443
hostname: "*.orbit.test"
tls:
mode: Terminate
certificateRefs:
- kind: Secret
name: orbit-wildcard-tls # kubernetes.io/tls Secret in gw-system
allowedRoutes:
namespaces:
from: AllallowedRoutes.namespaces.fromdefaults to Same: only routes in the Gateway's own namespace can attach. This is the usual reason an app team's route is ignored.- Check
k -n gw-system get gateway public:PROGRAMMED Trueand anADDRESSmean the data plane is up.
The HTTPRoute
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: atlas
namespace: atlas
spec:
parentRefs:
- name: public
namespace: gw-system
sectionName: https # attach to one listener only (optional)
hostnames:
- maps.orbit.test
rules:
- matches:
- path:
type: PathPrefix # Exact | PathPrefix | RegularExpression
value: /tiles
backendRefs:
- name: atlas-v1
port: 8080
weight: 90
- name: atlas-v2
port: 8080
weight: 10
- matches:
- headers:
- name: x-beta
value: "on"
path:
type: PathPrefix
value: /tiles # same path as above, so the header match decides
backendRefs:
- name: atlas-v2
port: 8080- Inside one
matchesentry, conditions are ANDed (path and header and method). Separate entries are ORed. - With no
matches, a rule matches everything (PathPrefix /). When several rules match, the most specific wins: Exact path, then longest prefix, then more header matches, and so on. weightis relative: 90 and 10 send about 10% to v2.weight: 0sends none. Omitted means 1.backendRefs.portis the Service port, and the Service must be in the route's namespace unless a ReferenceGrant allows otherwise.
Filters
| Filter | Use it for |
|---|---|
RequestRedirect | HTTP to HTTPS (scheme: https, statusCode: 301), host or path redirects |
URLRewrite | Rewrite the hostname or path prefix before forwarding (type: ReplacePrefixMatch) |
RequestHeaderModifier / ResponseHeaderModifier | set, add or remove headers |
RequestMirror | Copy traffic to a second backend, responses ignored |
rules:
- filters:
- type: RequestRedirect
requestRedirect:
scheme: https
statusCode: 301Exam signal
A redirect rule has no backendRefs. Attach that route to the http listener with sectionName: http, and
attach the real route to sectionName: https. Otherwise the HTTPS listener would redirect to itself.
Cross-namespace references
- Route to Gateway in another namespace: the Gateway's
allowedRoutesmust permit the route's namespace. - Route to a Service in another namespace, or Gateway to a Secret in another namespace: the target namespace must create a ReferenceGrant allowing it.
apiVersion: gateway.networking.k8s.io/v1 # v1beta1 before Gateway API 1.5
kind: ReferenceGrant
metadata:
name: allow-atlas-routes
namespace: tiles-backend # where the Service lives
spec:
from:
- group: gateway.networking.k8s.io
kind: HTTPRoute
namespace: atlas
to:
- group: ""
kind: ServiceRoute created, 404 everywhere
Read the route's status before anything else: k -n atlas get httproute atlas -o yaml, under
status.parents[].conditions. Accepted: False with NotAllowedByListeners means allowedRoutes excludes the
namespace; NoMatchingListenerHostname means the route's hostnames don't fit the listener's. ResolvedRefs: False
with RefNotPermitted means a cross-namespace backend without a ReferenceGrant, and BackendNotFound means a
wrong Service name or port.
Coming from Ingress
| Ingress | Gateway API |
|---|---|
| IngressClass | GatewayClass |
| Controller's shared listener | Gateway, owned and configured by you |
spec.rules[].host | HTTPRoute hostnames |
pathType: Prefix / Exact | PathPrefix / Exact |
spec.tls on each Ingress | TLS on the Gateway listener (once) |
| Controller-specific annotations (rewrites, canaries) | Portable fields: filters, weight, header matches |
The ingress2gateway tool converts existing Ingress objects into Gateway API resources as a starting point.
Scenarios
The listener's default from: Same rejects routes from other namespaces. Set from: All or Selector with a
label on shop. Cross-namespace attachment is allowed when the Gateway permits it. ReferenceGrant is for references
to Services and Secrets, not route-to-Gateway attachment. GatewayClass is cluster-scoped.
Weighted backendRefs in a single rule split traffic proportionally. Two rules with identical matches don't split:
one wins. Mirroring copies requests and ignores the canary's responses. Replica ratios would require changing
Deployments and give only an approximate split.
The owner of the target creates the grant, so it lives in data, with from the HTTPRoute kind in web and to
Service. The grant in web is backwards. allowedRoutes governs route attachment, which already succeeded. A
NetworkPolicy affects packets, not reference resolution.
Drill
kubectl config use-context lab-gw
A Gateway API implementation and the GatewayClass envoy are installed. In namespace atlas, Services atlas-v1
and atlas-v2 listen on port 8080.
- Create Gateway
publicin namespaceatlaswith an HTTP listener on port 80 for hostnamemaps.orbit.test. - Create HTTPRoute
atlasthat sends/tilestraffic 70/30 toatlas-v1/atlas-v2, except that/tilesrequests with headerx-beta: onalways go toatlas-v2.
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: public
namespace: atlas
spec:
gatewayClassName: envoy
listeners:
- name: http
protocol: HTTP
port: 80
hostname: maps.orbit.test
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: atlas
namespace: atlas
spec:
parentRefs:
- name: public
hostnames:
- maps.orbit.test
rules:
- matches:
- path:
type: PathPrefix
value: /tiles
backendRefs:
- name: atlas-v1
port: 8080
weight: 70
- name: atlas-v2
port: 8080
weight: 30
- matches:
- path:
type: PathPrefix
value: /tiles
headers:
- name: x-beta
value: "on"
backendRefs:
- name: atlas-v2
port: 8080k apply -f atlas-gw.yaml
k -n atlas get gateway public # PROGRAMMED True, note ADDRESS
k -n atlas get httproute atlas -o jsonpath='{.status.parents[0].conditions[*].type}{"\n"}{.status.parents[0].conditions[*].status}{"\n"}'
GW=$(k -n atlas get gateway public -o jsonpath='{.status.addresses[0].value}')
for i in $(seq 20); do curl -s -H 'Host: maps.orbit.test' http://$GW/tiles; done # mix of v1 and v2
curl -s -H 'Host: maps.orbit.test' -H 'x-beta: on' http://$GW/tiles # v2 every timeThe header rule wins for /tiles requests carrying x-beta: on: both rules have the same path, so the tie goes
to the match with more header conditions. Leave the path out of the header match and it becomes PathPrefix /,
which loses to the longer /tiles prefix, so beta users would still be split 70/30.
Further reading
Services
ClusterIP, NodePort, LoadBalancer, headless and ExternalName Services, the port, targetPort and nodePort mapping, EndpointSlices, traffic policies, and fixing a Service that has no endpoints.
Ingress
Ingress resources and controllers, IngressClass and the default class, host and path rules, Exact vs Prefix vs ImplementationSpecific path types, TLS Secrets, and kubectl create ingress.