Linux网络虚拟化2
Linux网络虚拟化2
今天我们接着上节课介绍的 Linux 网络知识,继续来学习它们在虚拟化网络方面的应用,从而为后续学习容器编排系统、理解各个容器是如何通过虚拟化网络来协同工作打好基础。
这一篇内容较多,可以重点看标红部分和标粗部分。实在可啃不下去可以直接看下一篇内容,用到linux网络知识再过来回顾。
概念补充
交换机的作用可以理解为将一些机器连接起来组成一个局域网。工作于二层网络。
而路由器与交换机有明显区别,它的作用在于连接不同的网段并且找到网络中数据传输
最合适的路径。工作于三层网络。
一般家用路由器是集成了这两个功能,网关是作为两个网段的出入口,也是第三层网络的设备。
阅读本篇需要一定的网络协议基础知识,不然读的很生涩很难看下去。如果实在理解困难可以仅看加粗部分,参考我给的,班长组长老师传话的例子
在消化输出的过程中,我力求易理解,易读和准确性,但是很难三全,所以读者可以根据这篇的反馈帮我提下建议,我是省去复杂准确的定义,写成类似通识易于理解的形式,还是严格按照准确定义的形式,这会有过于抽象对于阅读理解不是非常友好的问题。还是本篇的严格定义中穿插易懂但没那么精确的讲解,这照顾了易理解和准确性,缺显得排版和思路有点乱,丧失了易读性。
我们可以留言讨论一下
虚拟化网络设备
首先我们要知道,虚拟化网络并不需要完全遵照物理网络的样子来设计。不过,由于现在大量现成的代码,原来就是面向于物理存在的网络设备来编码实现的,另外也有出于方便理解和知识继承方面的考虑,因此虚拟化网络与物理网络中的设备还是具有相当高的相似性。
所以接下来,我就会从网络中那些与网卡、交换机、路由器等对应的虚拟设施,以及如何使用这些虚拟设施来组成网络入手,给你介绍容器间网络的通信基础设施。
网卡:tun/tap、veth
目前主流的虚拟网卡方案有tun/tap和veth两种,其中 tun/tap 出现得时间更早,它是一组通用的虚拟驱动程序包,里面包含了两个设备,分别是用于网络数据包处理的虚拟网卡驱动,以及用于内核空间与用户空间交互的字符设备(Character Devices,这里具体指/dev/net/tun)驱动。
tun 和 tap 是两个相对独立的虚拟网络设备,其中 tap 模拟了以太网设备,操作二层数据包(以太帧),tun 则是模拟了网络层设备,操作三层数据包(IP 报文)。
那么,使用 tun/tap 设备的目的,其实是为了把来自协议栈的数据包,先交给某个打开了/dev/net/tun字符设备的用户进程处理后,再把数据包重新发回到链路中。这里你可以通俗地理解为,这块虚拟化网卡驱动一端连接着网络协议栈,一端假装自己是物理网卡,处理一轮以后丢给真正的虚拟网卡。
如此一来,只要协议栈中的数据包能被用户态程序截获并加工处理,程序员就有足够的舞台空间去玩出各种花样,比如数据压缩、流量加密、透明代理等功能,都能够在此基础上实现。
这里我就以最典型的 VPN 应用程序为例,程序发送给 tun 设备的数据包,会经过如下所示的顺序流进 VPN 程序:
应用程序通过 tun 设备对外发送数据包后,tun 设备如果发现另一端的字符设备已经被 VPN 程序打开(这就是一端连接着网络协议栈,另一端连接着用户态程序),就会把数据包通过字符设备发送给 VPN 程序,VPN 收到数据包,会修改后再重新封装成新报文,比如数据包原本是发送给 A 地址的,VPN 把整个包进行加密,然后作为报文体,封装到另一个发送给 B 地址的新数据包当中。
这种把一个数据包套进另一个数据包中的处理方式,就被形象地形容为“隧道”(Tunneling),隧道技术是在物理网络中构筑逻辑网络的经典做法。而其中提到的加密,实际上也有标准的协议可以遵循,比如IPSec协议。
不过,使用 tun/tap 设备来传输数据需要经过两次协议栈,所以会不可避免地产生一定的性能损耗,因而如果条件允许,容器对容器的直接通信并不会把 tun/tap 作为首选方案,而是一般基于 veth 来实现的。
但 tun/tap 并没有像 veth 那样,有要求设备成对出现、数据要原样传输的限制,数据包到了用户态程序后,我们就有完全掌控的权力,要进行哪些修改、要发送到什么地方,都可以通过编写代码去实现,所以 tun/tap 方案比起 veth 方案有更广泛的适用范围。
那么这里我提到的 veth,就是另一种主流的虚拟网卡方案了,在 Linux Kernel 2.6 版本,Linux 开始支持网络名空间隔离的同时,也提供了专门的虚拟以太网(Virtual Ethernet,习惯简写为 veth),让两个隔离的网络名称空间之间可以互相通信。
veth 实际上也不是一个设备,而是一对设备,因而它也常被称作 veth pair。我们要使用 veth,就必须在两个独立的网络名称空间中进行才有意义,因为 veth pair 是一端连着协议栈,另一端彼此相连的,在 veth 设备的其中一端输入数据,这些数据就会从设备的另一端原样不动地流出,它在工作时的数据流动如下图所示:
由于两个容器之间采用 veth 通信,不需要反复多次经过网络协议栈,这就让 veth 比起 tap/tun 来说,具备了更好的性能,也让 veth pair 的实现变得十分简单,内核中只用几十行代码实现一个数据复制函数,就可以完成 veth 的主体功能。
不过 veth 其实也存在局限性。
虽然 veth 以模拟网卡直连的方式,很好地解决了两个容器之间的通信问题,然而对多个容器间通信,如果仍然单纯只用 veth pair 的话,事情就会变得非常麻烦,毕竟,让每个容器都为与它通信的其他容器建立一对专用的 veth pair,根本就不实际,真正做起来成本会很高。
因此这时,就迫切需要有一台虚拟化的交换机,来解决多容器之间的通信问题了。
交换机:Linux Bridge
既然有了虚拟网卡,我们很自然就会联想到让网卡接入到交换机里,来实现多个容器间的相互连接。而Linux Bridge就是 Linux 系统下的虚拟化交换机,虽然它是以“网桥”(Bridge)而不是“交换机”(Switch)为名,但在使用过程中,你会发现 Linux Bridge 看起来像交换机,功能使用起来像交换机、程序实现起来也像交换机,所以它实际就是一台虚拟交换机。
Linux Bridge 是在 Linux Kernel 2.2 版本开始提供的二层转发工具,由brctl命令创建和管理。Linux Bridge 创建以后,就能够接入任何位于二层的网络设备,无论是真实的物理设备(比如 eth0),还是虚拟的设备(比如 veth 或者 tap),都能与 Linux Bridge 配合工作。当有二层数据包(以太帧)从网卡进入 Linux Bridge,它就会根据数据包的类型和目标 MAC 地址,按照如下规则转发处理:
- 如果数据包是广播帧,转发给所有接入网桥的设备。
- 如果数据包是单播帧,且 MAC 地址在地址转发表中不存在,则洪泛(Flooding)给所有接入网桥的设备,并把响应设备的接口与 MAC 地址学习(MAC Learning)到自己的 MAC 地址转发表中。
- 如果数据包是单播帧,且 MAC 地址在地址转发表中已存在,则直接转发到地址表中指定的设备。
- 如果数据包是此前转发过的,又重新发回到此 Bridge,说明冗余链路产生了环路。由于以太帧不像 IP 报文那样有 TTL 来约束,所以一旦出现环路,如果没有额外措施来处理的话,就会永不停歇地转发下去。那么对于这种数据包,就需要交换机实现生成树协议(Spanning Tree Protocol,STP)来交换拓扑信息,生成唯一拓扑链路以切断环路。(学算法与数据结构的最小生成树,每次选取图中路径最短的通路,且不构成环,连接所有节点)
刚刚提到的这些名词,比如二层转发、泛洪、STP、MAC 学习、地址转发表,等等,都是物理交换机中已经非常成熟的概念了,它们在 Linux Bridge 中都有对应的实现,所以我才说,Linux Bridge 不仅用起来像交换机,实现起来也像交换机。
不过,它与普通的物理交换机也还是有一点差别的,普通交换机只会单纯地做二层转发,Linux Bridge 却还支持把发给它自身的数据包,接入到主机的三层协议栈中。
对于通过brctl命令显式接入网桥的设备,Linux Bridge 与物理交换机的转发行为是完全一致的,它也不允许给接入的设备设置 IP 地址,因为网桥是根据 MAC 地址做二层转发的,就算设置了三层的 IP 地址也没有意义。
然而,Linux Bridge 与普通交换机的区别是,除了显式接入的设备外,它自己也无可分割地连接着一台有着完整网络协议栈的 Linux 主机,因为 Linux Bridge 本身肯定是在某台 Linux 主机上创建的,我们可以看作是 Linux Bridge 有一个与自己名字相同的隐藏端口,隐式地连接了创建它的那台 Linux 主机。
因此,Linux Bridge 允许给自己设置 IP 地址,这样就比普通交换机多出了一种特殊的转发情况:如果数据包的目的 MAC 地址为网桥本身,并且网桥设置了 IP 地址的话,那该数据包就会被认为是收到发往创建网桥那台主机的数据包,这个数据包将不会转发到任何设备,而是直接交给上层(三层)协议栈去处理。
这时,网桥就取代了物理网卡 eth0 设备来对接协议栈,进行三层协议的处理。
那么设置这条特殊转发规则的好处是什么呢?就是只要通过简单的 NAT 转换,就可以实现一个最原始的单 IP 容器网络。这种组网是最基本的容器间通信形式,
在这个处理过程中,Linux 主机独立承担了三层路由的职责,一定程度上扮演了路由器的角色。而且由于有 Netfilter 的存在,对网络层的路由转发,就不需要像 Linux Bridge 一样,专门提供brctl这样的命令去创建一个虚拟设备了。
通过 Netfilter,很容易就能在 Linux 内核完成根据 IP 地址进行路由的功能。你也可以把 Linux Bridge 理解为是一个人工创建的虚拟交换机,而 Linux 内核是一个天然的虚拟路由器。
网络:VXLAN
SDN 的核心思路是在物理的网络之上,再构造一层虚拟化的网络,把控制平面和数据平面分离开来,实现流量的灵活控制,为核心网络及应用的创新提供良好的平台。就像用虚拟机隔离硬件资源与软件操作。
由于跨主机的容器间通信用的大多是 Overlay 网络(SDN网络位于上层的那部分,对比理解成虚拟机,逻辑层面的网络即可),所以接下来,我会以 VXLAN 为例,给你介绍 Overlay 网络的原理。
VXLAN 你可能没怎么听说过,但VLAN相信只要从事计算机专业的人都会有所了解。VLAN 的全称是“虚拟局域网”(Virtual Local Area Network),从名称来看,它也算是网络虚拟化技术的早期成果之一了。
由于二层网络本身的工作特性,决定了 VLAN 非常依赖于广播,无论是广播帧(如 ARP 请求、DHCP、RIP 都会产生广播帧),还是泛洪路由,它的执行成本会随着接入二层网络设备数量的增长而等比例地增加,当设备太多,广播又频繁的时候,很容易就会形成广播风暴(Broadcast Radiation)。
因此,VLAN 的首要职责就是划分广播域,把连接在同一个物理网络上的设备区分开来。
划分的具体方法是在以太帧的报文头中加入 VLAN Tag,让所有广播只针对具有相同 VLAN Tag 的设备生效。这样既缩小了广播域,也附带提高了安全性和可管理性,因为两个 VLAN 之间不能直接通信。如果确实有通信的需要,就必须通过三层设备来进行,比如使用单臂路由(Router on a Stick)或者三层交换机。
说人话就是班长管理一个班级,当老师(公网)与同学们(设备)之间的传话筒。班级人数太多班长忙不过来,就选了三个小组长(vlan) 组长A,组长B和组长C(ABC就是VLAN Tag),班长给组长传话,组长给组员传话就行,几个组长和相应组员之间不能说话,如果不同组之间有必要说话的话,去找老师,老师让班长给这两个组传话。
可是,VLAN 有两个明显的缺陷
第一个缺陷在于 VLAN Tag 的设计VLAN ID 最多只能有 212=4096 种取值,
VLAN 的第二个缺陷:跨数据中心传递。
两个组想说话,每次都要找老师,老师再找班长太麻烦了。
为了统一解决以上两个问题,IETF 定义了 VXLAN 规范,这是三层虚拟化网络(Network Virtualization over Layer 3,NVO3)的标准技术规范之一,是一种典型的 Overlay 网络。
VXLAN 采用 L2 over L4 (MAC in UDP)的报文封装模式,把原本在二层传输的以太帧,放到了四层 UDP 协议的报文体内,同时加入了自己定义的 VXLAN Header。在 VXLAN Header 里直接就有 24 Bits 的 VLAN ID,同样可以存储 1677 万个不同的取值。
如此一来,VXLAN 就可以让二层网络在三层范围内进行扩展,不再受数据中心间传输的限制了。VXLAN 的整个报文结构如下图所示:
VXLAN 对网络基础设施的要求很低,不需要专门的硬件提供特别支持,只要三层可达的网络就能部署 VXLAN。
说人话就是一种协议的封装,是属于逻辑层面的。只要硬件层面有三层网络就能用。
VXLAN 带来了很高的灵活性、扩展性和可管理性,也带来了额外的复杂度和性能开销。传输效率和性能下降,多了额外的拆装包动作和更大的报头。
副本网卡:MACVLAN
我理解是作为逻辑层面的协议,可以运行在只要硬件支持的网络设备中。现在加上了虚拟ip和虚拟mac,以此实现更为灵活的转发规则。
现在,理解了 VLAN 和 VXLAN 的原理后,我们就有足够的前置知识,去了解MACVLAN这最后一种网络设备虚拟化的方式了。
前面我提到,两个 VLAN 之间位于独立的广播域,是完全二层隔离的,要通信就只能通过三层设备。而最简单的三层通信就是靠单臂路由了。
接下来,我就以这里的示意图中给出的网络拓扑结构为例,来给你介绍下单臂路由是如何工作的。
单臂路由不属于任何 VLAN,它与交换机之间的链路允许任何 VLAN ID 的数据包通过,这种接口被称为 TRUNK。
这样,A1 要和 B2 通信,A1 就把数据包先发送给路由(只需把路由设置为网关即可做到),然后路由根据数据包上的 IP 地址得知 B2 的位置,去掉 VLAN-A 的 VLAN Tag,改用 VLAN-B 的 VLAN Tag 重新封装数据包后,发回给交换机,交换机收到后就可以顺利转发给 B2 了。
这个过程并没什么复杂的地方,但不知道你有没有注意到一个问题:路由器应该设置怎样的 IP 地址呢?
由于 A1、B2 各自处于独立的网段上,它们又各自要把同一个路由作为网关使用,这就要求路由器必须同时具备 192.168.1.0/24 和 192.168.2.0/24 的 IP 地址。当然,如果真的就只有 VLAN-A、VLAN-B 两个 VLAN,那把路由器上的两个接口分别设置不同的 IP 地址,然后用两条网线分别连接到交换机上,也勉强算是一个解决办法。
但要知道,VLAN 最多可以支持 4096 个 VLAN,那如果要接四千多条网线就太离谱了。因此为了解决这个问题,802.1Q 规范中专门定义了子接口(Sub-Interface)的概念,它的作用是允许在同一张物理网卡上,针对不同的 VLAN 绑定不同的 IP 地址。
所以,MACVLAN 就借用了 VLAN 子接口的思路,并且在这个基础上更进一步,不仅允许对同一个网卡设置多个 IP 地址,还允许对同一张网卡上设置多个 MAC 地址,这也是 MACVLAN 名字的由来。
原本 MAC 地址是网卡接口的“身份证”,应该是严格的一对一关系,而 MACVLAN 打破了这层关系。方法就是在物理设备之上、网络栈之下生成多个虚拟的 Device,每个 Device 都有一个 MAC 地址,新增 Device 的操作本质上相当于在系统内核中,注册了一个收发特定数据包的回调函数,每个回调函数都能对一个 MAC 地址的数据包进行响应,当物理设备收到数据包时,会先根据 MAC 地址进行一次判断,确定交给哪个 Device 来处理,如下图所示。
这样,我们以交换机一侧的视角来看,这个端口后面就像是另一台已经连接了多个设备的交换机一样。
用 MACVLAN 技术虚拟出来的副本网卡,在功能上和真实的网卡是完全对等的,此时真正的物理网卡实际上也确实承担着类似交换机的职责。
在收到数据包后,物理网卡会根据目标 MAC 地址,判断这个包应该转发给哪块副本网卡处理,由同一块物理网卡虚拟出来的副本网卡,天然处于同一个 VLAN 之中,因此可以直接二层通信,不需要将流量转发到外部网络。(可以理解为,有一个动态小组可以随时加入,有需要的时候两个小组都加入紧急通讯组,那么他们就相当于一个组可以相互传话了)
那么,与 Linux Bridge 相比,这种以网卡模拟交换机的方法在目标上其实没有什么本质上的不同,但 MACVLAN 在内部实现上,则要比 Linux Bridge 轻量得多。
从数据流来看,副本网卡的通信只比物理网卡多了一次判断而已,就能获得很高的网络通信性能;从操作步骤来看,由于 MAC 地址是静态的,所以 MACVLAN 不需要像 Linux Bridge 那样,要考虑 MAC 地址学习、STP 协议等复杂的算法,这也进一步突出了 MACVLAN 的性能优势。
而除了模拟交换机的 Bridge 模式外,MACVLAN 还支持虚拟以太网端口聚合模式(Virtual Ethernet Port Aggregator,VEPA)、Private 模式、Passthru 模式、Source 模式等另外几种工作模式,有兴趣的话你可以去参考下相关资料,我就不再逐一介绍了。
小结
这节课我从模拟网卡、交换机这些网络设备开始,给你介绍了如何在 Linux 网络名称空间的支持下,模拟出一个物理上实际并不存在,但可以像物理网络一样,让程序可以进行通讯的虚拟化网路。