linux vxlan设备的不同场景下的四种使用方法

在不同场景下,我们vxlan设备通常有多种使用方法

  • 点对点的 VXLAN:用于实现明确的端到端隧道
  • VXLAN + Bridge:用于实现明确的端到端隧道,且端与端上的服务是多对多的
  • 多播模式的 VXLAN:让同一个 VXLAN 网络中容纳多个节点,形成一张vxlan连通网
  • 手动维护 vtep 组:用于自己维护网络拓扑,可自由规划,如:k8s flannel cni

场景1:点对点的 VXLAN

点对点 VXLAN 即两台主机构建的 VXLAN 网络,每台主机上有一个 VTEP,VTEP 之间通过它们的 IP 地址进行通信。点对点 VXLAN 网络拓扑图如图所示:

vxlan外部长度 vxlan实现_网络


实验环境示例:

# vm1
[root@test-1 ~]# uname -sr
Linux 3.10.0-693.el7.x86_64
[root@test-1 ~]# ip addr show dev eth0 
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1446 qdisc pfifo_fast state UP qlen 1000
    link/ether fa:16:3e:0c:e3:c6 brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.11/24 brd 10.10.10.255 scope global dynamic eth0
       valid_lft 84967sec preferred_lft 84967sec
       
# vm2
[root@test-2 ~]# uname -sr
Linux 3.10.0-693.el7.x86_64
[root@test-2 ~]# ip addr show dev eth0 
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1446 qdisc pfifo_fast state UP qlen 1000
    link/ether fa:16:3e:50:61:0d brd ff:ff:ff:ff:ff:ff
    inet 10.10.10.9/24 brd 10.10.10.255 scope global dynamic eth0
       valid_lft 84929sec preferred_lft 84929sec

开始配置

[root@test-1 ~]# ip link add vxlan1 type vxlan id 1 dstport 4789 remote 10.10.10.9 local 10.10.10.11 dev eth0
  • vxlan1 即为创建的接口名称 ,type 为 vxlan 类型。
  • id 即为 VNI。
  • dstport 指定 UDP的目的端口,IANA 为vxlan分配的目的端口是 4789。
  • remote 和 local ,即远端和本地的IP地址,因为vxlan是MAC-IN-UDP,需要指定外层IP,此处即指。
  • dev 本地流量接口。用于 vtep 通信的网卡设备,用来读取 IP 地址。注意这个参数和 local参数含义是相同的,在这里写出来是为了告诉大家有两个参数存在。

创建完成可看到

[root@test-1 ~]# ip addr show type vxlan
3: vxlan1: <BROADCAST,MULTICAST> mtu 1396 qdisc noop state DOWN qlen 1000
    link/ether 86:65:ef:3d:a1:e7 brd ff:ff:ff:ff:ff:ff

接口现在还没有地址,也没有开启,接下来进行如下配置

[root@test-1 ~]# ip addr add 192.168.1.3/24 dev vxlan1
[root@test-1 ~]# ip link set vxlan1 up
[root@test-1 ~]# ip addr show type vxlan
3: vxlan1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1396 qdisc noqueue state UNKNOWN qlen 1000
    link/ether 86:65:ef:3d:a1:e7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.3/24 scope global vxlan1
       valid_lft forever preferred_lft forever

vxlan1已经配置完成,之后看一下路由表即vxlan 的 FDB表

#(仅列出vxlan部分)
[root@test-1 ~]# ip route 
192.168.1.0/24 dev vxlan1 proto kernel scope link src 192.168.1.3 
[root@test-1 ~]# bridge fdb
00:00:00:00:00:00 dev vxlan1 dst 10.10.10.9 via eth0 self permanent
# 即默认vxlan1 对端地址为 10.10.10.9,通过eth0进行报文交换

对test-2进行同样的配置,保证VNI和dstport一致。VNI一致是为了不进行vxlan隔离,
dstport一致是因为IANA 为vxlan分配的目的端口是 4789。

之后在test-1上进行测试连通性

[root@test-1 ~]# ping 192.168.1.4 -c 3
PING 192.168.1.4 (192.168.1.4) 56(84) bytes of data.
64 bytes from 192.168.1.4: icmp_seq=1 ttl=64 time=0.424 ms
64 bytes from 192.168.1.4: icmp_seq=2 ttl=64 time=0.437 ms
64 bytes from 192.168.1.4: icmp_seq=3 ttl=64 time=0.404 ms

