Neutron是OpenStack核心项目之一,提供云计算环境下的虚拟网络功能。Neutron的功能日益强大,并在Horizon面板中已经集成该模块。作为Neutron的核心开发者之一,个人觉得Neutron完全代替Nova Network模块作为云计算网络管理中心是必然趋势。要使用好OpenStack,了解Neutron概念及其相应操作就显得格外重要。为此目的,这篇博客主要讲述Neutron网络的一些基本概念,网络规划和Horizon中如何使用Neutron中的网络功能。
Neutron基本概念
-
网络
在普通人的眼里,网络就是网线和供网线插入的端口,一个盒子会提供这些端口。对于网络工程师来说,网络的盒子指的是交换机和路由器。所以在物理世界中,网络可以简单地被认为包括网线,交换机和路由器。当然,除了物理设备,我们还有软的物件:IP地址,交换机和路由器的配置和管理软件以及各种网络协议。要管理好一个物理网络需要非常深的网络专业知识和经验。
Neutron网络目的是(为OpenStack云更灵活地)划分物理网络,在多租户环境下提供给每个租户独立的网络环境。另外,Neutron提供API来实现这种目标。Neutron中“网络”是一个可以被用户创建的对象,如果要和物理环境下的概念映射的话,这个对象相当于一个巨大的交换机,可以拥有无限多个动态可创建和销毁的虚拟端口。
-
端口
在物理网络环境中,端口是用于连接设备进入网络的地方。Neutron中的端口起着类似的功能,它是路由器和虚拟机挂接网络的着附点。
-
路由器
和物理环境下的路由器类似,Neutron中的路由器也是一个路由选择和转发部件。只不过在Neutron中,它是可以创建和销毁的软部件。
-
子网
简单地说,子网是由一组IP地址组成的地址池。不同子网间的通信需要路由器的支持,这个Neutron和物理网络下是一致的。Neutron中子网隶属于网络。
Neutron网络的一种典型结构
有这么多的基本概念,使用起来是不是很麻烦?其实只要我们好好梳理一下,neutron的基本操作还是很简单的。
首先我们规划一下我们的网络。Neutron典型的网络结构如下图所示:

在这个图中,我们有一个和互联网(互联网可以由其它网络代替,比如办公网络)连接的外部网络。这个外部网络是租户虚拟机访问互联网或者互联网访问虚拟机的途径。外部网络有一个子网,它是一组在互联网上可寻址的IP地址。一般情况下,外部网络只有一个(neutron是支持多个外部网络的),且由管理员创建。租户网络可由租户任意创建。当一个租户的网络上的虚拟机需要和外部网络以及互联网通信时,这个租户就需要一个路由器。路由器有两种臂,一种是gateway网关臂,另一种是interfaces接口臂。网关臂只有一个,连接外部网。接口臂可以有多个,连接租户网络的子网。经过这样的网络规划,我们有下面的步骤来实施:
- 首先管理员拿到一组可以在互联网上寻址的IP地址,并且创建一个外部网络和子网
- 租户创建一个网络和子网
- 租户创建一个路由器并且连接租户子网和外部网络
- 租户创建虚拟机
Horizon上操作网络
现在我们规划好了网络,我们就可以在Horizon上进行操作了。
管理员创建外网
我们说过外网要由管理员创建。假设我们拿到的外部网络的IP地址是20.0.2.0/24,可用的地址段是20.0.2.10-20.0.2.20,网关是20.0.2.1。我们现在就以管理员的身份在horzion上创建一个外网和子网:
- 以管理员身份登录后,选择管理员面板,点击’Networks’项后显示当前网络列表:

- 在上图中点击“Create Network”按钮弹出“Create Network”窗口:
在上面的弹出窗口中,填入网络名:ExternalNet,而且勾选External Network然后点击“Create Network”。
- 下面是我们刚才创建好的网络:

- 现在我们点击“ExternalNet”查看这个网络的详细情况:

- 在网络详细情况页面中,我们点击“Create Subnet”弹出窗口创建外部网络的子网:

