我们这次看一个丢包问题的分析方法
丢包是指网络数据的收发过程中,由于各种原因,数据包还没到应用程序,就被丢弃了,被丢包的数量除以总的传输包数,就是丢包率
我们今天,就拿着Nginx进行举例
hping3和curl是Nginx的客户端
我们进行启动一个Docket镜像
在容器启动之后,尝试ping对应的Nginx服务器
记性相关的hping3命令,在其中,我们使用的是TCP命令进行的测试,而不是ICMP的ping命令
获取输出如下
# -c表示发送10个请求,-S表示使用TCP SYN,-p指定端口为80
$ hping3 -c 10 -S -p 80 192.168.0.30 HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=3 win=5120 rtt=7.5 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=4 win=5120 rtt=7.4 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=3.3 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=7 win=5120 rtt=3.0 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=3027.2 ms — 192.168.0.30 hping statistic — 10 packets transmitted, 5 packets received, 50% packet loss round-trip min/avg/max = 3.0/609.7/3027.2 ms |
hping的输出中,可以发现有10个请求包,只有5个回复,50%的包都丢失了
那么,我们就需要分析,哪里出现了丢包的问题,对于网络协议栈,从下到上的不同层次都有着丢包问题的可能性
接下来,我们就根据网络协议栈中丢包的可能性,列出如下的一张图
从最下层的,黑盒的网络传输来看起
网卡收包之后,环形缓冲区可能会溢出
链路层有QOS导致丢弃
IP层可能有路由失败 MTU太大的问题
传输层有着,端口未监听资源占用太大的问题
套接字层,有着套接字缓冲区溢出的问题
应用层有着应用程序异常导致的丢包问题
我们假设VM1和VM2本机没有问题,而是直接看容器相关的问题
我们直接进入容器内部,根据协议栈,逐步排查问题
首先是链路层
首先是最底下的链路层,可能存在缓冲区溢出的问题,但是如果出现了溢出的问题,Linux会在网卡收发的过程中,记录下错误次数
可以通过ethtool 或者 netstat来查看网卡丢包情况,比如如下的查看
root@nginx:/# netstat -i
Kernel Interface table Iface MTU RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg eth0 100 31 0 0 0 8 0 0 0 BMRU lo 65536 0 0 0 0 0 0 0 0 LRU |
输出中的RX-OK RX-ERR RX-DRP RX-OVR
分别表示接受总包数 总错误包数,进入Ring Buffer后因为其他原因导致的丢包数,RingBuffer溢出导致的丢包数
在输出中,没有任何的错误,说明容器的虚拟网卡没有丢包
不过要注意,如果要tc等工具配置QoS,那么导致的丢包,就不会统计到这里面
我们就需要查看是否配置了tc规则,是否有丢包
root@nginx:/# tc -s qdisc show dev eth0
qdisc netem 800d: root refcnt 2 limit 1000 loss 30% Sent 432 bytes 8 pkt (dropped 4, overlimits 0 requeues 0) backlog 0b 0p requeues 0 |
在tc的输出中,我们看出来了,eth0配置了一个网络模拟排队规则,配置了丢包率为30%
那么我们直接删除掉 netem模块就可以了,执行如下的命令,删除tc中的netem模块
root@nginx:/# tc qdisc del dev eth0 root netem loss 30% |
删除后继续执行刚刚的命令,看看还有没有问题
$ hping3 -c 10 -S -p 80 192.168.0.30
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=5120 rtt=7.9 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=2 win=5120 rtt=1003.8 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=5 win=5120 rtt=7.6 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=6 win=5120 rtt=7.4 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=5120 rtt=3.0 ms — 192.168.0.30 hping statistic — 10 packets transmitted, 5 packets received, 50% packet loss round-trip min/avg/max = 3.0/205.9/1003.8 ms |
从hping3的输出中,可以看出,还是一如既往的丢包,RTT的波动也很大
那么,链路层已经排查完了,我们看下网络层和传输层的问题
在容器内部,查看netstat -s 命令,查看协议的收发汇总,以及错误信息
root@nginx:/# netstat -s
Ip: Forwarding: 1 //开启转发 31 total packets received //总收包数 0 forwarded //转发包数 0 incoming packets discarded //接收丢包数 25 incoming packets delivered //接收的数据包数 15 requests sent out //发出的数据包数 Icmp: 0 ICMP messages received //收到的ICMP包数 0 input ICMP message failed //收到ICMP失败数 ICMP input histogram: 0 ICMP messages sent //ICMP发送数 0 ICMP messages failed //ICMP失败数 ICMP output histogram: Tcp: 0 active connection openings //主动连接数 0 passive connection openings //被动连接数 11 failed connection attempts //失败连接尝试数 0 connection resets received //接收的连接重置数 0 connections established //建立连接数 25 segments received //已接收报文数 21 segments sent out //已发送报文数 4 segments retransmitted //重传报文数 0 bad segments received //错误报文数 0 resets sent //发出的连接重置数 Udp: 0 packets received … TcpExt: 11 resets received for embryonic SYN_RECV sockets //半连接重置数 0 packet headers predicted TCPTimeouts: 7 //超时数 TCPSynRetrans: 4 //SYN重传数 … |
netstat汇总了IP ICMP TCP UDP的收发信息
我们观察错误数 丢包数 重传数
对于TCP协议,我们发生了丢包和重传,分别是
11次的失败重传
4次重传
11次半连接重置
4次SYN重传
7次超时
那么TCP出现相对的错误,我们就考虑从TCP入手解决
对于TCP可能出现的问题,首先该考虑的是iptables导致的内核丢包的问题
我们首先查看iptables的配置规则,其中包含了filter nat mangle raw,每个表又包含一系列的链,用于对iptables规则进行分组管理
丢包问题自然要和filter进行队列,我们就要查看filter汇总有没有DROP 和 Reject的规则
我们先查看iptable的相关统计信息
我们可以使用Iptables -nvl,查看其统计信息,
# 在主机中执行
$ docker exec -it nginx bash # 在容器中执行 root@nginx:/# iptables -t filter -nvL Chain INPUT (policy ACCEPT 25 packets, 1000 bytes) pkts bytes target prot opt in out source destination 6 240 DROP all — * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.29999999981 Chain FORWARD (policy ACCEPT 0 packets, 0 bytes) pkts bytes target prot opt in out source destination Chain OUTPUT (policy ACCEPT 15 packets, 660 bytes) pkts bytes target prot opt in out source destination 6 264 DROP all — * * 0.0.0.0/0 0.0.0.0/0 statistic mode random probability 0.29999999981 |
上面给出了两条DROP规则的统计数值,分别在INPUT和OUTPUT的链中,统计的规则是一样的,进行30%的随机丢包
而0.0.0.0匹配所有的源IP和目的IP,导致这条的生效
我们可以进行直接的删除规则
root@nginx:/# iptables -t filter -D INPUT -m statistic –mode random –probability 0.30 -j DROP
root@nginx:/# iptables -t filter -D OUTPUT -m statistic –mode random –probability 0.30 -j DROP |
删除后,问题是否就被解决了呢?
我们可以重新执行刚才的hping3命令,查看是否正常
$ hping3 -c 10 -S -p 80 192.168.0.30
HPING 192.168.0.30 (eth0 192.168.0.30): S set, 40 headers + 0 data bytes len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=0 win=5120 rtt=11.9 ms len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=1 win=5120 rtt=7.8 ms … len=44 ip=192.168.0.30 ttl=63 DF id=0 sport=80 flags=SA seq=9 win=5120 rtt=15.0 ms — 192.168.0.30 hping statistic — 10 packets transmitted, 10 packets received, 0% packet loss round-trip min/avg/max = 3.3/7.9/15.0 ms |
接下来,我们校验一下,在此之后Nginx能不能正常响应HTTP请求了
进行curl命令,检查其响应
可能会发现Nginx没法响应,而tcp的连接还可以正常响应
那么我们需要考虑使用tcpdump进行相关的抓取操作
root@nginx:/# tcpdump -i eth0 -nn port 80
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes |
查看tcp的输出
在tcp的三次握手中,前三次没有问题,第四次进行了客户端的连接关闭
14:40:00.589235 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [S], seq 332257715, win 29200, options [mss 1418,sackOK,TS val 486800541 ecr 0,nop,wscale 7], length 0
14:40:00.589277 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [S.], seq 1630206251, ack 332257716, win 4880, options [mss 256,sackOK,TS val 2509376001 ecr 486800541,nop,wscale 7], length 0 14:40:00.589894 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [.], ack 1, win 229, options [nop,nop,TS val 486800541 ecr 2509376001], length 0 14:40:03.589352 IP 10.255.255.5.39058 > 172.17.0.2.80: Flags [F.], seq 76, ack 1, win 229, options [nop,nop,TS val 486803541 ecr 2509376001], length 0 14:40:03.589417 IP 172.17.0.2.80 > 10.255.255.5.39058: Flags [.], ack 1, win 40, options [nop,nop,TS val 2509379001 ecr 486800541,nop,nop,sack 1 {76:77}], length 0 |
而第三个包和第四个包之间的,存在3秒的延迟,这是因为curl的重置
也就是握手后,发送HTTP GET请求失败了
关于这一个观点的论证,可以重新执行netstat -i 观察网卡有没有丢包
那么我们考虑一下SYN包可以接受,但是TCP包被拒绝了,ip端口一致,是否是MTU配置导致的呢?
因为MTU限制了,导致SYN包能通过,HTTP请求包被拒绝了
我们将MTU设置为1500,进行了相关的命令
这样就解决了这个丢包问题