拓扑图
内核版本
v3.16
现象
内部环境集群拓扑下,本地终端浏览器访问web服务,偶尔浏览器报”网络访问拒绝”错误
分析
- 本地终端上抓包发现,tcp三次握手失败,终端的第一个syn包未能得到回复。
- 从主机进入ingress-nginx的网络命名空间,可发现nginx已经接受到syn。
- 分别对k8s_node1与k8s_node2的主机抓包,发现syn包是在node节点丢失。
- 由于tcpdump抓包的是在
__netif_receive_skb
函数,确定包未被netfilter处理,那么只可能是协议栈丢包了。包传递过程如下: - 通过下面命令,从主机进入容器网络命名空间使用tcpdump转包。
docker inspect -f '' <container_name_or_id> sudo nsenter -t <container_pid>
代码
tcp_conn_request
用于接受方处理第一次syn包,该函数处理失败情况下,会写入tcp统计信息,将LINUX_MIB_LISTENDROPS
值加1
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
...
if (tmp_opt.saw_tstamp &&
tcp_death_row.sysctl_tw_recycle &&
(dst = inet_csk_route_req(sk, &fl4, req)) != NULL &&
fl4.daddr == saddr) {
if (!tcp_peer_is_proven(req, dst, true)) {
//由于timestamp与sysctl_tw_recycle异常会执行下面函数
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}
...
return 0;
drop_and_release:
dst_release(dst);
drop_and_free:
reqsk_free(req);
drop:
//被丢包的时候,都会走到该函数,将`LINUX_MIB_LISTENDROPS`对应的值加1
NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
return 0;
...
}
INUX_MIB_LISTENDROPS
与LINUX_MIB_
sysctl_tw_recycle的值都可以在
/proc/net/netstat`中可以读取到。- 复重现现象,可以发现这个两个值都在增加,那基本确定问题出现在
LINUX_MIB_PAWSPASSIVEREJECTED
。 tcp_peer_is_proven
该函数判断同一终端的时间戳是否递增,不是递增就丢包。内网环境下,多个时间不完全同步的主机发tcp请求,包经过ingress-inginx
会被做snat将包转发到node节点,所以node协议栈判断发包源时间戳不递增。
tcp包状态统计
cat /proc/net/netstat
可获取tcp的状态统计数据,数据格式比较乱,使用脚本处理数据显示格式
cat /proc/net/netstat | awk '(f==0) {name=$1; i=2; while ( i<=NF) {n[i] = $i; i++ }; f=1; next} (f==1){ i=2; while ( i<=NF){ printf "%s%s = %d\n", name, n[i], $i; i++}; f=0} '
PAWSPassiveRejected
当tcp_tw_recycle
启动时,内核会将在time_wait
状态的连接重用。当有新连接过来且启动时间戳的时候,它检查传入连接的时间戳,并与之前的进行比较。如果传入连接的时间戳早于之前的时间戳,则被认为是一个无效的包。未等待2ML结束的连接被重用后,加入了一个时间判断机制,判断是否为旧包。
解决办法
关闭tw_recycle
,执行命令sudo sysctl -w net.ipv4.tcp_tw_recycle=0