Kubernetes 中实现会话保持(Session Affinity)
在 Kubernetes 中实现会话保持(Session Affinity),也称为粘性会话(Sticky Sessions),主要有以下几种方法,每种方法适用于不同的场景和层级。
核心方法概览
| 方法 | 实现层级 | 机制 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|---|
1. Service sessionAffinity |
L4 (传输层) | 基于客户端 IP 地址 | 配置简单,K8s 原生支持 | 不精确(同一 NAT 后的用户被视为同一客户端) | 对会话要求不高的简单应用 |
| 2. Ingress 注解/规则 | L7 (应用层) | 基于 Cookie | 精确、可靠、可配置 | 需要 Ingress 控制器支持(如 Nginx) | 生产环境最常用的方式 |
| 3. ServiceMesh (e.g., Istio) | L7 (应用层) | 基于 HTTP 头、Cookie 等 | 功能最强大,粒度最细 | 架构复杂,学习成本高 | 需要精细流量管理的大型微服务架构 |
方法详解
1. 使用 Service 的 sessionAffinity (IP-based)
这是 Kubernetes Service 自带的基础功能,工作在传输层(TCP/UDP)。
-
原理:将来自同一个客户端 IP 的所有请求都转发到同一个后端 Pod。
-
配置:在 Service 的 YAML 文件中设置
sessionAffinity: ClientIP。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
sessionAffinity: ClientIP # 默认为 None,这里设置为 ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 会话保持的持续时间(秒),默认是 10800(3小时)
-
优点:
-
配置非常简单,是 K8s 内置功能。
-
-
缺点:
-
不精确:在很多网络环境下(如公司 NAT、4G/5G 网络),多个真实用户会共享同一个公网 IP,导致他们的请求都被发往同一个 Pod,无法实现真正的用户级会话保持。
-
如果客户端 IP 发生变化(例如移动网络切换),会话就会中断。
-
2. 使用 Ingress 控制器 (Cookie-based)
这是生产环境中最推荐、最常用的方法,工作在应用层(HTTP/HTTPS)。它通过 Set-Cookie 来精确识别用户。
以最流行的 Nginx Ingress Controller 为例:
-
原理:Ingress 控制器在第一个响应中为客户端设置一个唯一的 Cookie,客户端后续请求会带上这个 Cookie,Ingress 根据 Cookie 的值将请求路由到对应的 Pod。
a) 使用注解(Annotations)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie" # 开启基于 Cookie 的会话保持
nginx.ingress.kubernetes.io/session-cookie-name: "route" # 自定义 Cookie 名称
nginx.ingress.kubernetes.io/session-cookie-expires: "172800" # Cookie 过期时间(秒)
nginx.ingress.kubernetes.io/session-cookie-max-age: "172800" # Cookie 最大年龄(秒)
spec:
ingressClassName: nginx
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
b) 使用 Ingress 规则中的 loadBalancer 配置 (更新、更规范的方式)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
spec:
ingressClassName: nginx
rules:
- host: my-app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-service
port:
number: 80
loadBalancer:
ingressClassName: nginx
# 核心配置在这里
affinity:
cookie:
name: SESSION_COOKIE # 自定义 Cookie 名称
maxAge: 86400 # 生存期
# 注意:不同版本的K8s和Ingress控制器,此处的API可能略有不同。
-
优点:
-
非常精确:基于每个浏览器会话,不受 NAT 影响。
-
可靠:即使 Pod 重启,只要 Service 能将请求发往替代的 Pod,应用层可以重建会话(需要应用支持会话外部化,见下文最佳实践)。
-
-
缺点:
-
需要特定的 Ingress 控制器支持(Nginx, Traefik, HAProxy 等都支持)。
-
3. 使用 Service Mesh (例如 Istio)
在更复杂的微服务架构中,Service Mesh 提供了最强大的流量控制能力。
-
原理:在 VirtualService 等 CRD 资源中配置基于 HTTP 头(如
x-user-id)或 Cookie 的负载均衡策略。
Istio 配置示例:
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: my-destination-rule
spec:
host: my-service
trafficPolicy:
loadBalancer:
consistentHash:
# 可以基于多种方式,这里使用 HTTP Header
httpHeaderName: "x-user-id" # 根据此 Header 的值进行哈希
# 也可以使用 httpCookie, useSourceIp 等
# httpCookie:
# name: user-cookie
# ttl: 0s
-
优点:
-
粒度最细:可以基于任何 HTTP 头、Cookie 甚至来源 IP 进行哈希。
-
功能强大:与其他流量管理功能(如金丝雀发布、超时、重试)无缝集成。
-
-
缺点:
-
复杂:需要部署和维护 Service Mesh(Istio、Linkerd等),学习和运维成本高。
-
最佳实践与重要考虑
-
会话数据外部化(无状态应用)
-
这是最重要的最佳实践! 不要将会话数据存储在单个 Pod 的内存中。
-
使用外部集中式存储来保存会话状态,例如 Redis、Memcached 或数据库。
-
好处:
-
即使 Pod 被销毁或重新调度,用户的会话也不会丢失。
-
可以实现真正的水平扩展,任何 Pod 都可以处理任何用户的请求,会话保持只是为了优化(例如缓存局部性),而非必需。
-
-
-
选择正确的方法
-
简单测试/内部应用:可以尝试 Service 的
sessionAffinity。 -
绝大多数生产 Web 应用:使用 Ingress 控制器(Cookie-based) 并配合 外部会话存储(如 Redis)。
-
大型、复杂的微服务架构:考虑使用 Service Mesh。
-
-
注意 Pod 终止
-
当 Pod 被终止时,K8s 会先将其从 Service 的 Endpoint 列表中移除,然后发送 SIGTERM 信号。确保你的应用能优雅处理终止,并在宽限期内完成正在处理的请求。
-
总结
| 场景 | 推荐方法 |
|---|---|
| 快速测试/对会话要求不高 | Service sessionAffinity: ClientIP |
| 标准的 Web 应用(生产环境) | Ingress (Nginx) Cookie Affinity + 外部 Redis 存储会话 |
| 需要极细粒度控制的微服务 | Service Mesh (如 Istio) |
结合 “Ingress 基于 Cookie 的会话保持” 和 “会话数据外部化”,是在 Kubernetes 中实现高可用、可扩展且有状态 Web 应用的黄金标准。
浙公网安备 33010602011771号