GEP-1619: Session Persistence
- Issue: #1619
- Status: Experimental
(See status definitions.)
Graduation Criteria
Note: This GEP was moved to Experimental in March 2024, before the new GEP process rules were established in discussion #4164 (October 2024). As a result, this GEP is grandfathered in and exempt from the new sponsor requirements and six-month progress rules that apply to GEPs entering Experimental after October 2024.
Standard
Before this GEP graduates to the Standard channel, we must fulfill the following criteria:
- At least 3 implementations passing conformance tests
- Comprehensive conformance test suite covering cookie and header persistence types
- Resolve attachment model (#4462) for session persistence — Backend resource (GEP-4894)
- Define cookie path default behavior (#4713)
- Define session name conflict resolution rules (#4268)
- Sign-off from GAMMA leads to ensure service mesh is fully considered
TLDR
This GEP initially proposes a definitions for session persistence, followed by the definition of an API spec for configuring it. Additionally, it explores example scenarios of session persistence and examines the approaches that implementations have taken to design APIs for session persistence. It intentionally refrains from defining an API for session affinity, as this design is expected to be addressed within a separate GEP.
Goals
- Define session persistence and session affinity to establish a common language
- Identify differences in session persistence functionality between implementations
- Define an API for session persistence
- Establish anticipated outcomes for specific API configurations or scenarios
Non-Goals
- Define an API for session affinity
- Mandate a default session persistence or session affinity functionality for implementations
- Prescribe the precise manner (the “how”) in which implementations should achieve session persistence or handle specific scenarios
- Add API configuration for supporting backend initiated sessions
Introduction
Naming
Naming is hard. We’ve had lots of discussion on the topic of naming session persistence. Adding to the complexity, the Gateway API implementations do not have a consensus on a naming convention for session persistence or affinity.
To start this discussion, lets establish the idea of strong session affinity (what this GEP calls session persistence) and weak session affinity (what this GEP calls session affinity) which we will define further in The Relationship of Session Persistence and Session Affinity. In this context, “strong” implies a guarantee, while “weak” indicates a best-effort approach.
Here’s a survey of how some implementations refers to these ideas:
| Implementation | Name for Strong Session Affinity | Name for Weak Session Affinity |
|---|---|---|
| Apache APISIX | Sticky Sessions | N/A |
| Avi Kubernetes Operator | Session Persistence | Session Persistence |
| Azure Application Gateway for Containers | Session Affinity | N/A |
| Cilium | N/A | Session Affinity |
| Contour | Session Affinity / Sticky Sessions | N/A |
| Envoy | Session Stickiness / Stateful Sessions (Strong) | Session Stickiness / Stateful Sessions (Weak) |
| Emissary-Ingress (Ambassador API Gateway) | Sticky Sessions / Session Affinity | Sticky Sessions / Session Affinity |
| Gloo Gateway 2.0 | Session Affinity / Sticky Sessions | Session Affinity / Sticky Sessions |
| Google Kubernetes Engine | Session Affinity | Session Affinity |
| HAProxy Ingress | Affinity | N/A |
| HAProxy Kubernetes Ingress Controller | Session Persistence | N/A |
| Istio | Strong Session Affinity | Soft Session Affinity |
| Kong | Persistent Session | N/A |
| Nginx (Proxy) | Session Persistence | Session Persistence |
| Traefik | Sticky Sessions | N/A |
Visualizing the result for what implementations call “Strong Session Affinity” (aka Session Persistence), it’s mostly inconclusive:
pie title What Implementations Call "Strong Session Affinity"?
"Session Affinity" : 5
"Sticky Sessions" : 5
"Session Persistence" : 3
"Persistent Session": 1
"Strong Session Affinity": 1
"Affinity": 1
"Session Stickiness": 1
"Stateful Sessions": 1
This GEP chooses to use “session persistence” as the preferred definition of strong session affinity (guaranteed) while reserving “session affinity” to mean weak session affinity (best-effort). We selected these names because “session persistence” and “session affinity” share a symmetry that indicates to users that there is a relationship between them. The term “Persistence” implies a sense of strong consistency or recurring behavior, whereas “Affinity” carries a weaker connotation related to liking or attraction.
Note: One concern for using the name “session persistence” is that it may be confused the idea of persisting a session to storage. This confusion is particularly common among Java developers, as Java defines session persistence as storing a session to disk.
Defining Session Persistence
Session persistence is when a client request is directed to the same backend server for the duration of a “session”. It is achieved when a client directly provides information, such as a header, that a proxy uses as a reference to direct traffic to a specific server. Persistence is an exception to load balancing: a persistent client request bypasses the proxy’s load balancing algorithm, going directly to a backend server it has previously established a session with.
Session persistence enables more efficient application workflows:
- Better performance: Maintaining a single session allows a server to cache information about a client locally reducing the need for servers to exchange session data and overall storage needs.
- Seamless client experience: Clients can reconnect to the same server without re-authenticating or re-entering their information.
Some of the concerns of session persistence are the duration and expiration of the session, security of the transaction stream, and storage of the context or state.
Session affinity, not to be confused with session persistence, uses an existing attribute of the request to consistently send to the same backend. Session affinity can be considered a weaker form of session persistence: it is not guaranteed to persist a connection to the same backend server if certain attributes of the request or the backends are changed.
Security and Privacy Implications
Session persistence can introduce security and privacy vulnerabilities if not properly implemented. These vulnerabilities can include:
- Session hijacking: Attackers intercepting or predicting a valid session token to gain unauthorized access.
- Session fixation: Attackers setting a client’s session ID to a known value, which they can then use to hijack the session.
- Session replay attacks: Attackers capturing and resending a client’s message with a valid session ID.
- Data leakage: Attackers can exploit sensitive session information cached on servers if not properly secured.
- Denial of service attacks: Attackers can use up server resources by creating and maintaining large numbers of sessions.
To mitigate these security concerns, it is important to implement session persistence using secure practices, such as using strong session ID generation algorithms, implementing session timeouts, encrypting sensitive data, and monitoring server resources for unusual activity.
IP address reuse may also be a security or privacy concern when using session persistence or session affinity. If Kubernetes reuses an IP address of previously shutdown pod, the new pod may receive session persistent traffic meant for the old pod.
Session affinity introduces fewer security and privacy vulnerabilities since there are no session tokens to protect or exploit.
Achieving Session Persistence
Session persistence is achieved using attributes residing in the application layer. The following are mechanisms for achieving session persistence:
1. Cookie-Based Session Persistence
The most common mechanism is by using cookies (described by RFC6265) with the set-cookie HTTP response header. A client will use the provided value in the set-cookie response header in a cookie request header in subsequent requests. Proxies can use this cookie header to maintain a persistent connection to a single backend server on behalf of the client.
2. Header-Based Session Persistence
Header-based stateful sessions are achieved by a backend or gateway providing an HTTP response header and the client using the same header in subsequent HTTP requests. Proxies can use this header to maintain a persistent connection to a single backend server on behalf of the client.
3. URL-Encoded Session Persistence
Session information can be also encoded into the request URL to establish a persistent session. The server rewrites the client’s URL to encode the new session information automatically. The server or the gateway then decodes the session information from the URL to identify the session.
Session Persistence Initiation
For all implementations of session persistence, the initiation of the persistent session is possible from various sources, including the gateway, intermediary gateway, backend, a sidecar in a backend, or any other infrastructure component.
Let’s consider a simple implementation comprised of gateways and backends. The following rules apply based on who initiates the session:
- If the gateway initiates the session, the backend will be presented with session attributes regardless if it enabled them.
- If the backend initiates the session, the gateway should allow this and not force persistent connections, unless specifically configured to. The gateway may decode and alter the cookie established by the backend to achieve session persistence.
It’s important to note that we can have more complex implementations which involve traversing global load balancers, regional load balancers, intermediary internal gateways, sidecars, or waypoints before reaching the backend. At any point within this architecture, a persistent session can be initiated. See Global Load Balancer Initiated Session Example for an example of one of these alternative implementations.
In the next sections, we will take a closer look at the initiation of sessions in both the gateway and the backend. Please note that in the next sections, we are examining the various scenarios in which a session can be initiated. We are not prescribing specific implementations for session persistence. The intention is to understand the possibilities and behaviors related to session initiation while the API section will provide more details on specific implementation details.
Gateway Initiated Session Example
To illustrate how a gateway can initiate a session, let’s examine an implementation that uses cookies for persistence.
This represents the most straightforward scenario for utilizing cookies. When a request is made, the gateway includes
the set-cookie header in the final response, prompting the client to store the cookie. This cookie is subsequently
used in future requests, allowing the gateway to consistently choose the same upstream, establishing a persistent
session.
Here an example implementation of a gateway initiating a session through cookies:
sequenceDiagram
actor C as Client
participant G as Gateway
participant B as Backend
C->>+G: Request Web Page
activate G
G->>+B: Request
B-->>-G: Response
G->>G: Add set-cookie header
G-->>-C: Response<br>[set-cookie]
Note right of G: [set-cookie] indicates a response<br> with a set-cookie header.<br>May include other set-cookie<br>headers from backend.
C->>C: Create Cookie(s)<br>from set-cookie header(s)
Note right of C: [cookie] indicates a request<br> with one or more cookies
C->>+G: Request Web Page<br>[cookie]
G->>G: Consistent lookup of<br>server using cookie value
G->>+B: Request<br>[cookie]*
Note right of G: *The Gateway-generated persistence<br>cookie MAY be removed before forwarding.
B-->>-G: Response
G-->>-C: Response
Backend Initiated Session Example
Important: While we took it into consideration, this GEP does not support configuring backend-initiated sessions. This could potentially affect frameworks that initiate sessions in the backend. Implementing this feature is complicated and requires careful design, making it suitable for exploration in a separate GEP.
Continuing with the cookie example, when dealing with backend-initiated sessions, the process becomes somewhat more complex. For cookie-based session persistence, the gateway needs to store a value within a cookie containing a backend identifier. This identifier can be then used as a reference to maintain a persistent session to a specific backend. There are several approaches a gateway could use in this situation to achieve session persistence:
- Insert an additional cookie
- Modify the existing cookie’s value
- Prefix the existing cookie
Additionally, there are variations to each of these approaches, such as making new or updated cookies transparent to the backend, either by remove an inserted cookie or reversing modifications of the cookie’s value.
Alternatively, if the backend is not configured for session persistence, the gateway should refrain from modifying or
inserting a cookie. In this situation, the gateway should remain passive and simply forward the set-cookie header as
it is.
Refer to the Session Initiation Guidelines section of the API for implementation guidance.
Here’s an example implementation of a backend initiating a session and the gateway modifies the cookie’s value:
sequenceDiagram
actor C as Client
participant G as Gateway
participant B as Backend
C->>+G: Request Web Page
activate G
G->>+B: Request
B->>B: Add set-cookie<br>header
B-->>-G: Response<br>[set-cookie]
G->>G: Modify set-cookie<br>header per configuration
G-->>-C: Response<br>[set-cookie*]
Note right of G: [set-cookie] indicates a response<br> with a set-cookie header<br>[set-cookie*] indicates a response<br>with a MODIFIED set-cookie header
C->>C: Create Cookie<br>from set-cookie header
Note right of C: [cookie] indicates a request<br>or response with a cookie
C->>+G: Request Web Page<br>[cookie]
G->>G: Consistent lookup<br>of server using cookie value
G->>+B: Request<br>[cookie]
B-->>-G: Response
G-->>-C: Response
Global Load Balancer Initiated Session Example
In a more complex architecture example, a global load balancer may need to use cookies in order to maintain persistent
connections to a regional load balancer. The regional cluster load balancer initiates the session by issuing the
set-cookie header and subsequently uses the cookie to maintain persistent connections to a specific backend. The
global load balancer then adds or modifies a cookie in order to establish persistent connection to a regional cluster
load balancer.
Here an example implementation of a global load balancer and a regional load balancer creating sessions through cookies:
sequenceDiagram
actor C as Client
participant G as Global<br>Load Balancer
participant R as Regional Cluster<br>Load Balancer
participant B as Backend
C->>+G: Request Web Page
G->>+R: Request
R->>+B: Request
B-->>-R: Response
R->>R: Initiates session by<br>adding set-cookie header
R-->>-G: Response<br>[set-cookie]
G->>G: Add or modify<br>set-cookie header
G-->>-C: Response<br>[set-cookie*]
Note right of G: [set-cookie] indicates a response<br> with a set-cookie header<br>[set-cookie*] indicates a response with a<br>modified or additional set-cookie header
C->>C: Create Cookie<br>from set-cookie header
Note right of C: [cookie] indicates a request<br> with one or more cookies
C->>+G: Request Web Page<br>[cookie]
G->>G: Consistent lookup of<br>regional cluster load balancer<br>using cookie value
G->>+R: Request<br>[cookie]
R->>R: Consistent lookup of backend<br>using cookie value
R->>+B: Request<br>[cookie]
B-->>-R: Response
R-->>-G: Response
G-->>-C: Response
When does an application require session persistence?
Enabling session persistence is a required configuration for applications intentionally designed by the application developer to use it, as they will encounter failures or malfunctions when it’s not enabled. However, it’s worth noting that certain applications may be designed to function both with and without session persistence. Regardless, the importance of Gateway API supporting session persistence remains emphasized because it is frequently seen as a necessary feature.
Conversely, apps that have not been designed or tested with session persistence in mind may misbehave when it is enabled, primarily because of the impacts of load distribution on the app. Apps using session persistence must account for aspects like load shedding, draining, and session migration as a part of their application design.
The Relationship of Session Persistence and Session Affinity
As discussed in Naming, we defined session persistence as “strong” and session affinity as “weak”. Though this GEP’s intention is not to define an API for session affinity, let’s understand its distinction with session persistence.
While session persistence uses attributes in the application layer, session affinity can also use attributes below the application layer. Session affinity doesn’t require a specific backend identifier to be encoded in a cookie or header; instead, it can use any existing connection attributes to establish a consistent hashing load balancing algorithm. This implies session affinity can use cookies or headers, as seen in Istio’s ConsistentHashLB. With session affinity, the cookie or header will be hashed on its arbitrary value.
It is important to note the session affinity is less reliable and doesn’t guarantee persistent connections to the same backend server. If a proxy or load balancer restarts, or if backends are added to the backend pool, the session affinity mechanism will likely redirect the user’s connection to a new backend. In contrast, session persistence encodes a backend identifier in a cookie or header, so as long as the backend still exists, it will be unaffected by proxy restarts or changes in the backend pool.
Session affinity can be achieved by deterministic load balancing algorithms or a proxy feature that tracks IP-to-backend associations such as HAProxy’s stick tables or Cilium’s session affinity.
We can also examine how session persistence and session affinity functionally work together, by framing the relationship into a two tiered logical decision made by the data plane:
- If the request contains a session persistence identity (e.g. in a cookie or header), then route it directly to the backend it has previously established a session with.
- If no session persistence identity is present, load balance as per load balancing configuration, taking into account the session affinity configuration (e.g. by utilizing a hashing algorithm that is deterministic).
This tiered decision-based logic is consistent with the idea that session persistence is an exception to load balancing. Though there are different ways to frame this relationship, this design will influence the separation between persistence and affinity API design.
Sessions in Java
Java application servers such as Tomcat and Jetty, were the first to standardize the API around cookies and sessions. These Java applications introduced the “jsessionid” cookie and session IDs encoded in URL parameters as well as more advanced features such as session migration, replication, and on demand session activation. It’s important for Gateway API to examine cookie use cases and history from Java APIs to ensure the API is designed appropriately.
Session Affinity in K8S Services
Kubernetes provides an API that allows you to enable session affinity on service objects. It ensures consistent sessions by utilizing the client’s IP address and also offers the option to set a timeout for the maximum session duration. Implementations of Gateway API, such as service mesh use cases, may use the service IP directly. In these cases where both Kubernetes service session affinity and Gateway API session persistence are both enabled, the route MUST be rejected, and a status should be set describing the incompatibility of these two configurations.
API
In this section, we will explore the questions and design elements associated with a session persistence API.
Session persistence is configured within the spec.endpointSelector field of the
Backend resource and is only available when the Backend type is EndpointSelector.
Backend API
type BackendSpec struct {
[...]
// EndpointSelector specifies the configuration for an EndpointSelector
// backend.
//
// +optional
EndpointSelector *EndpointSelectorBackend `json:"endpointSelector,omitempty"`
}
type EndpointSelectorBackend struct {
[...]
// SessionPersistence defines and configures session persistence
// across the endpoints selected by this backend.
//
// Support: Extended
//
// +optional
SessionPersistence *SessionPersistence `json:"sessionPersistence,omitempty"`
}
// SessionPersistence defines the desired state of SessionPersistence.
// +kubebuilder:validation:XValidation:message="AbsoluteTimeout must be specified when cookie lifetimeType is Permanent",rule="!has(self.cookie) || !has(self.cookie.lifetimeType) || self.cookie.lifetimeType != 'Permanent' || has(self.absoluteTimeout)"
// +kubebuilder:validation:XValidation:message="cookie must be nil if type is not Cookie",rule="!has(self.cookie) || self.type == 'Cookie'"
// +kubebuilder:validation:XValidation:message="cookie must be specified for Cookie type",rule="self.type != 'Cookie' || has(self.cookie)"
// +kubebuilder:validation:XValidation:message="header must be nil if type is not Header",rule="!has(self.header) || self.type == 'Header'"
// +kubebuilder:validation:XValidation:message="header must be specified for Header type",rule="self.type != 'Header' || has(self.header)"
type SessionPersistence struct {
// AbsoluteTimeout defines the absolute timeout of the persistent
// session. Once the AbsoluteTimeout duration has elapsed, the
// session becomes invalid.
//
// Support: Extended
//
// +optional
AbsoluteTimeout *Duration `json:"absoluteTimeout,omitempty"`
// Type defines the type of session persistence such as through
// the use of a header or cookie. Defaults to cookie based session
// persistence.
//
// Support: Core for "Cookie" type
//
// Support: Extended for "Header" type
//
// +unionDiscriminator
// +optional
// +kubebuilder:default=Cookie
Type *SessionPersistenceType `json:"type,omitempty"`
// Cookie provides configuration settings that are specific
// to cookie-based session persistence.
//
// Support: Core
//
// +optional
Cookie *CookieConfig `json:"cookie,omitempty"`
// Header provides configuration settings that are specific
// to header-based session persistence.
//
// Support: Extended
//
// +optional
Header *HeaderConfig `json:"header,omitempty"`
}
// Duration is a string value representing a duration in time. The format is as specified
// in GEP-2257, a strict subset of the syntax parsed by Golang time.ParseDuration.
//
// +kubebuilder:validation:Pattern=`^([0-9]{1,5}(h|m|s|ms)){1,4}$`
type Duration string
// +kubebuilder:validation:Enum=Cookie;Header
type SessionPersistenceType string
const (
// CookieBasedSessionPersistence specifies cookie-based session
// persistence.
//
// Support: Core
CookieBasedSessionPersistence SessionPersistenceType = "Cookie"
// HeaderBasedSessionPersistence specifies header-based session
// persistence.
//
// Support: Extended
HeaderBasedSessionPersistence SessionPersistenceType = "Header"
)
// CookieConfig defines the configuration for cookie-based session persistence.
type CookieConfig struct {
// Name defines the name of the cookie used for session persistence.
// If not specified, a unique cookie name will be generated.
// Users should avoid reusing cookie names to prevent unintended
// consequences, such as rejection or unpredictable behavior.
//
// <gateway:util:excludeFromCRD>
// This field is Extended because not all implementations can
// control the cookie name. Implementations SHOULD support this
// field if the underlying dataplane allows configuring the cookie
// name.
// </gateway:util:excludeFromCRD>
//
// Support: Extended
//
// +optional
Name *CookieName `json:"name,omitempty"`
// Path defines the cookie Path attribute. When not specified,
// implementations MUST default the cookie path to "/".
//
// Support: Extended
//
// +optional
// +kubebuilder:default="/"
// +kubebuilder:validation:MaxLength=1024
Path *string `json:"path,omitempty"`
// LifetimeType specifies whether the cookie has a permanent or
// session-based lifetime. A permanent cookie persists until its
// specified expiry time, defined by the Expires or Max-Age cookie
// attributes, while a session cookie is deleted when the current
// session ends.
//
// When set to "Permanent", AbsoluteTimeout indicates the
// cookie's lifetime via the Expires or Max-Age cookie attributes
// and is required.
//
// When set to "Session", AbsoluteTimeout indicates the
// absolute lifetime of the cookie tracked by the gateway and
// is optional.
//
// Support: Core for "Session" type
//
// Support: Extended for "Permanent" type
//
// +optional
// +kubebuilder:default=Session
LifetimeType *CookieLifetimeType `json:"lifetimeType,omitempty"`
}
// +kubebuilder:validation:Enum=Permanent;Session
type CookieLifetimeType string
const (
// SessionCookieLifetimeType specifies the type for a session
// cookie.
//
// Support: Core
SessionCookieLifetimeType CookieLifetimeType = "Session"
// PermanentCookieLifetimeType specifies the type for a permanent
// cookie.
//
// Support: Extended
PermanentCookieLifetimeType CookieLifetimeType = "Permanent"
)
// HeaderConfig defines the configuration for header-based session persistence.
type HeaderConfig struct {
// Name defines the name of the header used for session persistence.
// The client must include this header in subsequent requests to
// maintain the session.
//
// Support: Core
//
// +required
Name HeaderName `json:"name"`
}
API Granularity
The purpose of this session persistence API spec is to enable developers to specify that a specific backend expects a persistent session. However, it intentionally avoids specifying low-level details or configurations related to the session persistence implementation, such as cookie attributes. This decision is because the Gateway API supports various infrastructure types, and some implementations that already provide session persistence may not be able to adhere to a low-level API.
For instance, platforms using global load balancers to maintain persistent sessions between regional load balancers, or Tomcat servlets generating distinct cookies per server. In such scenarios, it is important that this GEP does not obstruct the existing use of cookies while enabling session persistence. Enabling particular low-level API configurations, like allowing customization of the cookie name, could prevent certain implementations from conforming to the spec. In other words, opting for a higher-level API provides better interoperability among our implementations.
However, this API spec does allow specifying specific forms or types of session persistence through the Type field in
the SessionPersistence struct, including options for cookie-based or header-based session persistence. This API field
accommodates implementations that offer multiple methods of session persistence, while also allowing users to specify
their preferred form of session persistence if desired.
Target Persona
Referring to the Gateway API Security Model, the target kubernetes role/persona for session persistence are application developers, as mentioned in the When does an application require session persistence? section. It is the responsibility of the application developers to adjust the persistence configuration to ensure the functionality of their applications.
Prior Art
The following table summarizes session persistence support across Gateway API dataplanes and implementations. Dataplanes are listed separately because implementations don’t always expose the full capabilities of their underlying dataplane. Only implementations with session persistence or session affinity support are included.
✅ = strong persistence 🟡 = session affinity (hash-based) ❌ = not supported
| Implementation | Cookie | Header | Attachment |
|---|---|---|---|
| Dataplanes | |||
| Envoy | ✅ | ✅ | route |
| HAProxy | ✅ | ❌ | service |
| NGINX | ✅ | ❌ | service |
| Implementations | |||
| Contour | 🟡 | ❌ | route + service |
| Envoy Gateway | ✅ | ✅ | route |
| GKE | 🟡 | 🟡 | service |
| Gloo Gateway | 🟡 | 🟡 | route |
| HAProxy Ingress | ✅ | ❌ | service |
| Istio | ✅ | ✅ | service |
| kgateway | ✅ | ✅ | route |
| Kong | 🟡 | 🟡 | route + service |
| NGINX Gateway Fabric | ✅ | ❌ | route |
| Traefik | ✅ | ✅ | service |
API Attachment Points
The Backend resource is the primary attachment point for session persistence. The Backend
resource (GEP-4894) provides a consumer-focused, namespace-scoped resource that can be referenced via backendRefs
in routes, combining the advantages of the previous attachment points:
- Session persistence is configured on the backend, where most prior art puts it
- Referenced directly from the route via
backendRef, making the configuration discoverable without a separate policy resource - Different consumers can configure different session persistence for the same underlying endpoints by creating separate Backend resources
Having a single attachment point for session persistence reduces the conflict surface and eliminates the need for complex precedence rules between multiple configuration sources.
See #4462 for background on the attachment model discussion.
Deprecated: Route-Inline and BackendTrafficPolicy Session Persistence
This GEP previously defined two attachment points for session persistence: route-inline (sessionPersistence on
HTTPRouteRule and GRPCRouteRule) and BackendTrafficPolicy. Both are deprecated in favor of the Backend resource
and will be removed once Backend session persistence reaches Standard. Implementations that have already shipped
route-inline support (NGINX Gateway Fabric, Envoy Gateway, kgateway) may need to support both during the transition.
How these implementations handle the intersection is left to the implementation.
Backend Protocol Relationship
The Backend object’s spec.protocol defines the protocol between the Gateway and the backend endpoints. If this
protocol does not support HTTP cookies or headers, the persistence cookie or header cannot be forwarded to the backend.
This is acceptable because this GEP does not guarantee that either reaches the backend. Session persistence is applied
by the Gateway when selecting an endpoint and does not depend on the persistence cookie or header being understood by
the backend. See Session Initiation Guidelines for forwarding behavior.
Traffic Splitting
In scenarios involving traffic splitting, session persistence operates after backend selection. Traffic splitting selects which backend receives the request based on configured weights, and session persistence then pins the client to a specific endpoint within the selected backend. Session persistence MUST NOT override which backend is selected during traffic splitting, and it MUST NOT impact the process of route matching.
When using multiple backends in traffic splitting, all backends should have session persistence enabled for consistent behavior. In scenarios where one backend has persistence enabled while the other does not, each backend operates independently: the backend with session persistence will pin returning clients to a specific endpoint, while the backend without session persistence will distribute requests normally.
See Edge Case Behavior for more use cases on traffic splitting.
Cookie Attributes
While the API is intended to be generic, as described in API Granularity, a majority of implementations will employ session persistence through cookies. Therefore, let’s explore the possibilities of cookie configuration for these APIs.
A cookie is composed of various attributes, each represented as key=value pairs. While some attributes may have optional values, the cookie name attribute is the only mandatory one, and the rest are considered optional.
The cookie attributes defined by RFC6265 are:
- Name=value
- Expires=date
- Max-Age=number
- Domain=domain
- Path=path-value
- Secure
- HttpOnly
Other cookie attributes not defined by RFC6265, but are captured in draft RFCs and could be considered de facto standards due to wide acceptance are:
- SameSite=[Strict|Lax|None]
- Partitioned
Unless a sessionPersistence API field can be satisfied through manipulating a cookie attribute, the attributes
of the cookies are considered as opaque values in this spec and are to be determined by the individual implementations.
Let’s discuss some of these cookie attributes in more detail.
Name
Cookie Name
The cookie name is configured via the cookie.name field (Extended support). Not all implementations support
configuring the cookie name. For example, GKE’s stateful generated cookie
(GSSA) uses a fixed cookie
name that cannot be changed by the user. If cookie.name is not specified, a unique cookie name SHOULD be generated.
The use case for configuring the cookie name is that certain users might need to align it with an existing cookie name,
such as Java’s JSESSIONID. Refer to Session Initiation Guidelines for details on
how this GEP supports existing sessions.
Header Name
The header name is configured via the header.name field (Core support). Unlike cookies, headers are not automatically
managed by browsers, so clients must know the header name to include it in subsequent requests. For this reason,
header.name is a required field.
Expires / Max-Age
The Expires and Max-Age cookie attributes are important in distinguishing between session cookies and permanent
cookies. Session cookies do
not include either of these attributes, while permanent cookies will contain one of them. Session cookies can still
have an expiration or timeout, but it will be accomplished through alternative mechanisms, such as the proxy tracking
the cookie’s lifetime via its value.
The LifetimeType API field specifies whether a cookie should be a session or permanent cookie. Additionally, the lifetime
or timeout for both session and permanent cookies is represented by AbsoluteTimeout. In the case of
LifetimeType being Permanent, AbsoluteTimeout MUST configure the Expires or Max-Age cookie attributes.
Conversely, if LifetimeType is Session, AbsoluteTimeout MUST regulate the cookie’s lifespan through a
different mechanism, as mentioned above. If LifetimeType is set to Permanent, then AbsoluteTimeout MUST
also be set as well. This requirement is necessary because an expiration value is required to set Expires or Max-Age.
LifetimeType of Session is core support level and the default, while LifetimeType of Permanent is extended.
See issue #2747 for more context regarding distinguishing between permanent and session cookies.
Path
The cookie’s Path attribute defines the URL path that must exist in order for the client to send the cookie header.
Implementations MUST default the cookie Path to / when no explicit path is configured.
This GEP previously specified that the cookie path should be computed from the matched route path. That approach was removed because no prior art computes a cookie path from route matches, it is underspecified for regex matches, and it breaks with edge proxy path rewrites (see #4713).
Defaulting to / was chosen because it is the most portable behavior — the majority of implementations default to /
and every dataplane can deliver it.
While Path=/ is broader than a route-specific path, the security impact is limited. The cookie value is an opaque
backend identifier (e.g., an encoded IP address), not a session token containing sensitive data. A cookie with Path=/
being sent to other routes on the same domain does not expose sensitive information — it only causes the receiving
route’s proxy to attempt (and fail) to use the persistence token, falling back to normal load balancing.
Users who need a cookie scoped to a specific path can configure it explicitly via the cookie.path field (Extended
support). When set, cookie.path overrides the default /.
Secure, HttpOnly, SameSite
The Secure, HttpOnly, and SameSite cookie attributes are security-related. The API implementers SHOULD follow the
security-by-default principle and configure these attributes accordingly. This means enabling Secure and HttpOnly,
and setting SameSite to Strict. However, in certain implementation use cases such as service mesh, secure values
might not function as expected. In such cases, it’s acceptable to make appropriate adjustments.
Session Persistence API with GAMMA
The object of the GAMMA (Gateway API for Mesh Management and Administration) initiative is to provide support for service mesh and mesh-adjacent use-cases with Gateway API. GAMMA is focused on defining how Gateway API could also be used for inter-service or east/west traffic within the same cluster.
Given that service meshes commonly have session persistence requirements, this API design should take into consideration session persistence needs in GAMMA and service mesh scenarios.
Session Initiation Guidelines
As illustrated in the examples provided in Session Persistence Initiation, implementations must consider how to manage sessions initiated by other components. As mentioned in Backend Initiated Session Example, this GEP does not support configuring backend-initiated persistent sessions. We leave the decision of handling existing sessions with each specific implementation. In the case of cookie-based session persistence, an implementation MAY either rewrite the cookie or insert an additional cookie, or to do nothing (resulting in the lack of a persistent session). In general, inserting an additional cookie is a generally safe option, but it’s important for implementations to exercise their own discretion. However, regardless of the implementation’s design choice, the implementation MUST be able to handle multiple cookies.
For gateway-initiated cookie-based or header-based session persistence, implementations SHOULD forward the Gateway-generated persistence cookie or header to the selected backend when supported by the backend protocol. Backends should not rely on receiving a Gateway-generated persistence cookie or header. This does not affect the normal forwarding of application cookies or headers that are unrelated to Gateway-managed session persistence.
Session Persistence Failure Behavior
In a situation where session persistence is configured and the backend becomes unhealthy or is draining, this GEP doesn’t specify a prescribed fallback behavior mechanism or HTTP status code. Implementations MAY exhibit different behaviors depending on whether active health checking is enabled. Data planes MAY continue routing existing persistent sessions to backends that are draining but still serving, and SHOULD fall back to available backends when the original backend is no longer serving, reestablishing session persistence with a new backend.
Edge Case Behavior
Implementing session persistence is complex and involves many edge cases. In this section, we will outline API configuration scenarios (use cases) and how implementations should handle them.
Session Naming Collision
Consider the situation in which two different Backends have cookie-based session persistence configured with the same cookie name in a traffic split:
kind: HTTPRoute
metadata:
name: split-route
spec:
rules:
- backendRefs:
- kind: Backend
name: backend-v1
weight: 50
- kind: Backend
name: backend-v2
weight: 50
---
kind: Backend
metadata:
name: backend-v1
spec:
type: EndpointSelector
endpointSelector:
sessionPersistence:
type: Cookie
cookie:
name: split-route-cookie
---
kind: Backend
metadata:
name: backend-v2
spec:
type: EndpointSelector
endpointSelector:
sessionPersistence:
type: Cookie
cookie:
name: split-route-cookie
This is an invalid configuration as two separate sessions cannot have the same cookie name. Implementations SHOULD address this scenario in manner they deem appropriate. Implementations MAY choose to reject the configuration, or they MAY non-deterministically allow one cookie to work (e.g. whichever cookie is configured first).
Traffic Splitting with Session Persistence
Consider the scenario where a route is traffic splitting between two Backends, both with session persistence:
kind: HTTPRoute
metadata:
name: split-route
spec:
rules:
- backendRefs:
- kind: Backend
name: backend-v1
weight: 50
- kind: Backend
name: backend-v2
weight: 50
---
kind: Backend
metadata:
name: backend-v1
spec:
type: EndpointSelector
endpointSelector:
sessionPersistence:
type: Cookie
cookie:
name: session-v1
---
kind: Backend
metadata:
name: backend-v2
spec:
type: EndpointSelector
endpointSelector:
sessionPersistence:
type: Cookie
cookie:
name: session-v2
Traffic splitting selects a backend based on the weight configuration. Once a backend is selected and session
persistence is configured on that backend, the client is pinned to a specific endpoint within that backend. On
subsequent requests, traffic splitting selects a backend again based on weights. If the same backend is selected,
the session cookie routes the client to the same endpoint. If a different backend is selected, a new session is
established within that backend.
In summary, session persistence applies after the routing decision, not before it.
A Service’s Selector is Dynamically Updated
In Kubernetes, it’s possible to modify the selector of a service after the gateway has established persistent sessions with it.
kind: Service
metadata:
name: my-service
spec:
selector:
app.kubernetes.io/name: MyApp # Service selector can change
The expected behavior is that the gateway SHOULD retain existing persistent sessions, even if the pod is no longer selected, and establish new persistent sessions after a selector update. This use case is uncommon and may not be supported by some implementations due to their current designs.
Conformance Details
Note: Conformance tests are still WIP, and additional test coverage will be added in future PRs.
Feature Names
- HTTPRouteSessionPersistence - Core feature for session persistence support (Extended).
- HTTPRouteSessionPersistenceCookieLifetimeTypePermanent - Permanent Lifetime for cookie-based session persistence (Extended).
HTTPRoute Conformance tests
| Description | Outcome | Features |
|---|---|---|
| Simple Cookie Session Persistence: An HTTPRoute with sessionPersistence configured with type: Cookie (default) on a single backend in gateway-conformance-infra namespace. | HTTPRoute MUST have Accepted=True in parent status. First request MUST receive a Set-Cookie header in response. Subsequent requests with the cookie MUST route to the same backend pod. Verify by checking pod identity across N requests (N>=50). | HTTPRouteSessionPersistence |
| Session Cookie Lifetime (Default): HTTPRoute with sessionPersistence and cookie.lifetimeType: Session (default). | HTTPRoute MUST have Accepted=True in parent status. Cookie MUST NOT contain Expires or Max-Age. |
HTTPRouteSessionPersistence |
| Multiple Weighted Backends - Session Persistence Does Not Override Traffic Splitting: HTTPRoute with sessionPersistence and multiple backendRefs with weights (e.g., 70/30) on a path. | HTTPRoute MUST have Accepted=True in parent status. Requests MUST respect weight distribution (~70/30 within statistical tolerance). Session persistence MUST NOT override backend selection. | HTTPRouteSessionPersistence |
| Session Persistence with cookie.lifetimeType: Permanent and absoluteTimeout: 5min. | HTTPRoute MUST have Accepted=True in parent status. Response Set-Cookie header MUST contain Expires or Max-Age attribute. The expiry value MUST correspond to the configured absoluteTimeout duration. Session persistence MUST function correctly until cookie expires. |
HTTPRouteSessionPersistence, HTTPRouteSessionPersistenceCookieLifetimeTypePermanent |
GRPCRoute Feature Names
- GRPCRouteSessionPersistence - Core feature for session persistence support on GRPCRoute (Extended).
- GRPCRouteSessionPersistenceHeader - Header-based session persistence on GRPCRoute (Extended).
GRPCRoute Conformance tests
| Description | Outcome | Features |
|---|---|---|
| Simple Cookie-based Session Persistence (GRPCRoute): A GRPCRoute with sessionPersistence configured with type: Cookie (default) on a single backend. The test client MUST explicitly extract the Set-Cookie response header and include it as a Cookie header in subsequent requests. | GRPCRoute MUST have Accepted=True in parent status. First request MUST receive a Set-Cookie response header. Subsequent requests with the cookie header MUST route to the same backend pod. Verify by checking pod identity across N requests (N>=50). | GRPCRouteSessionPersistence |
| Header-based Session Persistence (GRPCRoute): A GRPCRoute with sessionPersistence configured with type: Header on a single backend. | GRPCRoute MUST have Accepted=True in parent status. First request MUST receive a session identity header in the response metadata. Subsequent requests with that header included in request metadata MUST route to the same backend pod. Verify by checking pod identity across N requests (N>=50). | GRPCRouteSessionPersistence, GRPCRouteSessionPersistenceHeader |
| Session Cookie Lifetime (Default) (GRPCRoute): GRPCRoute with sessionPersistence and cookie.lifetimeType: Session (default). | GRPCRoute MUST have Accepted=True in parent status. Cookie MUST NOT contain Expires or Max-Age attributes. |
GRPCRouteSessionPersistence |
| Multiple Weighted Backends - Session Persistence Does Not Override Traffic Splitting (GRPCRoute): GRPCRoute with sessionPersistence and multiple backendRefs with weights (e.g., 70/30). | GRPCRoute MUST have Accepted=True in parent status. Requests MUST respect weight distribution (~70/30 within statistical tolerance). Session persistence MUST NOT override backend selection. | GRPCRouteSessionPersistence |
Alternatives
IdleTimeout
The idleTimeout field was originally included in the SessionPersistence API but
was removed because no Gateway API implementation has implemented it and no
implementation has requested it. HTTP cookies have no native idle timeout
mechanism — Max-Age/Expires are absolute, not idle-based. Implementing idle
timeout requires server-side session state tracking (e.g., HAProxy’s maxidle or
NGINX’s sticky learn with timeout=), which is additional complexity beyond
what the current cookie-based model requires. Some dataplanes have no mechanism
for idle timeout at all, making it unimplementable for those controllers. This
field could be re-introduced in a future GEP if implementations demonstrate
viable approaches and express interest.
SessionPersistence API Alternative
Taking a different approach, this GEP could design a more specific policy for configuring session persistence. Rather
than containing all load balancing configuration within a single metaresource, we could opt for a more specific design
with a metaresource called SessionPersistencePolicy, specifically to handle session persistence configuration.
The advantage of SessionPersistencePolicy is that it is more specific, which may enable a smoother transition to
attaching to routes in the future.
// SessionPersistencePolicy provides a way to define session persistence rules
// for a service or route.
type SessionPersistencePolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
// Spec defines the desired state of SessionPersistencePolicy.
Spec SessionPersistencePolicySpec `json:"spec"`
// Status defines the current state of SessionPersistencePolicy.
Status PolicyStatus `json:"status,omitempty"`
}
// SessionPersistencePolicySpec defines the desired state of
// SessionPersistencePolicy.
// Note: there is no Override or Default policy configuration.
type SessionPersistencePolicySpec struct {
// TargetRef identifies an API object to apply policy to.
TargetRef gatewayv1a2.PolicyTargetReference `json:"targetRef"`
// SessionName defines the name of the persistent session token
// (e.g. a cookie name).
//
// +optional
// +kubebuilder:validation:MaxLength=4096
SessionName String `json:"sessionName,omitempty"`
}
HTTPCookie API Alternative
Alternatively, the API for session persistence could be tightly coupled to cookies rather than being generic as
described in API Granularity. The advantage here is the API’s ability to offer greater control
through specific cookie attributes and configuration, catering to the needs of advanced users. However, there could be
challenges with implementations adhering to an API that is closely tied to cookies. This alternative could apply to the
current BackendTrafficPolicy design or the SessionPersistencePolicy
alternative.
The cookie attributes can be defined either as a loosely-typed list of attributes or as strongly-typed attribute fields. A loosely-typed list approach offers a more flexible specification, particularly when new attributes need to be introduced. However, loosely-typed lists may not be as user-friendly due to the lack of validation.
// HttpCookie defines a cookie to achieve session persistence.
type HttpCookie struct {
// Name defines the cookie's name.
//
// +kubebuilder:validation:MaxLength=4096
Name String `json:"name,omitempty"`
// CookieAttributes defines the cookie's attributes.
//
// +optional
CookieAttributes []CookieAttribute `json:cookieAttributes`
}
// CookieAttribute defines the cookie's attributes.
type CookieAttribute map[string][]string
)
Strongly-typed attribute fields provide a more user-friendly experience by offering stronger validation for each of the
fields. A strongly-type cookie API could be a mix of individual fields and listed attributes. More specifically, we
could separate the key attributes with no value into a list. This approach is taken by Haproxy Ingress
with their session-cookie-keywords field. This provides flexibility for simple boolean-typed attributes, while
validating attributes that have values. However, this approach may be confusing to users as uses two different API
patterns for cookie attributes.
// HttpCookie defines a cookie to achieve session persistence.
type HttpCookie struct {
// Name defines the cookie's name.
//
// +kubebuilder:validation:MaxLength=4096
Name String `json:"name,omitempty"`
// SameSite defines the cookie's SameSite attribute.
//
// +optional
// +kubebuilder:validation:Enum=Strict;Lax;None
SameSite SameSiteType `json:"sameSite,omitempty"`
// Domain defines the cookie's Domain attribute.
//
// +optional
// +kubebuilder:validation:MaxLength=4096
Domain String `json:"domain,omitempty"`
// CookieKeywords defines the cookie's attributes that have no value.
//
// +optional
CookieKeywords []CookieKeyword `json:cookieKeywords`
}
// CookieKeyword defines the cookie's attributes that have no value.
type CookieKeyword string
const (
// CookieKeywordsHttpOnly specifies the HttpOnly cookie attribute.
CookieKeywordsHttpOnly HttpOnlyMode = "HttpOnly"
// CookieKeywordsSecure specifies the Secure cookie attribute.
CookieKeywordsSecure HttpOnlyMode = "Secure"
)
Alternate Naming
This GEP describes session persistence and session affinity as the idea of strong and weak connection persistence respectively. Other technologies use different names or define persistence and affinity differently:
- Envoy defines stateful sessions as what we’ve defined as session persistence
- Google Cloud Run defines session affinity as what we’ve defined as session persistence
- Nginx defines session persistence as what we’ve defined as both session persistence and affinity
- Traefik defines sticky sessions as what we’ve defined as session persistence
- Apache httpd defines sticky sessions or stickiness as what we’ve defined as session persistence
- Kubernetes defines session affinity based on client IP hashing (same as our session affinity)
- Microsoft Application Gateway defines session affinity, session persistence, and sticky sessions as what we’ve defined as session persistence
Though session persistence is a ubiquitous name, session affinity is more inconsistently used. An alternate decision could be made to use a different name for session affinity based on the prevalence of other naming conventions.
References
- LBPolicy (proposed extension for session persistence API)
- gRPC Stateful Session Affinity Proposal (info on session draining and session persistence in gRPC)
- Kube-Proxy Session Affinity
- GEP-713: Metaresources and PolicyAttachment
- RFC6265
- Policy Attachment
- Envoy Session Persistence Design Doc
- Envoy Session Persistence Issue