Skip to content

v1.0.3 (k8s chart 1.0.5) registering broken routes #22

@codercms

Description

@codercms

Summary

After upgrading HAProxy Unified Gateway from chart 1.0.2 (app 1.0.2) to chart 1.0.5 (app 1.0.3), public routes for shared hostnames started returning 404 NOT FOUND from HUG backend_not_found.

This reproduces when multiple HTTPRoute objects use the same hostname on the same listener (for example domain.tld split across two routes).

Impact

  • User-facing routes on shared hostnames became unavailable.
  • Example:
    • api.domain.tld continued to work.
    • domain.tld and preview.domain.tld returned 404 NOT FOUND.

Environment

  • Kubernetes client used during investigation: kubectl v1.34.1
  • HUG chart upgraded to: haproxy-unified-gateway-1.0.5
  • HUG app version in that chart: 1.0.3
  • Previous working state: chart 1.0.2 (app 1.0.2)

Relevant Configuration

Gateway listener (public)

Public gateway has a listener without hostname, routes selected by namespace labels:

  • Gateway: haproxy-unified-gateway/public-http
  • listener: http on port 20080

Routes (problematic shape)

Multiple routes share the same hostname on the same listener:

  • HTTPRoute app/app-front
    • hostnames: domain.tld
    • paths: /
  • HTTPRoute app/app-admin
    • hostnames: domain.tld
    • paths: /admin, /admin/static

Same pattern exists for preview:

  • app-preview/app-front and app-preview/app-admin both use preview.domain.tld.

Observed Behavior

Request behavior from public HUG pod

  • Host: api.domain.tld -> 200
  • Host: whoami.domain.tld -> 200
  • Host: domain.tld -> 404
  • Host: preview.domain.tld -> 404

HUG-generated map content (public controller)

/usr/local/hug/maps/hug_http_20080/listener_route_exact_match.map includes duplicate key rows for the same hostname:

haproxy-unified-gateway/public-http_http/domain.tld haproxy-unified-gateway/public-http_http/app/app-admin
haproxy-unified-gateway/public-http_http/domain.tld haproxy-unified-gateway/public-http_http/app/app-front

And similarly for preview.domain.tld.

HAProxy access log evidence

For failing domain.tld requests, the log shows LR (selected listener route) as a comma-separated list instead of a single route:

LR:haproxy-unified-gateway/public-http_http/app/app-admin,haproxy-unified-gateway/public-http_http/app/app-front

Then request falls through to:

hug_http_20080 backend_not_found/<NOSRV> ... 404 ... "GET domain.tld/ HTTP/1.1"

This indicates route selection becomes ambiguous/non-scalar and path lookup fails.

Comparison with 1.0.2 (working behavior)

1.0.2 map/config model observed

On chart 1.0.2 (app 1.0.2), public listener uses direct host+path lookups and does not use listener_route_exact_match.map:

show map:
  /usr/local/hug/maps/hug_http_20080/path_exact.map
  /usr/local/hug/maps/hug_http_20080/path_prefix.map
  /usr/local/hug/maps/hug_http_20080/domain_wildcard_path_exact.map
  /usr/local/hug/maps/hug_http_20080/path_regex.map

/usr/local/hug/haproxy.cfg route chain:

set-var(txn.base) var(txn.host),concat("",txn.path)
set-var(txn.route) var(txn.base),map(path_exact.map)
set-var(txn.route,ifnotexists) path,map(path_exact.map)
set-var(txn.route,ifnotexists) var(txn.base),map_beg(path_prefix.map)
set-var(txn.route,ifnotexists) path,map_beg(path_prefix.map)
...
use_backend %[var(txn.route)]

Chart 1.0.2 request outcomes for the same route layout

With the same split-hostname layout:

  • GET domain.tld/ -> 200 (front backend selected)
  • GET domain.tld/admin -> 200 (admin backend selected)
  • GET preview.domain.tld/ -> 200
  • GET preview.domain.tld/admin -> 200

So the split-hostname routes are handled correctly on 1.0.2, but not on 1.0.3.

Why this looks like a regression

  • Same route layout previously worked on chart 1.0.2 (app 1.0.2).
  • Regression appears after moving to chart 1.0.5 (app 1.0.3) which introduced new map/listener selection behavior.
  • Failure is specific to hostnames shared by multiple HTTPRoute resources; unique hostnames still route correctly.

Repro Steps

  1. Install HUG chart 1.0.5 (app 1.0.3) with an HTTP listener without explicit hostname.
  2. Create two HTTPRoutes attached to that listener with the same hostname:
    • Route A: hostname example.com, path /
    • Route B: hostname example.com, path /admin
  3. Send requests with Host: example.com to / and /admin.
  4. Inspect:
    • runtime map files under /usr/local/hug/maps/...
    • HAProxy access log fields L, LR, BLR

Expected

  • Deterministic route selection and correct path dispatch:
    • / -> Route A backend
    • /admin -> Route B backend

Actual

  • Requests return 404 NOT FOUND from backend_not_found.
  • LR/BLR become malformed/ambiguous (comma-joined multi-route value).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions