跳转到内容

会话保持

会话保持(也称为粘性会话)确保同一用户会话的所有请求被路由到同一个后端实例。当后端在本地存储状态时,这是必需的,例如内存中的购物车,或与特定进程绑定的 WebSocket 连接。

用户首次发起请求时,网关使用标准的负载均衡算法将请求路由到一个后端实例。如果该路由配置了会话保持,网关会在响应中附带一个会话标识符。在后续来自同一用户的请求中,网关读取该标识符,并将请求路由到处理原始会话的同一后端实例。

会话标识符是一个经过签名的令牌,编码了后端名称、端点地址以及可选的过期时间。签名机制可防止篡改:如果客户端修改了标识符,签名校验将失败,网关会将其视为新会话,路由到一个新的后端实例。

sessionTypeCookie(默认值)时,网关在首次响应中设置一个 Cookie。该 Cookie 包含经过签名的会话令牌。Cookie 名称通过 sessionName 字段配置。如果传入请求中已存在该 Cookie,网关会校验其签名并将请求路由到记录的后端。

Cookie 携带一个可选的 absoluteTimeout,用于限制会话的生命周期。当超时到达时,会话被丢弃,下一个请求将路由到一个新的后端实例。如果未设置绝对超时,会话将无限期持续。

Cookie 的生命周期由 cookieConfig.lifetimeType 控制:

生命周期类型Cookie 过期行为说明
Session浏览器会话(无 Expires/Max-Age关闭浏览器时删除 Cookie。这是默认值。
PermanentabsoluteTimeout 设置Cookie 包含与配置的超时匹配的 Max-Age 属性。需要设置 absoluteTimeout

sessionTypeHeader 时,网关不会设置 Cookie。相反,它会在响应中设置一个请求头,其名称由 sessionName 配置。客户端应在后续请求中回显该请求头的值。此模式适用于不处理 Cookie 的 API 客户端,或 Cookie 不常用的 gRPC 服务。

基于请求头的会话与基于 Cookie 的会话具有相同的签名、超时和路由保证。唯一的区别在于传输机制。

会话保持通过 BackendLBPolicy CRD 的 session_persistence 字段进行配置:

apiVersion: gateway.networking.k8s.io/v1alpha2
kind: BackendLBPolicy
metadata:
name: cart-sticky
namespace: nantian-demo
spec:
targetRefs:
- group: ""
kind: Service
name: shopping-cart-service
session_persistence:
sessionName: ROUTEID
sessionType: Cookie
absoluteTimeout: 3600s
idleTimeout: 600s
cookieConfig:
lifetimeType: Session

此策略以 shopping-cart-service 后端为目标。请求被路由到一个后端实例,网关设置一个名为 ROUTEID 的 Cookie,其中包含经过签名的会话令牌。会话 Cookie 在浏览器会话期间有效。3600 秒(1 小时)的绝对超时限制了最大会话生命周期,即使浏览器未关闭也是如此。如果用户空闲 600 秒(10 分钟),该会话将有资格被过期清理。

targetRefs 包含多个 Service 时,每个 Service 都将获得自己的会话保持配置。一个 BackendLBPolicy 可以对一个 Service 应用基于 Cookie 的会话保持,对另一个 Service 应用基于请求头的会话保持,只要它们各有独立的策略即可。

会话令牌使用共享密钥进行签名以确保完整性。网关支持三种配置密钥的方式:

方式配置适用场景
内联密钥数据面配置中的 shared_secret使用预共享密钥的小型部署
基于文件的密钥sharedSecretFile 指向文件路径需要密钥轮换的部署(文件每 250ms 重新读取一次)
自动生成无需配置仅限开发环境和单副本部署

对于多副本数据面部署,请在所有副本上配置一致的 shared_secretsharedSecretFile。如果每个副本自动生成不同的密钥,由副本 A 签名的会话令牌将在副本 B 上校验失败,导致流量在副本之间切换时用户丢失会话。

会话保持在以下场景中是合适的选择:

  • 后端将状态存储在本地内存中,而不是共享数据库或缓存中。
  • 你运行的是一个必须固定到一个实例上的 WebSocket 或长轮询服务。
  • 后端依赖未跨实例复制的文件系统本地数据。
  • 你正在逐步迁移服务,需要将用户固定到特定的后端版本。

会话保持是一种权衡。它会将同一会话的流量集中到单个后端,从而降低负载均衡的效果。如果某个后端实例宕机,固定到该实例的所有会话都会丢失,直到建立新的会话。仅对有粘性路由真正需求的后端使用此功能。

会话保持在数据面重启后无法保持。网关将会话到后端的映射存储在内存中。当数据面进程重启时,所有会话状态都会丢失。携带重启前有效会话令牌的传入请求会被视为未知,并路由到一个新的后端实例。用户将自动分配一个新会话,但绑定到旧后端实例的任何状态都会丢失。

会话令牌对客户端是不透明的。客户端不应尝试解析或修改 Cookie 值。任何修改都会使签名失效,导致网关创建一个新会话。

如果用户反馈意外退出登录或丢失会话状态,最常见的原因是缺少会话保持配置。与 nginx-ingress 可以通过注解启用粘性会话(nginx.ingress.kubernetes.io/affinity: cookie)不同,nantian-gw 需要显式创建 BackendLBPolicy

  1. 检查是否已有 BackendLBPolicy。列出命名空间中的策略:

    Terminal window
    kubectl get backendlbpolicies -n your-namespace

    如果没有看到以你的后端 Service 为目标的策略,则说明会话未处于粘性状态,每个请求可能会落在不同的后端 Pod 上。

  2. 确认策略以正确的 Service 为目标targetRefs.name 必须与你的 Service 名称完全匹配:

    Terminal window
    kubectl describe backendlbpolicy <name> -n your-namespace
  3. 检查多副本部署。如果你运行了多个数据面副本,请配置共享密钥:

    # 数据面配置
    session_persistence:
    shared_secret: "your-32-byte-secret"

    没有此项配置,由副本 A 签名的会话 Cookie 会被副本 B 拒绝。

  4. 确认 Cookie 是否被正确设置。检查响应头:

    Terminal window
    curl -v https://your-app.example.com 2>&1 | grep -i set-cookie

    你应该看到一个 Set-Cookie 响应头,其名称为你配置的 sessionName

  5. 检查 absoluteTimeout 和 idleTimeout。如果其中任何一项设置得太短,会话会过早过期。建议从 absoluteTimeout: 1hidleTimeout: 15m 开始,然后根据应用需求进行调整。

nginx-ingress 注解nantian-gw 等效配置
nginx.ingress.kubernetes.io/affinity: cookie创建包含 session_persistence.type: CookieBackendLBPolicy
nginx.ingress.kubernetes.io/session-cookie-namesession_persistence.sessionName
nginx.ingress.kubernetes.io/session-cookie-expiressession_persistence.absoluteTimeout
nginx.ingress.kubernetes.io/session-cookie-max-agesession_persistence.cookieConfig.lifetimeType: Permanent
nginx.ingress.kubernetes.io/session-cookie-path不适用(Cookie 路径默认为 /