Skip to content

Coalesce xDS config for listeners/routes with identical settings but different hostnames #7836

@nareddyt

Description

@nareddyt

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-cert

HTTPRoute 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: 8080

Current 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:

xdsIR[irKey].HTTP = append(xdsIR[irKey].HTTP, irListener)

FilterChain creation in xDS translation is 1:1 IR HTTPListener → FilterChain:

xdsListener.FilterChains = append(xdsListener.FilterChains, filterChain)

Virtual Host & Route

Route duplication in Gateway API translation, where a route copy is made per hostname:

hostRoute.Hostname = host
perHostRoutes = append(perHostRoutes, hostRoute)

VirtualHost creation in xDS translation is 1:1 IR HTTPRoute Hostname → VirtualHost:

// 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions