Skip to content

feat(gateway-api): allow header-based sticky session (ConsistentHash.Header)#746

Open
HR-Gabriel-Cornesse wants to merge 2 commits intoQovery:engine-publicfrom
HR-Gabriel-Cornesse:feat/gateway-api-sticky-session-header
Open

feat(gateway-api): allow header-based sticky session (ConsistentHash.Header)#746
HR-Gabriel-Cornesse wants to merge 2 commits intoQovery:engine-publicfrom
HR-Gabriel-Cornesse:feat/gateway-api-sticky-session-header

Conversation

@HR-Gabriel-Cornesse
Copy link
Copy Markdown

Summary

Adds a new service advanced setting network.gateway_api.sticky_session_header that switches the generated BackendTrafficPolicy.loadBalancer.consistentHash from Cookie to Header when set.

The existing cookie-based affinity (INGRESSCOOKIE_QOVERY) is the default and remains unchanged when the setting is unset (null) — fully backward compatible.

Motivation

Cookie-based affinity is unusable for clients that cannot persist HTTP cookies:

  • MCP servers (Model Context Protocol — the emerging standard for LLM tool integration) key sessions off the Mcp-Session-Id header per spec.
  • MCP clients (n8n, Claude Desktop, Claude Code, MCP Inspector, Cursor, Google ADK, OpenAI Agents SDK, etc.) use raw fetch() / undici without cookie jar support, so Set-Cookie from the ingress is simply dropped.
  • Without header-based affinity, stateful MCP servers cannot scale past one replica behind Qovery's gateway — every POST gets randomly routed and the client ends up re-initializing in a loop.

The same applies to any HTTP client that uses bearer-token auth + a custom session header (common in gRPC + metadata, or internal microservices).

Changes

  • Add network_gateway_api_sticky_session_header: Option<String> to ApplicationAdvancedSettings, ContainerAdvancedSettings, and HelmChartAdvancedSettings with serde alias network.gateway_api.sticky_session_header.
  • Propagate the new field in ApplicationAdvancedSettings::to_container_advanced_settings.
  • Update the HTTPRoute and GRPCRoute BackendTrafficPolicy Jinja templates to emit ConsistentHash.Header when the setting is non-empty, keep the existing Cookie branch otherwise.
  • Update the four tests/helm/mod.rs struct literals to include the new field (defaults to None, no behavior change).

Backward compatibility

Zero behavior change for existing users: the setting defaults to None, which preserves the current INGRESSCOOKIE_QOVERY cookie-based ConsistentHash.

Test plan

  • cargo check passes (verified locally — 2m04s).
  • cargo check --tests passes (verified locally — 1m18s, all tests/helm/mod.rs struct literals compile).
  • Manual: templates render identically when setting is absent/None (cookie branch unchanged — should diff-clean vs engine-public).
  • Manual: templates render header-based ConsistentHash when setting contains a header name.
  • End-to-end: deploy a service with network.gateway_api.sticky_session_header = "Mcp-Session-Id" on a 3-replica backend, verify successive requests with the same header value hit the same pod.

homer-bot and others added 2 commits April 21, 2026 12:49
Allow clients that cannot persist cookies (e.g. MCP SDK clients keying
off `Mcp-Session-Id`) to benefit from session affinity by letting users
pick a request header to hash on.

When `network.gateway_api.sticky_session_header` is set (and
`network.gateway_api.enable_sticky_session` is true), the generated
BackendTrafficPolicy uses `consistentHash.type: Header` instead of the
default `Cookie`. The existing cookie-based behavior is unchanged when
the setting is left unset (null), preserving backward compatibility
for all current users.

Use case: MCP servers (Model Context Protocol) need sticky routing on
`Mcp-Session-Id` because their clients (n8n, Claude Desktop, MCP
Inspector, etc.) do not implement HTTP cookie storage — they key
sessions off a single request header per the MCP spec. Without
header-based affinity, stateful MCP servers cannot scale past one
replica behind Qovery's gateway.

Applies to both HTTPRoute and GRPCRoute BackendTrafficPolicy templates.
@erebe erebe force-pushed the engine-public branch 4 times, most recently from 32deea3 to 47c8a31 Compare April 23, 2026 14:43
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.

1 participant