公司介绍
Soul 是基于兴趣图谱和游戏化玩法的产品设计,属于新一代年轻人的虚拟社交网络。成立于2016年,Soul 致力于打造一个“年轻人的社交元宇宙”,最终愿景是“让天下没有孤独的人”。在 Soul,用户可以无顾虑地表达自己,认知他人,探索世界,交流兴趣和观点,获得精神共鸣和认同感,在交流中获取信息,并获得有质量的新关系。
问题与挑战
多层网关链路长
Soul 在2020年开始逐渐试探容器服务,在ECS转型容器阶段,出现了容器入口网关(Ingress-Nginx),微服务网关,加上统一接入层的SLB+Tengine;造成了多重网关的架构;链路太长不仅带来成本和RT的问题,而且导致排查一个请求异常,需要拉非常多的人解决,定位问题代价非常大。
Ingress-Nginx 开源问题
今年 Ingress-Nginx 社区反馈稳定性和安全问题比较多,暂时停止接收新功能,对 Soul 是一个巨大隐患。
Grpc转发负载不均衡问题
- 内网部分服务开放gRPC入口,gRPC是基于HTTP/2之上的,而HTTP/2被设计为一个长期存在的TCP连接,所有都通过该连接进行多路复用。这样虽然减少了管理连接的开销,但是在负载均衡上又引出了新的问题。
- 由于我们无法在连接层面进行均衡,为了做gRPC负载均衡,我们需要从连接级均衡转向请求级均衡。换句话说,我们需要打开一个到每个目的地的HTTP/2连接,并平衡这些连接之间的请求。
- 这就意味着我们需要一个7层负载均衡,而K8s的Service核心使用的是kube proxy,这是一个4层负载均衡,所以不能满足我们的要求。
- 目前使用独立evnoy + headless 方案解决gPRC转发不均衡问题,slb暴露envoy的端口供其他服务调用;但维护成本较高,evnoy节点资源浪费较为严重
Ingress稳定性及局限性
- 由于业务的不确定性,随着业务请求的波动,nginx ingress controller会出现连接数突增,导致ingress controller健康检查不通过;nginx ingress controller上游的检测需要时间及fail次数积累,导致这一阶段用户请求大量失败或重试。(如下图)
- HTTP 路由仅支持host和path匹配,对于高级路由功能没有通用配置,只能通过 annotation 来实现,比如使用Nginx Ingress Controller实现URL重定向,需要配置 nginx.ingress.kubernetes.io/rewrite-target annotation已无法适应可编程路由的需求。
- 不同命名空间中的服务要绑定到同一个网关中的情况在实际情况下经常出现,而入口网关无法在多个命名空间中共享;这样就增加Ingress-Nginx 及Ingress-Controller的拆分难度。
业务发布抖动
- 虽然Kubernetes自身具备优雅线上机制,及Liveness和Readiness等就绪检查,但服务启动后,瞬间开始接收请求,服务还是会受到瞬间流量的冲击及链接层面的压力。
- 服务发布可分为多批,但我们将整个发布过程中看做整体时,看到的是服务RT忽然升高,造成局部业务阶段性响应变慢,给用户最直观的感受是卡顿(单次请求较慢或请求失败后的重试),在用户侧可能感知到服务降级或服务不可用,从而影响用户体验。
技术选型
由于开源Ingress-Nginx遇到比较多的问题,由于线上流量巨大难以定位和解决概率超时问题,因此我们考虑投入更多研发人员解决这个问题,还是选择Envoy网关解决,还是选择阿里云ASM、MSE云原生网关两个产品,因此我们针对这三个新技术方向做了全面评估。
综上所述, Envoy已是现阶段数据面较好的选择(可以解决现有nginx ingress controller的性能和稳定性问题),由于性能要求比较高,因此我们优先做了性能压测。
压测数据
我们通过对线上服务三种不同方案的压测数据对比(SLB+Envo+headless svc、ALB、MSE),主要测试性能和gRPC负载均衡能力两方面;压测数据显示,MSE云原生网关在RT和成功率上均有优势,并且能满足 Soul gRPC的转发需要;那MSE是否能满足 Soul 所有业务需求呢?是否能解决最大集群超时问题呢?因此我们对MSE进行了更全面的评估。
全面技术评估
- 对MSE云原生网关进行功能、稳定性、性能、安全等全方位评估,看看是否满足 Soul 未来要求。
- Soul的业务场景比较复杂,评估MSE云原生网关将流量网关、微服务网关、安全网关三合一,集成10+云产品,开箱即用,满足业务需求。
- Soul 对稳定性要求非常高,任何抖动都会导致大量用户影响,考虑MSE云原生网关经历阿里双十一大规模生产验证,久经打磨,奠定了我们生产使用的信心。
- 由于 Soul 流量非常大,网关机器规模大,因此成本是一个关键的考量点,压测显示MSE云原生网关采用软硬一体解决方案,比自建性能高1倍左右。
- Soul 后端有大量Dubbo服务,目前通过自研业务网关做HTTP到Dubbo协议转换,考虑MSE云原生网关支持HTTP到Dubbo协议转换,支持直接挂Dubbo服务,有利于未来架构收敛。
迁移方案
- 由于MSE兼容Ingress标准,因此创建完云原生网关实例,监听已有的Ingress资源,就可以直接迁移后端到路由转发规则;
- MSE与Ingress-Nginx可以共存,因此只需要从上游把流量从Ingress-Nginx逐渐切到MSE云原生网关即可,按照不同的域名进行灰度,降低变更风险。
- 在Soul的场景中,流量切换MSE后,Ingress-Nginx没有完全的下线,保持了2个节点,并增加HPA配置,以备不时之需;
- gRPC转发MSE替换原有的独立Envoy,业务服务修改svc中服务暴露协议及端口即可,逐个服务迁移;
技术方案
短期方案
Soul 的网关链路比较长,解决最紧迫超时问题、服务发布预热问题,因此第一期先替换Ingress-Nginx,并将容器入口网关/微服务网关合并。
终态方案
将网关链路降为最短;下线微服务网关,将http转发rpc能力托管MSE;下线Tengine,将ECS转发能力托管在MSE;最终实现SLB->MSE->POD/ECS
落地效果
稳定性及RT前后对比
MSE切换后处理及响应请求时间平稳,从峰值500ms下降至峰值50ms
服务发布产生的错误码对比
Ingress-Nginx与MSE错误码对比,服务发布期间502降为0,499平均降低10%;
预热与启动RT问题
落地解决了大部分超时问题,但是启动慢Java程序发布超时问题还没解决,因此我们开启服务预热功能,业务启动逐步打流量过来,防止大量流量打到刚启动Java进程超时。
开启预热效果:从图中可以看出,Pod在刚刚启动后,并没有瞬间接收到全量,而是在5分钟的时间里逐渐预热服务,这一点在服务http入口请求数量,Pod网络进出流量,Pod CPU使用率均可以看到;Nginx需要自己从底层到上层的各种监控,采用云原生网关后,提供一站式观测视图,提供丰富网关prometheus指标,方便观测和解决复杂问题。
未来规划
- 采用云原生网关将流量、安全、微服务网关三合一,大幅降低请求链路条数、降低架构复杂度
- 降低运维和排查成本,降低整个链路RT,提升客户满意度。
- 开启HTTP 3.0,提升网络传输效率,提升客户体验
- 采用服务自治(在线抓包、诊断、巡检)降低排查问题消耗
- 采用混沌工程提前识别稳定性风险;
落地经验总结
service-weight与canary-weight
- 集群导入MSE后发现有service-weight的ingress都是不可用状态
- 容器服务ACK控制台调整了灰度发布的功能用法,分为两种:
1)、canary-*注解方式:使用canary-* Annotation配置蓝绿与灰度发布。canary-* Annotation是社区官方实现的灰度发布方式;
2)、service-*注解方式:使用service-* Annotation 配置蓝绿与灰度发布。service-* Annotation是ACK Nginx Ingress Controller早起的实现方式;
- 根因:此注解不是nginx推荐的标准注解- nginx.ingress.kubernetes.io/canary-weight 才是标准nginx按权重路由的用法:service-weight(是ack自己添加的,并且不在维护要废弃的,但是是能正常使用的)
x-Real-ip的问题
- 服务切换至MSE后发现业务拿到的用户IP均为100网段,100网段为阿里内部网段,防黑产、IP特征库、使用的是Nginx里的 X-Real-ip字段;怀疑请求头处理不一致
- Ingress-Nginx设置并处理了X-Real-ip和xff请求头
- MSE Envoy透传,无特殊处理
- 根因:MSE Envoy缺少了对X-Real-ip和xff header头的处理;
MSE入口NAT转发模式与FullNAT模式
- xx服务切换MSE后,由于请求量增加,一个SLB(传统型CLB)无法满足流量要求;MSE入口由一个SLB增加为4个SLB实例;
- 发现业务请求失败率有轻微上涨,请求拨测报警次数也有不同程度上升;报错均为HTTPSConnectionPool(host='xxx.xx.cn', port=443): Read timed out. (read timeout=10)
- 通过rquestid 发现,请求nginx正常接受与转发,后端MSE日志中也可以找到对应的请求,且处理时间均为200-300ms,但nginx未收到会包,一直处于等待状态,http1.1 长链接未断开
- 通过抓包分析,发现nginx发送13个包后,主动断开了链接;怀疑nginx 回收链接机制bug,将keepalive空闲资源池数量加大,减少回收的次数;修改后,报错数量有所下降,但依然存在
- 继续观察Tengine错误日志,发现日志中只有recv failed一种日志,继续抓包分析2022/12/15 21:28:16 [error] 14971#0: *35284778395 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 106.119.54.167
- 抓包发现链接被reset,经排查发生reset是在大并发的场景下,源IP/源PORT/目的IP/目的PORT一致, 四元组冲突linux内核RESET了链接;
- 将MSE接入的SLB由NAT模式改为FullNat,解决四元组冲突问题;四元组冲突问题不仅仅是出现在MSE的接入场景,在多个四层SLB引入相同ECS模式下均会出现,由于错误数量相对较低,一致疏忽了这个问题;
rewrite-target注解,NGINX-Ingress与Envoy差别
- xx业务接入MSE后,发现出现404问题,upstream为nginx ingress controller时访问正常;
- 查看请求日志,发现请求路径没有了..
- 发布脚本
- ingress配置文件
- 根因:nginx ingress controller 发现请求path是/,rewrite/相当于不重写,因此生成的nginx.conf中并没有rewirte规则;
- MSE中的Envoy rewirte/,就真的会生成rewrite配置,因访问的业务网关的域名后加路径,导致路径丢失;去掉网关ingress里此rewrite-target的注解后正常访问,Envoy也需要兼容这种rewrit,rewrite / 默认不做处理