VXLAN协议实战

理论:

https://support.huawei.com/enterprise/zh/doc/EDOC1100087027?sendFrom=mobile#zh-cn_topic_0244198677_fig46008802144621

实战

https://blog.csdn.net/bc_vnetwork/article/details/53535315

https://blog.csdn.net/chenleiking/article/details/88142580

https://www.cnblogs.com/wipan/p/9220615.html

 

在下面的场景中有两服务器,由三层物理网络连接。这两台服务器可能在同一个机架上,或者在不同的机架上,亦或在物理距离相距甚远的不同数据中心里。有4个VxLAN overlay网络,标识分别为VNI 22, 34, 74, 98。来看看Server 1上的虚拟机VM1-1和Server 2上的虚拟机VM2-4,它们属于相同的标识为VNI 22的VXLAN overlay网络。虚拟机并不知道overlay网络,也不知道VxLAN报文的封装和解封过程,因为这些过程都由物理服务器上的VTEP完成。看到这里,即使你没听说过VTEP这个概念,也大概知道它的作用了吧,VTEP (VXLAN Tunnel End Point), An entity that originates and/or terminates VXLAN tunnels,VTEP负责VxLAN的封装和解封装。

这种部署方法是基于物理机能够感知VXLAN协议,从而可以作为VxLAN的封装和解封点(VTEP) 。另外一种部署方法是把VTEP设备的位置放在网络设备中,比如交换机,可以作为一个VXLAN网络来负责VxLAN报文的封装和解封。在数据中心的部署中,物理机作为VTEP的部署方式能更好地和物理网络设备解耦,并且更加方便运维。

Linux上对VXLAN的支持

快速搭建和理解VXLAN的方法之一就是利用Linux。从内核3.7版本开始,Linux就开始支持VXLAN。到了内核3.12版本,Linux对VXLAN的支持已经完备,支持单播和组播,IPv4和IPv6。利用man查看ip的link子命令,可以查看是否有vxlan type,如下:

[root@localhost ~]# man ip link 
[root@localhost ~]# 
[root@localhost ~]# modinfo vxlan
filename:       /lib/modules/3.10.0-1127.el7.x86_64/kernel/drivers/net/vxlan.ko.xz
alias:          rtnl-link-vxlan
description:    Driver for VXLAN encapsulated traffic
author:         Stephen Hemminger <stephen@networkplumber.org>
version:        0.1
license:        GPL
retpoline:      Y
rhelversion:    7.8
srcversion:     EC0D42004ADF2831F27E171
depends:        udp_tunnel,ip6_udp_tunnel
intree:         Y
vermagic:       3.10.0-1127.el7.x86_64 SMP mod_unload modversions 
signer:         CentOS Linux kernel signing key
sig_key:        69:0E:8A:48:2F:E7:6B:FB:F2:31:D8:60:F0:C6:62:D8:F1:17:3D:57
sig_hashalgo:   sha256
parm:           udp_port:Destination UDP port (ushort)
parm:           log_ecn_error:Log packets received with corrupted ECN (bool)
[root@localhost ~]#
搜索vxlan,可以看到如下描述,可以利用ip link add增加类型为VXLAN的link。

VXLAN Type Support
For a link of type VXLAN the following additional arguments are supported:

ip link add DEVICE type vxlan id VNI [ dev PHYS_DEV ] [ { group | remote } IPADDR ] [ local { IPADDR | any } ] [ ttl TTL ] [ tos TOS ] [ dstport PORT ] [ srcport MIN
MAX ] [ [no]learning ] [ [no]proxy ] [ [no]rsc ] [ [no]l2miss ] [ [no]l3miss ] [ [no]udpcsum ] [ [no]udp6zerocsumtx ] [ [no]udp6zerocsumrx ] [ ageing SECONDS ] [ maxad‐
dress NUMBER ] [ gbp ]

