1. 概述
会话保持(Sticky Session),也称为会话亲和(Session Affinity),用于在客户端与某个后端 Pod 之间建立一致的连接。这种路由机制可以确保用户的所有请求都被转发到同一个 Pod,从而保留会话数据或缓存内容。对于那些将用户特定信息保存在内存中的应用(如会话状态或临时数据)来说,这一机制尤为重要。如果请求被重定向到其他 Pod,可能会导致会话丢失和不可预知的行为。
在本文中,我们将探讨 Kubernetes 中会话保持的工作原理、它对有状态应用的重要性,以及几种常见的实现方式。
2. 理解会话保持机制
会话保持确保只要目标 Pod 可用,客户端的请求会始终路由到同一个 Pod。这种机制对于将登录 Token、购物车内容或 CSRF Token 等会话数据存储在内存中的应用至关重要,可以避免因请求被转发到不同 Pod 而导致的数据丢失。
默认情况下,Kubernetes Service 使用轮询方式的负载均衡,将请求平均分配给所有可用的 Pod。这种方式对于无状态应用非常高效,但对于有状态应用可能会破坏会话连续性。没有会话保持时,用户可能会遇到会话中断或行为不一致的问题。
会话保持通过将客户端流量绑定到一个 Pod 上来解决这个问题。一旦建立首次连接,后续来自同一客户端的请求都会继续转发到该 Pod,只要它仍在运行。这种路由行为提升了应用一致性,并减少了对会话外部存储的依赖。
接下来我们将介绍如何通过 Service、Ingress 控制器和 StatefulSet 实现会话保持。
3. 在 Kubernetes 中实现会话保持
会话保持的实现方式取决于应用的暴露方式。Kubernetes 的 Service 支持基础的会话亲和,Ingress 控制器可以通过注解配置会话保持功能,而 StatefulSet 则用于维护 Pod 的身份和存储。
3.1 使用 Service 的会话亲和
Kubernetes 的 Service 支持通过 sessionAffinity
字段实现基础的会话保持。该功能根据客户端的 IP 地址进行流量路由:
apiVersion: v1
kind: Service
metadata:
name: ops-app-service
spec:
selector:
app: ops-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
sessionAffinity: ClientIP
将 sessionAffinity
设置为 ClientIP
,可以确保来自同一客户端 IP 的所有请求始终被路由到同一个 Pod。只要 Pod 保持健康且客户端 IP 不变,连接就会保持“粘性”。
不过,这种方法并不完全可靠。在客户端 IP 经常变化的环境中(如移动网络或使用 NAT 的场景),该方法可能无法稳定工作。
3.2 使用 Ingress 控制器的会话保持
Ingress 控制器负责将 HTTP/HTTPS 流量引入集群,可以通过注解配置会话保持功能。例如,NGINX Ingress 控制器支持基于 Cookie 的会话亲和,即使客户端 IP 发生变化,也能确保会话绑定到同一个后端 Pod:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ops-app-ingress
annotations:
nginx.ingress.kubernetes.io/affinity: "cookie"
nginx.ingress.kubernetes.io/session-cookie-name: "route"
nginx.ingress.kubernetes.io/session-cookie-expires: "3600"
nginx.ingress.kubernetes.io/session-cookie-max-age: "3600"
nginx.ingress.kubernetes.io/session-cookie-path: "/"
spec:
rules:
- host: opsapp.cloud.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ops-app-service
port:
number: 80
通过设置 nginx.ingress.kubernetes.io/affinity: "cookie"
,Ingress 控制器会在客户端响应中注入一个 Cookie。后续请求将根据该 Cookie 被路由到同一个 Pod,即使客户端 IP 发生变化也能保持会话绑定。
3.3 使用 StatefulSet 实现会话持久化
某些应用不仅需要负载均衡,还需要稳定的网络身份和持久化的存储。这时 StatefulSet 就显得尤为重要。它特别适用于管理内存中会话数据或直接写入本地存储的应用。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: ops-app
spec:
serviceName: "ops-app-service"
replicas: 3
selector:
matchLabels:
app: ops-app
template:
metadata:
labels:
app: ops-app
spec:
containers:
- name: ops-app-container
image: ops-app-image
ports:
- containerPort: 8080
volumeMounts:
- name: session-storage
mountPath: /data
volumeClaimTemplates:
- metadata:
name: session-storage
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 1Gi
通过使用 StatefulSet,每个 Pod 都会获得一个固定的名称(如 ops-app-0
、ops-app-1
)和专属的持久化卷。即使 Pod 被重启或重新调度,也能保留会话数据。这种方式使得应用可以在不依赖外部存储的情况下保持会话连续性。
4. 会话保持方法对比
前面我们介绍了几种会话保持的实现方式。但在实际应用中,选择哪种方式取决于网络流量模式、应用行为和运维需求:
方法 | 优点 | 缺点 |
---|---|---|
sessionAffinity: ClientIP |
配置简单;适用于 IP 稳定的内部流量 | 在 NAT 或代理环境下失效;对移动和动态 IP 支持差 |
Ingress 控制器会话保持 | 基于 Cookie;对 IP 变化有弹性;与 Web 客户端集成良好 | 依赖 Ingress 配置;需要管理注解 |
StatefulSet | 保留 Pod 身份和存储;适合会话持久化 | 资源消耗大,管理复杂,扩展性差 |
虽然 ClientIP
的配置最为简单,但在云原生环境中 IP 经常变动,这种方法容易失效。通过 Ingress 实现的基于 Cookie 的会话亲和更能应对 IP 变动,更适合面向浏览器的应用。对于需要内存中会话持久化的应用(如数据库或缓存密集型服务),StatefulSet 提供了更强的一致性,但也会带来更高的运维复杂度。
5. 性能与可扩展性考量
会话保持虽然能提升有状态应用的用户体验,但也存在一些性能和可扩展性方面的权衡。需要注意以下几点:
✅ 监控流量分布是否均衡:会话保持可能导致某些 Pod 成为流量热点,尤其是在会话持续时间不一致的情况下。应监控 Pod 使用率,并配置自动扩缩容来应对负载不均衡。
✅ 利用指标指导路由调整:使用 Prometheus 和 Grafana 等工具可以帮助识别因会话绑定导致的流量不均。负载均衡器的配置可能需要微调以实现更好的流量分布。
✅ 考虑会话数据外迁:当可扩展性是关键目标时,将会话状态迁移到 Redis、Memcached 等外部存储中,可以减少对会话保持的依赖,实现更灵活的 Pod 扩展。
6. 总结
本文介绍了 Kubernetes 中会话保持的工作原理,以及通过 Service、Ingress 控制器和 StatefulSet 实现会话保持的几种方式。每种方法都有其适用场景,具体选择应根据应用的结构和暴露方式进行判断。合理使用这些策略,可以显著提升会话一致性、系统稳定性以及整体用户体验。