问题描述

当 kube-proxy 使用 ipvs 模式时,客户端段长连接静默时间超过 15min 时,连接将被重置。

问题分析

通过 ipvsadm -lnc 命令可以查看节点上 ipvs 连接的持久化时间:

ipvs 管理连接超时时间有以下两方面的考量:

  • ipvs 支持 persistence_timeout ,通过这个参数用户可以在持续时间内把同一个 Client IP 的请求转发到同一个 RS。

  • TCP 在传输过程中可能出现报文分片,ipvs 即使不配置 persistence_timeout 时也需要管理连接的超时机制,以此保证把来自同一个 Client(IP+Port)的不同分片调度同一个 RS。

上述两点对应了两不同的超时设置,但 ipvs 将他们记录在同一张表中并用不同的 state 区别。

通过 ipvsadm -l –timeout 可以查看 ipvs 的超时时间设置,包括 tcp、tcpfin、udp 三种不同类型的超时间:

  • tcp 超时时间:如果客户端和服务端建立了连接,则 ipvs 中会出现一条 ESTABLISHED 的记录。每当客户端和服务端的连接中有信息交互时,该超时时间都会刷新为初始值。如果连接处于空闲状态,即一直没有信息交互,则等到该值超时后,ESTABLISHED 的记录会被 ipvs 直接清理。这种情况下如果 Client 后续发送的请求(Client 不知道服务端已经删除了记录),ipvs 将重新调度请求到一个新的RS,这将导致连接中断。
  • tcpfin 超时时间:如果客户端发起了 FIN 断连,则 ipvs 中连接状态会从 ESTABLISHED 变为 FIN_WAIT ,此时等待 tcpfin 时间后,记录将被移除。

通过 ipvsadm –set tcp tcpfin udp 可以全局设置该超时时间(重启主机失效):

用户如果为服务指定了 persistence_timeout ,这种情况下对每个 ClientIP 会出现一条 ASSURED 记录,表示指定 persistence_timeout 时间内,对应 ClientIP 的请求将转发到相同的 RS:

kube-proxy 在创建 Service 时支持指定 sessionAffinity 参数为 None 或者 ClientIP,当指定为 ClientIP 可以进一步指定 SessionAffinityConfig 参数,该参数即对应 ipvs 规则的 persistence_timeout 参数。

解决方案

根据上述描述我们知道,当 Client 不主动对长连接进行保活时 900s 后 ipvs 主动删除连接记录,导致长连接被重置。为此我们可以修订系统 tcp keepalive 配置,每隔 600s 发送一次保活心跳,以重置 ipvs 的连接超时计数。

1
2
3
net.ipv4.tcp_keepalive_time = 600
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 10

在内核 4.13 版本中,tcp_keepalive_* 属性是独立 namespace 的,因此 Pod 不会独立从宿主机继承该配置,因此需要对在容器的 securityContext 中手工指定上述参数。

注意:参考 kubernetes 官方 issues/71358 , ipvs 中 timeout 是全局的,用户无法单独为某个服务设置超时时间。

参考

  1. https://github.com/moby/moby/issues/31208
  2. https://cloudmessage.top/archives/mysql-ipvs-tcp-timeout
  3. https://github.com/cloudnativelabs/kube-router/issues/598