背景信息:
某客户反馈,ECS上自建nginx server 通过proxy_pass 反向代理 云上k8s集群 nodeport类型的svc,存在大量1s的延迟请求的问题,在nginx所在的ecs上,使用netstat可以看到syn_sent状态的connection,如下图所示,但是在pod所在的worker节点上是看不到syn_RECV状态的connection(nodeport上也无)
集群网络模式:
客户的k8s集群使用的网络模式是flannel+ipvs,这二者的组合我首先想到的是:
- pod 具备自己的 cidr ,网络通过eth0转进相关的veth虚拟网卡再到pod里面
- svc通过ipvs做转发,过ipvs会被fnat转换
flannel图示(本图摘自网络):
三次握手示意图:
- 半连接队列,保存SYN_RECV
状态的连接。队列长度由net.ipv4.tcp_max_syn_backlog
设置
- accept队列,保存ESTABLISHED
状态的连接。队列长度为min(net.core.somaxconn, backlog)
。其中backlog是我们创建ServerSocket(int port,int backlog)
时指定的参数
如果是队列满导致的syn丢弃,则可以通过netstat -st观察到,如
netstat -st | egrep -i "drop|reject|overflowed|listen|filter"
访问链路:
访问链路,client --(nginx server)-- ecs nodeport (ipvs转发给某个pod)-- pod,这种链路当nginx server转发请求给后端的ecs nodeport时,ipvs会介入做一次fnat转换,即受访的ecs本地还会起一个随机端口,去跟ipvs给的pod地址建联,相当于有2个会话来完成这个请求
抓包信息图示:
如下图所示,我们可以通过相同的(seq_number wireshark 用 tcp.seq_raw == 赋值)以及(tcp.ack_raw)找到节点的nodeport转发给pod的真实流,而从图上可以看出确实存在一次syn重传,第一次的syn包,实际pod没有收到,说明问题出现在转发阶段,同时采集了ipvs 以及conntrack的相关session,发现当前端存在异常syn状态的时候,ipvs 以及conntrack 实际并不存在syn状态的session,这种情况我们就要看下还会影响这个报文转发的因素有哪些了,先来了解一下网络报文的内核路径吧
小知识:
1,相同秒级时间的seq num相同可以视为一条流,即在不同的网卡上抓到的这1个请求,seq是一样的
从网卡到内存:
网卡需要有驱动才能工作,驱动是加载到内核中的模块,负责衔接网卡和内核的网络模块,驱动在加载的时候将自己注册进网络模块,当相应的网卡收到数据包时,网络模块会调用相应的驱动程序处理数据。
下图展示了数据包(packet)如何进入内存,并被内核的网络模块开始处理:
1: 数据包从外面的网络进入物理网卡。如果目的地址不是该网卡,且该网卡没有开启混杂模式,该包会被网卡丢弃。
2: 网卡将数据包通过DMA的方式写入到指定的内存地址,该地址由网卡驱动分配并初始化。注: 老的网卡可能不支持DMA,不过新的网卡一般都支持。
3: 网卡通过硬件中断(IRQ)通知CPU,告诉它有数据来了
4: CPU根据中断表,调用已经注册的中断函数,这个中断函数会调到驱动程序(NIC Driver)中相应的函数
5: 驱动先禁用网卡的中断,表示驱动程序已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存就可以了,不要再通知CPU了,这样可以提高效率,避免CPU不停的被中断。
6: 启动软中断。这步结束后,硬件中断处理函数就结束返回了。由于硬中断处理程序执行的过程中不能被中断,所以如果它执行时间过长,会导致CPU没法响应其它硬件的中断,于是内核引入软中断,这样可以将硬中断处理函数中耗时的部分移到软中断处理函数里面来慢慢处理。
内核的网络模块
软中断会触发内核网络模块中的软中断处理函数,后续流程如下:
7: 内核中的ksoftirqd进程专门负责软中断的处理,当它收到软中断后,就会调用相应软中断所对应的处理函数,对于上面第6步中是网卡驱动模块抛出的软中断,ksoftirqd会调用网络模块的net_rx_action函数
8: net_rx_action调用网卡驱动里的poll函数来一个一个的处理数据包
9: 在pool函数中,驱动会一个接一个的读取网卡写到内存中的数据包,内存中数据包的格式只有驱动知道
10: 驱动程序将内存中的数据包转换成内核网络模块能识别的skb格式,然后调用napi_gro_receive函数
11: napi_gro_receive会处理GRO相关的内容,也就是将可以合并的数据包进行合并,这样就只需要调用一次协议栈。然后判断是否开启了RPS,如果开启了,将会调用enqueue_to_backlog
12: 在enqueue_to_backlog函数中,会将数据包放入CPU的softnet_data结构体的input_pkt_queue中,然后返回,如果input_pkt_queue满了的话,该数据包将会被丢弃,queue的大小可以通过net.core.netdev_max_backlog来配置
13: CPU会接着在自己的软中断上下文中处理自己input_pkt_queue里的网络数据(调用__netif_receive_skb_core)
14: 如果没开启RPS,napi_gro_receive会直接调用__netif_receive_skb_core
15: 看是不是有AF_PACKET类型的socket(也就是我们常说的原始套接字),如果有的话,拷贝一份数据给它。tcpdump抓包就是在这里进行的,也就是上面说的为什么能抓到包就可以确认不是前后端丢包或者软中断导致了
16: 调用协议栈相应的函数,将数据包交给协议栈处理。
17: 待内存中的所有数据包被处理完成后(即poll函数执行完成),启用网卡的硬中断,这样下次网卡再收到数据的时候就会通知CPU
enqueue_to_backlog函数也会被netif_rx函数调用,而netif_rx正是lo设备发送数据包时调用的函数
Linux 数据包收发内核路径图
未严格分层,仅做表示
协议栈
IP层
由于是TCP包,所以第一步会进入IP层,然后一级一级的函数往下调,看到这里,相信眼尖的同学就已经发现过了ip协议栈,进入tcp协议栈之前,还要过iptables的相关链,那么在过iptables的时候,到哪个阶段会填充conntrack表呢?
Conntrack介入的时机-iptables&ipvs表链路径:
iptables的表链路径:
既然进入tcp协议栈之前需要过一次iptables,那么过iptable在哪个阶段会往conntrack表里面填充的呢?我们继续往下走,看下iptables各个表链工作的路径
先放一张内核协议栈各 hook 点位置和 iptables 规则优先级的经典配图,详细的内核路径图可以往上翻下,当然看图说话还是比较费劲的,我们直接看表格吧
经典大图
下面的表格展示了 table 和 chain 的关系。横向是 table, 纵向是 chain,Y 表示 这个 table 里面有这个 chain。例如,第二行表示 raw
table 有 PRETOUTING
和 OUTPUT
两 个 chain。具体到每列,从上倒下的顺序就是 netfilter hook 触发的时候,(对应 table 的)chain 被调用的顺序。
有几点需要说明一下。在下面的图中,nat
table 被细分成了 DNAT
(修改目的地址) 和 SNAT
(修改源地址),以更方便地展示他们的优先级。另外,我们添加了路由决策点 和连接跟踪点,以使得整个过程更完整全面:
Tables/Chains |
PREROUTING |
INPUT |
FORWARD |
OUTPUT |
POSTROUTING |
(路由判断) |
|
|
|
Y |
|
raw |
Y |
|
|
Y |
|
(连接跟踪) |
Y |
|
|
Y |
|
mangle |
Y |
Y |
Y |
Y |
Y |
nat (DNAT) |
Y |
|
|
Y |
|
(路由判断) |
Y |
|
|
Y |
|
filter |
|
Y |
Y |
Y |
|
security |
|
Y |
Y |
Y |
|
nat (SNAT) |
|
Y |
|
Y |
Y |
当一个包触发 netfilter hook 时,处理过程将沿着列从上向下执行。 触发哪个 hook (列)和包的方向(ingress/egress)、路由判断、过滤条件等相关。
特定事件会导致 table 的 chain 被跳过。例如,只有每个连接的第一个包会去匹配 NAT 规则,对这个包的动作会应用于此连接后面的所有包。到这个连接的应答包会被自动应用反 方向的 NAT 规则。
我之前的理解以为过了raw表prerouting链其实就应该往conntrack表里面填充了,但是实际并不是这样的,这里只是相当于打了一个标记而已,后面还要过ipvs的链,下面给大家看个简单的trace的demo
flannel+ipvs的流量在iptables上的进出路径,下面curl的endpoint是pod在本机上的流量展示 源是 172.16.3.85,访问的目标机器 172.16.2.112:32125,pod 10.111.0.25 就在这个机器上,不同的场景,如terway-eni+ipvs ,svc是nodeport,访问的是clusterip,流量策略是local/cluster这些不同的元素都会走不同的路径,这里只做一个简单的demo
Trace在目标机器上添加的
# iptables -t raw -A PREROUTING -p tcp -s 172.16.3.85 --dport 32125 -j TRACE
# iptables -t raw -A OUTPUT -p tcp -s 172.16.3.85 --dport 32125 -j TRACE
第一阶段,刚开始过各种表的 prerouting链,可以结合上面的表格看
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: raw:PREROUTING:policy:5 IN=eth0 OUT= MAC=00:16:3e:16:ea:fb:ee:ff:ff:ff:ff:ff:08:00 SRC=172.16.3.85 DST=172.16.2.112 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=2229 DF PROTO=TCP SPT=49704 DPT=32125 SEQ=1668671541 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A251AE0800000000001030307)
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: mangle:PREROUTING:policy:1 IN=eth0 OUT= MAC=00:16:3e:16:ea:fb:ee:ff:ff:ff:ff:ff:08:00 SRC=172.16.3.85 DST=172.16.2.112 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=2229 DF PROTO=TCP SPT=49704 DPT=32125 SEQ=1668671541 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A251AE0800000000001030307)
......
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: nat:PREROUTING:policy:2 IN=eth0 OUT= MAC=00:16:3e:16:ea:fb:ee:ff:ff:ff:ff:ff:08:00 SRC=172.16.3.85 DST=172.16.2.112 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=2229 DF PROTO=TCP SPT=49704 DPT=32125 SEQ=1668671541 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A251AE0800000000001030307) MARK=0x4000
第二阶段,过input链,可以观察各个表链
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: mangle:INPUT:policy:1 IN=eth0 OUT= MAC=00:16:3e:16:ea:fb:ee:ff:ff:ff:ff:ff:08:00 SRC=172.16.3.85 DST=172.16.2.112 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=2229 DF PROTO=TCP SPT=49704 DPT=32125 SEQ=1668671541 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A251AE0800000000001030307) MARK=0x4000
......
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: filter:INPUT:policy:4 IN=eth0 OUT= MAC=00:16:3e:16:ea:fb:ee:ff:ff:ff:ff:ff:08:00 SRC=172.16.3.85 DST=172.16.2.112 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=2229 DF PROTO=TCP SPT=49704 DPT=32125 SEQ=1668671541 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A251AE0800000000001030307) MARK=0x4000
第三阶段,input链过完走output,这个时候可以看到流量被转给了cni0的网卡,目的地址换成了10.111.0.25:80,这个ip是真实的pod ip + port
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: raw:OUTPUT:policy:9 IN= OUT=cni0 SRC=172.16.3.85 DST=10.111.0.25 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=2229 DF PROTO=TCP SPT=49704 DPT=80 SEQ=1668671541 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A251AE0800000000001030307) MARK=0x4000
......
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: nat:KUBE-POSTROUTING:rule:4 IN= OUT=cni0 SRC=172.16.3.85 DST=10.111.0.25 LEN=60 TOS=0x00 PREC=0x00 TTL=63 ID=2229 DF PROTO=TCP SPT=49704 DPT=80 SEQ=1668671541 ACK=0 WINDOW=29200 RES=0x00 SYN URGP=0 OPT (020405B40402080A251AE0800000000001030307)
在这之前的包都是syn包,可以看关键字 RES=0x00 SYN URGP=0 ,这里的syn就是syn包,同时可以看到 SEQ=1668671541 ACK=0 一般都是代表了独立的syn包,trace是不到万不得已不建议用,实在是比较难读的,可以结合抓包一起看
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: raw:PREROUTING:policy:5 IN=eth0 OUT= MAC=00:16:3e:16:ea:fb:ee:ff:ff:ff:ff:ff:08:00 SRC=172.16.3.85 DST=172.16.2.112 LEN=52 TOS=0x00 PREC=0x00 TTL=64 ID=2230 DF PROTO=TCP SPT=49704 DPT=32125 SEQ=1668671542 ACK=3151037026 WINDOW=229 RES=0x00 ACK URGP=0 OPT (0101080A251AE0816F0DFBA1)
...省略数十万字
Oct 21 14:34:49 iZwz92a65mqaa9jbf5z4irZ kernel: TRACE: mangle:POSTROUTING:policy:1 IN= OUT=cni0 SRC=172.16.3.85 DST=10.111.0.25 LEN=52 TOS=0x00 PREC=0x00 TTL=63 ID=2234 DF PROTO=TCP SPT=49704 DPT=80 SEQ=1668671625 ACK=3151037305 WINDOW=237 RES=0x00 ACK URGP=0 OPT (0101080A251AE0856F0DFBA4)
IPVS工作的HOOK点:
IPVS的实现利用了Netfilter的三个Hook点,分别是:NF_INET_LOCAL_IN、NF_INET_LOCAL_OUT和NF_INET_FORWARD。在每个Hook点,IPVS注册了两个钩子函数
如下表所示,IPVS中对于Request和Reply的定义,是按照由外部客户端到IPVS内部的报文为Request;而由IPVS内部回复到外部客户端的报文为Reply。所以,Hook函数的命名中带有request的都对应IPVS核心函数ip_vs_in;而Hook函数命名中带有reply的函数都对应IPVS的核心函数ip_vs_out。
Hook点LOCAL_IN
在Hook点NF_INET_LOCAL_IN上,IPVS挂载了两个函数ip_vs_reply4和ip_vs_remote_request4,其中前者优先级高于后者。前者ip_vs_reply4主要用于NAT/Masq转发模式,其核心处理函数为ip_vs_out,负责处理IPVS系统回复给外部客户端的报文,包括修改IP地址等。
由于是回复报文,要求系统中已经存在连接,否则处理IPVS系统中真实服务器可能发送的ICMP报文。
static unsigned int ip_vs_out(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, int af)
{
/* Check if the packet belongs to an existing entry
*/
cp = pp->conn_out_get(ipvs, af, skb, &iph);
if (likely(cp)) {
if (IP_VS_FWD_METHOD(cp) != IP_VS_CONN_F_MASQ)
goto ignore_cp;
return handle_response(af, skb, pd, cp, &iph, hooknum);
}
if (sysctl_nat_icmp_send(ipvs) &&
(pp->protocol == IPPROTO_TCP ||
pp->protocol == IPPROTO_UDP ||
pp->protocol == IPPROTO_SCTP)) {
另外一个Hook函数ip_vs_remote_request4,其核心函数为ip_vs_in,负责处理由外部客户端进入IPVS系统的报文,如果没有可用的连接,将使用调度函数进行调度处理,创建连接结构。
static unsigned int ip_vs_in(struct netns_ipvs *ipvs, unsigned int hooknum, struct sk_buff *skb, int af)
{
if (unlikely(!cp)) {
int v;
if (!ip_vs_try_to_schedule(ipvs, af, skb, pd, &v, &cp, &iph))
return v;
}
ip_vs_in_stats(cp, skb);
ip_vs_set_state(cp, IP_VS_DIR_INPUT, skb, pd);
if (cp->packet_xmit)
ret = cp->packet_xmit(skb, cp, pp, &iph);
}
Hook点LOCAL_OUT
与前一节的NF_INET_LOCAL_IN Hook点不同,此处的NF_INET_LOCAL_OUT的Hook点用于处理IPVS本机发送的报文。而前者用于处理外部客户端进入IPVS系统的报文。
在Hook点NF_INET_LOCAL_OUT上,IPVS挂载了两个函数ip_vs_local_reply4和ip_vs_local_request4,其中前者优先级高于后者。前者ip_vs_local_reply4的核心函数为ip_vs_out,主要用于NAT/Masq转发模式,负责NAT地址的修改。
函数ip_vs_local_request4的核心函数为ip_vs_in,其负责处理由本机应用层进入IPVS系统的报文的调度和发送。
Hook点FORWARD
在Hook点NF_INET_FORWARD上,IPVS挂载了两个函数ip_vs_forward_icmp和ip_vs_reply4,其中前者优先级高于后者。前者ip_vs_forward_icmp的核心处理函数为ip_vs_in_icmp,用于处理外部进入IPVS系统的ICMP报文,将其调度到对应的真实服务器上。
static int ip_vs_in_icmp(struct netns_ipvs *ipvs, struct sk_buff *skb, int *related, unsigned int hooknum)
{
cp = pp->conn_in_get(ipvs, AF_INET, skb, &ciph);
if (!cp) {
int v;
if (!sysctl_schedule_icmp(ipvs))
return NF_ACCEPT;
if (!ip_vs_try_to_schedule(ipvs, AF_INET, skb, pd, &v, &cp, &ciph))
return v;
new_cp = true;
}
verdict = ip_vs_icmp_xmit(skb, cp, pp, offset, hooknum, &ciph);
函数ip_vs_reply4,核心函数为ip_vs_out,主要用于NAT/Masq转发模式,负责NAT地址的修改。对于真实服务器回复的报文,其目的地址为外部客户端的地址,非IPVS系统的虚拟地址,所以其将进入此转发Hook点,此时进行SNAT转换,将源地址转换为IPVS的虚拟地址。
以上路径参考内核版本 4.15
conntrack:
如下图所示,Netfilter 在四个 Hook 点对包进行跟踪,之前以为的过raw表PREROUTING链时会创建一个conntrack连接,实际不是直接填充到表内的
- PRE_ROUTING和LOCAL_OUT:调用 nf_conntrack_in() 开始连接跟踪, 正常情况下会创建一条新连接记录,然后将 conntrack entry 放到unconfirmed list。为什么是这两个 hook 点呢?因为它们都是新连接的第一个包最先达到的地方,
- PRE_ROUTING是外部主动和本机建连时包最先到达的地方
- LOCAL_OUT是本机主动和外部建连时包最先到达的地方
- POST_ROUTING和LOCAL_IN:调用 nf_conntrack_confirm() 将 nf_conntrack_in() 创建的连接移到 confirmed list。同样要问,为什么在这两个 hook 点呢?因为如果新连接的第一个包没有被丢弃,那这 是它们离开 netfilter 之前的最后 hook 点:
- 外部主动和本机建连的包,如果在中间处理中没有被丢弃,LOCAL_IN是其被送到应用(例如 nginx 服务)之前的最后 hook 点
- 本机主动和外部建连的包,如果在中间处理中没有被丢弃,POST_ROUTING 是其离开主机时的最后 hook 点
一点个人理解:
nf_conntrack_in创建的连接是在一个内存列表内,过了input再由nf_conntrack_confirm来插入conntrack表,而ipvs在input链上做了一些流量劫持的动作,需要做nat转换ecs ip+nodeport到pod ip+port,ipvs没丢这个包才会被正经插入到conntrack表内,因此这个问题需要继续往下看
排查分析过程:
采集conntrack表里面 syn状态的信息,判断一下session有没有被填充到conntrack里面,
for i in {1..600};do echo `date` >>c.log;ipvsadm -Ln -c |grep -i syn >>c.log;grep -i syn /proc/net/nf_conntrack >>c.log;sleep 1s.done
实际conntrack还支持状态的采集,以及源目端口的用法等,如:
conntrack -L -p tcp --dport 8611 --state SYN_RECV
###支持以下状态
--state [NONE | SYN_SENT | SYN_RECV | ESTABLISHED | FIN_WAIT | CLOSE_WAIT | LAST_ACK | TIME_WAIT | CLOSE | LISTEN]
TCP state
参见conntrack man手册
http://conntrack-tools.netfilter.org/conntrack.html
iptables添加trace日志,为什么添加trace日志,需要看被丢弃的syn包到了哪个表链上来判断问题所在,如 过了raw表的prerouting,manage表的prerouting等等,最终input后没有下文了,说明ipvs大概率是有问题的
iptables -t raw -A OUTPUT -p tcp -s 源ip --dport 目标port -j TRACE
iptables -t raw -A PREROUTING -p tcp -s 源ip --dport 目标port -j TRACE
###排查过程中发现有的时候不添加port的话trace不工作
使用bcctools跟踪重传的包,实际这个问题在排查的时候,bcctools没能抓到被drop的syn包
# ./tcpretrans
Tracing retransmits ... Hit Ctrl-C to end
TIME PID IP LADDR:LPORT T> RADDR:RPORT STATE
14:14:49 3300458 4 192.168.40.72:45584 R> 192.168.0.2:10255 SYN_SENT
14:14:49 3300480 4 192.168.40.182:50610 R> 192.168.0.242:10250 SYN_SENT
14:14:50 0 4 192.168.40.182:53896 R> 192.168.0.241:10250 SYN_SENT
14:14:50 0 4 192.168.40.182:50500 R> 192.168.0.242:10250 SYN_SENT
14:14:50 0 4 192.168.40.182:36276 R> 192.168.0.2:10250 SYN_SENT
tcpdump指定flags采集syn的包
tcpdump可以组合flags抓包,就不用每次都下载到本地看了
tcpdump -i any "tcp[tcpflags] & (tcp-syn) != 0" and "tcp[tcpflags] & (tcp-ack) != 0" -nv
上面的手段用完,依然不能确认问题在哪,就需要采集全量的conntrack 以及 ipvs 相关连接做对比了,这个问题的原因是因为当net.ipv4.vs.conn_reuse_mode=1时,根据ip_vs_in()的处理逻辑,当开启了net.ipv4.vs.conntrack时,且conntrack表中相同的五元组是time_wait状态,则会 DROP 掉第一个 SYN 包,等待 SYN 的重传然后分配新的连接,有 1S 延迟。而 Kube-proxy 在 IPVS 模式下,使用了 iptables 进行MASQUERADE,也正好开启了net.ipv4.vs.conntrack。
对应的代码说明:
下面来自 可参考 Julian Anastasov (这位可是写在ipvs源码里面的Authors之一)的回复:
What could be causing the delay? How can we get rid of it? It should be this code that leads to delay: if (uses_ct) return NF_DROP; What happens is that we drop SYN packet that hits IPVS connection in TIME_WAIT state if such connection uses Netfilter connection tracking (conntrack=1). The conn_reuse_mode=1 relies on selecting different real server but as we can not alter the Netfilter conntrack tuple after it is confirmed, we drop the conntrack, the IPVS connection and current packet and expect next SYN (retransmitted after 1 second, as you observe) to create new IPVS connection and corresponding conntrack to some available real server. And that is what happens after 1 second. To get rid of this delay you have the following options: 1. do not enable IPVS conntrack mode (can be slower to create and drop conntrack on every packet), use conntrack=0 for this. This allows IPVS to ignore the TIME_WAIT connection and to create a new one. 2. Use NOTRACK for IPVS connections, it should be faster because conntracks are not created/removed iptables -t raw -A PREROUTING -p tcp -d VIP --dport VPORT -j CT --notrack For local clients use -A OUTPUT -o lo If needed, such traffic can be matched with -m state --state UNTRACKED 3. Reduce the TIME_WAIT timeout in IPVS source, table tcp_timeouts[]. It does not solve the problem but reduces its rate. Regards -- Julian Anastasov <ja@ssi.bg>
一句话总结:
tcpdump能抓到包的话,说明过了网卡,以及cpu中断等环节,即非前后端丢包,抓包之后是 ip_rcv再到iptables,根据iptables的链表规则,先进raw表,可以在raw表上开启trace看看是否进了iptables来判断是否存在ip_rcv丢包的情况,进了iptables后,再看trace跟踪的流走的相关表链,以及fnat发生在哪个表链上(看源目的变化),当使用了IPVS的的时候,ipvs会在input链上拿走数据进行处理,做完转换才会往conntrack表里填充,而不是过了raw表后就填充,实际上net.ipv4.vs.conn_reuse_mode=0时在老版的内核中也会引起问题,详情可见我的另一篇文章《k8s网络诊断之我的流量去哪了》,借着这个问题,让我们学习了一个包进到系统内走的各个路径,同时也学到了iptables的trace 以及conntrack 表工具的使用,写这个文档主要是想分享一下网络问题的处理思路,其中很多资料借鉴以及个人理解可能会有偏差,欢迎斧正
参考文献:
《One second connection delay in masquerading mode》
《连接跟踪(conntrack):原理、应用及 Linux 内核实现》
《Monitoring and Tuning the Linux Networking Stack: Receiving Data》