手动创建vxlan网络连接container
本文主要对linux下实现vxlan网络有个初步的认识,涉及较多的是动手操作,而不是理论知识。如果想知道更多关于vxlan协议的一些理论,包括产生的北京,报文的格式,请参考vxlan 协议的介绍文章。
在下述内容中我们主要是在linux上模拟vxlan设备,实现docker跨主机的overlay网络。
实验环境
本次实验我们使用两个linux机器,系统为centos7,IP信息如下:
- 192.168.1.100
- 192.168.1.200
我们搭建的overlay网络的网段为172.55.1.0/24 。实验目的就是vxlan能够通过overlay IP互相连接。
实验
点对点的vxlan
1
|
先来搭建一个最简单的 vxlan 网络,两台机器构成一个 vxlan 网络,每台机器上有一个 vtep,vtep 通过它们的 IP 互相通信。这次实验完成后的网络结构如下图所示: |
首先使用 ip
命令创建我们的 vxlan interface,在192.168.1.100上执行下述命令:
ip link add vxlan1 type vxlan id 444 dstport 4789 remote 192.168.1.200 local 192.168.1.100 dev eth0
大概解释下上述参数的含义:
- vxlan1:所创建的网络设备的名字
- type vxlan:所创建的网络设备的类型
- id 444:所创建vxlan VNI,这个值可以在 1 到 2^24 之间
- dstport 4789:vtep 通信的端口,linux 默认使用 8472(为了保持兼容,默认值一直没有更改),而 IANA 分配的端口是 4789,所以我们这里显式指定了它的值
- remote 192.168.1.200: 对方 vtep 的地址,类似于点对点协议
- local 192.168.1.100: 当前节点 vtep 要使用的 IP 地址
- dev eth0: 当节点用于 vtep 通信的网卡设备,用来读取 IP 地址。注意这个参数和
local
参数含义是相同的,在这里写出来是为了告诉大家有两个参数存在
执行完之后会在宿主机上生成对应的网络设备,可以通过ip命令查看该网络设备的详情:
|
|
给vxlan1配置ip并启用:
|
|
再次观察宿主机上几个点:
路由变化
1 2
# ip r |grep vxlan1 172.55.1.0/24 dev vxlan1 proto kernel scope link src 172.55.1.2
vxlan fdb表变化
1 2
# bridge fdb| grep vxlan1 00:00:00:00:00:00 dev vxlan1 dst 192.168.1.200 via eth0 self permanent
这个表项的意思是说,默认的而 vtep 对端地址为 192.168.1.200
,换句话说,如果接收到的报文添加上 vxlan 头部之后都会发到 192.168.1.200
。
在另外一台虚拟机(192.168.1.200
)上也进行相同的配置,要保证 VNI 也是 444,dstport 也是 4789,并修改 vtep 的地址和 remote IP 地址到相应的值。测试两台 vtep 的连通性:
|
|
那么问题来了,这两个vxlan设备是可以正常通信,怎么证明其走的就是隧道网络呢?其实可以通过抓包来看包的头部包含哪些内容来判断。
多播模式的vxlan
上述实现了点对点的vxlan,其实没啥大用。因为一般集群不止两个节点,因此多播模式的vxlan需要研究一下。
这个实验和前面一个非常相似,只不过主机之间不是点对点的连接,而是通过多播组成一个虚拟的整体。最终的网络架构也很相似(为了简单图中只有两个主机,但这个模型可以容纳多个主机组成 vxlan 网络)。
同样的,先创建vxlan设备,命令如下:
|
|
这个命令和上述点对点的vxlan设备创建命令相似,主要不同的是group,很容易理解,点对点的时候,只需要指明对端的地址就行。而在多播模式下,需要把不同的主机加到一个组(group)内,因此需要制定一个group address。关于多播的原理和使用不是这篇文章的重点,这里选择的多播 IP 地址也没有特殊的含义,关于多播的内容可以自行了解。
上述命令运行完之后,在宿主机上路由不变,和点对点的一样:
|
|
不同的是bridge fdb信息:
|
|
这里默认表项的 dst 字段的值变成了多播地址 239.1.1.1
,而不是之前对方的 vtep 地址。同理给所有需要通信的节点进行上述配置,可以验证他们能通过 172.55..1.0/24 网络互相访问。
我们来分析这个模式下 vxlan 通信的过程:
在配置完成之后,所有linux机器的vtep 通过 IGMP 加入同一个多播网络 239.1.1.1
。
- 发送 ping 报文到
172.55.1.3
,查看路由表,报文会从 vxlan1 发出去 - 内核发现 vxlan1 的 IP 是
172.55.1.2/24
,和目的 IP 在同一个网段,所以在同一个局域网,需要知道对方的 MAC 地址,因此会发送 ARP 报文查询 - ARP 报文源 MAC 地址为 vxlan1 的 MAC 地址,目的 MAC 地址为全 1 的广播地址
- vxlan 根据配置(VNI 444)添加上头部
- 因为不知道对方 vtep 在哪台主机上,根据配置,vtep 会往多播地址 239.1.1.1 发送多播报文
- 多播组中所有的主机都会受到这个报文,内核发现是 vxlan 报文,会根据 VNI 发送给对应的 vtep
- vtep 去掉 vxlan 头部,取出真正的 ARP 请求报文。同时 vtep 会记录
<源 MAC 地址 - vtep 所在主机 IP 地址>
信息到 fdb 表中 - 如果发现 ARP 不是发送给自己的,直接丢弃;如果是发送给自己的,则生成 ARP 应答报文
- 应答报文目的 MAC 地址是发送方 vtep 的 MAC 地址,而且 vtep 已经通过源报文学习到了 vtep 所在的主机,因此会直接单播发送给目的 vtep。因此 vtep 不需要多播,就能填充所有的头部信息
- 应答报文通过 underlay 网络直接返回给发送方主机,发送方主机根据 VNI 把报文转发给 vtep,vtep 解包取出 ARP 应答报文,添加 arp 缓存到内核。并根据报文学习到目的 vtep 所在的主机地址,添加到 fdb 表中
- vtep 已经知道了通信需要的所有信息,后续 ICMP 的 ping 报文都是单播进行的。
在这个过程中,在主机上抓包更容易看到通信的具体情况,下面是 ARP 请求报文的详情:
可以看到 vxlan 报文可以分为三块:
- 里层(图中最下面)是 overlay 网络中实体看到的报文(比如这里的 ARP 请求),它们和经典网络的通信报文没有任何区别,除了因为 MTU 导致有些报文比较小
- 然后是 vxlan 头部,我们最关心的字段 VNI 确实是 444
- 最外层(图中最上面)是 vtep 所在主机的通信报文头部。可以看到这里 IP 地址为多播
239.1.1.1
,目的 MAC 地址也是多播对应的地址
而 ARP 应答报文不是多播而是单播的事实也能看出来:
从上面的通信过程,可以看出不少信息:
- 多播其实就相当于 vtep 之间的广播,报文会发给所有的 vtep,但是只有一个会做出应答
- vtep 会通过接收到的报文学习
MAC - VNI - Vtep IP
的信息,减少后续不必要的多播报文 - 对于 overlay 网络中的通信实体来说,整个通信过程对它们的透明的,它们认为自己的通信过程和经典网络没有区别
通信结束之后,可以在主机上看到保存的 ARP 缓存:
|
|
另外查看bridge的fdb信息也有所变化:
|
|
可以看到,增加了一条点对点的信息。
上述我们演示了点对点的vxlan,以及多播模式的vxlan,但是测试都是通过vtep设备的ip来进行通信的,下面使用linux bridge以及namespace来模拟docker跨宿主机之间使用overlay(vxlan)通信过程。
利用bridge接入容器
尽管上面的方法能够通过多播实现自动化的 overlay 网络构建,但是通信的双方只有 vtep,在实际的生产中,每台主机上都有几十台甚至上百台的虚拟机或者容器需要通信,因此我们需要找到一种方法能够把这些通信实体组织起来。
在 linux 中把同一个网段的 interface 组织起来正是网桥(bridge,或者 switch,这两个名称等价)的功能,因此这部分我们介绍如何用网桥把多个虚拟机或者容器放到同一个 vxlan overlay 网络中。最终实现的网络架构如下图所示:
因为创建虚拟机或者容器比较麻烦,我们用 network namespace 来模拟,从理论上它们是一样的。关于 network namespace 和 veth pair 的基础知识,请参考我这篇文章。
对于每个容器/虚拟机,我们创建一个 network namespace,并通过一对 veth pair 把容器中的 eth0 网络连接到网桥上。同时 vtep 也会放到网桥上,以便能够对报文进行 vxlan 相关的处理。
首先我们创建 vtep interface,使用的是多播模式:
|
|
然后创建网桥 br0
,把 vtep interface 绑定到上面:
|
|
下面使用network namespace来模拟container,利用veth pair设备将容器桥接到br0上:
|
|
这样子在主机10.104.109.48就设置好了vxlan设备和容器,可以查看具体的设备情况:
|
|
上述步骤在另外一台机器上执行,然后验证网络连通性:
|
|
容器通信过程和前面的实验类似,只不过这里容器发出的 ARP 报文会被网桥转发给 vxlan0
,然后 vxlan0
添加 vxlan 头部通过多播来找到对方的 MAC 地址。
从逻辑上可以认为,在 vxlan1
的帮助下同一个 vxlan overlay 网络中的容器是连接到同一个网桥上的,示意图如下:
多播实现很简单,不需要中心化的控制。但是不是所有的网络都支持多播,而且需要事先规划多播组和 VNI 的对应关系,在 overlay 网络数量比较多时也会很麻烦,多播也会导致大量的无用报文在网络中出现。现在很多云计算的网络都会通过自动化的方式来发现 vtep 和 MAC 信息,也就是自动构建 vxlan 网络。下面的几个部分,我们来解开自动化 vxlan 网络的秘密。
手动维护 vtep 组
经过上面几个实验,我们来思考一下为什么要使用多播。因为对 overlay 网络来说,它的网段范围是分布在多个主机上的,因此传统 ARP 报文的广播无法直接使用。要想做到 overlay 网络的广播,必须把报文发送到所有 vtep 在的节点,这才引入了多播。
如果有一种方法能够不通过多播,能把 overlay 的广播报文发送给所有的 vtep 主机的话,也能达到相同的功能。当然在维护 vtep 网络组之前,必须提前知道哪些 vtep 要组成一个网络,以及这些 vtep 在哪些主机上。
Linux 的 vxlan 模块也提供了这个功能,而且实现起来并不复杂。创建 vtep interface 的时候不使用 remote
或者 group
参数就行:
|
|
这个 vtep interface 创建的时候没有指定多播地址,当第一个 ARP 请求报文发送时它也不知道要发送给谁。但是我们可以手动添加默认的 FDB 表项,比如:
|
|
这样的话,如果不知道对方 VTEP 的地址,就会往选择默认的表项,发到 192.168.8.100
和 192.168.8.200
,相当于手动维护了一个 vtep 的多播组。
在所有的节点的 vtep 上更新对应的 fdb 表项,就能实现 overlay 网络的连通。整个通信流程和多播模式相同,唯一的区别是,vtep 第一次会给所有的组内成员发送单播报文,当然也只有一个 vtep 会做出应答。
使用一些自动化工具,定时更新 FDB 表项,就能动态地维护 VTEP 的拓扑结构。
这个方案解决了在某些 underlay 网络中不能使用多播的问题,但是并没有解决多播的另外一个问题:每次要查找 MAC 地址要发送大量的无用报文,如果 vtep 组节点数量很大,那么每次查询都发送 N 个报文,其中只有一个报文真正有用。
手动维护fdb表
如果提前知道目的容器 MAC 地址和它所在主机的 IP 地址,也可以通过更新 fdb 表项来减少广播的报文数量。
创建 vtep 的命令:
|
|
这次我们添加了 nolearning
参数,这个参数告诉 vtep 不要通过收到的报文来学习 fdb 表项的内容,因为我们会自动维护这个列表。
然后可以添加 fdb 表项告诉 vtep 容器 MAC 对应的主机 IP 地址:
|
|
如果知道了对方的 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
参数:
|
|
这条命令和上部分相比多了 proxy
参数,这个参数告诉 vtep 承担 ARP 代理的功能。如果收到 ARP 请求,并且自己知道结果就直接作出应答。
当然我们还是要手动更新 fdb 表项来构建 vtep 组,
|
|
然后,还需要为 vtep 添加 arp 表项,所有要通信容器的 IP - MAC
二元组都要加进去。
|
|
在要通信的所有节点配置完之后,容器就能互相 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 的时候需要加上额外的参数:
|
|
这次多了两个参数 l2miss
和 l3miss
:
l2miss
:如果设备找不到 MAC 地址需要的 vtep 地址,就发送通知事件l3miss
:如果设备找不到需要 IP 对应的 MAC 地址,就发送通知事件
ip monitor
命令能做到这点,监听某个 interface 的事件,具体用法请参考 man 手册.
# ip monitor all dev vxlan0
如果从本节点容器 ping 另外一个节点的容器,就先发生 l3 miss,这是 l3miss 的通知事件,:
|
|
l3miss
是说这个 IP 地址,vtep 不知道它对应的 MAC 地址,因此要手动添加 arp 记录:
|
|
上面这条命令设置的 nud reachable
参数意思是,这条记录有一个超时时间,系统发现它无效一段时间会自动删除。这样的好处是,不需要手动去删除它,删除后需要通信内核会再次发送通知事件。 nud
是 Neighbour Unreachability Detection
的缩写, 当然根据需要这个参数也可以设置成其他值,比如 permanent
,表示这个记录永远不会过时,系统不会检查它是否正确,也不会删除它,只有管理员也能对它进行修改。
这时候还是不能正常通信,接着会出现 l2miss 的通知事件:
|
|
类似的,这个事件是说不知道这个容器的 MAC 地址在哪个节点上,所以要手动添加 fdb 记录:
|
|
在通信的另一台机器上执行响应的操作,就会发现两者能 ping 通了。
总结
上面提出的所有方案中,其中手动的部分都可以使用程序来自动完成,需要的信息一般都是从集中式的控制中心获取的,这也是大多数基于 vxlan 的 SDN 网络的大致架构。当然具体的实现不一定和某种方法相同,可能是上述方法的变形或者组合,但是设计思想都是一样的。
虽然上述的实验中,为了简化图中只有两台主机,而且只有一个 vxlan 网络,但是利用相同的操作很容易创建另外一个 vxlan 网络(必须要保证 vtep 的 VNI 值不同,如果使用多播,也要保证多播 IP 不同),如下图所示:
主机会根据 VNI 来区别不同的 vxlan 网络,不同的 vxlan 网络之间不会相互影响。如果再加上 network namespace,就能实现更复杂的网络结构。