MySQL 在 Kubernetes IPVS 模式下引发的 TCP 超时问题
以下数据库上Kubernetes都是研发、测试环境,非生产环境
我们研测测试环境的数据库如 Mysql ,redis ,rabbitmq都以容器的方式在我们自研的paas平台上创建;
但是当我们把mysql移到k8s上时, 我们经常发现MYSQL 的error日志爆出如下错误:
然后我们的客户端则会经常报 Lost Mysql Connection错误
, 重试后,连接恢复:
对于该问题,我们首先思路是排查我们代码上是否存在未正常关闭连接, 同时,我们也部署了另一套非容器的 mysql 环境,相同代码下,非容器mysql的表现的非常正常,没有一条Aborted记录,因此我们怀疑一定中间某个网络环节导致了该问题。
Percona
社区写了一篇比较好的文章,这篇文章有必要阅读下,因为这种错误原因是不确定的。
但是并没有给出这类问题的根本原因,或者说导致该错误的原因其实可能有多种。
由于是基本确立问题范围出现在 docker网络,所以我们必须要深入了解下 kubernetes 1.11上的 IPVS
模式原理。
Kubernetes ipvs 基于 LVS-NAT模型,基本流程如下图:
-
当用户请求到达Director Server,此时请求的数据报文会先到达内核的PREROUTING链,此时报文的源IP是CIP,目标IP是VIP(K8S 每个node都会有该VIP子接口,名为kube-ipvs0,为虚拟接口)。
-
PREROUTING链检查发现数据包的目标IP是本机,将数据包送至INPUT链。
iptables命令是查不到的INPUT这个规则的,因为是ipvs的缘故, 使用 ipvsadm -ln
可以查看到如下规则:
-
POSTROUTING链通过选路,将数据包发送到Real Server(这个我们不好看到,因为内核态或者有没有好的办法可以告知我。) -
Real Server比对发现目标IP是自己的IP,开始建立响应报文发回给Director Server,此时报文的源IP是RIP,目标IP是CIP. 我们这里CIP就是 192.100.183.187
-
Director Server在响应客户端之前,此时会将源IP地址修改为自己的IP地址,然后响应给客户端,做SNAT转换。此时报文的源IP是VIP ( 10.101.219.112
)。目标IP是CIP。
至此 IPVS原理结束。
回到问题, 我们的问题其实遇到比较长时间了,大概一个背景就是,连续使用问题不大,不会报错,但是搁置一段时间后去访问, 就会出现一些异常(我们web上体现到一些查询查不到数据), 所以我们怀疑网络拓扑中存在着连接超时的问题,究竟哪一块有超时呢?我决定抓包。
在哪抓, 我们客户端是我们一个后端,当它启动时,如果有查询任务,肯定是向mysql 发起请求连接的, 所以我们查到该 Client所在 Node上,宿主机上执行tcpdump;
我们的是calico网络, 需要抓 tunl0
网卡流量;
命令如下(幸好第一份工作做网络的。。。。):
tcpdump -i tunl0 host 192.100.111.84 -s0 -w client-mysql.pcap
注意不用指定src dst,那样只能抓到单向报文。。
同时我们的 client发起一个mysql查询请求, 并另开一个shell 在该node执行 ipvsadm -lnc
结果如下:
这个图。。。。
第二列 ,很可疑。。。expired date。。。。。超时。。。,先不管,证明我们的猜想。。继续往下走,不断执行该命令, 第二列在减少,看起来默认是15min,那么这15min我client不再做任何查询,等等吧。。。。
waiting。。。
还有5分钟, 好紧张
我们想抓到到15min那时候, 会发生什么, 然后client再去查询,会不会触发 mysql的错误以及client的错误(文初的错误)
时间到:
我凑, 看起来该链接被清掉了,这时候我们去执行一条查询看看;
我们再看看抓包情况:(我是在POD所在节点宿主上抓的跟数据库交互的报文,然后没有抓到连接被RST的报文略疑惑,lvs清空连接对连接本身究竟做了什么操作仍需要关注。。)
在此之后,查询时候又会新键连接, 即没有任何征兆该链接就被断开了,按理说client使用 mysql connection pool 连接复用是不会轻易就更换连接的。
基本确定了问题, 看起来是 ipvs 维护 VIP的这条链接存在15min左右的超时阈值设定,这个值是否跟系统默认的tcp_keepalive_timeout
有协同影响?那么系统的默认tcp超时时间是多少呢?
确立这个思路,查阅资料(基于15min ipvs为关键词):
-
https://github.com/moby/moby/issues/31208
-
https://success.docker.com/article/ipvs-connection-timeout-issue
-
https://access.redhat.com/solutions/23874
基于上述资料,基本可以分析到, ipvs维护链接有个超时时间,默认为900s为15分钟;然后操作系统默认的tcp_keepalive_timeout 默认为7200s,当一个空闲 tcp连接达到900s时,首先他被ipvs断了,但是操作系统认为该链接还没有到保活超时,所以客户端还会使用之前的连接去发送查询请求,但是ipvs已经不维护该链接了,所以 Lost Connection。。所以只要减小系统的tcp_keepalive_timeout时间,比如到600,后发送一个心跳包,让tcp保活, 这样, ipvs的连接超时也会被重置计数为15min。
我紧急基于上述资料做了k8s node全量修改配置:
playbook如下:
k8s.conf
如下:
关于这三个参数解释下:
当启用与内核参数或守护程序端配置或客户端配置相关的选项时,它将根据这些选项终止tcp会话。例如,当您将以上述内核参数选项视为示例时,首先将在600秒后开始发送keepalive数据包,之后每隔30秒发送一次下一个数据包10次。当客户端或服务器在这段时间内根本没有应答时,tcp会话将被视为已损坏,并将终止。
-
为什么我们要设置为600s呢, 其实只要比 ipvs的默认值900小即可!
经修复后, 断链问题基本解决, 数据库的 error communication packget
报文基本不再复现, 多么痛的领悟。。
抓到如下报文(修改后ip等数值与上文不一样,发生了变化):
-
(可以清楚看到13:33 到 13:43 期间连接无数据,但是10分钟到了,里面有一个KeepAlive报文,修改之前linux 这个值默认是7200s也就是2小时!)
我们可以在 https://access.redhat.com/solutions/23874
看到一些解释
-
该client的数据库连接池的连接被保活而不是被ipvs clear掉。
原文链接:https://berlinsaint.github.io/blog/2018/11/01/Mysql_On_Kubernetes%E5%BC%95%E5%8F%91%E7%9A%84TCP%E8%B6%85%E6%97%B6%E9%97%AE%E9%A2%98%E5%AE%9A%E4%BD%8D/