GEP-718: Rework forwardTo segment in routes

  • Issue: #718
  • Status: Standard

Motivation

Complications due to the serviceName shortcut

Within RouteForwardTo (and HTTPRouteForwardTo by extension) there exists two mechanisms to specify the upstream network endpoint where the traffic should be forwarded to: - ServiceName: the name of the Kubernetes Service - BackendRef: a LocalObjectReference to a resource, this is an extension point in the API

While working on #685, it came to light that: 1. BackendRef itself could be used to point to a Kubernetes Service 2. With GEP-709, there will be a need to introduce a new namespace field which will enable use-cases to forward traffic to a services and backends located in different namespaces

While (1) can be fixed with more validation and documentation, it only solves for the specific case. (2) requires adding two fields: serviceNamespace and BackendRef.Namespace - something we would like to avoid since the serviceName field was introduced only as a shortcut and adding more service-specific fields defeats the original purpose.

These problems are in addition to the original problem that #685 attempts to solve: clarifying port requirements when a port is required or not. We have been updating documentation to tackle such corner-cases but that continues to result in more and more elaborations. Some excerpts from our existing documentation:

  1. // ServiceName refers to the name of the Service to mirror matched requests
  2. // to. When specified, this takes the place of BackendRef. If both
  3. // BackendRef and ServiceName are specified, ServiceName will be given
  4. // precedence.
  5. // BackendRef is a local object reference to mirror matched requests to. If
  6. // both BackendRef and ServiceName are specified, ServiceName will be given
  7. // precedence.
  8. // Weight specifies the proportion of HTTP requests forwarded to the backend
  9. // referenced by the ServiceName or BackendRef field. This is computed as
  10. // Port specifies the destination port number to use for the
  11. // backend referenced by the ServiceName or BackendRef field.
  12. // If unspecified, the destination port in the request is used
  13. // when forwarding to a backendRef or serviceName.

Simplifying RouteForwardTo

RouteForwardTo is not the best of the names. Using a noun instead of a verb would help with in-person communication and documentation. Since we are already considering dropping the special case of service as a backend, we have an opportunity to further simplify RouteForwardTo for better UX.

Proposal

  • Remove the ServiceName field from RouteForwardTo
  • Introduce Service as a default for BackendRef.Kind
  • Pull fields from RouteForwardTo.BackendRef into RouteForwardTo itself
  • Rename RouteForwardTo to BackendRef
  • Rename Route.Spec.Rules[].ForwardTo[] to Route.Spec.Rules[].BackendRefs[] in all routes
  • Apply the same to HTTP types and filters

API

The updated BackendRef (formerly RouteForwardTo) struct will be: (HTTP version has been omitted for brevity)

  1. // BackendRef defines how and where a request should be forwarded from the Gateway.
  2. type BackendRef struct {
  3. // Group is the group of the backend resource.
  4. //
  5. // +kubebuilder:validation:MinLength=1
  6. // +kubebuilder:validation:MaxLength=253
  7. Group string `json:"group"`
  8. // Kind is kind of the backend resource.
  9. //
  10. // Support: Core (Kubernetes Service)
  11. // Support: Implementation-specific (any other resource)
  12. //
  13. // +optional
  14. // +kubebuilder:validation:MinLength=1
  15. // +kubebuilder:validation:MaxLength=253
  16. // +kubebuilder:default="service"
  17. Kind *string `json:"kind"`
  18. // Name is the name of the backend resource to forward matched requests to.
  19. //
  20. // If the referent cannot be found, the rule is not included in the route.
  21. // The controller should raise the "ResolvedRefs" condition on the Gateway
  22. // with the "DegradedRoutes" reason. The gateway status for this route should
  23. // be updated with a condition that describes the error more specifically.
  24. //
  25. // +kubebuilder:validation:MinLength=1
  26. // +kubebuilder:validation:MaxLength=253
  27. Name string `json:"name"`
  28. // Port specifies the destination port number to use for the
  29. // backend resource.
  30. // This field is required when the backend is a Kubernetes Service.
  31. //
  32. // Support: Core
  33. //
  34. // +optional
  35. Port *PortNumber `json:"port,omitempty"`
  36. // Weight specifies the proportion of HTTP requests forwarded to the backend
  37. // This is computed as weight/(sum of all weights in this Backends list).
  38. // For non-zero values, there may be some epsilon from the exact proportion
  39. // defined here depending on the precision an implementation supports. Weight
  40. // is not a percentage and the sum of weights does not need to equal 100.
  41. //
  42. // If only one backend is specified and it has a weight greater than 0, 100%
  43. // of the traffic is forwarded to that backend. If weight is set to 0, no
  44. // traffic should be forwarded for this entry. If unspecified, weight
  45. // defaults to 1.
  46. //
  47. // Support: Extended
  48. //
  49. // +optional
  50. // +kubebuilder:default=1
  51. // +kubebuilder:validation:Minimum=0
  52. // +kubebuilder:validation:Maximum=1000000
  53. Weight *int32 `json:"weight,omitempty"`
  54. }

