K8S – 滚动发布应用导致调用方超时报错的排查

这两天和运维一起排查一个棘手的K8S网络问题,其最初的现象是每次滚动发布代码都会引起大量的调用方超时报错。

经过单纯的扩容和缩容比对后,我们明确是POD下线引起的,而不是POD上线引起的,所以有了如下的后续分析。

排查报告

现象

缩容时,即便应用代码进行了优雅退出,还是有调用方报错。

诡异问题

ipvs在pod优雅退出期间,降权对应pod的weight=0,但是仍旧有新连接流入。

问题原因

ipvs开启了conn_reuse_mode=0选项,其原理是当客户端使用与之前相同的ip+port再次新建链接的时候,ipvs会根据此前缓存的决策结果,继续向原先的realserver做流量转发。

结合公司业务特点

调用都是短链接,每次报错零星几个,猜测是因为客户端端口快速回收,在POD优雅退出期间,调用方同一个IP出现了端口复用。

技术内幕

conntrack -> prerouting这块,上次在conntrack里留下的缓存转发关系,下次新流量进入,会复用,直接在conntrack环节就改写到位了,后续prerouting -> forward直接送走了。

复现场景

仅在IPVS模式下会触发。

当前解决方案

conn_reuse_mode=1,让新建链接不用复用之前的conntrack改写关系,而是重新过NAT表做计算。

社区ISSUE

https://github.com/kubernetes/kubernetes/issues/81775

必须谈谈conntrack

这次排查过程中,对netfilters的工作原理有了一些更深入的理解,这个重要的理解就是conntrack。

实际上,在netfilters的链条中,有一个环节是conntrack,网上很少谈及,但是不理解conntrack实际就不能很好的理解NAT表的工作原理。

(图片右键在新tab打开)

我们会发现conntrack这个链在prerouting链/output链中,处理NAT表之前。

我们知道在流量去程的时候,prerouting/output可以用来做DNAT,postrouting用来做SNAT,但是我们也要想一下当流量回程的时候也是要从prerouting开始进入的netfilters链条的,对SNAT和DNAT显然要做一个反向的复原,而这个工作就是conntrack做的。

我们可以观察一下conntrack是如何记录流量去程时候的改写关系的(我截取了一行记录):

上述就是conntrack缓存的改写映射关系。

原始的去程流量是从192.168.2.166:54599发来的,目标是146.148.242.196:80,对应这一部分:

src=192.168.2.166 dst=146.148.242.196 sport=54599 dport=80

经过我机器上NAT表的DNAT改写后,源地址还是192.168.2.166:54599,但是目标地址改写为了192.168.2.100:12345。但是注意哈,conntrack记录是按流量回程的视角记录的改写后的状态,所以是这样的:

src=192.168.2.100 dst=192.168.2.166 sport=12345 dport=54599

所以,当流量回程的时候,conntrack根据src=192.168.2.100:12345,dst=192.168.2.166:54599,可以直接把流量恢复为改写前的状态(注意是回程,所以颠倒一下src和dst):src=146.148.242.196:80,dst=192.168.2.166:54599,所以就可以forward流量到192.168.2.166:54599,完成回程流量的递送了。

同样的,后续的去程流量也将因为conntrack记录的存在,直接映射为改写后src/dst,因此就不会重复应用连接建立时候要命中的那些nat或者filter规则了,这也是为什么即便我们下线一个转发规则也不会影响现有已经建立连接的流量的原因。

 

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~