Description
The router.vhosts.main / router.vhosts.sandbox configuration (in gateway-helm-chart values → rendered into the controller's config.toml) has two fields — default and domains — whose behavior is very hard to reason about. The field names imply one thing but the code does another. This configuration surface was introduced in #403.
The core confusion: default reads like "the fallback hostname the vhost matches", but in the code it is really a bucket identifier (a routing key), and it is only matched as a Host when domains is empty. The moment domains is set, default is no longer matched against any request — domains becomes the sole Host-match list, and default silently degrades to a label.
Example config:
vhosts:
main:
domains: ["api.example.com"]
default: "foo.com"
sandbox:
domains: ["sandbox.api.example.com"]
default: "sandbox-foo.com"
With this, foo.com / sandbox-foo.com never match any request. Only api.example.com → main upstream and sandbox.api.example.com → sandbox upstream work. An operator who set default: "foo.com" will reasonably expect foo.com to be reachable, and it is not.
Second source of confusion — what is stored vs. what is live:
- The per-API vhost name is resolved to a concrete value at deploy time (
resolveVhostSentinels, gateway/gateway-controller/pkg/utils/api_deployment.go:308) and frozen into the DB (marshaled from SourceConfiguration).
domains is not stored per-API — it is read live from config.toml at xDS translation time (getVHostDomains, gateway/gateway-controller/pkg/xds/translator.go:974-1013).
As a result, changing domains in config.toml behaves inconsistently depending on whether default is also changed:
- Change only
domains (keep default): existing APIs are silently re-hosted to the new domains after a restart.
- Change
default: existing APIs stay pinned to their old frozen name and the new domains is ignored for them — it only applies to APIs deployed after the change.
This "freeze" is intentional, but it is undocumented and surprising, and the default/domains naming makes it nearly impossible to predict the Envoy VirtualHost.domains that will actually be produced.
Affected areas
kubernetes/helm/gateway-helm-chart/values.yaml (+ templates/gateway/gateway-config.yaml:211-221)
gateway/gateway-controller/pkg/config/config.go (VHostEntry, validateVHostsConfig)
gateway/gateway-controller/pkg/xds/translator.go (getVHostDomains)
gateway/gateway-controller/pkg/utils/api_deployment.go (resolveVhostSentinels)
Suggested improvements (pick/refine)
- Rename fields to reflect their real roles, e.g.
default → id/name (the bucket identity) and keep domains as the Host-match list; or collapse to a single clearer model.
- If both are kept, document explicitly that
domains overrides default for Host matching, and that default is only matched when domains is empty.
- Document the deploy-time freeze: the vhost name is persisted per-API, so changing
default does not re-route existing APIs, while changing domains (with an unchanged default) does.
- Consider a validation/warn when an API's explicit vhost equals one of the configured
domains (produces a duplicate-domain Envoy config that is rejected).
- Add operator-facing docs with a worked example mapping config → resulting Envoy
VirtualHost.domains.
Version
No response
Related Issue
Introduced by #403
Description
The
router.vhosts.main/router.vhosts.sandboxconfiguration (ingateway-helm-chartvalues → rendered into the controller'sconfig.toml) has two fields —defaultanddomains— whose behavior is very hard to reason about. The field names imply one thing but the code does another. This configuration surface was introduced in #403.The core confusion:
defaultreads like "the fallback hostname the vhost matches", but in the code it is really a bucket identifier (a routing key), and it is only matched as a Host whendomainsis empty. The momentdomainsis set,defaultis no longer matched against any request —domainsbecomes the sole Host-match list, anddefaultsilently degrades to a label.Example config:
With this,
foo.com/sandbox-foo.comnever match any request. Onlyapi.example.com→ main upstream andsandbox.api.example.com→ sandbox upstream work. An operator who setdefault: "foo.com"will reasonably expectfoo.comto be reachable, and it is not.Second source of confusion — what is stored vs. what is live:
resolveVhostSentinels,gateway/gateway-controller/pkg/utils/api_deployment.go:308) and frozen into the DB (marshaled fromSourceConfiguration).domainsis not stored per-API — it is read live fromconfig.tomlat xDS translation time (getVHostDomains,gateway/gateway-controller/pkg/xds/translator.go:974-1013).As a result, changing
domainsinconfig.tomlbehaves inconsistently depending on whetherdefaultis also changed:domains(keepdefault): existing APIs are silently re-hosted to the new domains after a restart.default: existing APIs stay pinned to their old frozen name and the newdomainsis ignored for them — it only applies to APIs deployed after the change.This "freeze" is intentional, but it is undocumented and surprising, and the
default/domainsnaming makes it nearly impossible to predict the EnvoyVirtualHost.domainsthat will actually be produced.Affected areas
kubernetes/helm/gateway-helm-chart/values.yaml(+templates/gateway/gateway-config.yaml:211-221)gateway/gateway-controller/pkg/config/config.go(VHostEntry,validateVHostsConfig)gateway/gateway-controller/pkg/xds/translator.go(getVHostDomains)gateway/gateway-controller/pkg/utils/api_deployment.go(resolveVhostSentinels)Suggested improvements (pick/refine)
default→id/name(the bucket identity) and keepdomainsas the Host-match list; or collapse to a single clearer model.domainsoverridesdefaultfor Host matching, and thatdefaultis only matched whendomainsis empty.defaultdoes not re-route existing APIs, while changingdomains(with an unchangeddefault) does.domains(produces a duplicate-domain Envoy config that is rejected).VirtualHost.domains.Version
No response
Related Issue
Introduced by #403