如何确保与特定GCP Cloud Run实例保持持久连接
- 部署环境:GCP Cloud Run
- 技术栈:Flask + Flask-Login + Dash,已实现用户登录、可视化仪表盘、页面评论功能
- 现有运行状态:实例启动快、访问延迟低,自研BigQuery接口运行稳定,用户交互触发的Pub/Sub消息均可正常执行
- 故障现象:用户完成登录后,跳转其他受鉴权保护的页面时随机出现401错误;已尝试将前端容器最大并发请求数设置为1,问题仍偶发,用户需要反复重新登录,体验很差
- 初步推测:跳转请求触发Cloud Run自动扩容,请求被路由到新启动的实例导致鉴权失败,期望找到强制用户请求绑定固定容器实例的方案
Cloud Run 是无状态Serverless服务,本身没有提供100%可靠的请求-实例强绑定能力。你遇到的401问题核心不是请求被路由到新实例本身,而是Flask-Login默认使用进程本地内存存储会话数据,新启动的实例上没有对应用户的登录会话记录,校验请求携带的Cookie时找不到有效登录态,直接返回401。
之前设置单实例最大请求数为1的操作不生效是正常的:该参数仅限制单个实例同时处理的并发请求上限,只要请求并发达到扩容阈值,Cloud Run依然会启动新实例,负载均衡会按默认规则把请求分发到任意就绪实例,完全不感知用户会话状态。
方案1:替换为集中式共享会话存储(长期最优解,符合Serverless架构最佳实践)
不要强行做实例绑定,这类方案和Cloud Run的设计逻辑相悖,后续维护成本高、故障隐患多。直接把会话存储从本地内存替换为所有实例都能访问的共享存储,不管请求被路由到哪个实例,都能查到对应的登录会话,从根源解决问题:
- 存储选型参考:
- Memorystore(Redis):读写延迟最低,适合会话类高频访问场景,直接通过
flask-session组件对接即可,代码改动量极小 - Firestore:如果不想单独维护Redis实例,可直接用Firestore做会话持久化,运维成本更低,延迟完全满足登录鉴权场景需求
- Memorystore(Redis):读写延迟最低,适合会话类高频访问场景,直接通过
- 配置注意事项:
所有运行的容器实例必须配置完全一致的SECRET_KEY,否则会话签名校验不通过也会触发鉴权失败;Cloud Run默认提供HTTPS接入,可正常开启Cookie的Secure属性。
方案2:开启Cloud Run会话亲和性(仅临时过渡使用,不推荐长期依赖)
Cloud Run提供基于Cookie的会话亲和性能力,开启后同一个客户端的请求会尽量路由到之前处理过它请求的实例。注意这个能力是尽力而为,不是强保证:如果绑定的实例因扩缩容被回收、负载过高、健康检查失败、版本更新被替换,请求还是会被切到其他实例,依然有概率出现401。
开启命令参考:gcloud run services update [你的服务名] --session-affinity --region [你的部署区域]
方案3:固定实例数+会话亲和性(成本最高,仅极端场景临时用)
如果一定要走实例绑定的路线,可以配置服务最小实例数等于峰值承载所需的实例数,关闭实例自动缩容到0,配合会话亲和性尽量减少实例新建/销毁的概率。但该方案会大幅提升Cloud Run运行成本,且遇到服务版本更新、实例故障重启的场景,本地存储的会话依然会丢失,无法彻底解决问题。
内容的提问来源于stack exchange,提问作者randomdatascientist

