ADR-036 — Functional FQDN naming for the user-facing layer¶
Date : 2026-04-25 (Sprint 47 V2) Status : Accepted, in production on Netcup Supersedes : none — extends ADR-021 (Catalog Manager Pro) reasoning
Context¶
AKKO's user-facing surface (cockpit cards, OIDC redirect URIs, Ingress
hosts, IDE remote settings, notebook bookmarks) historically named each
service after the implementation tech : jupyter.<domain> for
JupyterHub, superset.<domain> for Apache Superset, trino.<domain>
for Trino, keycloak.<domain> for Keycloak, etc.
Two recurring issues with this convention :
-
Lego swap costs — every replacement of an underlying technology (Spark → Dremio, MinIO → Ceph, Loki → VictoriaLogs, Grafana → Perses, LLDAP → 389-DS) churns every bookmark, every Keycloak
redirectUri, every IDE remote URL, every notebook config, every dashboard link. The Sprint 43.5 Lego swap (MinIO/Grafana/Loki → SeaweedFS/Perses/ VictoriaLogs) cost ~2 days of follow-up doc and link fixes. -
Confused user mental model — users ask "what is the BI tool?" not "where does Apache Superset run?" The cockpit card already says "Dashboards & SQL Lab" — but the URL beneath says
superset.<domain>, coupling the user to today's vendor choice.
Decision¶
Promote a stable functional FQDN for every layer the user reaches in a browser. The functional name describes the FUNCTION, not the implementation.
Mapping (Sprint 47 canonical)¶
| Layer (function) | Functional FQDN | Today's implementation | Legacy alias |
|---|---|---|---|
| Notebooks & Code | lab.<domain> |
JupyterHub + code-server | jupyter.<domain> |
| Dashboards & SQL Lab | bi.<domain> |
Apache Superset | superset.<domain> |
| Pipeline orchestration | orchestrator.<domain> |
Apache Airflow 3 | airflow.<domain> |
| Federated SQL | federation.<domain> |
Trino | trino.<domain> |
| Distributed compute | compute.<domain> |
Apache Spark | spark.<domain> |
| ML experiments | experiments.<domain> |
MLflow | mlflow.<domain> |
| AI gateway | llm.<domain> |
LiteLLM | litellm.<domain> |
| Identity provider | identity.<domain> |
Keycloak | keycloak.<domain> |
| Directory service | directory.<domain> |
LLDAP | lldap.<domain> |
| Object storage | storage.<domain> |
SeaweedFS S3 | seaweedfs.<domain> |
| Data catalog | catalog.<domain> |
OpenMetadata | openmetadata.<domain> |
| Observability dashboards | metrics.<domain> |
Perses | perses.<domain> |
| Log search | logs.<domain> |
VictoriaLogs | loki.<domain> |
| Alert routing | alerts.<domain> |
Alertmanager | alertmanager.<domain> |
| MCP servers | mcp.<domain> / mcp-catalog.<domain> |
MCP Trino + OpenMetadata | mcp-trino.<domain> / mcp-openmetadata.<domain> |
| Container registry | registry.<domain> |
Harbor | harbor.<domain> |
The cockpit lives on its own dedicated FQDN (demo.<domain>), the
documentation site on docs.<domain>, and the marketing landing on the
apex (<domain>).
Implementation choices¶
- Helm umbrella template owns the alias Ingresses
(
templates/functional-aliases-ingresses.yaml). Touching the 5 affected sub-charts to add an alias host would couple Lego layers we want independent. Each entry maps<functional>.<domain>→ existing Service via a tiny Ingress withtraefik.ingress.kubernetes.io/ router.priority: "90"so the alias loses to a more specific route if one ever exists. - OAuth2 ForwardAuth middleware (
oauth2-forward-auth) is re-applied on every alias exceptidentity.<domain>(Keycloak serves its own login — wrapping it in oauth2-proxy would deadlock the SSO handshake). registry.<domain>lives in theharbornamespace (Harbor has its own deploy script). The alias Ingress is shipped athelm/harbor/registry-alias-ingress.yamland re-applied byhelm/scripts/deploy-harbor.shafter every Harbor redeploy.- OIDC redirectUris for the 5 OIDC clients (jupyterhub / superset /
airflow / trino / oauth2-proxy + mlflow via oauth2-proxy) carry both
the legacy alias and the new functional FQDN.
helm/scripts/generate- domain-values.shrenders both intorealm-domain.json. - Cockpit JS / HTML use
data-subdomain="<functional>"— never the tech name. A future Spark→Dremio swap means changing one HelmChart value (compute.targetfromspark-mastertodremio-coordinator) with zero churn on the cockpit, the docs, the Keycloak realm, or user bookmarks. - TLS is provided by the wildcard
*.<domain>Secret managed by cert-manager (ADR-035). Adding a new functional FQDN only requires appending tohelm/akko/templates/functional-aliases-ingresses.yaml.
Legacy aliases — sunset window¶
For one full sprint after introduction (Sprint 47 ⟶ Sprint 48), legacy
aliases (jupyter.<domain>, superset.<domain>, …) keep resolving so
old bookmarks survive the rollout. Sprint 49+ : the legacy Ingresses
collapse to a 301 → functional name. Sprint 50+ : drop entirely.
Consequences¶
Positive¶
- Lego swaps no longer churn user-visible URLs.
- Cockpit cards, OIDC, IDE remote settings, dashboards, notebooks all reference the same canonical name.
- Adding a new functional alias is a 5-line append to the umbrella template + values regeneration; no code change in any sub-chart.
- Documentation (
docs/architecture/functional-urls.mdEN+FR) becomes the single reference for "where do I go to do X".
Negative / risks¶
- Two FQDNs per service during the sunset window means 2× wildcard cert SAN entries (already covered) and 2× Ingress entries (low overhead).
- Operators must remember to add new functional FQDN to:
templates/functional-aliases-ingresses.yamlscripts/generate-domain-values.shEXTRA_REDIRECTStemplates/oauth2-callback-ingresses.yamlhost listbranding/cockpit/{app.js,index.html}Documented runbook indocs/architecture/functional-urls.md.- Multi-host Keycloak frontend caused a
cookie_not_foundcascade whenrealm.attributes.frontendUrlwas set toidentity.<domain>while the AUTH_SESSION_ID cookie was attached tokeycloak.<domain>. Fix: do NOT setfrontendUrlin the realm; letKC_HOSTNAMEenv var define the canonical host (keycloak.<domain>) and serveidentity. <domain>as a stateless alias. Documented in~/.claude/memory/gotcha_keycloak_frontendurl_alias.md.
Long-term¶
When a Lego swap actually happens (e.g. Spark → Dremio in Sprint 60), the only changes needed:
- Update
templates/functional-aliases-ingresses.yaml— changeservice.namefromakko-akko-spark-mastertodremio-coordinator. helm upgrade akko ...— done.
User bookmarks, OIDC redirects, IDE settings, dashboards : zero churn.
References¶
- Sprint 47 V2 PR #39 (this implementation)
- ADR-021 — Catalog Manager Pro (precursor reasoning)
- ADR-035 + .update.md — TLS wildcard cert via cert-manager DNS-01
docs/architecture/functional-urls.md(EN) +.fr.md(FR)- Memory
gotcha_keycloak_frontendurl_alias.md