Skip to content

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 :

  1. 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.

  2. 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 with traefik.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 except identity.<domain> (Keycloak serves its own login — wrapping it in oauth2-proxy would deadlock the SSO handshake).
  • registry.<domain> lives in the harbor namespace (Harbor has its own deploy script). The alias Ingress is shipped at helm/harbor/registry-alias-ingress.yaml and re-applied by helm/scripts/deploy-harbor.sh after 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.sh renders both into realm-domain.json.
  • Cockpit JS / HTML use data-subdomain="<functional>" — never the tech name. A future Spark→Dremio swap means changing one HelmChart value (compute.target from spark-master to dremio-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 to helm/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.md EN+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.yaml
  • scripts/generate-domain-values.sh EXTRA_REDIRECTS
  • templates/oauth2-callback-ingresses.yaml host list
  • branding/cockpit/{app.js,index.html} Documented runbook in docs/architecture/functional-urls.md.
  • Multi-host Keycloak frontend caused a cookie_not_found cascade when realm.attributes.frontendUrl was set to identity.<domain> while the AUTH_SESSION_ID cookie was attached to keycloak.<domain>. Fix: do NOT set frontendUrl in the realm; let KC_HOSTNAME env var define the canonical host (keycloak.<domain>) and serve identity. <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:

  1. Update templates/functional-aliases-ingresses.yaml — change service.name from akko-akko-spark-master to dremio-coordinator.
  2. 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