K8s部署Gateway API扩至2个Pod后出现令牌过期错误求助
这种情况我之前在多副本网关的生产环境里碰到过好几次——明明Hazelcast集群状态显示正常,但扩副本后就出现随机的"Expired token"错误,而且还是刚创建的会话。核心问题往往不是Hazelcast集群本身,而是会话同步、时钟一致性或者令牌验证环节的细节没处理到位,下面给你拆解可能的原因和排查步骤:
1. 最常见的元凶:多Pod时钟不一致
JWT令牌的过期判断完全依赖服务器本地时钟,如果两个网关Pod的时间差超过了Keycloak颁发的access token有效期(比如你的token是5分钟有效期,但Pod之间差了6分钟),就会出现"这个副本认为token刚签发,那个副本却觉得它已经过期"的情况。
排查方法:
直接登录两个网关Pod检查时间:
kubectl exec <gateway-pod-1> -- date kubectl exec <gateway-pod-2> -- date
要求两个Pod的时间差必须控制在1秒以内。如果不一致,检查K8s集群的节点时钟同步服务(比如chrony、ntpd)是否正常运行,或者Pod是否配置了正确的时区/同步策略。
2. Spring Session Hazelcast的会话同步延迟
Hazelcast集群状态正常不代表会话数据已经实时同步到所有节点。比如用户登录后,会话数据先写入第一个Pod的Hazelcast实例,第二个Pod的实例可能因为同步策略(比如最终一致性)还没拿到数据,此时请求打到第二个Pod,就会因为找不到有效会话误判为令牌过期。
排查&修复方向:
- 检查Spring Session的配置:确保
@EnableHazelcastHttpSession的maxInactiveInterval和Keycloak的access token TTL匹配(至少大于token有效期),避免会话提前过期。 - 调整Hazelcast的同步策略:如果用的是分布式Map存储会话,设置
replication-factor: 2(对应你的2个副本),确保会话数据复制到所有节点;如果开启了Near Cache,检查缓存过期时间是否合理,避免缓存未更新导致的旧数据。 - 查看Spring Boot日志:搜索
HazelcastSessionRepository相关的日志,确认会话创建后是否有同步到其他节点的记录。
3. Keycloak令牌验证的时钟偏差配置缺失
即使Pod时钟接近一致,网络延迟或者Keycloak服务器的时钟偏差也可能导致令牌被误判为过期。默认情况下,Spring Security OAuth2的令牌验证是严格匹配时钟的,没有预留偏差空间。
修复方法:
在网关的配置文件(比如application.yml)中添加时钟偏差允许值:
spring: security: oauth2: resourceserver: jwt: clock-skew: 30s # 允许30秒的时钟偏差
这个配置会让令牌验证时忽略30秒以内的时钟差,避免因为微小的时间不同步导致误判。
4. 网关负载均衡的会话亲和问题
如果网关的负载均衡策略是无粘性的轮询,而会话同步又存在延迟,就会出现请求随机打到未同步会话的副本上,触发过期错误。不过因为你用了Hazelcast做会话共享,理论上不需要粘性会话,但如果同步延迟确实存在,可以临时开启粘性会话作为临时解决方案(但长期还是要解决同步问题)。
排查方法:
检查Gateway API的路由配置,是否有会话亲和的设置;或者查看Ingress Controller的负载均衡策略,确认是否开启了基于Cookie的会话亲和。
快速验证步骤
- 先确认所有Pod时钟一致,这是最容易排查的点;
- 开启网关的debug日志,查看令牌解析的详细过程,是否真的是
exp时间过期,还是其他原因导致的错误被封装成了"Expired token"; - 在Hazelcast中直接查询会话数据:用
hz-cli工具连接到任意一个Pod的Hazelcast实例,查看会话Map中是否存在对应的令牌数据,确认数据是否同步到了所有节点。
内容的提问来源于stack exchange,提问作者Huỳnh Đăng Khoa

