Routing: how a developer's traffic reaches their copies
Nezal runs one router per Baseline next to the application's services. Every
in-cluster Service name (ledger or payments-api) points at the router. The real pods sit
behind <name>-baseline. An Environment's copies live in their own namespace and are
reached only through the router.
The routing key
Requests carry the environment name in X-Nezal-Env: <environment>. The router also
accepts and sets the W3C baggage member nezal-env=<environment> so services that
already propagate OpenTelemetry baggage need no change.
Your services must forward X-Nezal-Env (or baggage) on every outgoing call the
same way they forward trace context. Without it a call from a baseline service to a
service the environment overrides lands on the baseline copy.
What the router does with a request
| Arrives on | Environment taken from | Client routing headers | Goes to |
|---|---|---|---|
| Service port (in-cluster) | X-Nezal-Env or else baggage |
trusted | copy if the environment overrides the service or else <name>-baseline |
| Edge port (from the Ingress) | Host header only | discarded | the Baseline entrypoint service with copy or baseline chosen as above |
Unknown environment names fall open to the baseline. Each occurrence is counted in
nezal_router_unknown_env_total and recorded as an UnknownEnvironment Event on the
Baseline so a developer whose environment was deleted mid-session is visible.
Ports declared protocol: tcp and services marked shared: true never pass through the
router. They always reach the baseline pods directly.
Preview URLs and TLS
With --ingress-domain=<domain> the operator publishes:
<app>.<domain>for the baseline<env>--<app>.<domain>for every environment
Both are exact-host Ingress objects in the application namespace and are backed by the
router's edge Service. Set --ingress-class if the cluster has more than one ingress
controller.
TLS has two shapes and Nezal issues nothing in either. The product never contacts a certificate authority so certificates are yours whether you hold them or run something that obtains them.
- A wildcard for
*.<domain>: create a TLS Secret holding it in each application namespace and pass its name through--ingress-tls-secret. cert-manager can fill that Secret through a DNS-01 challenge if you have a provider for your DNS. - A certificate per host: pass
--ingress-per-host-tlsand give--ingress-annotationsthe annotation your certificate manager reads such ascert-manager.io/cluster-issuer=letsencrypt-prod. Every Ingress the operator creates then names its own Secret<host>-tlsand carries the annotation so cert-manager issues a certificate for each preview host over HTTP-01 as soon as it appears. No wildcard and no DNS provider. The cost is the authority's rate limit on new certificates.
Preview URLs are served over plain HTTP until one of these is in place and each Baseline
shows the condition IngressTLS=False with reason NoTLSSecret. With per-host issuance the
reason is CertificatePending until the baseline host's Secret exists.
Preview URLs require sign-in by default (previewAuth.enabled in the chart). The router
checks a signed cookie that the API issues after the normal session check. A visitor without
one is sent to the sign-in page. This covers shared mode. A full-mode environment has no
router and its preview URL does not require sign-in yet.
Diagnostics
- Response header
X-Nezal-Upstream: copy|baselinesays which instance answered. Baseline.status.routeslists the router listener port for every service port.Baseline.status.conditions[RoutingReady]reportsRouterStartingorRouterSwitching(brief resets possible) orRouterReady.- Metrics on the router Service port 9090:
nezal_router_requests_totalandnezal_router_request_duration_secondsandnezal_router_unknown_env_totalandnezal_router_edge_headers_dropped_total.