我们总是希望一切都能如期进行。不过残酷的现实是,大多数时候,总会出现问题。
在排除网络故障时,第一个碰到的问题总是这个——“流量在哪里?”
事情变得有点“复杂”
对于虚拟网络来说,也依然如此!即使是在Tungsten Fabric集群内部,按理说,我们在故障排除环节的第一步,也会进行某种流量嗅探或流量识别。
那么……和传统的物理网络有什么不同呢?从概念上讲,没有什么不同……但是,在实践当中,事情会更复杂一些。我们所说的复杂,并不是指难以理解无从下手。所谓复杂,是指有更多的变数在起作用,但这并不一定意味着故障排除会非常难。相反,从另一个角度来看,这意味着我们可以使用更多的工具来更好地了解网络中发生的事情。
回到Tungsten Fabric集群,或者说是一般的虚拟环境,我们的目标是检查/监控进出某个虚拟机的流量。与传统的物理设备相比,如前所述,环境更加复杂。有了物理设备,就像有了一台一体化设备。但当移动到虚拟环境时,一体化设备就不存在了!你有物理服务器(计算)和连接到DC架构的NIC,你有管理程序,最后,你有虚拟机。虚拟机的流量会经过所有这些层次。在每个层次,都有工具可以用来检查/监控流量。这就是我说的“更复杂的场景”的意思,但也是为什么说,从另一个角度来看,这意味着有很多有用的武器可以满足我们的需求。
因此,了解在每个层面可以使用哪些工具是很重要的。我们必须掌握复杂性,并利用它!
让我们用一个例子来解决这个问题。例如有一个IP为192.168.10.3的虚拟机(VM1),这个虚拟机运行在compute1上。在compute2上,有另一个IP为192.168.10.4的虚拟机(VM2)。让我们从VM1 ping到VM2:
代码语言:javascript复制$ ping 192.168.10.4
PING 192.168.10.4 (192.168.10.4): 56 data bytes
64 bytes from 192.168.10.4: seq=0 ttl=64 time=44.812 ms
64 bytes from 192.168.10.4: seq=1 ttl=64 time=32.076 ms
64 bytes from 192.168.10.4: seq=2 ttl=64 time=6.418 ms
^C
--- 192.168.10.4 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 6.418/27.768/44.812 ms
$
Ping成功了!现在,我们要做的是识别和监控这些流量。
三个层次,三套工具
首先,正如之前预想的那样,我们必须掌握复杂性。在处理Tungsten Fabric集群时,我们可以识别3个层次:VNF,hypervisor和vRouter。
现在来看一下整个流程。
在VNF层面,我们拥有VNF提供的所有工具,可能有类似tcpdum的命令,或者如果VNF是一个防火墙,有一些东西可以查看流表内容。由于这个级别取决于厂商/VNF,这里不打算详细介绍。
再来看看vRouter。vRouter是Tungsten Fabric解决方案的核心,它提供并管理计算节点内部的所有虚拟网络。此外,它还提供了一系列不错的工具,可以用来了解集群内的流量是如何流动的。
要访问这套工具,首先要访问vRouter容器,通过连接到计算节点并使用知名的docker命令来实现。
代码语言:javascript复制[root@compute1 ~]# docker ps | grep vrouter
c1f441949cb5 hub.juniper.net/contrail/contrail-vrouter-agent:1911.31 "/entrypoint.sh /usr…" 8 days ago Up 8 days vrouter_vrouter-agent_1
4333e1b1cf92 hub.juniper.net/contrail/contrail-nodemgr:1911.31 "/entrypoint.sh /bin…" 8 days ago Up 8 days vrouter_nodemgr_1
[root@compute1 ~]# docker exec -it vrouter_vrouter-agent_1 bash
(vrouter-agent)[root@compute1 /]$
一旦我们进行到这里,就可以开始寻找流量了。
首先,确定VM的虚拟机接口(vif)。通过使用“vif”实用程序,匹配端口IP地址(192.168.10.3)来实现:
代码语言:javascript复制(vrouter-agent)[root@compute1 /]$ vif --list | grep -B 1 -A 1 192.168.10.3
vif0/3 OS: tapcae84676-cb NH: 33
Type:Virtual HWaddr:00:00:5e:00:01:00 IPaddr:192.168.10.3
Vrf:2 Mcast Vrf:2 Flags:PL3L2DEr QOS:-1 Ref:6
在这里,我们学到了很多有用的东西。
接口属于Vrf 2,这是一种路由表索引。虚拟接口索引是3(vif0/3)。最后,对应的tap接口是tapcae84676-cb。
下一个工具,我们可以使用rt。记住,我们要ping的是地址为192.168.10.4的远程虚拟机。
代码语言:javascript复制(vrouter-agent)[root@compute1 /]$ rt --get 192.168.10.4/32 --vrf 2
Match 192.168.10.4/32 in vRouter inet4 table 0/2/unicast
Flags: L=Label Valid, P=Proxy ARP, T=Trap ARP, F=Flood ARP
vRouter inet4 routing table 0/2/unicast
Destination PPL Flags Label Nexthop Stitched MAC(Index)
192.168.10.4/32 0 LP 23 20 2:e7:fd:ee:27:78(148824)
上面的输出结果告诉我们流量将被发送到下一跳20。此外,它还说到将使用标签23。这表明,为了到达目的地,流量将不得不离开计算节点,并被封装成一个MPLSoUDP(或MPLSoGRE)包。这个标签就是将要推送的服务标签。
我们来检查下一跳:
代码语言:javascript复制(vrouter-agent)[root@compute1 /]$ nh --get 20
Id:20 Type:Tunnel Fmly: AF_INET Rid:0 Ref_cnt:6 Vrf:0
Flags:Valid, MPLSoUDP, Etree Root,
Oif:0 Len:14 Data:56 68 ac c3 28 02 56 68 ac c3 28 04 08 00
Sip:192.168.200.3 Dip:192.168.200.4
正如想象的那样,下一跳是一个MPLSoUDP隧道,将流量从compute1(vhost0地址192.168.200.3)发送到compute2(vhost0地址192.168.200.4)。
请注意输出中的“Vrf:0”字段,这告诉我们,流量将通过VRF 0发送出去(如前所述,VM在VRF 2中)。Vrf 0是“fabric network”,连接计算节点和underlay的网络。此外,发送到下一跳20的流量将通过接口0(Oif 0)发送到Vrf 0(如前所述)。Oif 0是连接计算和底层的物理接口接口:
代码语言:javascript复制(vrouter-agent)[root@compute1 /]$ vif --get 0
Vrouter Interface Table
vif0/0 OS: ens3f1 (Speed 1000, Duplex 1) NH: 4
Type:Physical HWaddr:56:68:ac:c3:28:04 IPaddr:0.0.0.0
Vrf:0 Mcast Vrf:65535 Flags:TcL3L2VpEr QOS:-1 Ref:7
RX packets:738213 bytes:47096090 errors:0
TX packets:808295 bytes:42533041 errors:0
Drops:30
这是vhost0所在的接口。Oif 0会“看到”封装的数据包。
综上所述,我们的虚拟机的流量属于VRF 2,在这个VRF里面,发生了查找(lookup)动作。在那里,通过Oif 0接口(物理接口),流量将被封装成MPLSoUDP数据包,然后发送到另一个计算节点。
下一个有用的工具,是“流(flow)”。vRouter默认是基于流的(可以通过将vmis设置为数据包模式来选择性地禁用每个vmi的流模式)。
让我们根据目的地来匹配流量:
代码语言:javascript复制(vrouter-agent)[root@compute1 /]$ flow --match 192.168.10.4
Listing flows matching ([192.168.10.4]:*)
Index Source:Port/Destination:Port Proto(V)
-----------------------------------------------------------------------------------
512500518060 192.168.10.3:49409 1 (2)
192.168.10.4:0
(Gen: 1, K(nh):33, Action:F, Flags:, QOS:-1, S(nh):33, Stats:4/392, SPort 54199,
TTL 0, Sinfo 3.0.0.0)
518060512500 192.168.10.4:49409 1 (2)
192.168.10.3:0
(Gen: 1, K(nh):33, Action:F, Flags:, QOS:-1, S(nh):20, Stats:4/392, SPort 65513,
TTL 0, Sinfo 192.168.200.4)
到目前为止,我们只看到了控制面信息:接口索引、路由表、下一跳。
这是第一次确认流量真的在虚拟机之间流动。
我们有2个流,因为每个流都是单向的。
这个输出有这么多信息!流量是流动的,因为Action被设置为F,也就是转发。
我们还知道,流量是ICMP,因为proto等于1。Proto旁边有“(V)”。这代表了VRF id,不出意外的话,它等于2!
最后,看到K(NH)和S(NH):这些是Key和Source(RPF)的下一跳。下一跳20我们已经看到了。此外,我们还看到下一跳33,它指向我们的本地虚拟机(vif 3):
代码语言:javascript复制(vrouter-agent)[root@compute1 /]$ nh --get 33 | grep Oif
EncapFmly:0806 Oif:3 Len:14
我们可以从vRouter内部使用的东西就到这里了。
最后,我们进入到hypervisor层。我所说的“hypervisor层”指的是虚拟机和外界之间的那个中间层。这是虚拟机接口与物理网卡连接的地方。在这个层面我们能做的主要是嗅探流量。
先退出vRouter容器。
当使用vif时,我们能够定位到与该端口相关的tap接口“tapcae84676-cb”。
可以使用标准的tcpdump:
代码语言:javascript复制[root@compute1 ~]# tcpdump -i tapcae84676-cb -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on tapcae84676-cb, link-type EN10MB (Ethernet), capture size 262144 bytes
09:24:55.082119 IP 192.168.10.3 > 192.168.10.4: ICMP echo request, id 49409, seq 688, length 64
09:24:55.107621 IP 192.168.10.4 > 192.168.10.3: ICMP echo reply, id 49409, seq 688, length 64
09:24:56.082344 IP 192.168.10.3 > 192.168.10.4: ICMP echo request, id 49409, seq 689, length 64
09:24:56.092153 IP 192.168.10.4 > 192.168.10.3: ICMP echo reply, id 49409, seq 689, length 64
^C
4 packets captured
10 packets received by filter
0 packets dropped by kernel
是的,这里就是我们的流量。
其次,可以直接在物理接口上嗅探流量。这里,我们将看到封装的流量。
在这个接口上,我们可能会看到许多不同类型的流量:到计算节点2的流量,到其它计算节点的流量,到控制节点的流量。此外,并非所有的流量都是MPLSoUDP。我们可以有VXLAN(L2虚拟化)流量或普通IP流量(XMPP)。出于这个原因,适当地过滤流量可能是有用的。
MPLSoUDP的流量可以通过6635端口过滤udp流量来缩小范围;VXLAN可以通过设置4789端口过滤udp流量。此外,我们还可以在目的主机IP上进行过滤,如之前学习的使用“rt”和“nh”,流量被发送到192.168.200.4:
代码语言:javascript复制[root@compute1 ~]# tcpdump -i ens3f1 -nn udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens3f1, link-type EN10MB (Ethernet), capture size 262144 bytes
09:33:24.394590 IP 192.168.200.3.54199 > 192.168.200.4.6635: UDP, length 102
09:33:24.395915 IP 192.168.200.4.56128 > 192.168.200.3.6635: UDP, length 102
09:33:25.395362 IP 192.168.200.3.54199 > 192.168.200.4.6635: UDP, length 102
09:33:25.398488 IP 192.168.200.4.56128 > 192.168.200.3.6635: UDP, length 102
09:33:26.395541 IP 192.168.200.3.54199 > 192.168.200.4.6635: UDP, length 102
09:33:26.399384 IP 192.168.200.4.56128 > 192.168.200.3.6635: UDP, length 102
^C
6 packets captured
6 packets received by filter
0 packets dropped by kernel
正如你所看到的,我们在这些计算节点之间有双向的MPLSoUDP流量。无论如何,还不能确定这真的是我们的流量。为了确定这一点,我们可以将捕获到的数据保存到一个文件中,然后用wireshark打开,wireshark能够解码MPLSoUDP。
根据配置的封装优先级,可能VXLAN是用于计算到计算的流量。事实上,VXLAN是VN内部流量的默认选择,除非MPLSoUDP被配置在优先级列表中的第一位(无论如何,现在这并不重要……)。
我们只能说,我们必须期望处理不同类型的overlay流量。
其实VXLAN更方便用户使用,因为tcpdump可以显示内部数据包的内容:
代码语言:javascript复制[root@compute1 ~]# tcpdump -i ens3f1 -nn udp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens3f1, link-type EN10MB (Ethernet), capture size 262144 bytes
09:36:24.478515 IP 192.168.200.3.54199 > 192.168.200.4.4789: VXLAN, flags [I] (0x08), vni 5
IP 192.168.10.3 > 192.168.10.4: ICMP echo request, id 49409, seq 1377, length 64
09:36:24.479659 IP 192.168.200.4.56128 > 192.168.200.3.4789: VXLAN, flags [I] (0x08), vni 5
IP 192.168.10.4 > 192.168.10.3: ICMP echo reply, id 49409, seq 1377, length 64
09:36:25.478920 IP 192.168.200.3.54199 > 192.168.200.4.4789: VXLAN, flags [I] (0x08), vni 5
IP 192.168.10.3 > 192.168.10.4: ICMP echo request, id 49409, seq 1378, length 64
09:36:25.480214 IP 192.168.200.4.56128 > 192.168.200.3.4789: VXLAN, flags [I] (0x08), vni 5
IP 192.168.10.4 > 192.168.10.3: ICMP echo reply, id 49409, seq 1378, length 64
^C
4 packets captured
4 packets received by filter
0 packets dropped by kernel
[root@compute1 ~]#
在这里,我们可以看到实际的VM ICMP数据包。这样就可以确定是否为我们的流量。
只要我们的计算节点是在内核模式下,使用tcpdump就是可能的。如果我们有一个dpdk vRouter,那么就不能在主机上使用tcpdump,因为dpdk会“吃掉接口”,使它们对内核不可见(tcpdump在内核可见的接口上工作)。在这种情况下,hypervisor层消失了,我们必须依靠vRouter层。
一旦我们进入vRouter容器,一个至今没有见过的工具就成了基础:vifdump。Vifdump就像tcpdump一样,只是它工作在DPDK接口上,只能在vRouter容器内部运行。
要嗅探一个虚拟机接口:
代码语言:javascript复制vifdump -i vif0/3 -nn icmp
要嗅探一个物理接口:
代码语言:javascript复制vifdump -i vif0/0
总结
就是这样!让我们总结一下所有的可能性——
在VNF层面,使用厂商/VNF特定的工具。
在vRouter层面(工具要在vRouter容器内运行):
- vif,列出虚拟接口
- nh,了解流量计算一个特定的nexth-hop索引是在哪里发送的
- flow,查看vRouter上的活动流量
- rt,查看vRouter路由表内部
在hypervisor层面,使用tcpdump来嗅探虚拟接口和物理接口上的数据包。
最后,如果节点是DPDK节点,那么主机级的tcpdump就没有用了,用vRouter容器里面运行的“vifdump”代替。
现在没有秘密了吧?一句话,在正确的层面上使用正确的工具~
作者:Umberto Manferdini 译者:TF编译组 原文链接: https://iosonounrouter.wordpress.com/2020/04/13/troubleshooting-contrail-vms-traffic-the-right-tool-at-the-right-moment/