Skip to content

feat(api): per-route port overrides on NebariApp routes#121

Draft
viniciusdc wants to merge 1 commit into
docs/multi-backend-routes-designfrom
feat/multi-backend-routes
Draft

feat(api): per-route port overrides on NebariApp routes#121
viniciusdc wants to merge 1 commit into
docs/multi-backend-routes-designfrom
feat/multi-backend-routes

Conversation

@viniciusdc

@viniciusdc viniciusdc commented May 19, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the multi-port + namespace-removal portions of the design in #120 (stacked on top of that PR — base will switch to main once #120 merges). The streaming/BackendTrafficPolicy portion of #120 is not in this PR — open question on whether to extend this PR or land separately, see #120's PR body.

  • Adds optional RouteMatch.Port (*int32) so a single NebariApp can route different path prefixes to different ports on the same backend Service under one hostname.
  • A NebariApp still targets exactly one Service. Per-route backend Services are intentionally not supported (see docs(design): multi-port routes and streaming for NebariApp #120 for the rationale).
  • Drops ServiceReference.Namespace. Was a half-feature: the operator rendered cross-namespace BackendObjectReference but never created the Gateway API ReferenceGrant the gateway needs, so traffic silently failed. New contract: the backend Service lives in the NebariApp's own namespace. nebari-landing depends on this field but has graceful fallback; see docs(design): multi-port routes and streaming for NebariApp #120's Downstream consumer section.
  • Reconciler now emits one HTTPRouteRule per RouteMatch (with that route's resolved port) instead of one rule with N matches sharing a single port. The "no routes" case still emits a single rule with empty matches, so the Gateway-API "/" default still applies.
  • Both routing.routes[] and routing.publicRoutes[] support per-route ports.
  • ValidateService looks up spec.service once and confirms every effective port (default + each route override) is exposed by it. A route Port that the Service doesn't expose surfaces as a clear error.

What this PR does NOT include

  • routing.streaming field on RoutingConfig.
  • BackendTrafficPolicy reconciler / new streaming.go.
  • RBAC additions for gateway.envoyproxy.io/backendtrafficpolicies.

The design for those is in #120. Whether they land here or as a stacked third PR is the open question called out at the bottom of #120's body.

Iteration history

An earlier iteration exposed per-route backend: {name, port} overrides for multi-Service fan-out; narrowed after design feedback to ports-only on a single Service. Both PRs have been force-pushed in place (draft, no review activity).

Files touched

  • api/v1/nebariapp_types.goServiceReference slim-down, RouteMatch.Port, updated docstrings (hostname / single-Service constraints codified).
  • internal/controller/reconcilers/routing/httproute.gobuildHTTPRouteRules, buildPublicHTTPRoute, new routeToMatch / resolveRoutePort helpers, slimmed buildBackendRefs(name, port).
  • internal/controller/reconcilers/core/reconciler.goValidateService now multi-port aware against a single Service lookup.
  • Tests updated:
    • TestBuildBackendRefs rewritten for the new (string, int32) signature; same-namespace invariant asserted.
    • New TestBuildHTTPRouteRules_PerRoutePort and TestBuildHTTPRouteRules_NoRoutes cover the new behavior.
    • TestBuildHTTPRoute / TestBuildHTTPRouteRules / TestBuildPublicHTTPRoute updated for one-rule-per-route shape.
    • Cross-namespace test cases removed (testing dropped behavior); replaced with route-port positive and negative cases.
  • Regenerated: config/crd/bases/reconcilers.nebari.dev_nebariapps.yaml, docs/api-reference.md, zz_generated.deepcopy.go.

Backwards compatibility

  • NebariApp manifests that never set spec.service.namespace are unaffected.
  • Manifests that did set it will be rejected by the API server. No known internal user (all nebari-dev packs deploy via Argo CD into a single per-pack namespace; the only historical users — Keycloak/ArgoCD NebariApps in the kind dev cluster — have moved to NIC's foundational Argo-apps layer).
  • API version stays v1; field removal is permitted under the current README contract that flags v1 as unstable during NIC bring-up.

Test plan

  • go build ./... passes
  • go vet ./... clean
  • make lint (golangci-lint) — 0 issues
  • Unit tests for api/, internal/controller/reconcilers/{routing,core,tls,auth} — all green
  • make generate-dev regenerates CRD and deepcopy cleanly
  • make docs regenerates api-reference.md cleanly
  • TestControllers (envtest-based) — pre-existing failure on main for should successfully reconcile the resource. Reproduces on main with no code changes. Not introduced here.
  • e2e smoke against a real cluster — recommend before flipping out of draft
  • Decision on whether to fold streaming into this PR or land separately

Related

@viniciusdc viniciusdc force-pushed the docs/multi-backend-routes-design branch 2 times, most recently from 7a5ea5b to 71aad60 Compare May 19, 2026 20:10
@viniciusdc viniciusdc force-pushed the feat/multi-backend-routes branch from de42a7c to 158caae Compare May 19, 2026 20:16
@viniciusdc viniciusdc changed the title feat(api): per-route backend overrides on NebariApp routes feat(api): per-route port overrides on NebariApp routes May 19, 2026
@viniciusdc viniciusdc force-pushed the docs/multi-backend-routes-design branch from 71aad60 to 78e47b6 Compare May 20, 2026 01:50
@viniciusdc viniciusdc force-pushed the feat/multi-backend-routes branch from 158caae to fa905de Compare May 20, 2026 01:50
@viniciusdc viniciusdc force-pushed the docs/multi-backend-routes-design branch from 78e47b6 to bdc887b Compare May 20, 2026 13:53
@viniciusdc viniciusdc force-pushed the feat/multi-backend-routes branch from fa905de to 44a9e50 Compare May 20, 2026 13:53
@viniciusdc viniciusdc force-pushed the docs/multi-backend-routes-design branch 2 times, most recently from 7e1cc69 to 2d096ea Compare May 20, 2026 17:31
Adds an optional Port field on RouteMatch so a single NebariApp can
route different path prefixes to different ports on the same backend
Service under one hostname. Routes without a per-route Port continue
to forward to spec.service.port, so all existing NebariApp manifests
behave identically.

A NebariApp still targets exactly one Service. Per-route backend
Services are intentionally not supported — packs that need to fan out
to multiple Services should split into multiple NebariApps.

Tightens the same-namespace contract by removing
ServiceReference.Namespace. The field was a half-feature: the operator
emitted a cross-namespace BackendObjectReference on the HTTPRoute, but
never created the Gateway API ReferenceGrant the gateway needs in the
target namespace, so traffic would silently fail. The new contract is
"the backend Service lives in the NebariApp's own namespace";
workloads that need cross-namespace communication should use
in-cluster DNS rather than the public HTTPRoute.

Reconciler changes:
- buildHTTPRouteRules now emits one HTTPRouteRule per RouteMatch when
  routes are configured, each with its own resolved port (the route's
  Port override if set, else spec.service.port). The "no routes" case
  still emits a single rule with empty matches so Gateway API's "/"
  default applies (unchanged behavior).
- buildPublicHTTPRoute applies the same shape so per-route ports
  also work on routing.publicRoutes[].
- ValidateService looks up spec.service once and confirms every
  effective port (spec.service.port plus each route override) is
  exposed by the Service.

Design rationale: docs/design/multi-backend-routes.md. This PR
implements only the multi-port + namespace-removal portions of the
design; the streaming/BackendTrafficPolicy portion is a follow-up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants