Featured image of post 解决一个非常隐蔽的连接问题

解决一个非常隐蔽的连接问题

我们公司的一部分应用是通过docker swarm部署的,这部分应用对外提供后端的接口能力(以下简称实体地址),实体地址通过nginx网关代理之后对外提供使用(以下简称代理地址)。

本次出问题的测试环境所有应用都位于一台服务器上面,开始是测试和研发反馈发现诡异现象,代理地址在外部可以访问,同一台服务器的管理程序在外部也可以访问,但是实体地址在外部无法访问。

我开始只是以为是iptables的问题,因为对我来说遇到实体访问不了遇到最多的基本上就就是iptables转发出问题了,我想着大不了清理掉iptables的CHAIN,重启dockerd嘛,肯定能好的,没有太重视,前面都让研发自行在排查,后面发现无果。然后我上去看了一眼我发现dockerd bip的网络以及docker_gwbridge的网段跟公司内网冲突了,所以简单跟研发说了一下,说指定下bip再重建docker_gwbridge应该能好。

并提供了命令行:

1
2
# 编辑dockerd配置文件
$ vi /etc/docker/daemon.json
1
2
3
4
{
    ...,
    "bip":"192.168.222.1/24"
}
1
2
# 测试环境可以清理掉所有service,为后面重建docker_gwbridge做准备
$ docker service ls | awk '{print $1}' | xargs -L1 docker service rm
1
2
# 断开docker_gwbridge的连接
$ docker network disconnect -f docker_gwbridge
1
2
# 删除docker_gwbridge这个network
$ docker network rm docker_gwbridge
1
2
3
4
5
6
7
# 重建它,指定到固定网段
$ docker network create \
  --subnet 192.168.223.1/24 \
  --opt com.docker.network.bridge.name=docker_gwbridge \
  --opt com.docker.network.bridge.enable_icc=false \
  --opt com.docker.network.bridge.enable_ip_masquerade=true \
  docker_gwbridge
1
2
# 清理掉iptables规则,其实还应该删除网桥,但是我偷懒了
$ itpables -F && iptables -t nat -F
1
2
# 重启dockerd
$ systemctl restart docker

本来觉得这样应该就没问题了,但是我自己上去一看问题依旧!!很显然问题没这么简单,我只能从头梳理了一遍思路:

1、程序肯定是没有问题的;

2、代理能够正常用说明数据包在服务器内流转也是没问题的。

3、查看资源,使用top,vmstat等命令查看了一番发现服务器各项指标都没有异常。

4、本地使用ping服务器ip正常返回,无丢包,延迟也正常。

我大概考虑是否是网络波动(被自我否定,因为网络波动不可能只影响部分端口),但是问题只能出在网络上面,实在没有好办法了,想通过netstat来看看网卡的状态:

1
netstat -s | grep -i listen

没有装。。。查了下yum源里面也没有这个包,算了直接通过抓包来查吧:

1
2
# 先把tcpdump装了
$ yum install -y tcpdump

由于只是swarm相关的出问题,不涉及其他的,我只监听了docker_gwbridge:

1
tcpdump src host 192.168.31.27 -c 1000000 -i docker_gwbridge

不看不要紧一看问题就出来了,我们都知道tcp连接和断开的过程就是我们所说的三次握手和4次挥手嘛,每次包的头都是不同的,对于tcpdump而言呢,主要是以下这么几种:

  • [S] : SYN(开始连接)

  • [P] : PSH(推送数据)

  • [F] : FIN (结束连接)

  • [R] : RST(重置连接)

  • [.] : 没有 Flag,由于除了 SYN 包外所有的数据包都有ACK,所以一般这个标志也可表示 ACK

