IPVS 在 k8s 中连接保持引发的问题
k8s ipvs recorder
Last updated
k8s ipvs recorder
Last updated
在将业务(小部分长连接)迁移至k8s集群时, 项目能够平稳运行, 但是在测试环境发布时却经常遇到:刚刚发布成功后的1分钟左右,websocket连接出现:504 bad handshake。
这一错误引起了我们的注意:因为在做k8s部署时是通过配置 readiness 来保证服务正常可访问。同时, 遇到问题的这一服务 svcA 是单实例部署。
k8s 版本大于 1.8 时,默认采用的 ipvs。我们的集群 k8s 的底层负载采用的是 ipvs。
遇到问题后, 先确认了通过lb访问、在集群内通过nodeport访问 以及 直接cluster ip访问是否正常。经确认,集群内通过nodeport不能正常访问, 直接cluster ip正常。此时可以将问题缩小到 ipvs 路由相关的内容。
经腾讯运维同学提醒,我们注意到 ipvs 的一个配置参数:persistence_timeout
ipvs 工作模型
LVS DR原理详解图
LVS FULLNAT模式
LVS NAT原理详解图
LVS TUN原理
ipvs:
ipvs 通过ipvsadm命令和LVS内核打交道
参数配置:
/proc/net/ip_vs
获取当前LVS内核配置,包括VS和RS相关信息;同ipvsadm -ln;
/proc/net/ip_vs_conn
获取所有连接信息,建议不要用该命令,因为连接数很多时,CPU开销会很大;
/proc/net/ip_vs_stats
/proc/sys/net/ipv4/vs/expire_nodest_conn
默认值为0,当LVS转发数据包,发现目的RS无效(删除)时,会丢弃该数据包,但不删除相应连接;这样设计的考虑是,RS恢复时,如果Client和RS socket还没有超时,则 可以继续通讯;
如果将该参数置1,则马上释放相应 连接;个人建议采用默认值,配置为0;
/proc/sys/net/ipv4/vs/expire_quiescent_template
默认值为0,当RS的weight值=0(如,健康检测失败,应用程序将RS weight置0)时,会话保持的新建连接 还会继续调度到该RS上;
如果配置为1,则马上将 会话保持的连接模板 置为无效,重新调度新的RS;如果有会话保持的业务,建议该值 配置为1;
/proc/sys/net/ipv4/vs/nat_icmp_send
默认值为0;建议采用默认值,为0;
如果置为1,当LVS收到RS发送出来的数据包,但没有找到相应连接时,则发送目的不可达(端口不可达)的ICMP给RS;
/proc/sys/net/ipv4/vs/sync_threshold
默认值为 3 50;
这个参数和连接同步相关,LVS收到3个包后,开始启动同步;之后,每收到50个包,启动一次同步;可以根据LVS的流量,可以调整连接同步的频率,从而控制同步的系统开销;
在问题发生时,通过 ipvsadm -Ln | grep 10.111.103.161 -A3
查看ipvs路由表发现,已经无效的pod的rip依然存在,只不过weight=0。
联系上述的相关配置会发现,当服务发布时,先将新pod创建并注册rip到ipvs,再将旧pod删除,,但是但是,,旧pod删除时不是应该把对应的rip删除掉吗?
当旧pod删除时,已有连接被断开,客户端触发重连机制,但因为ipvs的连接保持特性,会依然路由到旧pod对应的rip(虽然此时weight为0)。这就是问题的根源~
持久连接(Persistence)的问题。持久连接使一个客户端在超时时间内(ipvsadmin -p参数指定,keepalived的配置文件persistence_timeout指令)会持续连接到同一台后端服务器
深究发现:1.12 的kube-proxy更改了实现:ipvs 下如果有 conn 存在就不删除 rip,只是将其权重设置为0
解决方案
将参数 /proc/sys/net/ipv4/vs/expire_quiescent_template 设置为 1
注意
之所以不调整 persistence_timeout 是因为,ftp 以及 ssl 等会依赖此特性,详见:persistence
k8s 中的 ipvs 是 NAT 模式
ipvs 是一个内核态的四层负载均衡,支持NAT、Gateway以及IPIP隧道模式,Gateway模式性能最好,但LB和RS不能跨子网,IPIP性能次之,通过ipip隧道解决跨网段传输问题,因此能够支持跨子网。而NAT模式没有限制,这也是唯一一种支持端口映射的模式。
由于 Kubernetes Service 需要使用端口映射功能,因此kube-proxy必然只能使用ipvs的NAT模式。
k8s 中为了流量能原路返回:
由于 ipvs 的 NAT 模式是 dnat,不做snat,所以需要跟iptables配合,增加snat
k8s 的 snat 是在 mangle 表的 POSTROUTING 链上进行的,一般来说,需要开启以下参数,才能实现这一功能:
ipvs是dnat,不做snat,所以如果单纯使用ipvs,会话负载均衡是可以的; 但是,k8s使用ipvs,必须跟iptables配合,还得加多个snat,这样对于ipvs来说,所有的来源ip就变成一样的了,使用会话负载均衡是否就有问题了呢?
不会,因为,ipvs 工作在 PREROUTING 上,而 snat 发生在 POSTROUTING 链上
svc有个参数 ExternalTrafficPolicy 设置为 local 时,可以避免 k8s 的跨节点路由