下面的实验在如下环境中完成:

  • 操作系统版本:CentOS Linux release 7.8.2003 (Core)
  • 内核版本:Linux wubo 3.10.0-1127.el7.x86_64 #1 SMP Tue Mar 31 23:36:51 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
  • 云虚机vm1 ens33网络接口IP 10.10.1.20,云虚机vm2 ens38网络接口IP 10.10.1.21

场景1: 最简单的点对点VXLAN

创建简单的点对点VXLAN环境非常简单。如下图所示,只需要在两个机器(物理机或者虚拟机都可以,本实验中是云上的虚拟机环境)中各创建一个vxlan类型的网络接口即可,vxlan类型的接口vxlan1可以作为上文中提到的VETP。

 

在上面的环境中,注意我们将vxlan网络接口(vxlan1)配置上IP地址,在192.168.20.0/24网段内。在IP地址分配后,Linux系统的路由表就会创建一条路由,去往192.168.20.0/24网段的报文走网络接口vxlan1出去。vm1上去往1192.168.20.0/24的报文,在vxlan1上会做VXLAN封装,内层地址是192.168.20.2,外层地址是10.10.1.20。VXLAN报文通过物理网络ens33达到对端vm2上的VETP vxlan1,在vm2的vxlan1接口上做VXLAN协议的解封装,从而结束整个过程。

上图是一个物理上的示意图,在逻辑上形成的VXLAN overlay网络环境如下图,虚线部分示意出来的Overlay Network和VXLAN Tunnel都是逻辑上的概念。如果有容器和虚机被接入逻辑上的Overlay网络192.16.20.0/24,它们完全不用感知底层物理网络,看起来对端是和自己在同一个二层环境里,就是像是在VTEP设备的上面直接构建了一条VXLAN Tunnel,把Overlay网络里的网络接口直接在二层打通。

具体的配置只需要3条命令。如下,在vm1上执行如下命令:

[root@localhost ~]# ip link add vxlan1 type vxlan id 1 remote 10.10.1.21 dstport 4789 dev ens33
[root@localhost ~]# ip link set vxlan1 up
[root@localhost ~]# ip addr add 192.168.20.2/24 dev vxlan1

第一条命令创建了一个Linux上类型为vxlan的网络接口,名为vxlan1。

  • id: VNI标识是1。
  • remote: 作为一个VTEP设备来封装和解封VXLAN报文,需要知道将封装好的VXLAN报文发送到哪个对端VTEP。Linux上可以利用group指定组播组地址,或者利用remote指定对端单播地址。在实验的云环境中默认不支持组播,这里利用remote指定点对点的对端IP地址为10.10.1.21。
  • dstport: 指定目的端口为4789。因为当Linux内核3.7版本首次实现VXLAN时,UDP端口还并没有规定下来。很多厂商利用了8472这个端口,Linux也采用了相同的端口。后来IANA分配了4789作为VXLAN的目的UDP端口。如果你需要使用IANA端口,需要用dstport指定。
  • dev: 指定VTEP通过哪个物理device来通信,这里是使用ens33。

第二条命令让vxlan1接口up起来。

第三条命令给设备分配IP地址192.168.20.2, 子网掩码为24 (255.255.255.0)。

在vm2上,利用类似方法创建名为vxlan1的网络接口。

[root@localhost ~]# ip link add vxlan1 type vxlan id 1  remote 10.10.1.20 dstport 4789 dev ens38
[root@localhost ~]# ip link set vxlan1 up
[root@localhost ~]# ip addr add 192.168.20.3/24 dev vxlan1

 

看下vm1的如下路由表,去往目的网段192.168.20.0/24的报文将走vxlan1接口。

