网络虚拟化
前言
网络虚拟化相对计算、存储虚拟化来说是比较抽象的,以我们在学校书本上学的那点网络知识来理解网络虚拟化可能是不够的。
在我们的印象中,网络就是由各种网络设备(如交换机、路由器)相连组成的一个网状结构,世界上的任何两个人都可以通过网络建立起连接。
带着这样一种思路去理解网络虚拟化可能会感觉云里雾里——这样一个庞大的网络如何实现虚拟化?
其实,网络虚拟化更多关注的是数据中心网络、主机网络这样比较「细粒度」的网络,所谓细粒度,是相对来说的,是深入到某一台物理主机之上的网络结构来谈的。
如果把传统的网络看作「宏观网络」的话,那网络虚拟化关注的就是「微观网络」。网络虚拟化的目的,是要节省物理主机的网卡设备资源。从资源这个角度去理解,可能会比较好理解一点。
传统网络架构
在传统网络环境中,一台物理主机包含一个或多个网卡(NIC),要实现与其他物理主机之间的通信,需要通过自身的 NIC 连接到外部的网络设施,如交换机上,如下图所示。
这种架构下,为了对应用进行隔离,往往是将一个应用部署在一台物理设备上,这样会存在两个问题
1)是某些应用大部分情况可能处于空闲状态
2)是当应用增多的时候,只能通过增加物理设备来解决扩展性问题。不管怎么样,这种架构都会对物理资源造成极大的浪费。
虚拟化网络架构
为了解决这个问题,可以借助虚拟化技术对一台物理资源进行抽象,将一张物理网卡虚拟成多张虚拟网卡(vNIC),通过虚拟机来隔离不同的应用。
这样对于上面的问题
针对问题 1),可以利用虚拟化层 Hypervisor (系统管理程序)的调度技术,将资源从空闲的应用上调度到繁忙的应用上,达到资源的合理利用;
针对问题 2),可以根据物理设备的资源使用情况进行横向扩容,除非设备资源已经用尽,否则没有必要新增设备。这种架构如下所示。
其中虚拟机与虚拟机之间的通信,由虚拟交换机完成,虚拟网卡和虚拟交换机之间的链路也是虚拟的链路,整个主机内部构成了一个虚拟的网络,如果虚拟机之间涉及到三层的网络包转发,则又由另外一个角色——虚拟路由器来完成。
一般,这一整套虚拟网络的模块都可以独立出去,由第三方来完成,如其中比较出名的一个解决方案就是 Open vSwitch(OVS)。
OVS 的优势在于它基于 SDN 的设计原则,方便虚拟机集群的控制与管理,另外就是它分布式的特性,可以「透明」地实现跨主机之间的虚拟机通信,如下是跨主机启用 OVS 通信的图示。
总结下来,网络虚拟化主要解决的是虚拟机构成的网络通信问题,完成的是各种网络设备的虚拟化,如网卡、交换设备、路由设备等。
Linux 下网络设备虚拟化的几种形式
为了完成虚拟机在同主机和跨主机之间的通信,需要借助某种“桥梁”来完成用户态到内核态(Guest 到 Host)的数据传输,这种桥梁的角色就是由虚拟的网络设备来完成,上面介绍了一个第三方的开源方案——OVS,它其实是一个融合了各种虚拟网络设备的集大成者,是一个产品级的解决方案。
但 Linux 本身由于虚拟化技术的演进,也集成了一些虚拟网络设备的解决方案,主要有以下几种:
(1)TAP/TUN/VETH
TAP/TUN 是 Linux 内核实现的一对虚拟网络设备,TAP 工作在二层,TUN 工作在三层。Linux 内核通过 TAP/TUN 设备向绑定该设备的用户空间程序发送数据,反之,用户空间程序也可以像操作物理网络设备那样,向 TAP/TUN 设备发送数据。
基于 TAP 驱动,即可实现虚拟机 vNIC 的功能,虚拟机的每个 vNIC 都与一个 TAP 设备相连,vNIC 之于 TAP 就如同 NIC 之于 eth。
当一个 TAP 设备被创建时,在 Linux 设备文件目录下会生成一个对应的字符设备文件,用户程序可以像打开一个普通文件一样对这个文件进行读写。
比如,当对这个 TAP 文件执行 write 操作时,相当于 TAP 设备收到了数据,并请求内核接受它,内核收到数据后将根据网络配置进行后续处理,处理过程类似于普通物理网卡从外界收到数据。当用户程序执行 read 请求时,相当于向内核查询 TAP 设备是否有数据要发送,有的话则发送,从而完成 TAP 设备的数据发送。
TUN 则属于网络中三层的概念,数据收发过程和 TAP 是类似的,只不过它要指定一段 IPv4 地址或 IPv6 地址,并描述其相关的配置信息,其数据处理过程也是类似于普通物理网卡收到三层 IP 报文数据。
VETH 设备总是成对出现,一端连着内核协议栈,另一端连着另一个设备,一个设备收到内核发送的数据后,会发送到另一个设备上去,这种设备通常用于容器中两个 namespace 之间的通信。
(2)Bridge
Bridge 也是 Linux 内核实现的一个工作在二层的虚拟网络设备,但不同于 TAP/TUN 这种单端口的设备,Bridge 实现为多端口,本质上是一个虚拟交换机,具备和物理交换机类似的功能。
Bridge 可以绑定其他 Linux 网络设备作为从设备,并将这些从设备虚拟化为端口,当一个从设备被绑定到 Bridge 上时,就相当于真实网络中的交换机端口上插入了一根连有终端的网线。
如下图所示,Bridge 设备 br0 绑定了实际设备 eth0 和 虚拟设备 tap0/tap1,当这些从设备接收到数据时,会发送给 br0 ,br0 会根据 MAC 地址与端口的映射关系进行转发。
因为 Bridge 工作在二层,所以绑定到它上面的从设备 eth0、tap0、tap1 均不需要设 IP,但是需要为 br0 设置 IP,因为对于上层路由器来说,这些设备位于同一个子网,需要一个统一的 IP 将其加入路由表中。
这里有人可能会有疑问,Bridge 不是工作在二层吗,为什么会有 IP 的说法?其实 Bridge 虽然工作在二层,但它只是 Linux 网络设备抽象的一种,能设 IP 也不足为奇。
对于实际设备 eth0 来说,本来它是有自己的 IP 的,但是绑定到 br0 之后,其 IP 就生效了,就和 br0 共享一个 IP 网段了,在设路由表的时候,就需要将 br0 设为目标网段的地址。
总结
传统网络架构到虚拟化的网络架构,可以看作是宏观网络到微观网络的过渡
TAP/TUN/VETH、Bridge 这些虚拟的网络设备是 Linux 为了实现网络虚拟化而实现的网络设备模块,很多的云开源项目的网络功能都是基于这些技术做的,比如 Neutron、Docker network 等。
OVS 是一个开源的成熟的产品级分布式虚拟交换机,基于 SDN 的思想,被大量应用在生产环境中。
容器虚拟化
名称空间:UTS、User、Mount、IPC、PID、NET
NET:网络名称空间
描述:主要是网络设备、协议栈等实现,假设物理机上有四块网卡,需要创建两个名称空间,这些设备可以单独关联给某个空间所使用的,如第一个网卡分配给第一个名称空间使用,其他就看不见这个设备了,一个设备一般只能授予一个空间,同样有四个网卡就可以使用四个名称空间,使得每个名称空间都可以配置IP地址与外界进行通信。
如果名称空间的数量超过物理网卡数量,每个名称空间内部的进程也是需要通过网络进行通信,应该如何上报,可以使用模拟技术,linux设备支持两种内核级的模拟,是二层设备和三层设备,网卡就是一个二层设备,工作在链路层,能够封装报文实现各设备之间报文转发的实现,这功能是完全可以在Linux之上利用内核中对二层虚拟设备的支持,创建虚拟网卡接口,而且这种虚拟网卡接口很独特,每个网络接口设备是成对出现的,可以模拟为一根网线的两头,其中一头可以插在主机之上,另一头插在交换机之上进行模拟,相当于一个主机连接到交换机上去了,而linux内核源生就支持模拟二层网络设备,使用软件来构建一个交换机。
如果有两个名称空间,那么两台主机就像连接到同一个交换机上进行通信,如果配置的网络地址在同一个网段就可以直接进行通讯了。这就是虚拟化的网络。
OVS: OpenVSwitch 可以模拟高级的网络技术,二层交换,甚至三层网络设备,vlan,,不属于Linux内核组件,要额外安装,由cisco众多公司所构建的,有云计算的浪潮下,构建网络是比较复杂的,然后才是网络之上所承载的主机,才能通讯,这个网络虚拟化所实现的功能,需要软件硬件结合起来实现,而且把传统意义上的网络平面,控制平面,传输平面等,隔离开来,集中到一个设备之上实现全局的调度,实现SDN,软件定义网络
单节点上容器通讯:同一个物理机上的两个容器,或者两个名称空间要通讯,就是在主机上建立一个虚拟的交换机,让两个容器各自使用纯软件的方式,建一对虚拟网卡,一半在交换机上,一半在容器上,从而实现单节点上容器进行通讯,但是也有比较复杂的情况,有可能会出现有两个软交换机的情况,连接不同的容器,这时两个软交换机要连接,需要再做一块网卡,一头在交换机1上,另一头在交换机2之上,如果不同交换机之间要实现路由转发,就需要在两能交换机上加一台路由器,linux内核自身可以当作路由器来使用,打开转发或者使用iptables规则,但是路由器是一个三层的设备,在linux内核直接使用一个单独的名称空间就可以实现,就是再做一个容器当作路由器来使用,当是要模拟出网卡来让它们建立关联关系
多节点:另一台主机上的一个容器,与1号主机上的容器进行通信,vmware实现不同主机上的虚拟机之间的通讯可以使用桥接的方式,就是把物理网卡当作交换机来使用,所有一台主机上的容器都到一个物理网卡来,通过MAC地址来确定交给那个容器,如果是到物理机的,就给物理机,也就是虚拟机里也有自身的独特的MAC地址,所以数据包来时可以区别各个设备,把物理网卡当作交换机来使用,把报文转发给各容器,如果报文目标是物理网卡时,需要虚拟出一个软网卡作为物理网卡的使用,这样就没有虚拟交换机概念,所以两台主机上的虚拟机要使用桥接通讯时,都是连接到各自主机上的物理网卡的的,但是这种通讯方式要实现有很大的代价,因为所有容器的桥接都在同一个平面中,很容易产生风暴,所以在大规模的虚拟机或容器的使用场景中使用桥接不太好,除非能隔离得很好(桥接)
Nat技术:如图中C3与C6通讯,C3是虚拟网卡,C3网卡与物理网卡物理地址不在同一个网段中,C3把网关指向S2,把S3当作宿主机的一个网卡来使用,IP地址与C3在同一个网段,把C3的网关指向S2,然后在物理机上打开核心转发功能,所以当C3与C6通讯时,先转给s2,再到达内核,内核判定查路由列不是自己要到另一个主机上的C6,这时报文回不来,因为C3和C4是一个私有地址,如果要报文能够回来,最后到报文送走物理机之前,要把源IP地址修改成物理网卡的IP地址,这样C5或者C6回复物理主机的IP就可以了,通过NAT表的查询是C3的访问,就把报文送给C3,这就使用NAT实现跨主机之间的通讯,但是这里有一个很大的问题,C6也可能是NAT的模式下工作,也就是说它也是使用私有地址的,如果C6要被访问只能把它暴露出去,在物理机的能外网卡上明确说明某个端口是提供服务的,如果要C4能够访问C6,就要先访问C6所在的宿主机的物理地址,再使用H2做dnat发给C6,但是C4发送报文时是通过SNAT出来的,C4也是隐藏在NAT背后的,发出去的报文要其他的主机可以响应就应该改写源地址。所以在跨服务主机实现两个虚拟机之间的通讯要实现两级的NAT操作,从C4到C6,首先C4出去就SNAT,到到C6要使用到DNAT,这样的效率不会高,但是网络比较容易管理。
Overlay Network: 叠加网络,是NAT和桥接的一个解决方案,有多个物理主机,在虚拟机上做一个虚拟的桥,让各虚拟机连接到虚拟桥上,通信时借用物理网络来完成报文的隧道转发,从而实现C1可以直接看见C5或C6,物理主机本来就是使用物理网络连接在一起的,C1与物理网络不在同一个地址段内,但是C1与C5是在同一地址段内的,C1发送报文时,先发送给虚拟机,假设它是知道C5是不要本地的物理主机上的,以是报文要从物理网卡发送出去,但是要做隧道转发,也就是C1的报文源IP地址是C1,目标地址是C5,然后再封装一个IP包头的首部源地址是C1所在物理主机的IP地址,目标地址是C5所在物理主机的IP地址,当报文送到C5所在的物理机,把报文拆完第一层后,第二层的目标地址就是C5的,就直接交给本地的软交换机,再交给C5,C1与C5之间的通讯直接源地址和目标地址就是各自双方,但是它寄于别的网络,本地自身就是一个三层的网络,应该封装二层,但是没有封装,又封装三层四层报文,就是一个TCP或者UDP的首部,再封装一个首部实现一个两级的三层封装,从而完成报文的转发
docker网络:bridge、host、none
bridge:桥接时网络,并不是物理桥,把本机上创建一个纯粹的软交换机docker0,也可以当作网卡来使用,每启动一个容器就可以给容器分配一段网卡的地址,一半在容器上,一半在docker0桥上,veth176661b这种在机器可以看到的无论容器还是KVM时,每次创建网卡时,都是创建一对的,一半放在虚拟机上,一半放在软交换机上,相当于一根网线连接着两个设备一样。
[root@node1 ~]# ifconfig
docker0: flags=4163
inet 172.17.0.1 netmask 255.255.0.0 broadcast 172.17.255.255
veth56d0ee7: flags=4163
inet6 fe80::8c58:bff:fed2:1390 prefixlen 64 scopeid 0x20
veth8853ed8: flags=4163
inet6 fe80::98fd:21ff:fe2e:54ef prefixlen 64 scopeid 0x20
veth93be8d9: flags=4163
inet6 fe80::2424:7dff:fe25:9886 prefixlen 64 scopeid 0x20
vethf4c3d12: flags=4163
inet6 fe80::6045:53ff:fee4:fd29 prefixlen 64 scopeid 0x20
注:因为启动了四个容器,所以生成了四个虚拟IP,同时这四个虚拟IP都是插在docker0桥上的
[root@node1 ~]# yum install bridge-utils -y
[root@node1 ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024201e0b27e no veth56d0ee7
veth8853ed8
veth93be8d9
vethf4c3d12
[root@node1 ~]# ip link show
1: lo:
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0:
link/ether 00:0c:29:52:7f:22 brd ff:ff:ff:ff:ff:ff
3: eth1:
link/ether 00:0c:29:52:7f:2c brd ff:ff:ff:ff:ff:ff
4: docker0:
link/ether 02:42:01:e0:b2:7e brd ff:ff:ff:ff:ff:ff
6: vethf4c3d12@if5:
link/ether 62:45:53:e4:fd:29 brd ff:ff:ff:ff:ff:ff link-netnsid 0
8: veth93be8d9@if7:
link/ether 26:24:7d:25:98:86 brd ff:ff:ff:ff:ff:ff link-netnsid 1
10: veth56d0ee7@if9:
link/ether 8e:58:0b:d2:13:90 brd ff:ff:ff:ff:ff:ff link-netnsid 2
12: veth8853ed8@if11:
link/ether 9a:fd:21:2e:54:ef brd ff:ff:ff:ff:ff:ff link-netnsid 3
注:红色为容器内部网卡
[root@node1 ~]# docker attach b1 //进入容器b1
/ # ls
bin data dev etc home proc root sys tmp usr var
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:02
inet addr:172.17.0.2 Bcast:172.17.255.255 Mask:255.255.0.0
/ # ping 172.17.0.1
PING 172.17.0.1 (172.17.0.1): 56 data bytes
64 bytes from 172.17.0.1: seq=0 ttl=64 time=0.249 ms
Nat桥:docker创建时默认就是nat桥,是使用Iptables来实现的
[root@node1 ~]# iptables -t nat -vnL
Chain PREROUTING (policy ACCEPT 13 packets, 1843 bytes)
pkts bytes target prot opt in out source destination
5 268 DOCKER all -- * * 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT 13 packets, 1843 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 69 packets, 5112 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
Chain POSTROUTING (policy ACCEPT 69 packets, 5112 bytes)
pkts bytes target prot opt in out source destination
0 0 MASQUERADE all -- * !docker0 172.17.0.0/16 0.0.0.0/0
in
: 从使用接口进来,只要不出docker0出去,源地址来自于172.17.0.0/16的,无论到达任何主机0.0.0.0/0,都要做地址伪装MASQUERADE,相当于SNAT,而且是自动实现SNAT,也就是自动选择一个最合适物理地址当作源地址,所以docker0桥默认就是nat桥
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
1 84 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
容器中网络通讯情况
a. 同一个宿主机中,使用同一个docker0中的软交换机进行通讯
[root@node1 ~]# docker exec -it web1 /bin/sh
/ # ifconfig
eth0 Link encap:Ethernet HWaddr 02:42:AC:11:00:05
inet addr:172.17.0.5 Bcast:172.17.255.255 Mask:255.255.0.0
/ # netstat -lnt
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN
/ # wget -O - -q http://172.17.0.5
[root@node1 ~]# curl http://172.17.0.5
b. 跨主机通讯
在同一个宿方机之间的容器通讯可以实现,但是跨主机就会产生问题,因为docker本身就是一个nat bridge,对外来说是不可见的,要实现不同主机之间的容器的实现通讯,就要做dnat,把接口中发布出来的,假设物理主机上有一个物理网卡,开通一个端口然后提供对外服务,外部主机访问容器中的服务时,使用dnat的方式转到容器中的虚拟网卡中,提供服务。
但是存在一个问,如果在同一台宿主机上,起了两个容器分别是两个nginx的web服务,但是对外的IP只有一个,只能使用端口来区分,假设nginx1使用80,另一个nginx2就只能使用非80的端口,这时client访问的出现问题,因为默认访问就要给80,如果是非80端口就请求不到。
如查使用ovetlay network叠加网络方式就可以直接使用隧道来承载,直接访问就可以了,可以不用对地址进行映射。一般的跨主机之间的虚拟机访问方式桥接、nat的。
容器特殊功能,在容器内部有6个隔离的名称空间,user,mount,pid,uts,net,ipc,每个容器都有自身独立的资源,假设让每个容器都有隔离而独立的user,mount,pid,而uts,net,ipc这三个资源是共享使用的,拥有同一个网卡,同一组网络协议栈,有同一个主机名和域名,对外使用同一个IP地址,优点是如第一个容器使用的tomcat服务,第二个容器的是redis服务时,如果tomcat要访问redis中的数据时,是同一个协议栈,之前如果是隔离的,通过127来访问是不可以的,实现有自己独立隔离的名称空间,却又共享一部分名称空间
docker中的host模式
物理机也有名称空间,也让容器使用物理机的名称空间,容器和容器之间可以共享名称空间,所以也可以共享物理服务器的名称空间来使用,如可以让第一个容器直接使用物理机的名称空间,也就是容器中修改网卡中的信息是直接改物理机中的网卡的信息的,第二个容器可以使用桥接,所以第一个容器就拥有了管理网络的特权host就是让容器使用宿主机的网络名称空间
docker中的none模式
设定容器使用none网络表示没的网络,相当于没有网卡,只有Loop接口,有些容器可能只需要不向外通讯的
docker网络模型
closed container 封闭式网络接口,只有Loop接口
bridged container 桥接,使用容器接口连接到docker0上,默认设置的
jonied container 联盟式容器网络,一部分名称空间是隔离的,文件系统,用户,pid是各自的,其他三个是共享的,
两个容器可以使用loop接口来通讯
开放式容器网络 直接共享物理机的网络接口
[root@node1 ~]# docker network inspect bridge|grep bridge.name
"com.docker.network.bridge.name": "docker0", #bridge网络所关联的是docker0
参考链接:
https://www.cnblogs.com/reid21/articles/9751275.html