K8S – 压测导致应用间互相影响的问题(上)

这几天对线上K8S集群的应用做性能压测,发现了诡异的现象:

压测A应用,引发了其他几个应用超时报错,然而这些应用间并没有直接或者间接调用关系。

更诡异的是,某个应用的POD CPU从平日的不到1核瞬间打爆到4核,然而它的流量压根没有因为压测而上升。同时,宿主机load高达数千,但仍旧可以登录宿主机。

注:K8S集群是1.17版本,kube-proxy使用的iptables,应用数量不多,服务对外暴露采用云厂商的LoadBalancer Service(cluster模式)。

复现

经过天马行空的排查,最终决定逐渐增加A的压测流量,看问题什么时候复现,以便观察现场。

为了排除各种嫌疑,我们把A应用亲和到了独立的Node上独占资源。

通过逐步增加压测流量,当到达500Mbps的时候发现问题复现,也就是说其他node上的应用又出现了响应超时。

此时A应用的redis负载已经被打满,A应用连接redis开始报错重试,为了避免该嫌疑,我们扩容了1台redis从库,A应用不再报错,同时其他应用也没有了超时问题。

原因

虽然POD已经隔离到独立的node,但是其他应用的LoadBalancer使用的cluster模式,还是会将流量打到A应用所在的node。

当A应用redis超载后,A应用开始大量重连redis,导致短链接突增,其node上的conntrack表记录数打高,进而影响了对其他应用service流量的iptables转发性能,导致其他应用被访问超时。

关于conntrack

k8s的service原理就是基于iptables做DNAT转发(kube-proxy也可以选择ipvs模式),转发关系会维护在内核conntrack哈希表中。

当因为重连导致大量短链接建立时,conntrack表中的记录数增多,因为没有对conntrack哈希表进行过优化,所以哈希表的拉链变长,查找效率降低。

实际上iptables的性能需要从2方面看待:

  • 一个是当有大量service在K8S集群内的时候,iptables规则匹配的耗时会增加。
  • 一个是当node有大量连接数的时候,conntrack查表的耗时会增加。

大家应该深入了解iptables原理与使用,才能理解conntrack表存了什么以及如何工作的,大家可以在我的其他博客中找到相关资料。

关于conntrack哈希表的优化,只需要把哈希桶的数量调大即可,大家可以参考《nf_conntrack 的数据结构和相关参数调优》。

简单的说,大家需要编辑/etc/sysctl.conf文件,配置conntrack如下参数:

  • net.netfilter.nf_conntrack_buckets:哈希桶的数量,越多则性能越好,冲突率越低,桶内拉链越短。
  • net.netfilter.nf_conntrack_max:可以容纳的连接总数,利用net.netfilter.nf_conntrack_max / net.netfilter.nf_conntrack_buckets可以得到每个桶内拉链最多可以容纳多少个连接,我们要确保拉链长度尽量保持在个位数、十位数、百位数这种量级,否则遍历查找链表将导致性能下降。

关于LoadBalancer

我们使用的是LoadBalancer类型的service,大部分云厂商的实现都是首先让service作为nodeport暴露端口,然后在K8S集群外创建云平台的LB负载均衡,并把LB的IP填写到service的status里。

在K8S官网中,LoadBalancer的service支持2种转发模式,可以在service的YAML中配置:

  • externalTrafficPolicy:local
  • externalTrafficPolicy:cluster

cluster模式是K8S集群外LB会向所有K8S node转发流量,并且LB会做SNAT再发给node,同时node收到流量后会随机选择一个POD再做DNAT转发,同时也会SNAT再次修改源地址。

cluster模式的缺点是:

  • 流量先随机打到某个node,再DNAT二次转发给随机的POD(可能在其他Node上),导致集群内流量翻倍。
  • 经过LB和转发node的SNAT后,客户端真实的源IP地址已经不见了。

我们压测过程中即便把A应用隔离在独立Node,也依然会影响其他应用的原因就是因为cluster模式。

而使用local模式可以保留客户端真实IP,因此:

  • LB是不会做SNAT的,因此POD应答时会直接发回给客户端而不经过LB。
  • node上只会维护本机POD的iptables规则,因此不会向其他node上的POD做DNAT转发,因此也就不会做SNAT变更源地址。

基于上述2点,可以实现保留请求源IP的目的,但是问题也显而易见:

LB必须知道Service的POD在哪些node上,避免向没有该service POD的node转发流量。

LB要实现这个的方法大部分就是探测所有node上该service的nodeport是否可以联通,因为local模式的iptables规则不会做2次转发,如果不存在本机pod则连接会立即失败。

所以,如果POD发生了node迁移,而LB探测不及时的话,可能造成流量仍旧发往没有该service POD的node,请求就会失败。

这里有一个案例分享给大家:《访问 externalTrafficPolicy 为 Local 的 Service 对应 LB 有时超时》

根据云厂商提供的经验值,单机跑满1000Mbps应该是没有问题的,前提是conntrack表做好优化。

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