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-0ops-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 实现会话保持的几种方式。每种方法都有其适用场景,具体选择应根据应用的结构和暴露方式进行判断。合理使用这些策略,可以显著提升会话一致性、系统稳定性以及整体用户体验。


原始标题:Sticky Session on Kubernetes Cluster