IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    nginx-ingress-controller会话保持的踩坑记录

    蔡晓建发表于 2021-03-27 00:00:00
    love 0

    诡异的会话丢失

    最近系统上线PAAS之后,IE11上出现一些页面打不开、窗口闪退、提示会话过期的问题,但客户反馈在360浏览器是正常的。

    为什么IE11有问题,360浏览器却正常呢?难道IE又有什么神奇的兼容性问题了?最好有请求抓包可以看看。

    很快,维护找到客户录制的请求抓包,找了几个反馈有问题的页面,除了cookie比较多之外,还发现有些请求报文比较特殊。

    	approute=xxxxxxxxxxxxx
    	JSESSIONID=yyyyyyyyyyyyyyyyyyyyyy
    	approute=uuuuuuuuuuuuu
    	JSESSIONID=vvvvvvvvvvvvvvvvvvvvvv
    

    请求头中的cookie如上,居然有同名的cookie,这个系统是比较老的基于Session的Web应用,除了用户认证,用户的业务信息是保存在Session上面的。 上PAAS之后,这些cookie是用来做会话保持用的。

    Set-Cookie: approute=2750fcbd2c7fa1fdd1189d95f63c3cd8; Expires=Sat, 27-Mar-21 01:37:04 GMT; Max-Age=172800; Path=/app-next; HttpOnly
    

    在响应中也通过Set-Cookie对cookie进行重设,这样就没办法维护会话绑定了。如果某个请求用到了上一个请求的Session信息,就会出现”会话丢失”的情况。 而且从请求上看,这种情况在后续的请求上反复出现,给人感觉就是设置了没效果。

    会话如何保持?

    环境部署

    正式的部署环境是有多层代理的,其中外层的nginx主要做静态资源托管,会话保持采用nginx-sticky-module-ng模块, 而ingress这一层是用的nginx-ingress-controller自带的Sticky sessions功能。

    对应的ingress配置如下,采用cookie来做会话保持。

    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
      name: approute-ingress
      annotations:
        kubernetes.io/ingress.class: "approute"
        nginx.ingress.kubernetes.io/affinity: "cookie"
        nginx.ingress.kubernetes.io/session-cookie-name: "approute"
        nginx.ingress.kubernetes.io/session-cookie-hash: "sha1"
        nginx.ingress.kubernetes.io/affinity-mode: persistent
        nginx.ingress.kubernetes.io/ssl-redirect: "false"
        nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
        nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
        nginx.ingress.kubernetes.io/proxy-body-size: 300m
    spec:
      rules:
      - http:
          paths:
          - backend:
              serviceName: approute-app
              servicePort: 8080
            path: /app
      ...更多ingress rule
    

    按当前的配置,就应该只有一个cookie来对,那这种同名cookie是怎么来的?

    同名Cookie从哪里来?

    以前没怎么听过同名Cookie,查了一下资料How to handle multiple cookies with the same name?,从中得到的初步结论是:

    • 怎么产生: 同名cookie是通过设置不同的子域名、子PATH等情况产生的。
    • 发送哪个: 如果不是很旧的浏览器,PATH越具体,优先级越高,排序越靠前。如果是其他属性不同,则没有约定。
    • 识别哪个: 服务端没有有明确的约定。

    当前我们主要是通过PATH来设置的,而目前的会话保持也是设置在应用的上下文的。 我初步猜想,是不是有个地方设置PATH为根路径”/”了?

    运气不错的是,根据会话丢失的判断,测试环境也拉起多个应用实例,发现可以重现问题。

    不过IE11不能直接查看cookie文件了,需要通过ie的导出功能(点击浏览器右上角的星星打开收藏夹-添加收藏夹下拉-选择导入和导出…)。

    a.b.c.d	TRUE	/app	    FALSE	1617197971	approute 880dffc13701aebc8315c4a881c82de4
    a.b.c.d	TRUE	/app-next	FALSE	1648561945	approute 43d24255920374268ed5abff09360163
    

    如上所示,可以发现两个cookie的值刚好对应/app和/app-next两个PATH。

    纳尼,不是应该按路径来划分的么?为什么调用/app-next会把/app的cookie也带过去了?

    IE对PATH的骚操作

    好吧,写段代码测试一下。设置了三个cookie如下:

    Set-Cookie: approute=6d7770821d2aa0bce5739b1a27fca753; Path=/app/; HttpOnly
    Set-Cookie: sna_cookie=ACB73F8C4EA88A6; Path=/
    Set-Cookie: JSESSIONID=0E817AD9FEF46123A2DAB6A9F99B38AC; Path=/app; HttpOnly
    

    对于Chrome来说,访问/app-next发送的cookie是

    JSESSIONID=0E817AD9FEF46123A2DAB6A9F99B38AC
    sna_cookie=ACB73F8C4EA88A6
    

    对于IE11来说,访问/app-next发送的cookie是

    sna_cookie=ACB73F8C4EA88A6
    

    从测试结果上看,IE直接采用prefix来判断Path,IE对cookie的Path处理方式是不同于chrome的,有点反常识,纯属骚操作。

    修复方案

    看来只能调整一下Path的设置方式,考虑到路径的优先级,尝试使用根路径”/”来设置,发现没有作用,nginx-ingress-controller的路由逻辑是去到不同的Path才来处理的, 由于找到对应的值,返回的时候又重新写回了一个,导致它还是不断的变。

    看来只能直接给规则文件中的所有的path结尾都添加一个”/”了。如下所示:

    spec:
      rules:
      - http:
          paths:
          - backend:
              serviceName: approute-app
              servicePort: 8080
    -       path: /app
    +       path: /app/
    

    同时考虑到这种cookie应该会话级的,而且后续方便调整,去掉了cookie的超时控制。

    -nginx.ingress.kubernetes.io/session-cookie-expires: "172800"
    -nginx.ingress.kubernetes.io/session-cookie-max-age: "172800"
    

    后续有什么可借鉴的?

    首先,为什么上线前没有发现?简单的说,还是部署方式,组网不同,很多老应用就是基于Session的,而不是无状态的,但是在环境上只起一个实例, 只有在压测等情况才进行扩容。后续要考虑增加集群资源,让此类应用至少可以保持在2个,提前发现问题。

    另外,对cookie的设置要慎重,虽然本次的cookie非业务用,但cookie始终是在客户端控制的,变更、维护都不能随心所欲。 同时,必须使用cookie,也要建议尽量使用会话级cookie。

    使用第三方软件、库,都通读文档,不要直接”拷贝”配置,即使是所谓官方的示例,像这些软件配置项都很多,几十项到几百项不等,通读一下后续分析会有更多印象。 同时,作为知识沉淀,后续也要开展培训分享,树立技术人的标杆。

    FAQ

    再记录一下过程中涉及的知识点和资料,用于释疑。

    • 修改Path的可能影响

    仔细想想,就可以发现,直接在path后面添加”/”虽然可以绕过IE的骚操作, 但是由于旧cookie的存在(非会话级cookie的锅,要忍耐172800秒,也就是2天才能清除影响),仍然会有”会话丢失”的现象,临时解决办法就是清理IE的cookie并重启IE(或者通过一键IE设置脚本重置)。

    考虑之前还有几个系统上线也有类似的隐患,后续考虑先去掉cookie的超时控制,过渡一下再修改Path,可以减少影响。

    • cookie的数量限制

    之前有案例和材料表明,IE对于某个域名的cookie数量限制是50个。所以也曾怀疑过cookie数量过多导致异常(抓包显示接近50个),但查阅相关资料和测试发现,IE11差不多能支持180个cookie。 想测试一下浏览器的cookie限制,可以参考这个链接:Browser Cookie Limits。

    • 第一层nginx反向代理的会话保持策略

    目前配置是类似这样的,针对upstream会设置一个sticky指令。

        upstream test {
             sticky expires=1h path=/;
             server a.b.c.d:10086   max_fails=2;
        }
    

    上面也提到,这是nginx-sticky-module-ng模块实现的(通过nginx -V可以看到加载的模块),从文档上可以看出它也是基于cookie的,默认的cookie名称是route。 但是如果你发现你没看到这个route的cookie,那么说明upstream server只有一个,这种情况下,它是不会增加这个cookie的, 可以参考代码。

    • nginx-ingress-controller怎么处理同名cookie

    上面提到,同名cookie发过去的时候,越具体的Path,越靠前,但服务端的处理没有约定。

    对于nginx-ingress-controller来说,它的会话保持功能不是通过nginx stricky模块或者其他方式实现的,是自己通过lua实现的。

    通过sticky.lua#L40 看到cookie是由lua-resty-cookie这个模块实现的。

    继续深入,可以发现cookie.lua#L109 就是解析cookie放到一个字典去的。

    所以后面的cookie会覆盖前面的cookie,这也符合我们处理cookie的常见做法。另外,有个PR23也提到要支持多Path的情况, 不过似乎lua-resty-cookie已经没人维护了。



沪ICP备19023445号-2号
友情链接