A summarized diff for the changes being proposed:

  1. diff --git a/apis/v1alpha1/shared_types.go b/apis/v1alpha1/shared_types.go
  2. index 458145f..de720cd 100644
  3. --- a/apis/v1alpha1/shared_types.go
  4. +++ b/apis/v1alpha1/shared_types.go
  5. @@ -94,51 +94,39 @@ type GatewayReference struct {
  6. Namespace string `json:"namespace"`
  7. }
  8. -// RouteForwardTo defines how a Route should forward a request.
  9. -type RouteForwardTo struct {
  10. - // ServiceName refers to the name of the Service to forward matched requests
  11. - // to. When specified, this takes the place of BackendRef. If both
  12. - // BackendRef and ServiceName are specified, ServiceName will be given
  13. - // precedence.
  14. +// BackendRef defines how and where a request should be forwarded from the Gateway.
  15. +type BackendRef struct {
  16. + // Name is the name of the backend resource to forward matched requests to.
  17. //
  18. // If the referent cannot be found, the rule is not included in the route.
  19. // The controller should raise the "ResolvedRefs" condition on the Gateway
  20. // with the "DegradedRoutes" reason. The gateway status for this route should
  21. // be updated with a condition that describes the error more specifically.
  22. //
  23. - // The protocol to use is defined using AppProtocol field (introduced in
  24. - // Kubernetes 1.18) in the Service resource. In the absence of the
  25. - // AppProtocol field a `networking.x-k8s.io/app-protocol` annotation on the
  26. - // BackendPolicy resource may be used to define the protocol. If the
  27. - // AppProtocol field is available, this annotation should not be used. The
  28. - // AppProtocol field, when populated, takes precedence over the annotation
  29. - // in the BackendPolicy resource. For custom backends, it is encouraged to
  30. - // add a semantically-equivalent field in the Custom Resource Definition.
  31. + // +kubebuilder:validation:MinLength=1
  32. + // +kubebuilder:validation:MaxLength=253
  33. + Name string `json:"name"`
  34. +
  35. + // Kind is kind of the backend resource.
  36. //
  37. - // Support: Core
  38. + // Support: Core (Kubernetes Service)
  39. + // Support: Custom (any other resource)
  40. //
  41. // +optional
  42. + // +kubebuilder:validation:MinLength=1
  43. // +kubebuilder:validation:MaxLength=253
  44. - ServiceName *string `json:"serviceName,omitempty"`
  45. + // +kubebuilder:default="service"
  46. + Kind *string `json:"kind"`
  47. - // BackendRef is a reference to a backend to forward matched requests to. If
  48. - // both BackendRef and ServiceName are specified, ServiceName will be given
  49. - // precedence.
  50. - //
  51. - // If the referent cannot be found, the rule is not included in the route.
  52. - // The controller should raise the "ResolvedRefs" condition on the Gateway
  53. - // with the "DegradedRoutes" reason. The gateway status for this route should
  54. - // be updated with a condition that describes the error more specifically.
  55. + // Group is the group of the backend resource.
  56. //
  57. - // Support: Custom
  58. - //
  59. - // +optional
  60. - BackendRef *LocalObjectReference `json:"backendRef,omitempty"`
  61. + // +kubebuilder:validation:MinLength=1
  62. + // +kubebuilder:validation:MaxLength=253
  63. + Group string `json:"group"`
  64. // Port specifies the destination port number to use for the
  65. - // backend referenced by the ServiceName or BackendRef field.
  66. - // If unspecified, the destination port in the request is used
  67. - // when forwarding to a backendRef or serviceName.
  68. + // backend resource.
  69. + // This field is required when the backend is a Kubernetes Service.
  70. //
  71. // Support: Core
  72. //
  73. @@ -146,11 +134,10 @@ type RouteForwardTo struct {
  74. Port *PortNumber `json:"port,omitempty"`
  75. // Weight specifies the proportion of HTTP requests forwarded to the backend
  76. - // referenced by the ServiceName or BackendRef field. This is computed as
  77. - // weight/(sum of all weights in this ForwardTo list). For non-zero values,
  78. - // there may be some epsilon from the exact proportion defined here
  79. - // depending on the precision an implementation supports. Weight is not a
  80. - // percentage and the sum of weights does not need to equal 100.
  81. + // This is computed as weight/(sum of all weights in this Backends list).
  82. + // For non-zero values, there may be some epsilon from the exact proportion
  83. + // defined here depending on the precision an implementation supports. Weight
  84. + // is not a percentage and the sum of weights does not need to equal 100.
  85. //
  86. // If only one backend is specified and it has a weight greater than 0, 100%
  87. // of the traffic is forwarded to that backend. If weight is set to 0, no

Examples

For Kubernetes Services, an updated forwardTo section will read as follows:

  1. ...
  2. backendRefs:
  3. - name: foo-service-v1
  4. port: 80
  5. weight: 80
  6. - name: foo-service-canary
  7. port: 80
  8. weight: 20
  9. ...

Here, the kind field is omitted as it will be injected as a default.

For custom backends, the API will look like the following:

  1. ...
  2. backendRefs:
  3. - name: foo-v1
  4. kind: server
  5. group: networking.acme.io
  6. port: 80
  7. weight: 80
  8. - name: foo-v1-canary
  9. kind: server
  10. group: networking.acme.io
  11. port: 80
  12. weight: 20
  13. ...

For completeness, here is an example of HTTPRoute:

  1. apiVersion: gateway.networking.k8s.io/v1alpha2
  2. kind: HTTPRoute
  3. metadata:
  4. name: bar-route
  5. labels:
  6. gateway: prod-web-gw
  7. spec:
  8. hostnames:
  9. - "bar.example.com"
  10. rules:
  11. - matches:
  12. - headers:
  13. type: Exact
  14. values:
  15. env: canary
  16. backendRefs:
  17. - name: foo-service-v1
  18. port: 80
  19. weight: 80
  20. - name: foo-service-canary
  21. port: 80
  22. weight: 20

Alternatives considered

Rename serviceName to backendName

As suggested in this comment, we could buy the best of both the worlds by introducing backendName:

  1. forwardTo:
  2. - backendName: foo
  3. port: 80
  4. kind: <defaults to Service>

While this saves one line of YAML (backendRef:), it could be potentially violating the Naming of the reference field API convention. Most of our object references are of the form XRef.

Concerns resolved

A concern was raised around flattening of object reference fields i.e. including port and weight field alongside object reference is by k8s API conventions or not. This is not a problem and has been confirmed with API maintainers (comment).

Out of scope

N/A

References

Existing documentation: - RouteForwardTo - HTTPRouteForwardTo - #685 - Comment thread that spawned this GEP - #578