-
Notifications
You must be signed in to change notification settings - Fork 632
Description
Problem
When an HTTPRoute has multiple hostnames or attaches to multiple Gateway listeners with different hostnames, EG creates duplicate xDS config for each hostname. The only difference between these duplicates is the hostname/domain - everything else (TLS, filters, route match, cluster) is identical.
This causes unnecessary config bloat and memory usage in both the control plane and Envoy.
Example
Gateway with 2 listeners (different hostname suffixes, identical config):
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
namespace: default
spec:
gatewayClassName: eg
listeners:
- name: https
hostname: "*.example.com"
port: 443
protocol: HTTPS
tls:
certificateRefs:
- name: wildcard-cert
- name: https-alt # Only difference
hostname: "*.example.io" # Only difference
port: 443
protocol: HTTPS
tls:
certificateRefs:
- name: wildcard-certHTTPRoute attached to both listeners. The route rules are identical for both hostnames/listeners:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api-route
namespace: default
spec:
parentRefs:
- name: example-gateway
sectionName: https
- name: example-gateway
sectionName: https-alt
hostnames:
# Target both listeners
- api.example.com
- api.example.io
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api-service
port: 8080Current xDS Output
Listener (port 443)
├── FilterChain 1
│ └── filter_chain_match.server_names: ["*.example.com"]
│ └── Network Filters
│ └── HTTP Connection Manager
│ └── RouteConfig 1
│ └── VirtualHost 1
│ └── domains: ["api.example.com"]
│ └── Route → Cluster httproute/default/api-route/rule/0
│
└── FilterChain 2 ← DUPLICATE
└── filter_chain_match.server_names: ["*.example.io"]
└── Network Filters ← DUPLICATE
└── HTTP Connection Manager ← DUPLICATE
└── RouteConfig 2 ← DUPLICATE
└── VirtualHost 2 ← DUPLICATE
└── domains: ["api.example.io"]
└── Route → Cluster httproute/default/api-route/rule/0 ← same cluster
Cluster: httproute/default/api-route/rule/0
FilterChain 2, RouteConfig 2, VirtualHost 2, and Route 2 are duplicates - identical config except for hostname.
Desired xDS Output
All these xDS objects allow for more than 1 domain.
Ideally, we should deduplicate the config and make use of the field cardinality.
Listener (port 443)
└── FilterChain 1
└── filter_chain_match.server_names: ["*.example.com", "*.example.io"] # Multiple
└── Network Filters
└── HTTP Connection Manager
└── RouteConfig 1
└── VirtualHost 1
└── domains: ["api.example.com", "api.example.io"] # Multiple
└── Route → Cluster httproute/default/api-route/rule/0
Cluster: httproute/default/api-route/rule/0
Single FilterChain, RouteConfig, VirtualHost, and Route with multiple hostnames/domains.
Where Duplication Happens
Listener
Listener duplication in Gateway API translation, where each Gateway Listener creates a separate IR HTTPListener:
gateway/internal/gatewayapi/listener.go
Line 144 in f8bb912
| xdsIR[irKey].HTTP = append(xdsIR[irKey].HTTP, irListener) |
FilterChain creation in xDS translation is 1:1 IR HTTPListener → FilterChain:
gateway/internal/xds/translator/listener.go
Line 488 in f8bb912
| xdsListener.FilterChains = append(xdsListener.FilterChains, filterChain) |
Virtual Host & Route
Route duplication in Gateway API translation, where a route copy is made per hostname:
gateway/internal/gatewayapi/route.go
Lines 1209 to 1210 in f8bb912
| hostRoute.Hostname = host | |
| perHostRoutes = append(perHostRoutes, hostRoute) |
VirtualHost creation in xDS translation is 1:1 IR HTTPRoute Hostname → VirtualHost:
gateway/internal/xds/translator/translator.go
Line 509 in f8bb912
| // 1:1 between IR HTTPRoute Hostname and xDS VirtualHost. |
Proposed Solution
Coalesce equivalent IR resources at the Gateway API → IR translation layer. Two resources are equivalent if all fields except name/hostnames are equal.
Current IR structs for reference:
Sketch of IR changes for HTTPListener IR:
// HTTPListenerInstance contains instance-specific fields excluded from
// equivalence comparison. Hostnames are merged when coalescing.
type HTTPListenerInstance struct {
Name string
Hostnames []string
}
type HTTPListener struct {
Instance HTTPListenerInstance
// All fields below are compared for equivalence
TLS *TLSConfig
TCPKeepalive *TCPKeepalive
HTTP1 *HTTP1Settings
HTTP2 *HTTP2Settings
// ...
}Same idea for HTTPRoute IR:
// HTTPRouteInstance contains instance-specific fields excluded from
// equivalence comparison. Hostnames are merged when coalescing.
type HTTPRouteInstance struct {
Name string
Hostnames []string
}
type HTTPRoute struct {
Instance HTTPRouteInstance
// All fields below are compared for equivalence
PathMatch *StringMatch
HeaderMatches []*StringMatch
Destination *RouteDestination
Traffic *TrafficFeatures
// ...
}Then the append statements above can be modified to only append based on equivalence, i.e. do NOT append a whole new listener if the HTTPListener minus HTTPListenerInstance fields are the same, and instead merge.
For equivalence checks, we can use go-cmp with cmpopts.IgnoreFields(..., "Instance").
This is maintainable and guarantees correctness: Anytime a new field is added to the IR structs above, we do NOT need to update the equivalence checks.