Session Persistence
Session persistence (also called sticky sessions) ensures that all requests belonging to the same user session are routed to the same backend instance. This is required when a backend stores state locally, such as a shopping cart in memory or a WebSocket connection tied to a specific process.
How Session Persistence Works
Section titled “How Session Persistence Works”The first time a user makes a request, the gateway routes it to a backend instance using the standard load balancing algorithm. If session persistence is configured for that route, the gateway attaches a session identifier to the response. On subsequent requests from the same user, the gateway reads the identifier and routes the request to the same backend instance that handled the original session.
The session identifier is a signed token that encodes the backend name, endpoint address, and optional expiry times. The signing prevents tampering: if a client modifies the identifier, the signature fails to verify and the gateway treats it as a new session, routing to a fresh backend instance.
Cookie-Based Persistence
Section titled “Cookie-Based Persistence”When sessionType is Cookie (the default), the gateway sets a cookie on the first response. The cookie holds the signed session token. The cookie name is configured through the sessionName field. If the cookie already exists on an incoming request, the gateway verifies its signature and routes to the recorded backend.
The cookie carries an optional absoluteTimeout that limits the lifetime of the session. When the timeout is reached, the session is discarded and the next request is routed to a new backend instance. If no absolute timeout is set, the session persists indefinitely.
Cookie lifetime is controlled by cookieConfig.lifetimeType:
| Lifetime Type | Cookie Expiry | Description |
|---|---|---|
Session | Browser session (no Expires/Max-Age) | The cookie is deleted when the browser closes. This is the default. |
Permanent | Set by absoluteTimeout | The cookie includes a Max-Age attribute matching the configured timeout. Requires absoluteTimeout to be set. |
Header-Based Persistence
Section titled “Header-Based Persistence”When sessionType is Header, the gateway does not set a cookie. Instead it sets a response header with the name configured in sessionName. The client is expected to echo the header value back on subsequent requests. This mode is useful for API clients that do not handle cookies, or for gRPC services where cookies are uncommon.
Header-based sessions have the same signing, timeout, and routing guarantees as cookie-based sessions. The only difference is the transport mechanism.
Configure Via BackendLBPolicy
Section titled “Configure Via BackendLBPolicy”Session persistence is configured through the BackendLBPolicy CRD under the session_persistence field:
apiVersion: gateway.networking.k8s.io/v1alpha2kind: BackendLBPolicymetadata: name: cart-sticky namespace: nantian-demospec: targetRefs: - group: "" kind: Service name: shopping-cart-service session_persistence: sessionName: ROUTEID sessionType: Cookie absoluteTimeout: 3600s idleTimeout: 600s cookieConfig: lifetimeType: SessionThis policy targets the shopping-cart-service backend. Requests are routed to a backend instance and the gateway sets a ROUTEID cookie with a signed session token. The session cookie lasts for the browser session. The absolute timeout of 3600 seconds (1 hour) caps the maximum session lifetime even if the browser does not close. If the user is idle for 600 seconds (10 minutes), the session is eligible for expiry.
When targetRefs contains multiple Services, each Service gets its own persistence configuration. A single BackendLBPolicy can apply cookie-based persistence to one Service and header-based persistence to another, as long as each has a separate policy.
Session Secret
Section titled “Session Secret”The session token is signed with a shared secret for integrity. The gateway supports three methods to configure the secret:
| Method | Configuration | Use Case |
|---|---|---|
| Inline secret | shared_secret in data plane config | Small deployments with a pre-shared key |
| File-based secret | sharedSecretFile pointing to a file path | Deployments with secret rotation (the file is re-read every 250ms) |
| Auto-generated | No configuration | Development and single-replica deployments only |
For multi-replica data plane deployments, configure a consistent shared_secret or sharedSecretFile across all replicas. If each replica auto-generates a different key, a session token signed by replica A will fail validation on replica B, causing the user to lose their session when traffic shifts between replicas.
When to Use Session Persistence
Section titled “When to Use Session Persistence”Session persistence is the right choice when:
- A backend stores state in local memory instead of a shared database or cache.
- You are running a WebSocket or long-polling service that must stay pinned to one instance.
- A backend relies on filesystem-local data that is not replicated across instances.
- You are gradually migrating services and need to pin users to specific backend versions.
Session persistence is tradeoff. It reduces the effectiveness of load balancing by concentrating traffic from the same session onto a single backend. If a backend instance goes down, all sessions pinned to it are lost until new sessions are established. Use it only for backends that genuinely need sticky routing.
Limitations
Section titled “Limitations”Session persistence does not survive data plane restarts. The gateway stores session-to-backend mappings in memory. When a data plane process restarts, all session state is lost. Incoming requests that carry a valid session token from before the restart are rejected as unknown and routed to a new backend instance. The user will be assigned a new session automatically, but any state tied to the old backend instance is gone.
Session tokens are opaque to the client. The client should not attempt to parse or modify the cookie value. Any modification will invalidate the signature and cause the gateway to create a new session.
Troubleshooting: Sessions Keep Dropping
Section titled “Troubleshooting: Sessions Keep Dropping”If users report being unexpectedly logged out or losing session state, the most common cause is missing session persistence configuration. Unlike nginx-ingress which can enable sticky sessions with an annotation (nginx.ingress.kubernetes.io/affinity: cookie), nantian-gw requires an explicit BackendLBPolicy.
Checklist
Section titled “Checklist”-
Check if you have a BackendLBPolicy. List policies in your namespace:
Terminal window kubectl get backendlbpolicies -n your-namespaceIf you don’t see one targeting your backend Service, sessions are not sticky and every request may land on a different backend Pod.
-
Verify the policy targets the correct Service. The
targetRefs.namemust match your Service name exactly:Terminal window kubectl describe backendlbpolicy <name> -n your-namespace -
Check for multi-replica deployments. If you run multiple dataplane replicas, configure a shared secret:
# dataplane configsession_persistence:shared_secret: "your-32-byte-secret"Without this, a session cookie signed by replica A will be rejected by replica B.
-
Verify the cookie is being set. Check response headers:
Terminal window curl -v https://your-app.example.com 2>&1 | grep -i set-cookieYou should see a
Set-Cookieheader with thesessionNameyou configured. -
Check absoluteTimeout and idleTimeout. If either is too short, sessions expire prematurely. Start with
absoluteTimeout: 1handidleTimeout: 15mand adjust based on your application’s needs.
Migrating from Nginx Ingress
Section titled “Migrating from Nginx Ingress”| nginx-ingress annotation | nantian-gw equivalent |
|---|---|
nginx.ingress.kubernetes.io/affinity: cookie | Create a BackendLBPolicy with session_persistence.type: Cookie |
nginx.ingress.kubernetes.io/session-cookie-name | session_persistence.sessionName |
nginx.ingress.kubernetes.io/session-cookie-expires | session_persistence.absoluteTimeout |
nginx.ingress.kubernetes.io/session-cookie-max-age | session_persistence.cookieConfig.lifetimeType: Permanent |
nginx.ingress.kubernetes.io/session-cookie-path | Not applicable (cookie path is / by default) |