Gateway API for E/W Traffic in Kubernetes
Gateway API for E/W Traffic in Kubernetes
Overview
The Gateway API describes a way of managing N/S (ingress) L4 and L7 traffic in a Kubernetes
cluster. Members of the Kubernetes, Istio and Service Mesh Interface (SMI) communities are
coming together to standardize an approach for using the Gateway API for E/W (mesh) traffic.
This document looks at the traffic routing APIs in a variety of service mesh implementations and
proposes a number of possible designs for a standard. When agreed upon by the community,
this work will form the basis of one or more Gateway API enhancement proposal.
Motivation
The Gateway API project has seen great success as a general specification for north/south L4
and L7 traffic routing. The features present in the spec are by no means specific to ingress
traffic; in fact, most service meshes provide near identical functionality to their users (e.g. traffic
splitting, timeouts, etc). Thus, it is clear that the greater cloud-native community would
greatly benefit from Gateway API being a universal set of resources to describe all
Kubernetes traffic, north/south and east/west.
Goal
This document aims to establish a common standard, API, and spec for using Gateway API
Routes (HTTPRoute, TCPRoute, etc) for service meshes.
While the existing route API takes care of a variety of behaviors (upstream backend selection,
header manipulation and matching, etc), there are a few areas that are ambiguous for Mesh
currently:
● How to associate a route to "the mesh"
● How to match traffic (while routes have matching behavior, it is incomplete; with ingress
there is matching logic split between Gateway and Route)
● Permission model on consumer and producer services/route
Terminology
Before discussing design, it's helpful to have a shared vocabulary on the various relevant
concepts. Many of these terms are highly overloaded or have many different meanings in
different communities; the goal here is not to make any new standards or definitions, but rather
to ensure readers are on the same page with important terms.
Service Mesh
This is both core to the design and highly overloaded. Many folks have wildly different views of
what "service mesh" means, and emotional responses to the word. For the purposes of this
document, a "mesh" just means some component in a cluster that is capable of adding
functionality to users network requests. The gateway-api for Ingress traffic has been described
as something that translates from things without context (outside of the cluster) to things with
context (inside the cluster). Similarly, a service mesh could be described as something that uses
that context to modify cluster network requests.
This is intentionally broad - while most see a service mesh as "userspace sidecar proxies using
iptables to redirect traffic", this document doesn't depend on this narrow definition, and includes
architectures such as Kubernetes itself, "proxyless" architectures, and more.
Policy
Policy is a broad term for any resource that can attach to another resource to configure it. This
is defined in the Policy Attachment GEP. Examples of (hypothetical) policies could be resources
like `TimeoutPolicy` or `AuthorizationPolicy`.
Routes
Routes are a collective term for a number of types in gateway-api: `HTTPRoute`, `TLSRoute`,
`TCPRoute`, `UDPRoute`, etc. In the future there may be more defined, in core or extensions
(such as `SQLRoute`).This document focuses on routes, rather than "Policy", as these are the
primary existing core resources in gateway, and help form a foundation for policy attachment.
Future work from GAMMA will likely look into policies, such as authorization policies.
Service
"service" is a key component to meshes. In this document, "service" refers to a broader concept
than just a Kubernetes Service resource, and attempts to differentiate between these by code
font when referring to the resource. A service can be a Service, but it can also be something
like a ServiceImport (from the MCS spec), or a custom type (a made up example would be a
CloudService abstracting cloud endpoints).
While a Service in Kubernetes is a single resource, logically it handles two different roles - a
"service frontend" and "service backend".
Resource level view of Service Decomposed view of Service
The "service frontend" refers to how we call a service. In a Service, this is an automatically
allocated DNS name (name.namespace.svc.cluster.local) and IP address
(ClusterIP). Not all services have frontends - for example, "headless" Services.
The "service backend" refers to the target of a service. In Service, this is Endpoints (or
EndpointSlice).
In the Gateway API today, Service is used as a "service backend". For example, in a route we
would refer to Service as the (aptly named) backendRef:
However, there are other ways that services could be used in the API, which are explored in the
design - by utilizing the "service frontend".
Below shows a hypothetical way to model splitting calls to the a service frontend to backends
composed of a-v1 and a-v2 (see real world example). In this case, service is used as both a
frontend and a backend. The route attaches to "Service a frontend", and any calls to that will be
directed to the a-v1 and a-v2 backends (as determined by the logic within the route.
A user connecting to a service (frontend), which has a Route forwarding to two different services (backends)
A service producer is someone that authors and operates a service. Some example actions
they may take are deploy a Service and Deployment, setup authorization policies to limit
access to their workloads, and setup routing rules to canary their workloads.
A service consumer is someone that utilizes a service by communicating with it. For example,
they may want to take actions like curl hello-world in their application - we could call this
"consuming the hello-world service".
Consumer and producer are personas, not concrete workloads or resources. One important
aspect of these personas is that they may live in different trust boundaries (typically
namespace).
Design
Fundamentally, we need to be able to give each implementation enough information to find out,
for a given request/connection, what routes should apply.
Mesh Representation describes how we actually refer to our "mesh" so that we can bind
routes, or other policies, to it. This ensures an implementation knows a route should apply to the
mesh, and not something else (another mesh, an ingress Gateway, etc).
Additional matching information, in the form of a Service binding, describes how we bind a
route to a specific service. Unlike for ingress, we need additional matching logic due to the
nature of mesh routing. For example, we may want to write a rule that matches TCP requests to
the foo Service; this cannot be represented by standard ingress-oriented route resources,
which rely on acting as reverse proxies rather than transparent proxies. Note that we may not
want any Service binding in some instances (for example, "match any request with Host:
example.com").
Below, we describe some options for primitives, and what various combinations of them would
look like.
Mesh Representation
Tracked via #1291
GEP: GEP-1291: Mesh Representation
Example:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: MeshClass
metadata:
name: cool-mesh
spec:
controllerName: example.com/mesh-controller
description: "my super cool mesh"
parametersRef: { ... }
MeshClass mirrors GatewayClass almost 1:1 and is a cluster scoped resource. Mesh
implementations would register a MeshClass, which would allow resources to bind to this
mesh.
Because the resource is cluster scoped, this requires that policy/route attachment handles
namespaces within those attachments.
The benefit of this approach would be that the Mesh resource becomes a namespaced
instantiation of the mesh. This could take a few different forms.
The Mesh could simply represent a configuration scoping, or it could actually enroll workloads
into a mesh. For example, deploying a Mesh in the foo namespace could trigger sidecars to be
injected into the foo namespace (for sidecar based Meshes - the pattern is generic).
In this simplest case, this could be represented as a Gateway with no listeners (validation would
be loosened):
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
name: mesh
spec:
gatewayClassName: my-mesh
For advanced use cases, explicit listeners could be described, allowing more advanced
configuration (this could be dropped from the proposal)
Example:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
name: gateway
spec:
gatewayClassName: my-mesh
listeners:
- name: terminate
hostname: "echo"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: my-cert-http
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: echo
spec:
parentRefs:
- name: gateway
sectionName: terminate
hostname: [echo.default.svc.cluster.local]
rules:
- backendRefs:
- name: echo
port: 80
This would allow accepting traffic on port 443, which is terminating TLS, for requests to the
"echo" service and forwarding to port 80. Note: this would be for application TLS. mTLS or other
encryption used as the transport protocol for the mesh is not related.
Service Binding
Tracked via #1294
GEP doc: GEP-1294: Mesh Service Binding
In this option, we are not explicitly declaring which mesh this applies to; only that it applies to
"mesh" in general. In the majority of cases, this is expected to not be an issue. Almost all users
have a single mesh, and even if they do use multiple meshes they likely have the same
configuration between them. While not specified explicitly, any mesh controller applying this
route could set a corresponding RouteParentStatus so it would be clear at runtime if and where
it has been applied.
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: my-route
namespace: default
spec:
parentRefs:
- kind: Service
name: httpbin
sectionName: port-http # optional, specify port
hostnames: [ … ]
rules:
- backendRefs:
- name: httpbin-v1
port: 80
- name: httpbin-v2
port: 80
This option doesn't easily allow a way to bind to the mesh but not to a Service, unless we also
allow binding directly to the Mesh Representation for these cases.
Example:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: my-route
namespace: default
spec:
parentRefs:
- kind: Mesh
name: cool-mesh
sectionName: subset-of-mesh # optional
services:
- name: my-frontend
# namespace: could be allowed, see further discussion on namespaces
kind: Service # the default, but this could be something else
port: 80 # optional; if not specified, applies to all ports
hostnames: [ … ]
rules:
- backendRefs:
- name: my-backend
port: 80
This example uses Mesh, but could use other resources decided above to represent the mesh.
One nice aspect of this is that services is optional. This would allow binding to the mesh
without binding to a service at all, allowing things like "Match all traffic on port 3306 to mysql-
svc" and "Match all traffic with Host: example.com".
Attach to a binding resource
We could bind to an indirection of Service. This would allow us to put additional information into
Service/Route without modifying those types.
Example:
kind: ServiceMeshBinding
metadata:
name: httpbin-binding
spec:
service: # what service we are configuring
name: httpbin
kind: Service
meshes: # define which Meshes this service is configured for
- kind: Mesh
name: my-cool-mesh
This has the benefit of not requiring any changes to the API. However, it does introduce a
substantial boilerplate. Another benefit is that it would also allow us to augment the Service with
any other metadata we may need (protocols, TLS settings, etc), if desired.
This option doesn't easily allow a way to bind to the mesh but not to a Service, unless we also
allow binding directly to the Mesh Representation for these cases.
Here, both requests have two routes potentially associated with requests to backend - the one
defined by the service producer (Route (producer)) and the ones defined by the service
consumers (Gateway or mesh client).
At a high level, there are two approaches to this problem: either pick one route to use, or apply
both.
Namespaced proxy
A namespace scoped proxy
A common implementation is with a sidecar proxy alongside each workload. In this diagram, we
have a proxy alongside both the consumer and producer, but in mixed states we could have
either one or the other. Note: for this discussion, it doesn't matter if the proxy is 1:1 with a
workload, just that it acts on behalf of the namespace (i.e. can handle traffic to/from that
namespace and view configurations in the namespace)
1:1 proxies
The "namespaced" aspect describes scoping of the proxy and permission/ownership models.
Within this, there is another class of proxies - the most common in mesh implementations -
where the proxy is 1:1 with a workload ("sidecar"). In this topology, it is hard/impossible to
implement routing at the server-side proxy; when a proxy receives a request it is expected to
either reject or forward the request to the same workload. While it may be mutated along the
way, it is atypical for it to be forwarded to a completely different workload.
A cross-namespace proxy
For Gateway cases, we almost certainly would need to pick the Gateway's route, as that is the
only thing that actually directs traffic to the backend -- the Route (producer) route only has
configuration for when requests are already going to the backend.
For mesh cases, I think the same option - using the consumer's route, if there is one, makes the
most sense here. This provides consistency with Gateway (consumer has precedence over
producer) and allows some user cases where we want per-client settings.
The major downside of this option is the producer of a service no longer has autonomy over
how requests to their service behave. This may lead to unexpected results. For example the
producer may have a rule that should send only 1% of traffic to the canary version, but
consumer overrides may accidentally ignore this rule.
Another concern is that for 1:1 proxies, all routing rules effectively need to apply to the
consumer namespace's proxy. This leads to problematic ownership models - configuration
created by one namespace impacts workloads running in another namespace. This can lead to
security concerns, and cost concerns (some configurations are computationally expensive).
In some cases, we could simply apply the two routes at the two proxies. For example, if the
consumer route defined some rules to configure different paths to different backends, while the
producer route modified headers, most proxies could implement this. However, if we end up
doing "routing" in the producer rule (for example, 1% traffic to canary), most implementations
would struggle with this -- the proxy is tightly coupled to the workload, so once a request arrives
at the workload it is assumed it will always be forwarded only to the workload, not to some
external location.
Here, we may need to apply two routes to the same requests on the same connection. Routes
cannot be statically merged in all cases (see the Route Delegation GEP for more discussion),
which would limit most implementations to doing two 'cycles' of processing in some way, which
may be complex or impossible.
Traffic matching
While the Gateway API defines some concrete matching logic for routes, there are some
ambiguities introduced in mesh use cases. While the core should remain unchanged (such as
header match semantics), we do still need to figure out which route should apply for a given
request.
With standard Gateways, this is done by explicitly defining listeners that listen on some port,
and optionally some subset of hosts (HTTP Host header or SNI). With mesh, we don't have this
extra matching metadata. Additionally, mesh use cases often differ - we often want to match
"requests to the foobar Service". This is not something Gateway can handle today; while it
could have a match for hostname: foobar.namespace.svc.cluster.local, this is not
quite the same, and certainly will not work for TCPRoute.
This is where the Service Binding concept is important, by binding routes directly to Services.
Consider the following example (using one form of Service Binding, but applies equally to any
option):
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: my-route
namespace: default
spec:
parentRefs:
- kind: Mesh
name: cool-mesh
services:
- name: my-frontend
kind: Service
port: 80 #
hostnames: ["frontend.example.com"]
rules:
- backendRefs:
- name: my-backend
port: 80
Implicit routing
While matching traffic for things explicitly defined in Routes is fairly straightforward, most users'
applications will make requests to things not defined in Routes. There are many options on how
these requests should be handled. For example, we could allow them or reject them. We can
treat them as opaque TCP streams or handle them as HTTP requests (based on automatic
detection, appProtocol, etc).
One challenge with this is that this behavior all happens before any API, aside from mesh
deployment/enablement, is used at all. This means that for existing mesh implementations, if
the spec chooses an option that didn't align with the original implementation, migration may be
challenging.
Below describes the end to end user experience with a few of the options above. Note: the
combination of Service Binding and Mesh Representation are largely picked at random here.
First, I deploy the mesh implementation (via some implementation specific mechanism, such as
helm install cool-mesh).
This would typically instantiate a MeshClass resource describing the mesh (user doesn't need
to do this):
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: MeshClass
metadata:
name: cool-mesh
spec:
controllerName: example.com/mesh-controller
description: "my super cool mesh"
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Mesh
metadata:
name: cool-mesh
namespace: httpbin
spec:
meshClassName: cool-mesh
At this point, the httpbin namespace now has the service mesh enabled. While this may have
some implementation specific default behaviors (such as traffic encryption, telemetry collection,
etc), there isn't yet any Gateway API driven behavior.
Since no one is calling httpbin-v2 directly, this should not yet receive traffic yet.
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: httpbin-canarying
namespace: httpbin
spec:
# Match requests to httpbin
parentRefs:
- kind: Service
name: httpbin
# ...and send 1% to the v2 version
rules:
- backendRefs:
- name: httpbin
port: 80
weight: 99
- name: httpbin-v2
port: 80
weight: 1
First, I deploy the mesh implementation (via some implementation specific mechanism, such as
helm install cool-mesh).
This would typically instantiate a MeshClass resource describing the mesh (user doesn't need
to do this):
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: MeshClass
metadata:
name: cool-mesh
spec:
controllerName: example.com/mesh-controller
description: "my super cool mesh"
Now I would need to use some implementation specific mechanism to enable the mesh in my
namespace(s).
At this point, the httpbin namespace now has the service mesh enabled. While this may have
some implementation specific default behaviors (such as traffic encryption, telemetry collection,
etc), there isn't yet any Gateway API driven behavior.
Since no one is calling httpbin-v2 directly, this should not yet receive traffic yet.
kind: ServiceMeshBinding
metadata:
name: httpbin-binding
spec:
service: # what service we are configuring
name: httpbin
kind: Service
meshes: # define which Meshes this service is configured for
- kind: MeshClass
name: my-cool-mesh
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: httpbin-canarying
namespace: httpbin
spec:
# Match requests to httpbin
parentRefs:
- kind: ServiceMeshBinding
name: httpbin-binding
# ...and send 1% to the v2 version
rules:
- backendRefs:
- name: httpbin
port: 80
weight: 99
- name: httpbin-v2
port: 80
weight: 1
First, I deploy the mesh implementation (via some implementation specific mechanism, such as
helm install cool-mesh).
This would typically instantiate a GatewayClass resource describing the mesh (user doesn't
need to do this):
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GatewayClass
metadata:
name: cool-mesh
spec:
controllerName: example.com/mesh-controller
description: "my super cool mesh"
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
name: cool-mesh
namespace: httpbin
spec:
gatewayClassName: cool-mesh
At this point, the httpbin namespace now has the service mesh enabled. While this may have
some implementation specific default behaviors (such as traffic encryption, telemetry collection,
etc), there isn't yet any Gateway API driven behavior.
1. As a service producer…
a. I want to deploy a canary version of my application that splits traffic based on
HTTP properties.
b. I want to change the behavior (such as timeouts, retries, header manipulation) of
my application through configuration, rather than modifying my application.
c. I want to apply authorization policies, using client identities and/or HTTP
properties.
d. I want to collect HTTP metrics for my application without modifying my
application.
e. I want to run a mix of protocols (HTTP, TCP, TLS, gRPC, …) within my
application.
f. I want to be able to gradually opt-in to a mesh (no mesh, L4 only, L7 enabled) so
I can choose the right fit for my application's performance and compatibility goals.
2. As a service consumer…
a. I want to change the behavior (such as timeouts, retries, header manipulation)
when my application connects to services through configuration, rather than
modifying my application.
b. I want to collect HTTP metrics for services I connect to.
c. I want to be able to connect to Kubernetes Services and external services.
d. I want to override the destination of my traffic, for example, to send requests to
external services to an internal replica, or to send all requests to an egress proxy.
e. I want to be able to gradually opt-in to a mesh (no mesh, L4 only, L7 enabled) so
I can choose the right fit for my application's performance and compatibility goals.
3. As a mesh administrator…
a. I want to enforce that all traffic within my cluster is encrypted.
Prior Art
It's helpful to look at what existing APIs are doing to find common ground.
Note: I am not an expert on all implementations, feel free to correct any details.
Service (Kubernetes)
While not quite a traditional mesh, this serves as the basis for most APIs. Service groups
endpoints and gives a single unique VIP (ClusterIP) to access these backends with very basic
load balancing (L4 only). Additionally, a DNS entry maps to the VIP.
Services are namespaced and can only select endpoints by label in the same namespace.
Advanced applications can manually fill in the endpoints for a Service; this is used by
applications such as Knative to “catch” inbound requests when scaling a serverless service from
zero replicas.
TrafficSplit (SMI/OSM)
Example:
kind: TrafficSplit
metadata:
name: ab-test
spec:
service: website
matches:
- kind: HTTPRouteGroup
name: ab-test
backends:
- service: website-v1
weight: 0
- service: website-v2
weight: 100
---
kind: HTTPRouteGroup
metadata:
name: ab-test
matches:
- name: firefox-users
headers:
user-agent: ".*Firefox.*"
Traffic is matched first by service. This is matching the Service IP. Further refinements to the
match (such as HTTP attributes) can be done through the matches section.
The Service binding can only apply to services in the same namespace.
VirtualService (Istio)
Example:
spec:
hosts:
- reviews.prod.svc.cluster.local # could also be just "reviews"
http:
- match:
- uri:
prefix: "/frontpage"
route:
- destination:
host: frontpage.prod.svc.cluster.local
The matching logic here is a bit tricky, as hosts is overloaded. First, the host is matched
against known service's hostnames.
Rules can apply to hostnames in other namespaces; cross-namespace rules only apply to
clients in that namespace, while same-namespace rules apply to all clients. "service" may be a
Service or any service known to the registry (typically from the ServiceEntry custom type).
Note: because hosts can reference arbitrary domains, and a single VirtualService can attach to
a Gateway and the mesh, a common pattern is to define a single VirtualService for E-W and N-
S traffic.
TrafficRoute (Kuma)
Example:
spec:
sources:
- match:
kuma.io/service: backend_default_svc_80
destinations:
- match:
kuma.io/service: redis_default_svc_6379
conf:
http:
- match: …
CiliumEnvoyConfig (Cilium)
Example:
spec:
services:
- name: httpbin
namespace: default
listener: envoy-lb-listener
backendServices:
- name: echo
namespace: default
resources: …
ServiceRouter (Consul)
Example:
apiVersion: consul.hashicorp.com/v1alpha1
kind: ServiceRouter
metadata:
name: web
spec:
routes:
- match:
http:
pathPrefix: /admin
destination:
service: admin
This matches the service web. I believe this is based on the Service IP and can only apply to
services in the same namespace.
ServiceProfile (Linkerd)
Example:
apiVersion: linkerd.io/v1alpha2
kind: ServiceProfile
metadata:
name: webapp.my-service-namespace.svc.cluster.local
spec:
routes: …
ServiceProfile is a bit different from the rest, as it is not defining how traffic should route, but
rather sub-dividing a service into various endpoints (/admin vs /store, for example) which
allows telemetry generation and configuring policies for these (timeout, retry, etc).
Matching is done based on the name, which will match the Host header of traffic. ServiceProfile
only applies to HTTP traffic. IP address is not used.
Common Trends
● Most implementations are matching traffic by Service IP. Exceptions:
○ Istio; but Istio is interested in moving to an IP based matching
○ ServiceProfile (Linkerd); but it is a somewhat different use case.
● Most implementations only allow configuring traffic for the same namespace.
Exceptions:
○ Istio; allows consumer overrides
○ Cilium has per-namespace and cluster-wide resources
○ Consul allows configuring traffic across namespaces as long as authorization
policy ("intentions") has been configured by the destination to allow it
● Some implementations allow only matching traffic to Service. Exceptions:
○ Istio; allows arbitrary additional services (via `ServiceEntry)
○ Traffic Director; doesn't match Service at all, instead matches arbitrary Host
headers and IP address
○ Kuma; allows non-Service types (I think, not quite sure the details)
○ Consul; allows arbitrary non-Kubernetes services registered with a Consul agent,
and services from Nomad
● No implementations support configuring traffic to Pods (beyond raw IP/Host header
support)
Old design
Below contains a copy of the initial iteration of the "Design" section of the doc. To avoid
confusion, this was rewritten but copied below to retain history and comments. See Design for
up to date discussion.
On route today, we have two main "attachment" points - parentRefs and hostnames (not
present on all types).
Mesh attachment
For a given implementation, when a request is received we need to be able to find out what
routes (if any) should apply to the request.
To do this, we need at the very least to know that a route applies to our mesh (rather than some
other mesh implementation, or to some Gateway, etc). We may also need to know what part of
the mesh it applies to. For example, does the route impact all namespaces or only some? Does
the route apply to traffic going into or coming out of this part of the mesh? Beyond namespace,
some implementations may also divide the mesh further if there are multiple installations of the
same mesh in the cluster, or a single installation with some partitioning concept (example).
Note that this doesn't necessarily mean we need to explicitly specify a particular Mesh
implementation in the route; we could declare a route that applies to "mesh". Each mesh
implementation (very typically a single one) can use that route and write their own status for it
by specifying controllerName in RouteParentStatus.
Once we know the route should apply to a given mesh, the next phase is matching actual
requests. The overwhelming pattern here is to attach routes to Services, and match these
requests based on the Service IP. I recommend we continue this pattern.
Finally, we may also need some additional matching information, as Gateway provides today. I
think the only missing piece is the port number.
Example:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Mesh
metadata:
name: cool-mesh
spec:
controllerName: example.com/mesh-controller
description: "my super cool mesh"
parametersRef: { ... }
This reads: "For traffic in 'cool-mesh' to the 'my-frontend' service on port 80, apply this route
which sends requests to 'my-backend'"
Note: in Gateway, hostname serves a dual purpose of matching the Host header and binding
to specific Gateway Listeners. In this model, hostname is purely a Host header match.
Because we are already matching on the service IP, adding a Host header match is not typical,
but can be used for some services which handle multiple vhosts.
Possible extensions:
● Vendor specific kinds that have equivalent semantics as Service (i.e. we can identify
that a request was intended for that service.
● Cross namespace services references. These would provide "consumer" policies,
while same namespace references provide "producer" policies.
○ Based on implementation details, some meshes may be able to support both of
these simultaneously (consumer and producer route for the same service).
Others may only be able to apply one or the other.
● Empty services could be extended to mean that it is not bound to any particular
service; instead, any traffic that doesn't match a more explicit route (one with a service
specified) should attempt to match against this route (using the standard matching rules
and precedence logic).
Example:
kind: ServiceMeshBinding
metadata:
name: httpbin
namespace: consumer
spec:
name: httpbin
kind: Service
meshes: # define which Meshes this service is configured for
- kind: Mesh
name: my-cool-mesh
This has the benefit of not requiring any changes to the API. However, it does introduce a
substantial boilerplate.
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: httpbin-route
spec:
parentRefs:
- kind: Mesh # This is for a mesh
name: httpbin # Attach to the httpbin service
namespace: my-ns # Namespace of httpbin service
sectionName: my-mesh # Attach to the "my-mesh" mesh
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: httpbin # match by name
spec:
rules: { … }
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
labels:
gateway.networking.k8s.io/mesh: my-mesh
spec:
ports:
- name: http
port: 80
This would attach the same-named route to the Service, and pick the mesh by annotation. This
is pretty simple, but does have a few problems:
● Any more customization (mesh section name, etc) require annotation proliferation which
is one of the anti-goals of gateway-api.
● Hard to bind multiple routes to a single Service (useful for canaries, for example, as well
as multiple ports)
● Uses annotation instead of explicit API
● Cross namespace references would require creating dummy Services that only exist to
point to another Service
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: my-route
namespace: default
spec:
parentRefs:
- kind: Service
name: httpbin
sectionName: port-http # optional, specify port
hostnames: [ … ]
rules:
- backendRefs:
- name: httpbin-v1
port: 80
- name: httpbin-v2
port: 80
If we want to support configuring traffic to arbitrary hostnames (e.g. match google.com and
send to google-internal service) we would need something else to bind to since we don't
have a Service. Some options:
● Bind to a new "Mesh" type, which has a similar definition as GatewayClass (see
examples above). This would allow matching purely on existing route properties. It would
not help if we want to bind to things that are not Service but do provide additional routing
information (IPs, etc).
● Bind to an actual Gateway which represents the mesh. This would allow for more
advanced options like utilizing the TLS settings in Gateway (see option 5 for more
details).
● Create a new type (or possibly abuse ExternalName) and reference that instead
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
name: mesh
spec:
gatewayClassName: my-mesh
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: echo
spec:
parentRefs:
- name: mesh
rules:
- backendRefs:
- name: echo
port: 80
Meaning:
● We have a Gateway that uses a mesh class; this means in our namespace we should
enable the mesh. This may mean deploying sidecars, configuring some proxy
somewhere, etc. Simply having the Gateway without any routes has meaning, as it
enables the mesh.
● Routes bind to the gateway explicitly. You can only bind to a gateway in your own
namespace.
The listeners field can be empty to use defaults, or allow entries for advanced configuration.
For example:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: Gateway
metadata:
name: gateway
spec:
gatewayClassName: my-mesh
listeners:
- name: terminate
hostname: "echo"
port: 443
protocol: HTTPS
tls:
mode: Terminate
certificateRefs:
- name: my-cert-http
---
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: echo
spec:
parentRefs:
- name: gateway
sectionName: terminate
rules:
- backendRefs:
- name: echo
port: 80
This would allow accepting traffic on port 443, which is terminated, then forwarded to port 80.
Note: this would be for application TLS. mTLS or other encryption used as the transport protocol
for the mesh is not related.
Pros:
● Most advanced configuration
● Also handles the "enable mesh for these workloads" problem
● Minimal changes to API (just allow listeners to be empty)
Cons:
● Doesn't solve how to bind a route to a specific service
● A bit complex, especially with listeners
● Can only scope mesh to a full namespace
Service binding
This option would be paired with one of the others to address the gaps.
Option 4:
Option 4 could be used by implicitly associating with the Gateway. For example, we simply
create a route for a Service:
apiVersion: gateway.networking.k8s.io/v1alpha2
kind: HTTPRoute
metadata:
name: my-route
namespace: default
spec:
parentRefs:
- kind: Service
name: httpbin
sectionName: port-http # optional, specify port
hostnames: [ … ]
rules: …
For types that are handled by our mesh-type Gateways that are running in that namespace,
they will automatically bind. For example, in the above example I have a Gateway named
gateway of class my-mesh. When the my-mesh implementation sees this route, it would