【NetDevOps】新一代网工需要了解的那点事儿(七)---VXLAN
7 VXLAN
7.1 VXLAN概述
VXLAN(Virtual eXtensible Local Area Network,虚拟扩展局域网),是由IETF定义的NVO3(Network Virtualization over Layer 3)标准技术之一,采用L2 over L4(MAC-in-UDP)的报文封装模式,将二层报文用三层协议进行封装,可实现二层网络在三层范围内进行扩展。VXLAN 本质上是一种隧道封装技术。它使用 TCP/IP 协议栈的惯用手法——封装/解封装技术,将二层的以太网帧(Ethernet frames)封装成四层的 UDP 数据报(datagrams),然后在三层的网络中传输,效果就像 L2 的以太网帧在一个广播域中传输一样,实际上是跨越了 L3 网络,但却感知不到三层网络的存在。VXLAN 是应网络虚拟化技术而生的。随着数据中心网络不断扩增,Cisco、VMware 和 Arista Networks 这些巨头发现,传统的 VLAN 隔离已经无法应对网络虚拟化技术所带来的成千上万的设备增长,于是便联合起草了这个协议,一直到 2014 年才定稿,由RFC 7348所定义。
7.2 VXLAN和VLAN区别
首先我们知道VLAN是二层网络设备或者虚拟化设备用来隔离广播域的一种技术,同时我们也知道VLAN本身被限制最多只能有4094个。在以前较为简单的IT基础架构中使用VLAN来隔离广播域显得绰绰有余,但是在现如今云计算基础架构中动则数百万的虚拟设备中,要隔离这些广播域如果使用VLAN就显得捉襟见肘了。所以这里迭代出VXLAN技术通过网络虚拟化来解决超大型广播域隔离的问题。
其次是资源的问题,传统网络使用VLAN来隔离广播域,为避免二层环路一般会使用STP来管理网络链路。那么在使用STP时我们知道,它是通过STP的根来负责转发,一般会根据优先级以及链路成本来选择一条最优的链路进行数据转发,其余链路均作为备份链路。虽然这样即避免了环路也增强了链路可用性,但这里有一点就是链路的使用率却没有提高,明明有多条链路可以使用实际上始终只能用其中一条。所以我们通过VXLAN来解决在大流量情况下实现链路负载和提升数据传输能力。还有一点是MAC地址表的限制,传统二层网络都需要交换机的MAC地址表进行转发。不同性能的交换机所能支持的MAC地址表条目书不同,那么一般数据中心用TOR交换机来连接物理服务器,如果一台服务器虚拟出上百台虚拟机时,TOR交换机的一个物理口就要对应上百个MAC地址,在交换机MAC地址表条目数量有限的情况下,这可能会造成网络接入的瓶颈。如果这里使用VXLAN就会解决这个问题,VXLAN使用VTEP封装二层帧并在三层网络中传输。所以一台物理机对应一个VTEP,而这个VTEP可以被这台物理机上的所有虚机使用。那么对于TOR交换机来说,一个物理口对应一台物理机对应一个VETP信息即可,这样就解决了因为虚拟化带来的交换机MAC地址表爆增的问题。
最后是关于网络灵活性,传统基于VLAN的网络环境是不存在overlay的,仅仅是underlay网络。物理网络与虚拟机属于同VLAN的数据可以互访,这就使得虚拟机所在的网络无法打破物理网络的界限。比如说在虚拟机的部署和迁移,传统网络仅支持在相同VLAN的网络中进行内部的操作,如果跨网段部署和迁移就显得非常麻烦。通过 VXLAN 的封装,在一个三层网络上构建了 二层 网络,或者说基于 underlay 网络的 overlay 网络,虚拟机的数据可以打破传统二层网络的限制,在三层网络上传输,虚拟机的部署和迁移也不受物理网络的限制,可以灵活部署和迁移,使得整个数据中心保持一个平均的利用率。
从以上几点来看,VXLAN 相比 VLAN 有很多的优势,不过至少现在还不能完全替代 VLAN,这需要根据使用场景具体分析。VXLAN 的优势更多体现大规模环境下,如果网络设备规模,不论是虚拟的还是物理的,只有百十台的样子,那么直接使用 VLAN 就足够了。另外一个 VXLAN 的封装解封装机制也会影响性能,所以需要综合考虑。
7.3 VXLAN网络模型
如下图所示,左右两边是 Layer2 广播域,中间跨越一个 L3 网络,VTEP 是 VXLAN 隧道端点(VxLAN Tunnel Point),当 Layer2 以太网帧到达 VTEP 的时候,通过 VxLAN 的封装,跨越 Layer3 层网络完成通信,由于 VXLAN 的封装"屏蔽"了 Layer3 网络的存在,所以整个过程就像在同一个 Layer2 广播域中传输一样。
+----------------------------------------------------------------------------------------------+
| +-----------+ +------------+ |
| +--------+ | | +--------------------+ | | +--------+ |
| | +-----+ +------+ VXLAN Tunnel +-------+ +-----+ | |
| | Host A | | VTEP A | +--------------------+ | VTEP B | | Host B | |
| +--------+ | | L3 IP Fabric | | +--------+ |
| +-----------+ +------------+ |
| |
| + + + + |
| | | | | |
| | Layer 2 | Layer 3 | Layer 2 | |
| +<------------------------>-<--------------------------------->-<------------------------->+ |
| | | | | |
| + + + + |
| By:[F0rGeEk] |
+----------------------------------------------------------------------------------------------+
传统网络中的交换机会根据本地的FDB地址表进行转发,FDB表存储的是MAC地址、vlan id和接口的对应关系。那么在VXLAN网络中,交换机则需要维护一张VTEP的信息表。这张表中主要内容是MAC地址、VNI、VTEP IP之间的对应关系。
-
VTEP(VXLAN Tunnel Endpoints,VXLAN隧道端点)
VXLAN网络的边缘设备,是VXLAN隧道的起点和终点,VXLAN报文的相关处理均在这上面进行。总之,它是VXLAN网络中绝对的主角。VTEP既可以是一***立的网络设备(比如华为的CE系列交换机),也可以是虚拟机所在的服务器。那它究竟是如何发挥作用的呢?答案稍候揭晓。 -
VNI(VXLAN Network Identifier,VXLAN 网络标识符)
上文提到,以太网数据帧中VLAN本身被限制最多只能有4094个,这使得VLAN的隔离能力在数据中心网络中力不从心。而VNI的出现,就是专门解决这个问题的。VNI是一种类似于VLAN ID的标识,一个VNI代表了一个租户,属于不同VNI的虚拟机之间不能直接进行二层通信。VXLAN报文封装时,给VNI分配了足够的空间使其可以支持海量租户的隔离。详细的实现,我们将在后文中介绍。 -
VXLAN Tunnel
“隧道”是一个逻辑上的概念,它并不新鲜,比如大家熟悉的GRE。说白了就是将原始报文“变身”后再加以“包装”,好让它可以在承载网络(比如IP网络)上传输。从主机的角度看,就好像原始报文的起点和终点之间,有一条直通的链路一样。而这个看起来直通的链路,就是“隧道”。顾名思义,“VXLAN隧道”便是用来传输经过VXLAN封装的报文的,它是建立在两个VTEP之间的一条虚拟通道。
-
UDP Header
VXLAN头和原始以太帧一起作为UDP的数据。UDP头中,目的端口号(VXLAN Port)固定为4789,源端口号(UDP Src. Port)是原始以太帧通过哈希算法计算后的值。 -
Outer IP Header
封装外层IP头。其中,源IP地址(Outer Src. IP)为源VM所属VTEP的IP地址,目的IP地址(Outer Dst. IP)为目的VM所属VTEP的IP地址。 -
Outer MAC Header
封装外层以太头。其中,源MAC地址(Src. MAC Addr.)为源VM所属VTEP的MAC地址,目的MAC地址(Dst. MAC Addr.)为到达目的VTEP的路径上下一跳设备的MAC地址。
7.4 VXLAN实践
本次关于VXLAN的实践均是在Linux中进行,主要分为两个实验:1. 点对点VXLAN通信;2.容器环境中跨物理机通信
7.4.1 点对点VXLAN
点对点VXLAN是最简单的一种实践方式,它有助于我们理解上文讲的那些干货。如下图所示,我们在两台server上各创建一个vxlan类型的接口,其中VXLAN1和VXLAN2为它们所在物理机的VTEP设备。我们制定VXLAN接口IP地址为10.1.1.0/24网段中,最终我们在VTEP设备之间构建一条VXLAN Tunnel,然后将10.1.1.0/24这个网络通过该隧道打通。
+------------------------------------------------------------------------+
| |
| SERVER A SERVER B |
| +-------------------+ +-------------------+ |
| | 10.1.1.1 | | 10.1.1.2 | |
| | +------+ | VXLAN Tunnel | +------+ | |
| | |VXLAN1| +---------------------------------+ |VXLAN2| | |
| | +---+--+ +---------------------------------+ +---+--+ | |
| | | | | | | |
| | | | | | | |
| | +----+----+ | | +----+----+ | |
| | | ens33 | | | | ens33 | | |
| +----+----+----+----+ +----+----+----+ ---+ |
| | 172.16.116.131/24 172.16.116.132/24 | |
| | | |
| +--------------------------------------------+ |
| |
| By:[F0rGeEk] |
+------------------------------------------------------------------------+
其实现还是非常简单的,具体操作过程如下:
# 设备OS版本信息
[root@d1 ~]# uname -r
3.10.0-957.el7.x86_64
[root@d1 ~]# cat /etc/centos-release
CentOS Linux release 7.6.1810 (Core)
- SERVER A上的配置
[root@d1 ~]# ip link add vxlan1 type vxlan id 33 remote 172.16.116.132 dstport 4789 dev ens33
[root@d1 ~]# ip link set vxlan1 up
[root@d1 ~]# ip addr add 10.1.1.1/24 dev vxlan1
# 查看创建的vxlan1网络设备信息
[root@d1 ~]# ifconfig vxlan1
vxlan1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.1.1.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::58d9:3eff:fed9:659d prefixlen 64 scopeid 0x20<link>
ether 5a:d9:3e:d9:65:9d txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 544 (544.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# 查看关于10.1.1.0/24网络路由情况
[root@d1 ~]# route -n | grep 10.1.1.0
10.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 vxlan1
- SERVER B上的配置
[root@d2 ~]# ip link add vxlan2 type vxlan id 33 remote 172.16.116.131 dstport 4789 dev ens33
[root@d2 ~]# ip link set vxlan2 up
[root@d2 ~]# ip addr add 10.1.1.2/24 dev vxlan2
# 查看创建的vxlan2网络设备信息
[root@d2 ~]# ifconfig vxlan2
vxlan2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.1.1.2 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::a8fd:5bff:fed4:7cb2 prefixlen 64 scopeid 0x20<link>
ether aa:fd:5b:d4:7c:b2 txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8 bytes 544 (544.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
# 查看关于10.1.1.0/24网络路由情况
[root@d2 ~]# route -n | grep 10.1.1.0
10.1.1.0 0.0.0.0 255.255.255.0 U 0 0 0 vxlan2
- 测试连通性
# 为方便测试这里将CentOS的防火墙关闭
[root@d1 ~]# systemctl stop firewalld
[root@d1 ~]# ping 10.1.1.2 -c 3
PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.
64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.826 ms
64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.973 ms
64 bytes from 10.1.1.2: icmp_seq=3 ttl=64 time=1.00 ms
- 抓包分析
# 在SERVER A上针对物理接口进行抓包
[root@d1 ~]# tcpdump -i ens33 -w vxlan.pcap
tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
^C90 packets captured
92 packets received by filter
0 packets dropped by kernel
# 在SERVER B上ping10.1.1.1
[root@d2 ~]# ping 10.1.1.1 -c 3
PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.
64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.991 ms
64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=1.30 ms
64 bytes from 10.1.1.1: icmp_seq=3 ttl=64 time=1.21 ms
7.4.2 跨主机容器之间的VXLAN
实验环境是在两台SERVER上分别部署一个docker容器,两个容器均在同一个网段,最终实现不同宿主机上的同一网段的容器之间可以互访。熟悉K8S的朋友可能要说,这个在K8S里部署docker时默认就可以互通啊。是的没错,这里我们就是将底层是如何实现的来做个讲解。
+------------------------------------------------------------------------+
| |
| SERVER A SERVER B |
| +-------------------+ +-------------------+ |
| | | | | |
| | +---------+ | | +---------+ | |
| | | Docker A| | | | Docker B| | |
| | +---------+ | | +---------+ | |
| | 172.33.1.11 | | 172.33.1.12 | |
| | | | | | | |
| | +------+------+ | | +------+------+ | |
| | | Bridge1 | | | | Bridge2 | | |
| | +------+------+ | | +------+------+ | |
| | | | | | | |
| | +------+------+ | | +------+------+ | |
| | |VXLAN_DockerA| | | |VXLAN_DockerB| | |
| | +------+------+ | | +------+------+ | |
| | | | | | | |
| | +----+----+ | | +----+----+ | |
| | | ens33 | | | | ens33 | | |
| +----+---------+----+ +----+---------+----+ |
| 172.16.116.131/24 172.16.116.132/24 |
| | | |
| +--------------------------------------------+ |
| By:[F0rGeEk] |
+------------------------------------------------------------------------+
这里需要说明一点,当启动docker服务后,系统默认会创建一个docker0的网络。默认是172.17.0.0/16这个网段,启动容器时默认会从172.17.0.2开始自动分配给容器。按照实验环境的需求,我们这里需要指定容器的ip地址。docker默认的docker0是不允许被指定IP给容器的,只有在用户定义的网络上才支持用户指定容器的IP地址。所以这里我们需要创建一个自定义的网络。
- 自定义网络
[root@d1 ~]# docker network create bridge1 \
-o com.docker.network.bridge.name=bridge1 \
--subnet 172.33.1.0/24
# 验证是否成功创建网络
[root@d1 ~]# docker network list
NETWORK ID NAME DRIVER SCOPE
0b57d2a226b4 bridge bridge local
eab6c2991f09 bridge1 bridge local
a96c8a04be60 host host local
8f8172bcf173 none null local
[root@d1 ~]# ifconfig
bridge1: flags=4099<UP,BROADCAST,MULTICAST> mtu 1500
inet 172.33.1.1 netmask 255.255.255.0 broadcast 172.33.1.255
ether 02:42:aa:6d:ae:45 txqueuelen 0 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
- 创建并启动容器
# SERVER A
[root@d1 ~]# docker run -itd --net bridge1 --ip 172.33.1.11 busybox
# 验证容器是否成功创建
[root@d1 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
b3b055b49d4c busybox "sh" 5 seconds ago Up 3 seconds quizzical_mestorf
#检查新创建容器的IP地址
[root@d1 ~]# docker exec -it b3b055b49d4c sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:21:01:0B
inet addr:172.33.1.11 Bcast:172.33.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:16 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1312 (1.2 KiB) TX bytes:0 (0.0 B)
# SERVER B
[root@d2 ~]# docker run -itd --net bridge2 --ip 172.33.1.12 busybox
00bb21c402f00fabe576b88841116515475a1b54fd660debccefc1d00ec464d0
[root@d2 ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
00bb21c402f0 busybox "sh" 4 seconds ago Up 2 seconds elated_mirzakhani
#检查新创建容器的IP地址
[root@d2 ~]# docker exec -it 00bb21c402f0 sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:21:01:0C
inet addr:172.33.1.12 Bcast:172.33.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:16 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1312 (1.2 KiB) TX bytes:0 (0.0 B)
- 创建VXLAN接口并加入docker网桥
# SERVER A
[root@d1 ~]# ip link add VXLAN_DockerA type vxlan id 33 remote 172.16.116.132 dstport 4789 dev ens33
[root@d1 ~]# ip link set VXLAN_DockerA up
[root@d1 ~]# brctl addif bridge1 VXLAN_DockerA
# 查看VXLAN信息
[root@d1 ~]# ip -d link show VXLAN_DockerA
12: VXLAN_DockerA: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master bridge1 state UNKNOWN mode DEFAULT group default qlen 1000
link/ether 5a:6e:12:f7:02:9c brd ff:ff:ff:ff:ff:ff promiscuity 1
vxlan id 33 remote 172.16.116.132 dev ens33 srcport 0 0 dstport 4789 ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx
bridge_slave state forwarding priority 32 cost 100 hairpin off guard off root_block off fastleave off learning on flood on port_id 0x8002 port_no 0x2 designated_port 32770 designated_cost 0 designated_bridge 8000.2:42:aa:6d:ae:45 designated_root 8000.2:42:aa:6d:ae:45 hold_timer 0.00 message_age_timer 0.00 forward_delay_timer 0.00 topology_change_ack 0 config_pending 0 proxy_arp off proxy_arp_wifi off mcast_router 1 mcast_fast_leave off mcast_flood on addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
# SERVER B
[root@d2 ~]# ip link add VXLAN_DockerB type vxlan id 33 remote 172.16.116.131 dstport 4789 dev ens33
[root@d2 ~]# ip link set VXLAN_DockerB up
[root@d2 ~]# brctl addif bridge2 VXLAN_DockerB
# 查看VXLAN信息
[root@d2 ~]# ip -d link show VXLAN_DockerB
10: VXLAN_DockerB: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master bridge2 state UNKNOWN mode DEFAULT group default qlen 1000
link/ether fa:8b:b6:00:70:1e brd ff:ff:ff:ff:ff:ff promiscuity 1
vxlan id 33 remote 172.16.116.131 dev ens33 srcport 0 0 dstport 4789 ageing 300 noudpcsum noudp6zerocsumtx noudp6zerocsumrx
bridge_slave state forwarding priority 32 cost 100 hairpin off guard off root_block off fastleave off learning on flood on port_id 0x8002 port_no 0x2 designated_port 32770 designated_cost 0 designated_bridge 8000.2:42:c2:0:9:dd designated_root 8000.2:42:c2:0:9:dd hold_timer 0.00 message_age_timer 0.00 forward_delay_timer 0.00 topology_change_ack 0 config_pending 0 proxy_arp off proxy_arp_wifi off mcast_router 1 mcast_fast_leave off mcast_flood on addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
- 连通性测试
# SERVER A
[root@d1 ~]# docker exec -it b3b055b49d4c sh
/ # ping 172.33.1.12 -c 3
PING 172.33.1.12 (172.33.1.12): 56 data bytes
64 bytes from 172.33.1.12: seq=0 ttl=64 time=1.793 ms
64 bytes from 172.33.1.12: seq=1 ttl=64 time=0.598 ms
64 bytes from 172.33.1.12: seq=2 ttl=64 time=1.511 ms
--- 172.33.1.12 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.598/1.300/1.793 ms
# SERVER B
[root@d2 ~]# tcpdump -n -i ens33 icmp -w docker_vxlan.pcap
tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
^C4 packets captured
4 packets received by filter
0 packets dropped by kernel
[root@d2 ~]# sz docker_vxlan.pcap