-
Notifications
You must be signed in to change notification settings - Fork 7
First pass at a schema for Backend plus musings on scope
#20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
First pass at a schema for Backend plus musings on scope
#20
Conversation
Sketch out some Go types and add a section on whether Backend is namespace or cluster-scoped. Signed-off-by: Keith Mattix II <[email protected]>
|
|
||
| Examples in this document are illustrative only. | ||
|
|
||
| #### Scope and Persona Ownership |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@howardjohn @mikemorris Feel free to chime in with any thoughts on the scoping story
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm maybe this isn't an issue since, at this point, Backend is only referenced via an xRoute? But I still wonder how the admin sets policy for a particular FQDN if any app owner can create a Backend
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having both a global and local Backend makes sense to me. If we go this route, the main question would be around resolving conflicts.
What seems correct, is for a globally scoped Backend to take precedence. This avoids the problem of needing to ensuring that a global policy -- which may be required for compliance -- isn't silently overridden.
If we go this route we'd need to set a status condition on the namespaced backend to indicate that it's being overridden.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 - Global backends should take precedence over local ones and we should report in status. This is slowly becoming my preferred option the more that I think about it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think my preferred approach for this would be having both namespace-scoped and cluster-scoped options for a frontend, but keeping Backend as a single namespaced resource, and treating cluster/global scoped definitions as a "last hop" rather than "override". I'll try to illustrate how I'm defining that difference below:
Namespace app {
Pod -> [HTTP request] -> namespaced FrontendThing (ServiceEntry, ExternalName, etc) -[backendRef] -> Backend{FQDN, Pods, IPAdddress, etc}
}
ClusterServiceEntry -[backendRef] -> Backend
Override
In the override model, a ClusterServiceEntry for example.com -> Backend{FQDNfoo.com} ensures that any traffic leaving the pod is forcibly redirected to foo.com, regardless of whether a namespaced ServiceEntry is trying to redirect it locally
"Last hop"
In the last hop model, a ClusterServiceEntry for example.com -> Backend{FQDNfoo.com} is only applied to traffic leaving the pod if example.com is still the destination after any local namespaced ServiceEntry has been applied. So if ServiceEntry{example.com} -> Backend{FQDNbar.com} exists, then the ClusterServiceEntry for example.com has no effect and the traffic egresses to bar.com
Thinking this through further, there might be use cases for each model, similar to the overrides vs defaults behavior described in https://gateway-api.sigs.k8s.io/geps/gep-2649/?h=override#hierarchy and maybe this behavior should be configurable?
Ref #20 (comment) for further exploration on explicitly routing through an egress Gateway.
| Protocol BackendProtocol `json:"protocol"` | ||
| // TLS defines the TLS configuration for the backend. | ||
| // +optional | ||
| TLS *BackendTLS `json:"tls,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whats the semantics of policy-attached BackendTLSPolicy + inline co-existing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now at least (pre-GEP), I'd say BackendTLSPolicy is not allowed to have Backend as a targetRef, so we can defer the decision after we get a better sense of Backend semantics (e.g scoping). I have a bias towards inlining, so my ideal would probably be to have the inline policy take precedence if defined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a decision to choose anything other than BackendTLSPolicy here requires significantly more discussion + detail in this proposal. If the goal is just to copy + inline BackendTLSPolicy types, that might make sense, but there are other benefits of a policy here, such as the ability to reuse config across different backends.
proposals/10-egress-gateways.md
Outdated
| // Use implementation's built-in TLS (e.g. service mesh powered mTLS). | ||
| BackendTLSModePLATFORM_PROVIDED BackendTLSMode = "PLATFORM_PROVIDED" | ||
| // Disable TLS. | ||
| BackendTLSModeINSECURE_DISABLE BackendTLSMode = "INSECURE_DISABLE" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we want a TLS policy to not do TLS?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thought was to try and make it explicit: this is not an optional field so you must set something and if you want to disable TLS, you must do so explicitly, acknowledging that it's insecure
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't non-TLS the default though? Or we are saying, TLS is always the default and you need to opt out? That would be pretty confusing behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For external FQDN, I would think some level of TLS should be the default for security reasons. But I'm willing to be convinced the other way
Signed-off-by: Keith Mattix II <[email protected]>
|
|
||
| Examples in this document are illustrative only. | ||
|
|
||
| #### Scope and Persona Ownership |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having both a global and local Backend makes sense to me. If we go this route, the main question would be around resolving conflicts.
What seems correct, is for a globally scoped Backend to take precedence. This avoids the problem of needing to ensuring that a global policy -- which may be required for compliance -- isn't silently overridden.
If we go this route we'd need to set a status condition on the namespaced backend to indicate that it's being overridden.
| Name string `json:"name"` | ||
| // +required | ||
| Type string `json:"type"` | ||
| // TODO: How does this work practically? Can we leverage Kubernetes unstructured types here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An unstructured type makes sense, but my assumption is that we should require a schema for every extension type even when there's no CRD.
The schemas can be stored in, or linked from, a config map, then the configs can be verified by a webhook or the controller.
It lets us have our cake and eat it too in terms of having config validation without requiring a CRD for each extension.
It also has the knock-on advantage of advertising all of the available extension types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I definitely think there must be some sort of schema, but I'm wondering if, from an implementation perspective, it makes sense to force folks to use Kubernetes schemes specifically (which I think is required if we rely on unstructured)
| BackendProtocolHTTP BackendProtocol = "HTTP" | ||
| BackendProtocolHTTP2 BackendProtocol = "HTTP2" | ||
| BackendProtocolTCP BackendProtocol = "TCP" | ||
| BackendProtocolMCP BackendProtocol = "MCP" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will we be expanding the core enum with additional AI protocols e.g. A2A ?
I wonder if it makes sense to reserve BackendProtocol for common transport protocols HTTP/2, TCP, gRPC and layer in MCP (and other AI protocols later) via BackendProtocolOptions. To make it easier to extend as things evolve in the AI space?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My plan was to expand it with core protocols we'll have in the API (like A2A and possibly LLM). Prior art in gateway API is to allow implementations to have their own protocols with a vendor prefix (e.g. istio.io/a2a); maybe BackendProtocolOptions should have a slot for vendor specific protocols too? The big drawback with that option is that we make so much vendor specific that it's too hard to standardize down the line /cc @kflynn @robscott @shaneutt
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: keithmattix, usize The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
| spec: | ||
| destination: | ||
| type: FQDN | ||
| fqdn: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to think about wildcard support here. This was a huge request in Istio since folks didn't want to manually iterate all of their backends (e.g. each individual s3 bucket). This likely intersects with the dynamic forward proxy use-case though, so my plan for now is to defer it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see this as the forward-proxy section listed in the proposal, which is intended not to cover first
|
|
||
| This proposal uses the following resource relationship: | ||
| ``` | ||
| Gateway <-[parentRef]- HTTPRoute -[backendRef]-> Backend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about how this would work for meshes (especially GAMMA-style implementations). In most implementations I'm aware of, services aren't aware of Gateways declaratively; the meshed client sees that the destination for the request should go through the gateway and it sends it there. This proposal defines the Backend role of the external service, but there's nothing for the frontend role. Should meshes just have this be implementation specific?
In Istio's case, I'd imagine a user creates an HTTPRoute with a ServiceEntry (acting as a frontend only) parentRef with a backendRef pointing to the egress gateway. Then, to send traffic from the gateway to the external service, the user creates another HTTPRoute with the hostname of the external service that has a backendRef of kind Backend.
Any thoughts on how to proceed here from mesh implementations? @howardjohn @kflynn @youngnick
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should meshes just have this be implementation specific?
Longer term I hope not, but maybe initially.
With decomposing the backend role of Service, we do still need some way to express a frontend. For in-cluster services, this probably stays as Service for now, but it would be nice to introduce a lower-level resource like a general FQDN or more specific ClusterDNSName that Service might eventually be refactored to use internally while making that functionality available independently for other use cases.
In Istio's case, I'd imagine a user creates an HTTPRoute with a ServiceEntry (acting as a frontend only) parentRef with a backendRef pointing to the egress gateway. Then, to send traffic from the gateway to the external service, the user creates another HTTPRoute with the hostname of the external service that has a backendRef of kind Backend.
ServiceEntry (or something simpler in Gateway API or k8s like an FQDN or ExternalName resource) as a frontend makes sense to me. My initial reaction to this was a preference to avoid making the backendRef to an egress Gateway resource explicit, but I think it might make sense if this can be configured with a cluster-scoped resource to be the "last hop" before egress after a namespaced resource has an opportunity to first redirect a request, rather than being an "override" - something like the diagram below:
Namespace app [
Pod -> [HTTP request] -> namespaced ServiceEntry | ExternalName
] -> ClusterServiceEntry | ClusterExternalName - [backendRef] ->
Namesapce egress [
Gateway <-[parentRef]- HTTPRoute -[backendRef] -> Backend[FQDNs, Pods, IPAddresses] <-[BackendTrafficPolicy]
]
For cases where the full centralization and functionality of an egress gateway isn't needed, this might instead look like:
Namespace app [
Pod -> [HTTP request] -> namespaced ServiceEntry | ExternalName
] -> ClusterServiceEntry | ClusterExternalName - [backendRef] ->
Namesapce egress [
Backend[FQDNs, Pods, IPAddresses] <-[BackendTrafficPolicy]
]
| // MCP protocol version. MUST be one of V2|V3. | ||
| // +optional | ||
| // +kubebuilder:validation:MaxLength=256 | ||
| Version string `json:"version,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now, the format is YYYY-MM-DD.
Better to point to https://modelcontextprotocol.io/specification/versioning and say it must be a valid version as per the project strategy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry you caught some of the AI autocomplete I missed. Yeah we should definitely do that
| spec: | ||
| destination: | ||
| type: FQDN | ||
| fqdn: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see this as the forward-proxy section listed in the proposal, which is intended not to cover first
| # clientCertificateRef: # if MUTUAL | ||
| # name: egress-client-cert | ||
| # possible extension semantics, for illustration purposes only. | ||
| ports: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure how does the gateway select which port as the target if the backend is refered by a httpRoute
| mode: SIMPLE | MUTUAL | PASSTHROUGH | PLATFORM_PROVIDED | INSECURE_DISABLE | ||
| sni: api.openai.com | ||
| caBundleRef: | ||
| name: vendor-ca |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not related, but i wonder where is the caBundle located, just a name is not enough
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we be using ClusterTrustBundle (which is cluster-scoped, so name alone could be sufficient) as the default kind here? kubernetes/enhancements#3257
| 1. Whether `Backend` should be namespaced or cluster-scoped. | ||
| 2. Whether we should define both namespaced and cluster-scoped variants of `Backend` (e.g. `GlobalBackend` or `ClusterWideBakcend`)to serve different personas (service owners vs platform operators). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for namespace scope
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's centralize this discussion in #20 (comment)?
| // Enable TLS with simple server certificate verification. | ||
| BackendTLSModeSimple BackendTLSMode = "Simple" | ||
| // Enable mutual TLS. | ||
| BackendTLSModeMutual BackendTLSMode = "Mutual" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not seeing BackendTLS support setting client key/certs
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not entirely clear on where client config for mTLS should live, but expect that should be related to discussions in kubernetes-sigs/gateway-api#4192 and/or kubernetes-sigs/gateway-api#3876
/cc @howardjohn
| // Enable mutual TLS. | ||
| BackendTLSModeMutual BackendTLSMode = "Mutual" | ||
| // Don't terminate TLS, use SNI to route. | ||
| BackendTLSModePassthrough BackendTLSMode = "Passthrough" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isn't this set at gateway listener?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think a Backend alone should be able to terminate TLS (not sure if that's implied by this API or not), doing that should require routing through a Gateway such as discussed in #20 (comment).
robscott
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @keithmattix!
| // Would implementations have to define a schema for their extensions (even if they aren't CRDs)? | ||
| // Maybe that's a good thing? | ||
| Config any `json:"config,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this works in a k8s API?
| Destination BackendDestination `json:"destination"` | ||
| // extensions defines optional extension processors that can be applied to this backend. | ||
| // +optional | ||
| Extensions []BackendExtension `json:"extensions,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a clear set of examples for how these extensions would be used? If not, can we omit them until we do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One example from above is CredentialInjector - these could look quite similar to the HTTPRouteFilter filters field on HTTPRoute backendRefs, with the added ability to specify ordering (which we've discussed in relation to filters previously, including potential difficulty for some implementations with predefined ordering).
| Protocol BackendProtocol `json:"protocol"` | ||
| // TLS defines the TLS configuration for the backend. | ||
| // +optional | ||
| TLS *BackendTLS `json:"tls,omitempty"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think a decision to choose anything other than BackendTLSPolicy here requires significantly more discussion + detail in this proposal. If the goal is just to copy + inline BackendTLSPolicy types, that might make sense, but there are other benefits of a policy here, such as the ability to reuse config across different backends.
|
I'm not sure if this meeting was recorded, but notes are available - in addition to the discussions we've had about this in WG AI Gateway, there was a discussion relevant to this scope during the October 22nd kube-agentic-networking meeting https://docs.google.com/document/d/1EQET_VWe_IAINyQhVj-wduZg99gBaObpz9612eZ1iYg/edit?tab=t.0#heading=h.e9nns9g5k28v |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The macro critique I have of this proposal is that I feel like it conflates frontend and backend facets in the same way that Service and Istio's ServiceEntry do.
I'd prefer to split this into 3-4 resources - a namespace-scoped Frontend (name TBD, could be separate type for or inclusive of external FQDN and Cluster DNS name, maybe Service actually fills this role for now if we can make a case this is progress toward decomposing it described as in https://www.youtube.com/watch?v=Oslwx3hj2Eg), a cluster-scoped Frontend, the namespace-scoped Backend, and possibly a separate FQDN (probably cluster-scoped?) resource (TBD on how to avoid loops here with Frontend and Backend both wanting an FQDN reference).
Within the Backend resource, I'd like to see as destinations (maybe even the backendRefs specifically if these are all external resources?):
- FQDN
- IPAddress
- Pod Selector (not Service)
While the inline BackendTLS config makes sense when the FQDN destination is inlined, if FQDN becomes an independent resource it could be more straightforward to target it with BackendTLSPolicy.
Sketch out some Go types and add a section on whether Backend is namespace or cluster-scoped. I usually design CRDs in protobuf since Istio does, so please chime in with missing Kubebuilder validations or otherwise incorrect conventions. I'm not married to much that's here, but wanted to get something out to get discussion started.