[root@localhost ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
10.10.1.0       0.0.0.0         255.255.255.0   U     0      0        0 ens33
192.168.20.0    0.0.0.0         255.255.255.0   U     0      0        0 vxlan1

在vm1上ping overlay网络的对端IP地址192.168.20.3,可以ping通。 

[root@localhost ~]# ping -I vxlan1 192.168.20.3 
PING 192.168.20.3 (192.168.20.3) from 192.168.20.2 vxlan1: 56(84) bytes of data.
64 bytes from 192.168.20.3: icmp_seq=1 ttl=64 time=0.509 ms
64 bytes from 192.168.20.3: icmp_seq=2 ttl=64 time=1.13 ms
64 bytes from 192.168.20.3: icmp_seq=3 ttl=64 time=1.97 ms
^C
--- 192.168.20.3 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 0.509/1.205/1.977/0.602 ms

在ping包的同时,用tcpdump抓vm1 ens33网卡的包。因为报文到达ens33前经过了网络接口vxlan1, 完成了VXLAN的封装,所以在抓包结果里应该能看到完整的VXLAN报文。

抓包时可以只抓和对端10.10.1.21通信的报文,如下:

[root@localhost ~]# tcpdump -i  ens33 host 10.10.1.21 -s0 -v 
tcpdump: listening on ens33, link-type EN10MB (Ethernet), capture size 262144 bytes
17:19:58.526773 IP (tos 0x0, ttl 64, id 7650, offset 0, flags [none], proto UDP (17), length 134)
    localhost.localdomain.40530 > 10.10.1.21.4789: VXLAN, flags [I] (0x08), vni 1
IP (tos 0x0, ttl 64, id 61713, offset 0, flags [DF], proto ICMP (1), length 84)
    localhost.localdomain > 192.168.10.3: ICMP echo request, id 6981, seq 94, length 64
17:19:58.527649 IP (tos 0x0, ttl 64, id 55169, offset 0, flags [none], proto UDP (17), length 134)
    10.10.1.21.34613 > localhost.localdomain.4789: VXLAN, flags [I] (0x08), vni 1
IP (tos 0x0, ttl 64, id 42487, offset 0, flags [none], proto ICMP (1), length 84)
    192.168.10.3 > localhost.localdomain: ICMP echo reply, id 6981, seq 94, length 64

 

还原

[root@localhost ~]# ifconfig vxlan1 down
[root@localhost ~]# ip link delete name dev vxlan1 type vxlan

场景2: 容器跨主机通信

上面最简单的点对点VXLAN实验只是个简答的演示,没有太多实际工程意义,本节用容器通信来演示一个更加完整的场景。

 

场景描述:在vm1和vm2上各部署一个docker容器,默认情况下,一个容器宿主机上的容器能够直接用私网IP地址通信,因为它们利用一个网桥接在一起。而不同宿主机上的容器无法直接用私网IP地址通信。k8s等docker部署软件中的网络组建实际上完成了这部分工作,让不同宿主机的容器能够直接通信。本节使用原生docker,以及在宿主机上自建的vxlan网络接口,来打通不同宿主机上容器,让它们可以直接利用内网IP通信。

注意:因为实验在云上的虚拟机上完成,上面提到的容器宿主机,用的是云上的虚拟机。容器宿主机也可以是物理机,实验效果不变。

准备docker容器

安装docker的过程不展开了,docker官方文档有详细的描述。在Linux安装了docker后,可以看到多了一个docker0的网络接口,默认在172.17.0.0/16网段。这个是连接本地多个容器的网桥。

# ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1450
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:44:e8:74:e8  txqueuelen 0  (Ethernet)
        RX packets 6548  bytes 360176 (351.7 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 7489  bytes 40249455 (38.3 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

使用默认172.17.0.0/16网段,docker容器的IP地址都会从172.17.0.2开始分配。为了能使vm1和vm2上的容器使用不同的IP地址,在利用docker run启动容器的时候需要能自定义IP地址,而利用--ip参数自定义IP地址的功能只能在自定网络中支持,所以先创建一个自定义网络,指定网段172.18.0.0/16。

# docker network create --subnet 172.18.0.0/16 mynetwork
3231f89d69f6b3fbe2550392ebe4d00daa3d19e251f66ed2d81f61f2b9184362
# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
1cb284a6cb33        bridge              bridge              local
069538be0246        host                host                local
3231f89d69f6        mynetwork           bridge              local
0b7934996485        none                null                local

利用docker network ls查看,可以看到一个新的bridge网络被创建,名称为我指定的mynetwork。利用ifconfig可以看到多了一个网络接口,名字不是dockerXX,而直接以br开头,是一个网桥。

br-3231f89d69f6: flags=4099<UP,BROADCAST,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 172.18.255.255
        ether 02:42:97:22:a5:f9  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

创建一个新的容器,如下:

# docker run -itd --net mynetwork --ip 172.18.0.2 centos
16bbaeaaebfccd2a497e3284600f5c0ce230e89678e0ff92f6f4b738c6349f8d
  • --net指定自定义网络
  • --ip指定IP地址
  • centos指定image

查看容器ID和状态,并且登录SHELL,如下:

[root@16bbaeaaebfc /]# ip a s eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.2  netmask 255.255.0.0  broadcast 172.18.255.255
        ether 02:42:ac:12:00:02  txqueuelen 0  (Ethernet)
        RX packets 3319  bytes 19221325 (18.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2015  bytes 132903 (129.7 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

在vm2上执行同样的操作,在创建新容器的时候,指定IP地址为172.18.0.3,容器的环境即准备完毕。在vm1上的centos 容器中ping 172.18.0.3,和预期一致,是无法ping通的。

[root@16bbaeaaebfc /]# ping 172.18.0.3 -c 2
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
From 172.18.0.2 icmp_seq=1 Destination Host Unreachable
From 172.18.0.2 icmp_seq=2 Destination Host Unreachable

--- 172.18.0.3 ping statistics ---
2 packets transmitted, 0 received, +2 errors, 100% packet loss, time 1000ms
pipe 2
[root@16bbaeaaebfc /]# ping 172.18.0.1 -c 2
PING 172.18.0.1 (172.18.0.1) 56(84) bytes of data.
64 bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=0.060 ms
64 bytes from 172.18.0.1: icmp_seq=2 ttl=64 time=0.079 ms

--- 172.18.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.060/0.069/0.079/0.012 ms

 

创建VXLAN接口接入docker网桥

先来梳理下docker及docker容器在Linux宿主机网络模块中做的操作,梳理清楚之后会发现打通不同宿主机上docker容器的方法非常简单。从宿主Linux系统的视角看操作系统中的网络设备,总结如下:

  • docker0接口:网桥,在安装完docker后默认被创建,网段是172.17.0.0/16,网桥的默认IP地址为172.17.0.1。
  • br-xxxx接口:网桥,在创建完自定义docker网络完被创建,网段是被用户指定的172.18.0.0/16,网桥的默认IP地址为172.18.0.1。
  • vethxxxx接口:veth网络接口,在创建一个具体的docker容器后被创建,如果有N个运行的容器,就会有N个veth网络接口。容器中的eth0接口和宿主机的veth网络接口是一个veth网络对,Linux上的veth接口作为一个端口连接入docker网桥,如docker0或其他自定义网桥。这也是为什么一个宿主机上的docker容器能够默认通信的原因,因为它们创建后就被接入到了同一个网桥上。

为了方便理解,在默认网段172.17.0.0/16中创建2个容器,在自定义网段中上文已经创建了1个docker容器,利用btctl查看网桥及其接口,如下:

# brctl show
bridge name    bridge id        STP enabled    interfaces
br-3231f89d69f6        8000.02429722a5f9    no        veth2fa4c50
docker0        8000.024244e874e8    no        vethc7cd982
                                       vethd3d0c18

从上面的输出结果可以看到,默认网桥docker0上,有vethc7cd982和vethd3d0c18两个网络接口接入。在定义网络网桥br-3231f89d69f6一个端口上,veth2fa4c50网络接口接入。这三个veth网络接口分别连接着一个docker容器的eth0网络接口,连接着同一个网桥的veth网络接口vethc7cd982和vethd3d0c18默认二层能通。

有了上面的梳理和本文第一节VXLAN网络接口的基础知识,想必打通不同宿主机上docker容器的方法也比较清晰了。思路就是在两个容器宿主机上各创建一个VXLAN接口,并且将VXLAN接口接入docker网桥的端口上,如下图:

 

有了VXLAN接口的连接后,从vm1上docker容器发出的包到达docker网桥后,可以从网桥的VXLAN接口出去,从而报文在VETP(VXLAN接口)处被封装成VXLAN报文,再从物理网络上到达对端VETP所在的主机vm2。对端VTEP能正确解包VXLAN报文的话,随后即可将报文通过vm2上的docker网桥送到上层的docker容器中。

具体的配置如下,在vm1上:

# ip link add vxlan_docker type vxlan id 200 remote 172.31.0.107 dstport 4789 dev ens33
# ip link set vxlan_docker up
# brctl addif br-3231f89d69f6 vxlan_docker

 

  • 第一条命令创建VNI为200的VXLAN网络接口,名称为vxlan_docker,参数设置和场景1中的各个参数类似。
  • 第三条命令把新创建的VXLAN接口vxlan_docker接入到docker网桥br-3231f89d69f6中。

在vm2上,输入如下命令:

# ip link add vxlan_docker type vxlan id 200 remote 172.31.0.106 dstport 4789 dev ens38
# ip link set vxlan_docker up
# brctl addif br-f4b35af34313 vxlan_docker

在vm1的docker容器上再ping 172.18.0.3,结果如下,ping可以通。注意RTT的时间,ping 172.18.0.3的RTT在10^(-1)毫秒级别,ping 172.18.0.1的RTT在10^(-2)毫秒级别,前者是走物理网络的延迟,后者是协议栈的延迟,两者有量级上的差别。

# docker exec -it 16bbaeaaebfc ip a s eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.2  netmask 255.255.0.0  broadcast 172.18.255.255
        ether 02:42:ac:12:00:02  txqueuelen 0  (Ethernet)
        RX packets 3431  bytes 19230266 (18.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2132  bytes 141908 (138.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# docker exec -it 16bbaeaaebfc ping 172.18.0.3 -c 2
PING 172.18.0.3 (172.18.0.3) 56(84) bytes of data.
64 bytes from 172.18.0.3: icmp_seq=1 ttl=64 time=0.544 ms
64 bytes from 172.18.0.3: icmp_seq=2 ttl=64 time=0.396 ms

--- 172.18.0.3 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.396/0.470/0.544/0.074 ms
#
# docker exec -it 16bbaeaaebfc ping 172.18.0.1 -c 2
PING 172.18.0.1 (172.18.0.1) 56(84) bytes of data.
64 bytes from 172.18.0.1: icmp_seq=1 ttl=64 time=0.072 ms
64 bytes from 172.18.0.1: icmp_seq=2 ttl=64 time=0.072 ms

--- 172.18.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.072/0.072/0.072/0.000 ms

最后说明,本节只是为了演示Linux VXLAN的用于而构造了这个简单但没有实际用处的场景,在跨主机环境的容器之间利用VXLAN从二层打通。在工程中做容器跨主机通信时有很多方面需要考虑,也有很多项目在致力于这方面的研究。比如Flannel,通过给每台宿主机分配一个子网的方式为容器提供虚拟网络,它基于Linux TUN/TAP,使用UDP封装IP包来实现L3 overlay网络,并借助etcd维护网络的分配情况。Github上有项目的文档,另外文章做了不错的基本介绍。

 

 

 

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值