这是一个多标签页面,在上面的“Subnet”页面中填入子网名,子网CIDR(Network Address)。在下面的“Subnet Detail”页中填入地址范围:
注意:因为外部网络不挂接虚拟机,可以不选泽“Enable DHCP“。最后点击子网创建窗口上的“Create”完成子网的创建。
租户demo创建租户网络
我们现在以普通租户登录horizon,并点击“Netowrks”项:
点击上图的“Create Network”弹出租户网络创建窗口:
显然,这是一个多Tab的界面,和我们先前管理员创建网络的界面不一样。在这个界面里,我们可以一气完成网络和子网的创建。我们在“Network”页中填入网络名,在“Subnet”页中填入CIDR (Network Address):
“Subnet Datail”页面可以让我们填入IP地址池,缺省情况是Neutron使用整个CIDR规定的地址范围。所以我们不关心“Subnet Datail”页面。我们点击“Create”按钮完成租户子网的创建。
租户创建路由器
选择“Routers”项,系统显示如下页面:
-
创建路由器
点击右面的“Create Router”弹出路由器创建对话框:
这个对话框及其简单,我们只需输入所有的路由器的名字,然后点击“Create Router”创建路由器。
-
配置网关臂
路由器创建出来之后,会显示在路由器列表中:
我们点击“Set Gateway”来设置这个路由器的网关臂:
如上图所示,我们选择管理员创建好的外部网络“ExternalNet”,然后点击“Set Gateway”。
-
配置接口臂
下图显示了租户刚才创建的路由器及其网关臂:
现在我们点击路由器名字查看路由器详情:
为了设置路由器的接口臂,我们点击“Add Interface”按钮弹出“Add Interface”窗口:
上图显示的窗口,我们只需选择我们所要连接的子网,然后点击“Add Interface”。
现在这个租户的路由器以及连接了外网和租户的子网,接下来这个租户可以创建虚拟机,这个虚拟机借助路由器就可以访问外部网络甚至互联网。
租户创建虚拟机
这里不想详述创建虚拟机的过程,我们着重叙述一下这个过程中如何选择网络:
从上面的图中我们可以看出,租户可以选择的网路包括了租户刚才创建的网络。点击网络右边的“+”符号,或者拖拽这个网络进入“Selected Networks”框中,租户所创建的虚拟机就会使用这个网络。
租户查看网络拓扑
最后我想要说的是Horizon工程师费尽心思实现的网络拓扑图:
从这个拓扑图可以看到我们前面所有操作的结果:
- 绿色的是管理员创建的外部网络
- 跨在外部网络和租户网络上的是具有网关臂和接口臂的路由器
- 蓝色的是租户网路及其子网
- 最后是租户创建的虚拟机
点击拓扑图上的虚拟机图像,我们可以使用VNC访问虚拟机的控制台,如下图所示:
我们可以看出,这个虚拟机从租户网络中获取到了IP地址:10.0.1.3。它通过租户自己的路由器能访问互联网。
总结
是不是有了Neutron就不需要物理网络的网络工程师了呢?不是的。虚拟网络的负载最终还需要跑到物理网络上去传递,Neutron只不过是把部分传统网络管理的功能推到了租户方,比如租户可以创建一个虚拟网络及其子网,创建一个路由器等等,这个从上面的操作中可以看出来。在虚拟网络功能的帮助下,基础物理网络就可以向外提供额外的网络服务了,比如租户完全可以创建一个属于自己的类似于数据中心网络的虚拟网络。
Neutron提供了比较完善的多租户环境下的虚拟网络模型以及API。像部署物理网络一样,使用Neutron创建虚拟网络时也需要做一些基本的规划和设计。本文提出了一个Neutron环境下的典型网络结构和Horzion下的操作流程。在后续的博客中,我会继续介绍Neutron的其它功能,比如负载均衡,防火墙,VPN等。
转载自知识共享Attribution-NonCommercial-NoDerivatives 4.0 国际许可协议
下面我们以这样一个场景来解释Open vSwitch如何在Neutron(OpenStack发挥作用),假定读者实践过前文第二章“Neutron与其他OpenStack模块安装 ”(暂未公开~不过基本类似于前边的OpenStack 安装脚本与常见问题),或对OpenStack有一定认识,最好实践过官方OpenStack安装手册的内容。
场景(一个租户,两个网络,一个路由,内部网络使用GRE,Libvirt VIF Driver使用LibvirtHybridOVSBridgeDriver):
场景一虚拟网络拓扑
Figure 11 场景一虚拟网络拓扑
如图我们有一个外网(External Network),IP段为172.16.0.0/16,两个内网,分别是Internal:10.18.0.0/24,和Internal2:10.22.22.0/24,值得注意的是这是两个网络(network),而不是子网(subnet)。
在这个场景下,计算节点的内部应当是这样的:
计算节点网络连接原理
下面我将解释如何得到这幅图。首先我们看下我们的虚拟机在libvirt的名称,通过 nova show 命令我们大概可以获得像这样输出(截取前半部分):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
< table border = "1" cellspacing = "0" cellpadding = "0" >
< tbody >
< tr >
< td valign = "top" width = "569" >
< p align = "left" > + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - < / p >
< p align = "left" > | < / p >
< p align = "left" > | Property | Value | < / p >
< p align = "left" > + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - < / p >
< p align = "left" > | Internal network | 10.18.0.3 , 172.16.19.232 | < / p >
< p align = "left" > | OS - DCF : diskConfig | MANUAL | < / p >
< p align = "left" > | OS - EXT - AZ : availability_zone | nova | < / p >
< p align = "left" > | OS - EXT - SRV - ATTR : host | compute1 | < / p >
< p align = "left" > | OS - EXT - SRV - ATTR : hypervisor_hostname | compute1 | < / p >
< p align = "left" > | OS - EXT - SRV - ATTR : instance_name | instance - 0000001e | < / p >
< / td >
< / tr >
< / tbody >
< / table >
|
我们看到这台虚拟机被部署在compute1节点上,instance_name为instance-0000001e,我们上compute1节点使用virsh dumpxml将instance-0000001e的信息打印出来(截取网络相关):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
< table border = "1" cellspacing = "0" cellpadding = "0" >
< tbody >
< tr >
< td valign = "top" width = "569" > & lt ; interface type = 'bridge' & gt ; & lt ; mac address = 'fa:16:3e:e9:26:5a' / & gt ;
& lt ; source bridge = 'qbr48e06cd2-60' / & gt ;
& lt ; target dev = 'tap48e06cd2-60' / & gt ;
& lt ; model type = 'virtio' / & gt ;
& lt ; alias name = 'net0' / & gt ;
& lt ; address type = 'pci' domain = '0x0000' bus = '0x00' slot = '0x03' function = '0x0' / & gt ;
& lt ; / interface & gt ; < / td >
< / tr >
< / tbody >
< / table >
|
在这里我们看到这台虚拟机的网络设备是tap48e06cd2-60,而且似乎连到了qbr48e06cd2-60上,让我们用brctl show再看下(截取相关部分):
|
< table border = "1" cellspacing = "0" cellpadding = "0" >
< tbody >
< tr >
< td valign = "top" width = "569" > qbr48e06cd2 - 60 8000.bed5536ff312 no qvb48e06cd2 - 60tap48e06cd2 - 60 < / td >
< / tr >
< / tbody >
< / table >
|
看到这里网桥qbr48e06cd2-60上接了两个接口,qvb48e06cd2-60和tap48e06cd2-60,其中的tap设备是我们虚拟机使用的虚拟网络设备,那qvb48e06cd2-60是什么?我们先用lshw –class network把所有网络设备打印出来(截取相关部分):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
< table border = "1" cellspacing = "0" cellpadding = "0" >
< tbody >
< tr >
< td valign = "top" width = "569" > * - network : 5description : Ethernet interface
physical id : 7
logical name : qvb48e06cd2 - 60
serial : be : d5 : 53 : 6f : f3 : 12
size : 10Gbit / s
capabilities : ethernet physical
configuration : autonegotiation = off broadcast = yes driver = veth driverversion = 1.0 duplex = full firmware = N / A link = yes multicast = yes port = twisted pair promiscuous = yes speed = 10Gbit / s < / td >
< / tr >
< / tbody >
< / table >
|
我们注意到这里显示这个设备的driver是veth,而veth总是成对出现的,我们用ethtool -S 看下这个veth的另一端连到了那里:
|
< table border = "1" cellspacing = "0" cellpadding = "0" >
< tbody >
< tr >
< td valign = "top" width = "569" > # ethtool -S qvb48e06cd2-60NIC statistics:
peer_ifindex : 16 < / td >
< / tr >
< / tbody >
< / table >
|
OK,看下16号是哪个设备,ip link(截取相关部分):
16: qvo48e06cd2-60: <BROADCAST,MULTICAST,PROMISC,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000link/ether aa:c0:0f:d2:e2:43 brd ff:ff:ff:ff:ff:ff |
通过上面两个步骤我们已经知道了这对从虚拟机的网络设备到veth pair这个流程,这个过程在官方文档中针对不同的 Libvirt VIF Driver有不同的简单的描述,见https://wiki.openstack.org/wiki/LibvirtVIFDrivers。
下面应该是连到Open vSwitch上吧,让我们验证下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
# ovs-vsctl show
1910d375 - 2692 - 4214 - acdf - d364382c25a4
Bridge br - int
Port br - int
Interface br - int
type : internal
Port patch - tun
Interface patch - tun
type : patch
options : { peer = patch - int }
Port "qvo48e06cd2-60"
tag : 1
Interface "qvo48e06cd2-60"
Port "qvodfdc29e2-9a"
tag : 2
Interface "qvodfdc29e2-9a"
Port "qvo18cec000-80"
tag : 2
Interface "qvo18cec000-80"
Port "qvob86d15f1-8f"
tag : 1
Interface "qvob86d15f1-8f"
Bridge br - tun
Port br - tun
Interface br - tun
type : internal
Port patch - int
Interface patch - int
type : patch
options : { peer = patch - tun }
Port "gre-1"
Interface "gre-1"
type : gre
options : { in_key = flow , local_ip = "192.168.10.11" , out_key = flow , remote_ip = "192.168.10.10" }
ovs_version : "1.11.0"
|
果然qvo48e06cd2-60是连到了br-int上, OpenStack采用这么复杂的机制,而不是把tap设备直接连到Open vSwitch上,这与安全组有关,将在3.2.4基于iptables的Security Group介绍。
在研究到OVS内部前,我们先注意下在poty “qvo48e06cd2-60”下有一个“tag: 1”,这个tag是Open vSwitch用来区分不同子网的。在这里,tag1表示我们的10.18.0.0/24子网,tag2表示10.22.22.0/24子网。
br-int和br-tun通过patch连接,在官方文档上patch的介绍并不多,但一旦两个OVS网桥通过网桥连接,这两个网桥将近乎为同一个网桥,参考资料见:Open vSwitch FAQ和Connecting OVS Bridges with Patch Ports。
首先看下bt-int的流表规则:
# ovs-ofctl dump-flows br-intNXST_FLOW reply (xid=0×4):
cookie=0×0, duration=246746.016s, table=0, n_packets=702, n_bytes=78521, idle_age=1324, hard_age=65534, priority=1 actions=NORMAL |
只有一个NORMAL的动作,在Open vSwitch的官方文档里解释为将包以传统的,非OpenFlow的方式进行交换,也就是说效果和没设置OpenFlow规则一样(见Open vSwitch Advanced Features Tutorial)。那么我们分析br-tun的流表规则,首先在计算节点上用ovs-ofctl dump-ports-desc查看br-tun上所有接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
< table border = "1" cellspacing = "0" cellpadding = "0" >
< tbody >
< tr >
< td valign = "top" width = "569" > OFPST_PORT_DESC reply ( xid = 0x2 ) : 1 ( patch - int ) : addr : ea : a2 : 71 : f5 : 9f : ad
config : 0
state : 0
speed : 0 Mbps now , 0 Mbps max
2 ( gre - 1 ) : addr : d6 : 89 : b0 : 03 : d2 : 72
config : 0
state : 0
speed : 0 Mbps now , 0 Mbps max
LOCAL ( br - tun ) : addr : 9a : 49 : 9a : 35 : d1 : 4e
config : 0
state : 0
speed : 0 Mbps now , 0 Mbps max < / td >
< / tr >
< / tbody >
< / table >
|
然后用ovs-ofctl dump-flows或者EasyOVS查看br-tun的流表规则(这里使用EasyOVS使排版相对好看):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
< table border = "1" cellspacing = "0" cellpadding = "0" >
< tbody >
< tr >
< td valign = "top" width = "569" >
< p align = "left" > ID TAB PKT PRI MATCH ACT < / p >
< p align = "left" > 0 0 339 1 in = 1 resubmit ( , 1 ) < / p >
< p align = "left" > 1 0 285 1 in = 2 resubmit ( , 2 ) < / p >
< p align = "left" > 2 0 3 0 * drop < / p >
< p align = "left" > 3 1 216 0 dl_dst = 00 : 00 : 00 : 00 : 00 : 00 / 01 : 00 : 00 : 00 : 00 : 00 resubmit ( , 20 ) < / p >
< p align = "left" > 4 1 123 0 dl_dst = 01 : 00 : 00 : 00 : 00 : 00 / 01 : 00 : 00 : 00 : 00 : 00 resubmit ( , 21 ) < / p >
< p align = "left" > 5 10 363 1 * learn ( table = 20 , hard_timeout = 300 , priority = 1 , NXM_OF_VLAN_TCI [ 0..11 ] , NXM_OF_ETH_DST [ ] = NXM_OF_ETH_SRC [ ] , load : 0 - & gt ; NXM_OF_VLAN_TCI [ ] , load : NXM_NX_TUN_ID [ ] - & gt ; NXM_NX_TUN_ID [ ] , output : NXM_OF_IN_PORT [ ] ) , output : 1 < / p >
< p align = "left" > 6 2 341 1 tun_id = 0x2 mod_vlan_vid : 1 , resubmit ( , 10 ) < / p >
< p align = "left" > 7 2 17 1 tun_id = 0x3 mod_vlan_vid : 2 , resubmit ( , 10 ) < / p >
< p align = "left" > 8 2 3 0 * drop < / p >
< p align = "left" > 9 20 0 0 * resubmit ( , 21 ) < / p >
< p align = "left" > 10 21 3 1 vlan = 2 strip_vlan , set_tunnel : 0x3 , output : 2 < / p >
< p align = "left" > 11 21 16 1 vlan = 1 strip_vlan , set_tunnel : 0x2 , output : 2 < / p >
< p align = "left" > 12 21 4 0 * drop < / p >
< p align = "left" > 13 3 0 0 * drop < / p >
< / td >
< / tr >
< / tbody >
< / table >
|
这里为了好看只显示了ID、表名、计数器、匹配规则和行为。先看这几条流:0、3、4、9、10、11、12,这些流定义了从br-int进入的包的行为,逐条从上往下看:
0. 表0:当匹配到从port 1(patch-int)进入的包时,提交给表1继续匹配;3. 表1:当目标MAC地址为单播地址时,提交给表20继续匹配;
4. 表1:当目标MAC地址为多播/广播地址时,提交给表21继续匹配;、
9. 表20:提交给21继续匹配(这个表并非只是转发,当OVS根据表10动态建立自动学习的规则时,会添加到表20,比如下面这条流表规则是自动建立的目标MAC地址为路由的规则:“cookie = 0×0, duration = 11.099s, table = 20, n_packets = 45, n_bytes = 6132, hard_timeout = 300, idle_age = 3, hard_age = 2, priority = 1,vlan_tci = 0×0001/0x0fff,dl_dst = fa:16:3e:a1:3f:19 actions = load:0 -> NXM_OF_VLAN_TCI[], load:0×2 -> NXM_NX_TUN_ID[], output:2”);
10. 表21:当目标VLan标签为2时,剥去VLan标签,然后将Tunnel Key设置为3(GRE通道的Key,详见rfc2890的相关描述)并从port 2(gre-1)发出去;
11. 表21:当目标VLan标签为1时,剥去VLan标签,然后将Tunnel Key设置为2并从port 2(gre-1)发出去;
12. 表21:对没成功匹配的包,丢弃。 |
再看1、6、7、5,这几个流定义了来自GRE通道(Network节点)的包的行为:
1. 表0:当匹配到从port 2(gre-1)进入的包时,提交给表2继续匹配;6. 表2:当Tunnel Key为2时,添加VLan tag 1,提交给表10继续匹配;
7. 表2:当Tunnel Key为3时,添加VLan tag 2,提交给表10继续匹配;
5. 表10:首先从报文中学习VLan、MAC等信息并把规则添加表20,然后再从port 1(patch-int)发出去。 |
至此,计算节点的网络分析已经基本完成。后面到网络节点的连接等主要涉及到3层路由,暂且不表。
所有评论(0)