You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

255 lines
20 KiB
Markdown

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 23 | 路径排查:没有网络设备权限要如何做排查?
你好,我是胜辉。
有一位同事问我“大V你的课程会教我们排查家庭网络问题的技巧吗”这是一个有意思的问题。我们课程里用到了大量的抓包分析虽然这套方法功效甚大但是在很多场景下也有比它更加合适的方法。在网络路径的排查方面这一点体现得尤为明显。比如一些小工具往往能起到意想不到的大作用。
不过,一个现实的问题是,我们一般不是专职的网络工程师,也没有相关的网络设备的查看权限,那要如何在这种条件下,尽可能做一些网络层排查的工作呢?这就需要我们对**协议**有深入的理解,对工具能做到灵活地运用。
在接下来的实战三模块“不用抓包就能做的网络排查篇”,我们就来学习一下网络层的案例和排查技巧。这样你以后遇到跟网络路径异常、丢包、时通时不通等问题的时候,不仅有抓包分析这样的“重型武器”,也有几把趁手的“瑞士军刀”,可以精确快速地搞定这些问题。
好,我们还是从案例开始。
## 为何TCP连接时常失败
有一次我们的一个内部客户团队报告了一个TCP连接失常的问题。这是一个MySQL的服务它有两台服务器都在同一个LB VIP的后面。这个团队发现从他们的客户端到这个LB VIP的TCP连接时常有失败的情况发生于是我们介入排查。
这个服务的具体架构是这样的客户团队从他们的客户端发起TCP连接到这台LB上的VIP这个VIP后面是两台MySQL主机LB把请求转发给其中的一台。这里我们使用了LB的一个特性就是可以平时只启用一台后端主机只有当这台主机离线时LB才会自动把流量切换到另外一台上。
![](https://static001.geekbang.org/resource/image/94/00/94b9dfe3ceb99f06126faf85eb028500.jpg?wh=2000x731)
> 补充那为什么不用MySQL双主 + Keep alive的方案呢其实在当时我们的云网络设计里漂移IP是不可以配置在主机上的只有LB可以这么配置所以就采用了这样的方案。
在前面很多次课里我都提到过用“排除法”或者说是“逐段验证法”来开展排查。这次也不例外我们就从客户端直接去连接MySQL主机重试了很多次TCP连接都可以正常建立。于是MySQL主机的嫌疑基本可以排除我们又集中到对LB的排查上来。
![](https://static001.geekbang.org/resource/image/5d/0e/5d65cc33e0dcac560610779f92c2070e.jpg?wh=2000x435)
在客户端上测试TCP连接的工具有很多其中**nc**是比较好用的一个。我们就选了一台客户端,**用nc命令对LB VIP发起了多次TCP连接**。我们看看当时的输出:
```bash
root@client:~# while true;do nc -zv 10.111.1.111 3306;sleep 1;done
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
nc: connect to 10.111.1.111 port 3306 (tcp) failed: Connection timed out
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
nc: connect to 10.111.1.111 port 3306 (tcp) failed: Connection timed out
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
nc: connect to 10.111.1.111 port 3306 (tcp) failed: Connection timed out
Connection to 10.111.1.111 3306 port [tcp/mysql] succeeded!
```
差不多每四次里就有一次的连接请求会超时失败确实是有问题。而在LB那边发生了什么呢
我们做了第二次nc测试。这次我们还在客户端和LB上都分别做了tcpdump抓包这样就可以观察到具体的握手阶段的情况了。客户端做nc的结果还是跟上面这个类似。我们再来看看客户端的抓包的情况下面是tcpdump命令的输出
```bash
23:32:37.084634 IP (tos 0x0, ttl 64, id 33781, offset 0, flags [DF], proto TCP (6), length 60)
    10.222.22.22.47849 > 10.111.1.111.3306: Flags [S], cksum 0x2eed (incorrect -> 0x7045), seq 3882715171, win 14600, options [mss 1460,sackOK,TS val 750324694 ecr 0,nop,wscale 7], length 0
23:32:38.082468 IP (tos 0x0, ttl 64, id 33782, offset 0, flags [DF], proto TCP (6), length 60)
    10.222.22.22.47849 > 10.111.1.111.3306: Flags [S], cksum 0x2eed (incorrect -> 0x6f4b), seq 3882715171, win 14600, options [mss 1460,sackOK,TS val 750324944 ecr 0,nop,wscale 7], length 0
23:32:40.086484 IP (tos 0x0, ttl 64, id 33783, offset 0, flags [DF], proto TCP (6), length 60)
    10.222.22.22.47849 > 10.111.1.111.3306: Flags [S], cksum 0x6d56 (correct), seq 3882715171, win 14600, options [mss 1460,sackOK,TS val 750325445 ecr 0,nop,wscale 7], length 0
23:32:44.098519 IP (tos 0x0, ttl 64, id 33784, offset 0, flags [DF], proto TCP (6), length 60)
    10.222.22.22.47849 > 10.111.1.111.3306: Flags [S], cksum 0x696b (correct), seq 3882715171, win 14600, options [mss 1460,sackOK,TS val 750326448 ecr 0,nop,wscale 7], length 0
```
这里的Flags \[S\]表示这个报文的TCP标志位为SYN。显然客户端连续发送了4个SYN但都没有收到SYN+ACK。
那么在LB这边的tcpdump情况又如何呢我们来看一下
```bash
23:31:30.210092 IP 10.222.22.22.47837 > 10.111.1.111.3306: Flags [S], seq 1376708963, win 14600, options [mss 1460,sackOK,TS val 750308151 ecr 0,nop,wscale 7], length 0
23:31:30.210099 IP 10.111.1.111.3306 > 10.222.22.22.47837: Flags [S.], seq 2920814195, ack 1376708964, win 8190, options [mss 1460], length 0
23:31:32.214334 IP 10.222.22.22.47837 > 10.111.1.111.3306: Flags [S], seq 1376708963, win 14600, options [mss 1460,sackOK,TS val 750308652 ecr 0,nop,wscale 7], length 0
23:31:32.214339 IP 10.111.1.111.3306 > 10.222.22.22.47837: Flags [S.], seq 2920814195, ack 1376708964, win 8190, options [mss 1460], length 0
23:31:36.611535 IP 10.222.22.22.47837 > 10.111.1.111.3306: Flags [S], seq 1376708963, win 14600, options [mss 1460,sackOK,TS val 750309654 ecr 0,nop,wscale 7], length 0
23:31:36.611542 IP 10.111.1.111.3306 > 10.222.22.22.47837: Flags [S.], seq 2532603288, ack 1376708964, win 8190, options [mss 1460], length 0
23:31:44.112084 IP 10.222.22.22.47837 > 10.111.1.111.3306: Flags [S], seq 1376708963, win 14600, options [mss 1460,sackOK,TS val 750311656 ecr 0,nop,wscale 7], length 0
23:31:44.112091 IP 10.111.1.111.3306 > 10.222.22.22.47837: Flags [S.], seq 2532603288, ack 1376708964, win 8190, options [mss 1460], length 0
```
这里的\[S.\]就是SYN+ACK点号\[.\]就是ACK。显然**在LB这里既收到了客户端发来的全部4个SYN自己也发出了4个SYN+ACK可是并没有收到任何一个\[.\]**,也就是握手的第三个包。顺便提一下,\[F.\]是指FIN+ACK\[P.\]是指PUSH+ACK\[R.\]就是RST+ACK了。
到这里我们能拼出问题的全貌了客户端发出了SYNLB回复了SYN+ACK但是却没到达客户端。显然SYN+ACK是在网络上丢失了。
![](https://static001.geekbang.org/resource/image/81/37/81665c475afe7969aeb00f3e86edae37.jpg?wh=2000x986)
那会不会是这个链路的偶发性的丢包导致的呢比如是否这个链路存在一定比例的丢包那么也可能在我们测试TCP握手的时候正好赶上了丢包时段所以就会观察到这样的现象。
于是我们就做了**长ping测试**。结果却发现长ping是100%成功的一点丢包都没有。这就奇怪了难道丢包还专门盯着SYN+ACK丢而ICMP echo reply报文就一点都不丢这好像是“玄学”。
![](https://static001.geekbang.org/resource/image/6d/35/6d0ed37dc0199de99495d20b2fb5a335.jpg?wh=1894x878)
所以,为了搞清楚真相,我们需要回答下面两个问题:
* 为什么ICMP测试一直能通呢
* 为什么TCP有时候连续丢包有时候又可以呢
我们需要到网络层寻找原因这里要进入一个新的知识点ECMP。
### 什么是ECMP
我们先复习一下三层网络的知识。现在随着网络上传输的数据量越来越大,网络基础架构要提供的传输带宽也迅速增大。这一方面需要提升“单兵作战能力”,就是增加网络接口和网线的带宽,另一方面也需要提升“多兵协同作战能力”,也就是把多个链路的带宽充分利用起来。
那么假设有这样一个网络场景一个起点连接到路由器r1它有两条10Gbps的线路连接到后面的两个节点分别是cost为10的r2和cost为20的r3而r2和r3都连向终点10.10.10.0/24这个网段。那么实际上从起点到终点的路径有2条也就是下图中的路径a和路径b。
![](https://static001.geekbang.org/resource/image/fd/fc/fd6ec614561039d62727078d75822bfc.jpg?wh=2000x856)
一般来说因为cost不一样路由器r1会选择cost更低的那条也就是路径a。当某些情况导致路径b的cost更低时流量就切换到路径b。这样的话这两条路径平时只能用一条带宽是10Gbps。
而如果把两个cost设置为相同的值并启用路由器的某个特性**这两条路径就可以被全部利用起来。**这个时候因为两条线路同时使用所以带宽有20Gbps。也就是下图这样
![](https://static001.geekbang.org/resource/image/f3/b1/f37yy3c70d794d0235ac32b02ffef8b1.jpg?wh=2000x863)
这个特性就是 **ECMP**全称是equal-cost multi-path它可以让路由器同时使用多条链路这样就使得通往同一个网段的带宽变成了原先的好几倍。我们来看看下面的示意图
![](https://static001.geekbang.org/resource/image/c3/f2/c35baefeeyyeeb114612b0a4ddd066f2.jpg?wh=1872x705)
如果你在Linux上做过网卡bonding那你可能对这个“并行使用带宽”的做法并不陌生。在Linux服务器上做多网卡的bonding也可以达到带宽合并使用的效果。
> 补充bonding有多种模式其中的一部分模式需要交换机配合。
实际上稍大一点的数据中心都已经使用了ECMP。这样做一方面极大地提升了网络带宽而另一方面也带来了新的挑战也就是网络路径的数量上升了一个数量级排错的复杂度也明显上升。
比如原先每一跳只有一个可用的next-hop从起点到终点的路径可能只有一条。而**启用了ECMP后路径条数就是各跳的next-hop数量的乘积**。以下图的拓扑为例这里一共是6跳从第1跳到第4跳都各有4个next hop那么就有 `1*4*4*4*1*1=64` 种可能的路径。
![](https://static001.geekbang.org/resource/image/76/a4/765cd53b0de055a00b3626cdd72866a4.jpg?wh=2000x1125)
如果网络报文也有思想面对ECMP这个场景可能会很兴奋了“我终于不用每次都走一样的路径了总算有多种选择了”就像下图展示的这样报文可能会走绿色的路线a也可能是走蓝色的路线b或者任何别的可行的路线。
![](https://static001.geekbang.org/resource/image/c3/bd/c3a89fc66ee588d87477cb39443dd2bd.jpg?wh=2000x1125)
不过看到现在这个ECMP跟我们的案例有什么关系呢其实了解了ECMP的核心特点我们就能回答前面的两个问题为什么ping可以成功为什么TCP连接就时常不行。
这个核心特点就是 **ECMP的路径选择策略**
### ECMP的路径选择策略
路由器面对多条等价的路径,是不是随便选一个转发策略就行了呢?比如用轮询策略,第一个报文转发给第一条路径,第二个报文给第二条路径,以此类推。
我们假设就用轮询策略。当ECMP场景下的某一个节点比如r8出现故障的时候经过这个节点的路径就都会受到影响。对于TCP来说可能前一个报文经过的路径都是好的节点就可以成功传送而下一个报文因为轮询策略的关系选择的路径里有r8这个故障节点这个报文就会丢失了。
那么同样地任何一个TCP流都可能会被这个故障节点影响到。仅仅一个节点出现问题就可能影响到所有的流量这是不合理的。
![](https://static001.geekbang.org/resource/image/fa/c0/fa2f59256fa17ae64841beef8ce22fc0.jpg?wh=2000x1125)
所以我们需要“立规矩”让同一个流走固定的链路这样可以限定影响的范围。比如启用基于哈希的转发机制也就是三层的负载均衡机制就可以做到这一点。比较常见的哈希算法是基于报文的五元组也就是源IP、目的IP、源端口、目的端口、协议这样就可以确定TCP流。
有了哈希转发机制即使有一个节点出现问题它所影响的就只是分配到这个节点上的TCP流而没有分配到这个节点的TCP流就不会被影响到这就起到了风险“隔离”的效果。
> 这段特殊时期,我们对“隔离”的认识肯定更加深刻了。
到这里,我们就终于可以回答前面两个问题了。
### 根因推理
先看第一个问题:**为什么ICMP测试一直能通呢**
ECMP用的哈希算法多数是基于五元组的哈希那么ICMP报文的五元组就会是下面这样
![](https://static001.geekbang.org/resource/image/3c/32/3cf7e1e44ac1de96bbb9dce9yy9f8132.jpg?wh=2000x539)
对于同一个客户端和服务端因为源IP、目的IP和协议都不会变而源端口和目的端口是空缺的那么哈希值就不会变。这意味着每次选择的路径都是一样的。那么**如果选到的路径是正常的做多少次ping也都会是正常的**!这就是第一个问题的答案。
其实,这时候你可能已经想到第二个问题的答案了:**为什么TCP有时候连续丢包有时候又可以呢**我们每次发起新的TCP连接的时候用的源端口一般跟前一次连接是不同的所以就会造成它跟ICMP的明显区别
![](https://static001.geekbang.org/resource/image/3a/d4/3a7cc7f59dfaf70e4324e659f8e81dd4.jpg?wh=2000x540)
可见不同的TCP连接哈希值也不同所以选择的路径也可能不同。如果某条路径有问题某一次TCP连接选到这条路径的话就会发生故障
我们可以用下面这张示意图来解释在测试中发现的现象。
![](https://static001.geekbang.org/resource/image/5d/76/5d8d754537bd32d8b70b887d9b00fb76.jpg?wh=2000x940)
> 补充:图中我去掉了各个路由节点,只保留了一条正常路径和一条故障路径。
那还有一个问题为什么客户端直接访问MySQL主机是正常的呢其实也是因为ECMP直接访问MySQL主机时候的路径跟访问LB的路径不同正好绕开了有问题的节点。
这就能完美解释我们遇到的所有现象了。玄学也不玄,只要你愿意深究,玄学就能变成科学。
不过,理论都解释通了,但没有证据还是不行的,让我们完成最后一击。
### 落实证据
既然源端口不同ECMP选择的路线就不同那如果我们能模拟不同的源端口来测试应该就能复现问题现象了。有什么工具可以指定源端口做测试呢
我们前面用过的 **nc命令**其实就可以,给它加上 **\-p参数指定源端口**就行。要知道像其他工具比如telnet、curl等都无法指定源端口也就没法在当前这个特定场景下帮上忙了。
所以我们用下面这个命令就可以指定用源端口30000来连接服务端了
```bash
nc -p 30000 -w 5 -vz 10.111.1.111 3306
```
于是我们用nc配合不同的源端口比如从30000到30010做了多次测试果然发现其中几个源端口可以稳定复现丢包现象。这其实就证实了我们的推测路径中有一个节点出现了丢包如果选择的路径中包含该节点就一定会遇到问题。
```bash
nc -p 30000 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30001 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30002 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30003 -w 5 -vz 10.111.1.111 3306 #不正常,可以稳定重现
nc -p 30004 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30005 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30006 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30007 -w 5 -vz 10.111.1.111 3306 #不正常,可以稳定重现
nc -p 30008 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30009 -w 5 -vz 10.111.1.111 3306 #正常
nc -p 30010 -w 5 -vz 10.111.1.111 3306 #正常
```
根据这个信息网络团队定位到了有问题的节点并做了修复这个TCP连接时通时不通的问题终于被解决了。
有了对ECMP的充分理解和对nc这个工具的灵活运用我们即使没有网络设备的权限也依然发现了某些路径存在丢包的现象从而推动相关团队更加高效地解决问题。可以说这又是一次知识上的胜利。
## 拓展ECMP的其他使用场景
ECMP大部分时候用来增加网络骨干环节的带宽不过它还有一个重要的使用场景就是实现多活的负载均衡LB。我们可以先看一个传统的一主一备的负载均衡架构
![](https://static001.geekbang.org/resource/image/34/e8/34052832f09307d48bf84891fed121e8.jpg?wh=2000x562)
而现在大型网站的负载均衡一般都选择做**多活架构**也就是所有的LB节点都提供服务这跟前面的一主一备就非常不同了。而要实现多活就必须让同一个VIP能在多个LB节点上都提供服务。
我们知道IP一般是通过ARP广播让广播域内的机器和交换机都学习到这个IP和MAC的对应关系从而“占据”这个IP。交换机学习到这条ARP记录后就会把去往这个IP的报文都转发给这台机器了。
那多个LB配置同一个IP岂不是要“打架”了吗如果大家都争先恐后地发起ARP那只有最后一个发ARP的节点可以真正的拿到这个IP其他节点是无法用这个IP来服务的。
所以ARP是肯定行不通的而ECMP配合BGP/OSPF就可以发挥作用了。这些LB节点都会启用BGP或者OSPF这样的路由协议而不是ARP协议并跟上联网关进行BGP配对。这样的话网关收到流量后会认为这多个LB节点不再是终端设备而是跟它一样的next-hop路由器再加上ECMP的加持流量就能相对均匀地分发到这些LB上。这就实现多活了。我们看一下示意图
![](https://static001.geekbang.org/resource/image/cf/d0/cf6d0cc1ae25ce849507efb42709c1d0.jpg?wh=2000x729)
## 小结
这节课我们通过案例学习了网络层的一个重要概念ECMP。**ECMP的最大作用是用多条链路实现更大的传输带宽**。而为了提升可用性ECMP一般会启用基于**哈希**的转发策略,实现网络流量在多个链路间的有状态的转发。
因为ECMP多路径的存在网络排查变得更加复杂。而了解它的哈希转发策略对我们的排查工作很有帮助。特别是ICMP报文会走固定线路而TCP/UDP的不同连接会因为五元组的变化而走不同的线路。
在排查技巧方面我们学习了一个实用的小工具nc。如果你怀疑ECMP的某个节点有问题可以尝试**用nc命令来发起不同哈希值的连接也就是使用-p参数指定源端口做测试**。如果发现某些源端口的表现跟其他端口不同那么很可能就是ECMP的节点问题。
另外我们也学习了一个tcpdump的小知识点。在tcpdump的命令行输出中
* \[S\]代表SYN
* \[.\]代表ACK
* \[F.\]代表FIN+ACK
* \[P.\]代表PUSH+ACK
* \[R.\]代表RST+ACK。
网络路径问题的排查是比较复杂的。当路径中某个节点的问题比较严重时用nc工具相对容易复现出问题。当问题比较隐蔽比如出错率比较低的时候可能单纯用nc就不够方便了我们需要另外一个工具mtr。它是加强版的traceroute可以做持续的三层可达性的探测。关于mtr的更多细节我们留到在下一讲里做详细的探讨。
## 思考题
最后再给你留两道思考题:
* ECMP除了用五元组做哈希还有哪些算法呢
* 这个案例中的问题用traceroute可以定位出来吗为什么呢
欢迎你在留言区分享你的答案,我们一同进步,成长。