--- 192.168.1.4 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.404/0.421/0.437/0.027 ms

场景2:VXLAN + Bridge

vxlan外部长度 vxlan实现_网络_02


下面用 network namespace来模拟tap(vm接口),进行如下配置

#创建vxlan接口
[root@test-1 ~]# ip link add vxlan1 type vxlan id 2 dstport 4789 group 224.0.0.6 dev eth0 
#创建NS
[root@test-1 ~]# ip netns add vm0
[root@test-1 ~]# ip netns exec vm0 ip link set dev lo up
#创建veth 接口,并将其中一个veth口放到 NS中
[root@test-1 ~]# ip link add veth0 type veth peer name veth1
[root@test-1 ~]# ip link set veth0 netns vm0
[root@test-1 ~]# ip netns exec vm0 ip link set veth0 name eth0
[root@test-1 ~]# ip netns exec vm0 ip addr add 192.168.1.3/24 dev eth0
[root@test-1 ~]# ip netns exec vm0 ip link set dev eth0 up
[root@test-1 ~]# ip netns exec vm0 ip addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
8: eth0@if7: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state LOWERLAYERDOWN qlen 1000
    link/ether 9e:ab:18:d5:25:55 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.3/24 scope global eth0
       valid_lft forever preferred_lft forever
#创建桥,并将vxlan、veth加入桥
[root@test-1 ~]# ip link add br0 type bridge
[root@test-1 ~]# ip link set vxlan1 master br0
[root@test-1 ~]# ip link set vxlan1 up
[root@test-1 ~]# ip link set dev veth1 master br0
[root@test-1 ~]# ip link set dev veth1 up
[root@test-1 ~]# ip link set dev br0 up
[root@test-1 ~]# bridge link
6: vxlan1 state UNKNOWN : <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1396 master br0 state forwarding priority 32 cost 100 
7: veth1 state UP @(null): <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 master br0 state forwarding priority 32 cost 2

其他设备进行类似配置(设备较多可用脚本实现)。利用命令 ip netns exec vm0 ping IP来验证连通性。

此过程为:

  • ns eth0 ARP广播,通过veth1到达br0。
  • br0上没有二层表,进行学习之后,每个口(除收报文口)进行转发,到达vxlan1。
  • vxlan1口fdb表默认走多播,进行外层封装之后从eth0接口发出。

场景3:多播模式的 VXLAN

要组成同一个 vxlan 网络,vtep 必须能感知到彼此的存在。多播组本来的功能就是把网络中的某些节点组成一个虚拟的组,所以 vxlan 最初想到用多播来实现是很自然的事情。拓扑如下

vxlan外部长度 vxlan实现_IP_03

[root@test-1 ~]# ip link add vxlan1 type vxlan id 2 dstport 4789 group 224.0.0.1 dev eth0 
[root@test-1 ~]# ip addr add 192.168.1.3/24 dev vxlan1
[root@test-1 ~]# ip link set vxlan1 up
[root@test-1 ~]# ip route 
192.168.1.0/24 dev vxlan1 proto kernel scope link src 192.168.1.3 
[root@test-1 ~]# bridge fdb
00:00:00:00:00:00 dev vxlan1 dst 224.0.0.1 via eth0 self permanent

vxlan外部长度 vxlan实现_服务器_04

ARP回应依然是单播报文。通信结束之后,查看ARP及fdb表项为

[root@test-1 ~]# ip neigh
192.168.1.4 dev vxlan1 lladdr a6:8b:d5:2d:83:3c STALE
[root@test-1 ~]# bridge fdb
00:00:00:00:00:00 dev vxlan1 dst 224.0.0.1 via eth0 self permanent
a6:8b:d5:2d:83:3c dev vxlan1 dst 10.10.10.9 self

场景4:手动维护 vtep 组

vxlan外部长度 vxlan实现_网络_05