具体到我的这个里面我发现只要我发起访问,只有Sync包没有ACK,多么像一个卑微的小舔狗得不到女神的怜爱,没有ACK就是服务器不会告诉客户端我收到你的消息了,难怪会超时。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
19:50:30.254988 IP 192.168.31.27.12718 > 192.168.223.2.pago-services1: Flags [S], seq 3256370190, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:30.944734 IP 192.168.31.27.12711 > 192.168.223.2.ndmps: Flags [S], seq 2534429468, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:30.944767 IP 192.168.31.27.12712 > 192.168.223.2.ndmps: Flags [S], seq 3820954260, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:31.267170 IP 192.168.31.27.12717 > 192.168.223.2.pago-services1: Flags [S], seq 3539768905, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:31.267211 IP 192.168.31.27.12718 > 192.168.223.2.pago-services1: Flags [S], seq 3256370190, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:31.869670 IP 192.168.31.27.12701 > 192.168.223.2.30007: Flags [S], seq 1305196162, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:31.869712 IP 192.168.31.27.12702 > 192.168.223.2.30007: Flags [S], seq 266524004, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:32.089775 IP 192.168.31.27.12723 > 192.168.223.2.30006: Flags [S], seq 3463287100, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:32.090212 IP 192.168.31.27.12722 > 192.168.223.2.30006: Flags [S], seq 1227445272, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:32.113028 IP 192.168.31.27.12706 > 192.168.223.2.30007: Flags [S], seq 1635773819, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:32.355153 IP 192.168.31.27.12727 > 192.168.223.2.30006: Flags [S], seq 3794453357, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:33.099053 IP 192.168.31.27.12723 > 192.168.223.2.30006: Flags [S], seq 3463287100, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:33.099070 IP 192.168.31.27.12722 > 192.168.223.2.30006: Flags [S], seq 1227445272, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:33.267205 IP 192.168.31.27.12717 > 192.168.223.2.pago-services1: Flags [S], seq 3539768905, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:33.267224 IP 192.168.31.27.12718 > 192.168.223.2.pago-services1: Flags [S], seq 3256370190, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:33.358501 IP 192.168.31.27.12727 > 192.168.223.2.30006: Flags [S], seq 3794453357, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:34.947814 IP 192.168.31.27.12712 > 192.168.223.2.ndmps: Flags [S], seq 3820954260, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:34.947846 IP 192.168.31.27.12711 > 192.168.223.2.ndmps: Flags [S], seq 2534429468, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:35.112436 IP 192.168.31.27.12722 > 192.168.223.2.30006: Flags [S], seq 1227445272, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:35.112447 IP 192.168.31.27.12723 > 192.168.223.2.30006: Flags [S], seq 3463287100, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:35.370682 IP 192.168.31.27.12727 > 192.168.223.2.30006: Flags [S], seq 3794453357, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:37.274205 IP 192.168.31.27.12717 > 192.168.223.2.pago-services1: Flags [S], seq 3539768905, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:37.274220 IP 192.168.31.27.12718 > 192.168.223.2.pago-services1: Flags [S], seq 3256370190, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
19:50:39.112383 IP 192.168.31.27.12723 > 192.168.223.2.30006: Flags [S], seq 3463287100, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0

到这儿我大概想起来一个可能:SYNC包被内核因为某种原因丢弃了,所以server端没给ACK回应。这让我想起了我很久以前看过的一个文章大意就是不要在NAT环境中开启net.ipv4.tcp_tw_recycle,net.ipv4.tcp_tw_recycle可以开启TCP连接中TIME-WAIT sockets的快速回收,一部分运维或者开发人员认为这个设置对于追求高并发的服务器来说是有帮助的。但是在NAT之后,在启用tcp_tw_recycle的情况下,一旦有客户端断开连接,服务器可能就会丢弃那些时间戳较小的客户端的SYN包,这也就导致了访问极不稳定,对外表现就是服务时好时坏。

docker容器的访问可以直接通过桥接在eth0的docker0网络直接访问,不受影响,而swarm的ingress网络事实上就是vip上面实现了一个NAT功能,所以很不幸地中了这一次招。

引用某位大佬的结论:

最合适的解决方案是增加更多的四元组数目,比如,服务器可用端口,或服务器IP,让服务器能容纳足够多的TIME-WAIT状态连接。在我们常见的互联网架构中(NGINX反代跟NGINX,NGINX跟FPM,FPM跟redis、mysql、memcache等),减少TIME-WAIT状态的TCP连接,最有效的是使用长连接,不要用短连接,尤其是负载均衡跟web服务器之间。尤其是链家事件中的PHP连不上redis

在服务端,不要启用net.ipv4.tcp_tw_recycle,除非你能确保你的服务器网络环境不是NAT。在服务端上启用net.ipv4.tw_reuse对于连接进来的TCP连接来说,并没有任何卵用。 在客户端(尤其是服务器上,某服务以客户端形式运行时,比如上面提到的nginx反代,连接着redis、mysql的FPM等等)上启用net.ipv4.tcp_tw_reuse,还算稍微安全的解决TIME-WAIT的方案。再开启net.ipv4.tcp_tw_recycle的话,对客户端(或以客户端形式)的回收,也没有什么卵用,反而会发生很多诡异的事情(尤其是FPM这种服务器上,相对nginx是服务端,相对redis是客户端)。

请各位不要随便动内核参数,再次感谢!!!

问题找到了解决起来当然很快,两行命令敲下去发现啥都好了:

1
2
$ sed -i 's/net.ipv4.tcp_tw_recycle=1/net.ipv4.tcp_tw_recycle=0/' /etc/sysctl.conf
$ sysctl -p

引用部分结论来自于本文章,感谢!