From ab390926a2426fcf61975f13062fb3919fd8b5c8 Mon Sep 17 00:00:00 2001 From: Andrey Maltsev Date: Fri, 31 Jul 2026 15:17:36 +0300 Subject: [PATCH] feat: warn on dots in HTTPRoute names to avoid duplicate Prometheus series Envoy derives Prometheus stat labels by splitting cluster names on ".", so an HTTPRoute whose name (or a rule name) contains a dot is truncated at the first dot and can collide with another route into duplicate metric series, which Prometheus rejects with "duplicate sample for timestamp". Set a Warning status condition (reason DottedName) on the HTTPRoute in that case, signaling users to avoid dots in names. The condition is set after the Accepted condition so it doesn't suppress it. Scoped to HTTPRoute; other route kinds can follow up. Fixes #9576 Signed-off-by: Andrey Maltsev --- internal/gatewayapi/route.go | 31 +++ internal/gatewayapi/status/error.go | 9 + .../httproute-with-dot-in-name.in.yaml | 32 ++++ .../httproute-with-dot-in-name.out.yaml | 177 ++++++++++++++++++ .../9576-httproute-dotted-name-warning.md | 1 + 5 files changed, 250 insertions(+) create mode 100644 internal/gatewayapi/testdata/httproute-with-dot-in-name.in.yaml create mode 100644 internal/gatewayapi/testdata/httproute-with-dot-in-name.out.yaml create mode 100644 release-notes/current/new_features/9576-httproute-dotted-name-warning.md diff --git a/internal/gatewayapi/route.go b/internal/gatewayapi/route.go index c793f31b61..cf3aa8f551 100644 --- a/internal/gatewayapi/route.go +++ b/internal/gatewayapi/route.go @@ -210,7 +210,38 @@ func (t *Translator) processHTTPRouteParentRefs(httpRoute *HTTPRouteContext, res "Route is accepted", ) } + + // Warn about dots in the HTTPRoute name or rule names. Envoy derives Prometheus + // stat labels by splitting cluster names on ".", so a dotted name is truncated at + // the first dot and can collide with another route into duplicate metric series. + // This is set after the Accepted condition so it doesn't suppress it (the block + // above only fires when no other condition is present). + // See https://github.com/envoyproxy/gateway/issues/9576. + if dotted := dottedRouteNames(httpRoute); len(dotted) > 0 { + status.SetRouteStatusCondition(GetRouteStatus(httpRoute), + parentRef.routeParentStatusIdx, + httpRoute.GetGeneration(), + status.RouteConditionWarning, + metav1.ConditionTrue, + status.RouteReasonDottedName, + fmt.Sprintf("A dot in the HTTPRoute name or rule name(s) %v causes Envoy to truncate Prometheus metric labels at the first dot, which can produce duplicate metric series; avoid dots in HTTPRoute and rule names.", dotted), + ) + } + } +} + +// dottedRouteNames returns the HTTPRoute name and any rule names that contain a dot. +func dottedRouteNames(httpRoute *HTTPRouteContext) []string { + var names []string + if strings.Contains(httpRoute.GetName(), ".") { + names = append(names, httpRoute.GetName()) + } + for _, rule := range httpRoute.Spec.Rules { + if rule.Name != nil && strings.Contains(string(*rule.Name), ".") { + names = append(names, string(*rule.Name)) + } } + return names } func formatDroppedRuleMessage(unacceptedRules []int, err status.Error) string { diff --git a/internal/gatewayapi/status/error.go b/internal/gatewayapi/status/error.go index 57b712fb8e..c1a8f9d28f 100644 --- a/internal/gatewayapi/status/error.go +++ b/internal/gatewayapi/status/error.go @@ -33,6 +33,15 @@ const ( // Network configuration related condition types RouteConditionBackendsAvailable gwapiv1.RouteConditionType = "BackendsAvailable" + + // RouteConditionWarning indicates that the route configuration contains + // non-critical issues that are accepted but require attention. + RouteConditionWarning gwapiv1.RouteConditionType = "Warning" + + // RouteReasonDottedName is used with the "Warning" condition when the route + // name or a rule name contains a dot, which causes Envoy to truncate Prometheus + // metric labels at the first dot and can produce duplicate metric series. + RouteReasonDottedName gwapiv1.RouteConditionReason = "DottedName" ) // Listener condition reasons for various error scenarios diff --git a/internal/gatewayapi/testdata/httproute-with-dot-in-name.in.yaml b/internal/gatewayapi/testdata/httproute-with-dot-in-name.in.yaml new file mode 100644 index 0000000000..0c432a4cc3 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-dot-in-name.in.yaml @@ -0,0 +1,32 @@ +gateways: + - apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: + - apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: echo.a.example.com + spec: + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 diff --git a/internal/gatewayapi/testdata/httproute-with-dot-in-name.out.yaml b/internal/gatewayapi/testdata/httproute-with-dot-in-name.out.yaml new file mode 100644 index 0000000000..b9cc982b60 --- /dev/null +++ b/internal/gatewayapi/testdata/httproute-with-dot-in-name.out.yaml @@ -0,0 +1,177 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 1 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + name: echo.a.example.com + namespace: default + spec: + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + - lastTransitionTime: null + message: A dot in the HTTPRoute name or rule name(s) [echo.a.example.com] + causes Envoy to truncate Prometheus metric labels at the first dot, which + can produce duplicate metric series; avoid dots in HTTPRoute and rule names. + reason: DottedName + status: "True" + type: Warning + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + ownerReference: + kind: GatewayClass + name: envoy-gateway-class + name: envoy-gateway/gateway-1 + namespace: envoy-gateway-system +xdsIR: + envoy-gateway/gateway-1: + accessLog: + json: + - path: /dev/stdout + globalResources: + proxyServiceCluster: + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + settings: + - addressType: IP + endpoints: + - host: 7.6.5.4 + port: 8080 + zone: zone1 + metadata: + kind: Service + name: envoy-envoy-gateway-gateway-1-196ae069 + namespace: envoy-gateway-system + sectionName: "8080" + name: envoy-gateway/gateway-1 + protocol: TCP + http: + - address: 0.0.0.0 + externalPort: 80 + hostnames: + - '*' + metadata: + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + metadata: + kind: HTTPRoute + name: echo.a.example.com + namespace: default + name: httproute/default/echo.a.example.com/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + metadata: + kind: Service + name: service-1 + namespace: default + sectionName: "8080" + name: httproute/default/echo.a.example.com/rule/0/backend/0 + protocol: HTTP + weight: 1 + hostname: '*' + isHTTP2: false + metadata: + kind: HTTPRoute + name: echo.a.example.com + namespace: default + name: httproute/default/echo.a.example.com/rule/0/match/0/* + pathMatch: + distinct: false + name: "" + prefix: / + readyListener: + address: 0.0.0.0 + ipFamily: IPv4 + path: /ready + port: 19003 diff --git a/release-notes/current/new_features/9576-httproute-dotted-name-warning.md b/release-notes/current/new_features/9576-httproute-dotted-name-warning.md new file mode 100644 index 0000000000..a96e622ec6 --- /dev/null +++ b/release-notes/current/new_features/9576-httproute-dotted-name-warning.md @@ -0,0 +1 @@ +Envoy Gateway now sets a `Warning` status condition (reason `DottedName`) on an HTTPRoute whose name or a rule name contains a dot. Envoy derives Prometheus stat labels by splitting cluster names on `.`, so a dotted name is truncated at the first dot and can collide with another route into duplicate metric series. The condition signals users to avoid dots in HTTPRoute and rule names.