这个问题的解决可以通过手动维护来解决

  • 手动维护 vtep 组
    经过上面几个实验,我们来思考一下为什么要使用多播。因为对 overlay 网络来说,它的网段范围是分布在多个主机上的,因此传统 ARP 报文的广播无法直接使用。要想做到 overlay 网络的广播,必须把报文发送到所有 vtep 在的节点,这才引入了多播。
    如果有一种方法能够不通过多播,能把 overlay 的广播报文发送给所有的 vtep 主机的话,也能达到相同的功能。当然在维护 vtep 网络组之前,必须提前知道哪些 vtep 要组成一个网络,以及这些 vtep 在哪些主机上。
    Linux 的 vxlan 模块也提供了这个功能,而且实现起来并不复杂。创建 vtep interface 的时候不使用 remote 或者 group 参数就行:
    $ ip link add vxlan0 type vxlan
    id 42
    dstport 4789
    dev enp0s8
    这个 vtep interface 创建的时候没有指定多播地址,当第一个 ARP 请求报文发送时它也不知道要发送给谁。但是我们可以手动添加默认的 FDB 表项,比如:
    $ bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.101
    $ bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.102
    这样的话,如果不知道对方 VTEP 的地址,就会往选择默认的表项,发到 192.168.8.101 和 192.168.8.102,相当于手动维护了一个 vtep 的多播组。
    在所有的节点的 vtep 上更新对应的 fdb 表项,就能实现 overlay 网络的连通。整个通信流程和多播模式相同,唯一的区别是,vtep 第一次会给所有的组内成员发送单播报文,当然也只有一个 vtep 会做出应答。
    使用一些自动化工具,定时更新 FDB 表项,就能动态地维护 VTEP 的拓扑结构。
    这个方案解决了在某些 underlay 网络中不能使用多播的问题,但是并没有解决多播的另外一个问题:每次要查找 MAC 地址要发送大量的无用报文,如果 vtep 组节点数量很大,那么每次查询都发送 N 个报文,其中只有一个报文真正有用。
  • 手动维护 fdb 表
    如果提前知道目的容器 MAC 地址和它所在主机的 IP 地址,也可以通过更新 fdb 表项来减少广播的报文数量。
    创建 vtep 的命令:
    $ ip link add vxlan0 type vxlan
    id 42
    dstport 4789
    dev enp0s8
    nolearning
    这次我们添加了 nolearning 参数,这个参数告诉 vtep 不要通过收到的报文来学习 fdb 表项的内容,因为我们会自动维护这个列表。
    然后可以添加 fdb 表项告诉 vtep 容器 MAC 对应的主机 IP 地址:
    $ bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.101
    $ bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.102
    $ bridge fdb append 52:5e:55:58:9a:ab dev vxlan0 dst 192.168.8.101
    $ bridge fdb append d6:d9💿0a:a4:28 dev vxlan0 dst 192.168.8.102
    如果知道了对方的 MAC 地址,vtep 搜索 fdb 表项就知道应该发送到哪个对应的 vtep 了。需要注意是的,这个情况还是需要默认的表项(那些全零的表项),在不知道容器 IP 和 MAC 对应关系的时候通过默认方式发送 ARP 报文去查询对方的 MAC 地址。
    需要注意的是,和上一个方法相比,这个方法并没有任何效率上的改进,只是把自动学习 fdb 表项换成了手动维护(当然实际情况一般是自动化程序来维护),第一次发送 ARP 请求还是会往 vtep 组发送大量单播报文。
    当时这个方法给了我们很重要的提示:如果实现知道 vxlan 网络的信息,vtep 需要的信息都是可以自动维护的,而不需要学习。
  • 手动维护 ARP 表
    除了维护 fdb 表,arp 表也是可以维护的。如果能通过某个方式知道容器的 IP 和 MAC 地址对应关系,只要更新到每个节点,就能实现网络的连通。
    但是这里有个问题,我们需要维护的是每个容器里面的 ARP 表项,因为最终通信的双方是容器。到每个容器里面(所有的 network namespace)去更新对应的 ARP 表,是件工作量很大的事情,而且容器的创建和删除还是动态的,。linux 提供了一个解决方案,vtep 可以作为 arp 代理,回复 arp 请求,也就是说只要 vtep interface 知道对应的 IP - MAC 关系,在接收到容器发来的 ARP 请求时可以直接作出应答。这样的话,我们只需要更新 vtep interface 上 ARP 表项就行了。
    创建 vtep interface 需要加上 proxy 参数:
    $ ip link add vxlan0 type vxlan
    id 42
    dstport 4789
    dev enp0s8
    nolearning
    proxy
    这条命令和上部分相比多了 proxy 参数,这个参数告诉 vtep 承担 ARP 代理的功能。如果收到 ARP 请求,并且自己知道结果就直接作出应答。
    当然我们还是要手动更新 fdb 表项来构建 vtep 组,
    $ bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.101
    $ bridge fdb append 00:00:00:00:00:00 dev vxlan0 dst 192.168.8.102
    $ bridge fdb append 52:5e:55:58:9a:ab dev vxlan0 dst 192.168.8.101
    $ bridge fdb append d6:d9💿0a:a4:28 dev vxlan0 dst 192.168.8.102
    然后,还需要为 vtep 添加 arp 表项,所有要通信容器的 IP - MAC二元组都要加进去。
    $ ip neigh add 10.20.1.3 lladdr d6:d9💿0a:a4:28 dev vxlan0
    $ ip neigh add 10.20.1.4 lladdr 52:5e:55:58:9a:ab dev vxlan0
    在要通信的所有节点配置完之后,容器就能互相 ping 通。当容器要访问彼此,并且第一次发送 ARP 请求时,这个请求并不会发给所有的 vtep,而是当前由当前 vtep 做出应答,大大减少了网络上的报文。
    借助自动化的工具做到实时的表项(fdb 和 arp)更新,这种方法就能很高效地实现 overlay 网络的通信。
  • 动态更新 arp 和 fdb 表项
    尽管前一种方法通过动态更新 fdb 和 arp 表避免多余的网络报文,但是还有一个的问题:为了能够让所有的容器正常工作,所有可能会通信的容器都必须提前添加到 ARP 和 fdb 表项中。但并不是网络上所有的容器都会互相通信,所以添加的有些表项(尤其是 ARP 表项)是用不到的。
    Linux 提供了另外一种方法,内核能够动态地通知节点要和哪个容器通信,应用程序可以订阅这些事件,如果内核发现需要的 ARP 或者 fdb 表项不存在,会发送事件给订阅的应用程序,这样应用程序从中心化的控制拿到这些信息来更新表项,做到更精确的控制。
    要收到 L2(fdb)miss,必须要满足几个条件:
  • 目的 MAC 地址未知,也就是没有对应的 fdb 表项
  • fdb 中没有全零的表项,也就是说默认规则
  • 目的 MAC 地址不是多播或者广播地址

要实现这种功能,创建 vtep 的时候需要加上额外的参数:

$ ip link add vxlan0 type vxlan 
 id 42 
 dstport 4789 
 dev enp0s8 
 nolearning 
 proxy 
 l2miss 
 l3miss

这次多了两个参数 l2miss 和 l3miss:

  • l2miss:如果设备找不到 MAC 地址需要的 vtep 地址,就发送通知事件
  • l3miss:如果设备找不到需要 IP 对应的 MAC 地址,就发送通知事件
    ip monitor 命令能做到这点,监听某个 interface 的事件,具体用法请参考 man 手册。
root@node0:~# ip monitor all dev vxlan0


如果从本节点容器 ping 另外一个节点的容器,就先发生 l3 miss,这是 l3miss 的通知事件:

root@node0:~# ip monitor all dev vxlan0
 [nsid current]miss 10.20.1.3 STALE


l3miss 是说这个 IP 地址,vtep 不知道它对应的 MAC 地址,因此要手动添加 arp 记录:

$ ip neigh replace 10.20.1.3 
 lladdr b2:ee:aa:42:8b:0b 
 dev vxlan0 
 nud reachable

上面这条命令设置的 nud reachable 参数意思是,这条记录有一个超时时间,系统发现它无效一段时间会自动删除。这样的好处是,不需要手动去删除它,删除后需要通信内核会再次发送通知事件。 nud 是 Neighbour Unreachability Detection 的缩写, 当然根据需要这个参数也可以设置成其他值,比如 permanent,表示这个记录永远不会过时,系统不会检查它是否正确,也不会删除它,只有管理员也能对它进行修改。

这时候还是不能正常通信,接着会出现 l2miss 的通知事件:

root@node0:~# ip monitor all dev vxlan0
 [nsid current]miss lladdr b2:ee:aa:42:8b:0b STALE

类似的,这个事件是说不知道这个容器的 MAC 地址在哪个节点上,所以要手动添加 fdb 记录:

root@node0:~# bridge fdb add b2:ee:aa:42:8b:0b dst 192.168.8.101 dev vxlan0
在通信的另一台机器上执行响应的操作,就会发现两者能 ping 通了。