前文

我非要捅穿这 Neutron(一)底层网络实现模型篇

Neutron 的资源模型

Network

Network 是 Network Connectivity as s Service 的 “根” 操作对象,是 Neutron 对二层网络的抽象,包含了用户对「大二层」网络的一切想象,支持 Local、 Flat、 VLAN、 VXLAN、 GRE、 Geneve 等多种网络类型并不断扩充。如果将 Network 映射到现实世界的话,它就相当于一个巨大的交换机:从介质的角度,它拥有许多端口;从网络的角度,它划分广播域;从功能的角度,它提供了 “隔离” 和 “转发”。

在《Networking 基本术语/概念》一文中,我们已经记录了关于大二层网络的介绍,这里不再赘述。

Neutron 网络概念的定义和类型花样之多,令人眼花缭乱。我们姑且从几个不同的方面对 “网络” 做一个分类归纳,区分理解。

从网络实现模型分层的角度看

  • 本地网络(br-int)

  • 租户网络(br-ethX/br-tun)

  • 外部网络(br-ex)

从网络实现技术的角度看

  • 非隧道网络(Local、Flat、VLAN)

  • 隧道网络(VxLAN、GRE、Geneve)

从网络从属关系的角度看

  • 运营商(物理)网络

  • 租户网络

从 Neutron 所追求的「多租户隔离多平面网络」实现来看,我们可以感受到每一个 Network 都应该具有以下 3 个核心要素,我将其称之为 “核心三要素”:

  • Network Type

  • VID Range

  • Physical Network Mapping

接下来的内容,主要就是围绕这三个核心要素展开。

运营商网络和租户网络

本章节我们要使用的就是第三种视角。所谓 “从属” 即是 “属于谁”:由 Neutron 创建的,属于 Neutron 管理范畴的网络,我们称之为租户网络(Tenant Network);属于运营商(e.g. OpenStack 平台的运营者)原有的,Neutron 无法管理、只能映射(记录)的网络,称之为运营商网络( Provider Network)。两种网络都采用了 Network 资源模型,从模型的角度来看,两者的区别在于是否会持久化下列三个字段属性值(运营商网络具有,租户网络反之):

  • provider:network_ type (string) — The type of physical network that this network is mapped to.

  • provider:physical_network (string) — The physical network where this network is implemented.

  • provider:segmentation_id (int) — The ID of the isolated segment on the physical network.

首先需要提出的问题是:为什么要区分租户网络和运营商网络?这是因为单存的 Neutron(虚拟网络)本身只是一座孤岛,如果希望与外界取得联系,就需要依赖于公共设施(e.g. 移动通信网络、桥梁)的支持。虽然 Neutron 对这些公共设施没有控制权,但却可以使用(e.g. 购买手机号码)它们,所以 Neutron 要创建一个 Network 资源对象来存储这些(运营商网络)信息。而存储的地方就是上述的 3 个字段了。

举例说明运营商网络的应用场景:用户希望 OpenStack 中的 VMs 与产品部(VLAN 10=100)的若干台个人电脑处于同一个二层网络,那么用户可以为这些 VMs 创建一个 VLAN ID=100 的运营商网络 p-network1,并且填入下列字段的值。

  • provider:network_ type ==> VLAN

  • provider:physical_network ==> “产品部网络”

  • provider:segmentation_id ==> 100

NOTE:以上只是举例,实际这三个字段的值并非如此。

需要注意的是,上述例子中无论是 VLAN 类型的网络,还是 VLAN ID=100 都并非是 Neutron 创建的,而是运营商本来就已经存在的网络,Neutron 只是创建了一个 Network 资源类型并记录下这些运营商网络的信息,继而进一步使用这个运营商网络的特性而已。

简而言之,运营商网络的作用就是为了让 Neutron 内部的虚拟网络可以与物理网络连接起来。运营商网络是运营商的某个物理网络在 Neutron 上的延伸,Neutron 租户无法管理这个物理网络的生命周期

创建运营商网络

创建运营商网络有两个要点:一是只能有管理员创建,二是需要手动填写 “核心三要素”。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

上图为通过 admin 创建一个 VLAN 类型的运营商网络,填写的三个表单就对应了 “核心三要素” 并存储到数据库中 networks 表的 provider:network_ type、provider:physicalnetwork、provider:segmentationid 字段中。“核心三要素” 中的 Network Type 和 VID Range 很好理解,两者描述了运营商网络的特征。但 Physical Network Mapping 的意义你或许会感到迷惑,其实它描述的是 Neutron Network 应该如何接入运营商实际的物理网络。直白的说,就是 Neutron Network 要通过哪一张 “网卡” 来接入到实际的运营商网络中。

但为什么上图 “物理网络” 表单项填写的 “网卡” 名称是 “public” 呢?其实 “public” 是 Neutron 实现的一种 Label 机制,本质是一个 Key-Value Mapping,将便于人类理解的 Label Name 与实际的 Physical Network 隐射起来,这就是所谓的 Physical Network Mapping。几乎所有的物理网络是使用 Label 的方式来标的。而这个 Physical Network Mapping 就定义在 Neutron OvS(本文以 OvS 为例)的配置文件中。e.g.

  1. # /etc/neutron/plugins/ml2/openvswitch_agent.ini


  2. bridge_mappings = physnet1:br-ethx1, physnet2:br-ethx2

那为什么 Lable “public” 对应的 Physical Network 是 “br-ethx1” 而不是一张具体的物理网卡名称(e.g. eth0)呢?这个问题其实我们在 Neutron 的网络实现模型已经提到过,因为 OvS Bridge 会将物理服务器上的物理网卡挂载到 OvS br-ethX(IP 地址也在此),所以 Physical Network 填入的是 br-ethX 而不是 eth0。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

上图为创建一个 VxLAN 类型的运营商网络。奇怪的是为什么没有了 “物理网络” 这个表单项呢?这还是要说到 Physical Network Mapping(provider:physical_network)描述的是 Neutron Network 应该如何接入到实际的运营商网络,由于 Flat、VLAN 等非隧道网络类型本质是一个二层网络,所以需要指定 “网卡” 才能让二层的数据帧流入运营商网络。但像 VxLAN、GRE 之类的隧道网络类型的实现原理是基于三层 IP 协议的,所以隧道网络能否与运营商网络互通的关键是隧道外层封装 IP 地址是否填写正确,而非一张 “网卡”。

再次强调一下创建运营商网络的要点:

1. 只能由管理员创建

2. 需要手动填写 “核心三要素”,并持久化到数据库中

  • 非隧道网络类型:需要传入 Physical Network Mapping(provider:physical_network)字段值,描述 Neutron Network 接入运营商网络的 “网卡(br-ethX)”。

  • 隧道网络类型:只需要保证节点上已经定义好了与运营商网络对应的 Tunnel 端口 IP 地址并且三层网络通信无障碍即可。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

上图为创建一个 Flat 类型的运营商网络。Flat(扁平)类型网络是没有 VID 的,不想 VLAN 类型在网络包从 Bridge 发出前还是打 VLAN tag 并进行内外 VID 转换。Flat 的网络包就是一个 Untag 网络包,直接接入物理网络,只需要告诉它用哪一张 “网卡(物理网络)” 即可。

创建租户网络

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

上图为创建一个租户网络,可见并没有填入 “核心三要素” 的表单项。这是因为 Neutron 认为:对于租户创建自己的网络而言,租户希望得到的只是一个二层网络,至于这个二层网络是由 VLAN 提供的或由 VxLAN 提供的并无所谓,VID 是多少也并无所谓,Physical Network 是怎么 Mapping 的就更无所谓了。

一言以蔽之,租户无需关心这个 Network(二层网络)的底层实现细节。Neutron 只有做到了这一点,才能称之为 “服务”!这不禁让我想起柯达公司的一句经典广告语 —— 你只管快门,其余我来!

但话又说回来,租户自然是不必操心这些细节,但云平台管理员可不行。云平台管理员是可以通过 Neutron 的配置文件来声明定义 “核心三要素” 的。e.g.

  1. [ml2]

  2. ...

  3. tenant_network_types = vlan,vxlan

  4. mechanism_drivers = openvswitch,linuxbridge


  5. [securitygroup]

  6. firewall_driver = openvswitch


  7. [ovs]

  8. datapath_type = system

  9. bridge_mappings = public:br-ex,

  10. tunnel_bridge = br-tun

  11. local_ip = 172.18.22.200


  12. [ml2_type_flat]

  13. flat_networks = public,


  14. [ml2_type_vlan]

  15. network_vlan_ranges = public:3001:4000,


  16. [ml2_type_geneve]

  17. vni_ranges = 1:1000


  18. [ml2_type_gre]

  19. tunnel_id_ranges = 1:1000


  20. [ml2_type_vxlan]

  21. vni_ranges = 1:1000

所以,就租户网络而言,其 “核心三要素” 是不需要持久化到数据库中的,也就不具有 provider:network_ type、provider:physicalnetwork、provider:segmentationid 这三个字段值了。再一个就是从配置中可以看出,只有非隧道类型是需要填写 “物理网络” Lable 的。

创建外部网络

创建外部网络(能够访问公网的网络)属于运营商网络的另一种应用场景。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

实际上所谓的外部网络和运营商网络的底层实现并无区别,不同之处在于外部网络具有分配 Floating IP 的功能,即外部网络在网络节点的 qrouter-XXX namespace 中配置的 iptables 的 NAT(SNAT/DNAT)功能,使得内部虚拟机得以访问到外部公网。

Network 小结

Network 是 Neutron 的二层网络资源模型,对外提供二层网络的服务接口(创建、删除二层网络)。租户可以通过这个接口来创建专属于自己的租户网络,管理员可以通过这个接口来创建(对接)Neutron 所无法管理的运营商的物理网络与外部网络。

从资源的角度来看,租户网络和运营商网络的主要区别在于「可控性」:租户网络是 Neutron 完全可控的,基于 Neutron 网络实现模型支撑的多租户隔离多平面网络需求,租户网络的 “核心三要素” 由云管人员通过 Neutron 的 ML2 配置文件定义;而运营商网络却是 Neutron 所无法管理的运营商的物理网络在 Neutron 上的一种延伸,其 “核心三要素” 只是对运营商物理网络信息的一个记录,并无管理性质。

运营商网络的两种典型应用场景:一是打通 Neutron Network 与运营商内部的物理网络,使得两者之间的虚拟机可以互相通信;再一个是打通 Neutron Network 与 Internet,使得 Neutron Network 中的虚拟机可以访问公网。第二种应用场景也就是 Neutron 的 Floating IP 功能,是通过网络节点上 qrouter-XXX 中的 iptables NAT 实现的。

Subnet

Subnet 下属于 Network,是 Neutron 对三层子网的抽象,它作为一个具体网段(CIDR)的 IP 地址池,同时为使用接入到该子网的 VMs 提供 IP 核心网络服务,还负责保证三层网络之间的路由互通。在通常情况下,Subnet 符合人们对 “子网” 的常规理解。

  • IP VERSION

  • IPADDR

  • NETMASK

  • GATEWAY

  • ROUTE

  • DNS

  • DHCP

  • IPAM

NOTE:在某些特定的应用场景中(e.g. Multi-Segments),Subnet 被赋予了更加深厚的含义,但由于非常少见,这里我们暂且不谈。

IP 核心网络服务

IP 核心网络服务(IP CoreNetwork Services),又称 DDI(DNS、DHCP、IPAM)服务。

  • DNS — Domain Name System,域名系统

  • DHCP — Dynamic Host Configuration Protocol,动态主机设置协议

  • IPAM — IP Address Manager,IP 地址管理

DNS 和 DHCP 相信大家不会陌生,这里介绍一下 IPAM 服务。IPAM 用于发现、监视、审核和管理企业网络上使用的 IP 地址空间,IPAM 还可以对运行的 DHCP 和 DNS 服务器进行管理和监视。简而言之,IPAM 就是一种 IP 地址的管理手段,目的是让 IP 地址的分配、使用更加便利

Subnet 资源模型中与 DDI 服务相关的字段

– enable_dhcp:布尔类型,表示是否为 Subnet 启用 DHCP 服务,若启用,再会在 qdhcp-XXX namesapce 中启动 dnsmasq 服务进程。

– allocation_pools:是一个数组,表示 DHCP 服务可分配的 IP 地址池列表,每个地址池的格式为 [start IP,endIP]。若没有配置则以 Subnet 的 cidr 作为地址池。

– dns_nameservers:是一个数组,用于指定一批 DNS Server 的地址。这里仅记录地址,实际的 DNS 服务并不由 Neutron 提供。

– subnetpool_id:是 tables subnetpools 的外键,指向 SubnetPools 资源模型。

NOTE:SubnetPools 与 allocation_pools 是两个 “同类不同源” 的功能。两者均为 IP 地址的资源池,而前者是对后者的一种优化,在 Kilo 版本引入,为了更好的管理子网网络资源池(e.g. 提供访问接口)。

小结一下,IP 核心网络服务(DNS、DHCP、IPAM)是 Subnet 资源模型提供的服务,而服务的对象是使用该 Subnet 的 VMs。可见,Subnet 不仅仅是一个抽象资源接口,其具有一定的管理功能。这一点非常重要,因为我们在启动虚拟机时,时常选择的是一个 Network 而非 Subnet。e.g.

  1. $ openstack server create -h

  2. ...

  3. [--nic <net-id=net-uuid,v4-fixed-ip=ip-addr,v6-fixed-ip=ip-addr,port-id=port-uuid,auto,none>]

这会让我们产生一种错觉,虚拟机网络来自于 Network,从表面看并没有问题,也是 Neutron 有意为之(对用户隐藏复杂细节的表现)。但对于开发者而言,如果带着这种认知去阅读源码就难免会产生疑惑,IP 核心网络服务的代码逻辑处理对象实际是 Subnet 而非 Network,这一点完全契合 Subnet 的资源模型设计。所以再次强调,Network 是 NaaS 的根操作对象,表示一个二层网络;而 Subnet 下属于 Network,为 Network 中的 VMs 提供 IP 核心服务

SubnetPools 资源模型

你是否考虑过,用户真的需要关心一个 Subnet 的网段是 192.X、还是 172.X、还是 10.X 吗?用户要关心的仅仅是这个 Subnet 可以提供多少个 IP 地址而已!出于这样的需求,Network 引入了 SubnetPools 资源模型,通过 subnetpool_id 字段与 Subnet 关联。SubnetPools 和 Sunbet 的关系就相当于:前者定义了一个大的(不重复的)网段,后者从中分配了一个小的网段。e.g.

  1. $ openstack subnet create -h

  2. ...

  3. [--subnet-pool <subnet-pool> | --use-prefix-delegation USE_PREFIX_DELEGATION | --use-default-subnet-pool]

NOTE:在较新的版本(Rocky)中,配合 OpenStack Placement Project,SubnetPools 还可以被一个第三方的网络软件提供的外部 IP 资源池代替。

  1. # 记录了一个 SubnetPool 的子网网段划分规则

  2. MariaDB [neutron]> select * from subnetpoolsG;

  3. *************************** 1. row ***************************

  4. project_id: 031cec3e2df143259d302aa1993fd410

  5. id: 5bbd5cab-c6a0-4851-b2d7-1137742e6adf

  6. name: shared-default-subnetpool-v4

  7. ip_version: 4

  8. default_prefixlen: 26

  9. min_prefixlen: 8

  10. max_prefixlen: 32

  11. shared: 1

  12. default_quota: NULL

  13. hash: 27e49575-31f7-4012-b57b-55c990cb6d82

  14. address_scope_id: NULL

  15. is_default: 1

  16. standard_attr_id: 1


  17. # 记录了一个 SubnetPool 的子网网段信息

  18. MariaDB [neutron]> select * from subnetpoolprefixes;

  19. +----------------+--------------------------------------+

  20. | cidr | subnetpool_id |

  21. +----------------+--------------------------------------+

  22. | 192.168.1.0/24 | 5bbd5cab-c6a0-4851-b2d7-1137742e6adf |

  23. +----------------+--------------------------------------+

Multi-Segments

在上文中我们反复提到,Network 是一个二层网络的抽象,Subnet 是一个三层子网的抽象。这是大多数 SDN 软件设计的常规定义,创建一个 Network 就相当于获得了一个广播域,创建一个 Subnet 就相当于获得了一个三层 IP 地址池。对用户的理解非常友好。

不过在实际的生产需求中 Neutron 面临一个问题:如果一个 Network 内的 VMs/Hosts 数量太多,就会出现诸如二层广播风暴、网络内交换机 ARP 表项容量不足等情况。简单来说,这就是 Network 作为云计算平台中的大二层网络却无法支撑起 “无限” 多个 VMs 的问题。为了满足这一需求 Neutron 引入了 Multi-Segments 设计。

Multi-Segments 解决这一问题的办法就是将 Network 的定义进一步升级为 “多个二层网络的容器”,同时也将 Subnet 的定义进一步升级为 “一个二层网络以及基于此的三层子网”,Subnet 之间通过三层路由互通。

Multi-Segments 顾名思义是一个 Network 具有多个 Segments,而一个 Segment 其实是一个运营商网络,这一点可以从 Segment 资源模型看出(每条 Segment 记录都包含一个运营商网络的 “核心三要素”)。

显然,天下没有免费的午餐,Multi-Segments 的设计实际上具有非常大的局限性 —— 需要规划整个物理、虚拟网络拓扑。它是一种通过 “物理手段” 来完成的 “Network 升级”。比如:Routed Network 应用场景。

所谓 Routed Network,就是一个 Network 内的 Subnet(对应运营商的物理网络)之间使用物理路由器连通,Subnet 的 gateway_ip 指向物理路由器直连网口的 IP 地址。在现实机房中,一个机柜接入物理路由器的网口基本固定,网口的 IP 地址也不会轻易改动,这意味着 Subnet 的三层子网需要与该物理路由器网口的 IP 处于同一个网段,否则无法通信。也就是说,使用 Routed Network 要求管理员提前做好物理、虚拟网络、每个机柜的网关 IP、DHCP Server 的全盘规划。

Multi-Segments 的应用场景除了 Routed Network 还有「VTEP 位于 TOR 交换机上」这一场景,暂不作介绍。可以感受到 Multi-Segments 的应用大多需要结合实际的物理网络拓扑对机房网络进行全盘规划,虽然都并非是一种通用的方案,但 Multi-Segments 的存在也的确让 Neutron 落地物理机房多了一些更加灵活的配置选项。不过,很可惜的是,至今为止笔者依然没有碰见过在生产环境中落地的案例,有时候难免让人怀疑 Multi-Segments 存在的必要性,这是一个大家都对其 “讳莫如深” 的话题。在本章节中提出也只是为了作一个了解而已。

Routed provider networks 官方示例文档:https://docs.openstack.org/newton/networking-guide/config-routed-networks.html

NOTE:有趣的是,经过上述文档的实验发现 Multi-Segments 无法实现预期的 Segments(Subnets)之间的二层隔离,这是因为 VMs 虽然在不同 Segments(Subnets)上,但 VMs 连接到同一个(计算节点的)OvS br-int 上的 qvo-XXX 却具有相同的 Tag ID,自然就无法进行隔离了。由于 Multi-Segments 的场景实在少见,笔者也就不再深究了,只是做出提醒,了解就好。

创建 Subnet

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

创建 Subnet 可以手动输入你预期的网段,也可以应用 SubnetPools 资源模型,直接选择一个 IP 地址资源池。显然后者要来的更加简便一些,只需要通过 “网络掩码” 表单来描述你想要的 IP 地址数量即可(e.g. NETMASK=24 即 2**8 == 256 个 IP 地址)。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

创建 Subnet 时你还可以 “手动输入网络地址”,该表单对应 Subnet 资源模型的 allocationpools 属性,所以 “手动输入网络地址” 是无法和 SubnetPools 共存的,只有通过手动指定网段的方式才可以使用。“主机路由” 对应 hostroutes 属性,格式正如上文所说: [目标网络,下一跳IP 地址]。“DNS 服务器” 对应 dns_nameservers 属性,是一个数值类型,填入 DNS Server 的 IP 地址。

NOTE:同一个 Network 不可以同时具有从资源池中分配(SubnetPools)和手动输入网络地址(allocation_pools)两种类型的 Subnet — Subnets hosted on the same network must be allocated from the same subnet pool.

Network 与 Subnet 的一对多关系

Network 和 Subnet 是一对多的关系,这跟应用了 Multi-Segments 与否无关。Network 下属的 Subnets 可以有不同的 IP 网段(CIDR)且不能重叠,但不同 Networks 的 Subnets 之间可以具有相同的 CIDR。这得益于 Neutron L3 Router 应用了 qrouter-XXX network namespace,解决了 Networks 之间 CIDR 冲突的问题。对于不同 Networks 的 Subnets 之间具有相同 CIDR 时,会有以下两种情况:

  1. 若两个 Subnets 通过同一个 Router 路由,根据 Router 的配置,只有指定的一个 Subnet 可被路由。

  2. 若两个 Subnets 通过不同的 Router 路由,因为 Router 的路由表隔离,所以两个 Subnets 都可以被路由。

我们知道常规的 Network 就是二层广播域,那么你是否想过,为什么 Network 与 Subnet 的聚合关系被设计成一对多?为什么 Neutron 允许在同一个二层广播域之上可以配置多个不同的子网?其实这并非出于什么特殊的意图,只是一对多的关系使得 Neutron 的网络应用更加灵活。通常情况下,用户大概率只会在一个 Network 下创建一个 Subnet,让它们尽可能维护一个 “表面上” 的一比一关系。但总有特殊的情况,例如:一个 Subnet 的 IP 地址用完了;例如:我要启用 Multi-Segments。无论从操作层面还是从架构设计层面上看,Network 与 Subnet 的一对多关系都让 Neutron 变得更加灵活。

那么选定一个具有多个 Subnets 的 Network 来启动一个虚拟机时 Neutron 会怎么处理呢(Nova 不支持选定 Subnet 来启动虚拟机)?Neutron 会按照 Subnets 的顺序选定第一个可用的 Subnet 来给虚拟机使用,知道该 Subnet 的 IP 被分配完毕为止。由此可以感受到,如果不是有特殊需求,还是尽量让 Network 与 Subnet 维持 “表面上” 的一比一关系吧。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

NOTE:虽然 Network 下属有多个 Subnet,但为整个 Network 服务的 DHCP 仍然只有一个。e.g.

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

Port

Network 是二层网络的抽象,Subnet 是三层子网的抽象,Port 就是网卡(NIC)的抽象,是连接 Nova 虚拟机与 Neutron Subnet 的桥梁,也是 Subnet 接入 L3 Router 的桥梁。

直白的说,Port 就是一个虚拟网卡(vNIC),Port 的关键属性就是 IP 地址和 MAC 地址。虚拟机需要绑定 Port,路由器也要绑定 Port。

Port 与 Subnet 一样下属于 Network,Port 与 Subnet 的关系是水平的,为多对多。Port 的 IP 地址来源于 Subnet,也就是说一个 Port 可以具有多个 IP 地址。不过通常情况下,Port 有且只有一个 MAC 地址,对应 Port 资源模型的 mac_address 属性。

Neutron 安全组(Security Group)

Neutron 提供了 Security Group 和 FWaaS 两种安全机制,前者的作用域是单个虚拟机或 Port,后者的作用域是一个具体的网络。而 Security Group 又分为 Nova Security Group 和 Neutron Security Group,这里我们主要讨论的是 Neutron(Port)Security Group。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

当用户将一个 Port 挂载到虚拟机时,底层逻辑会在该虚拟机所处的计算节点上创建一个 Tap 设备并将 Port 的特征信息隐射到这个 Tap 设备,从而实现了为虚拟机添上一张虚拟网卡(vNIC)。我们在 Neutron 网络实现模型的章节中提到过,虚拟机的 vNIC(Tap 设备)并非是直连到 OvS br-int(综合网桥)的,之间还存在一个安全网桥(Linux Bridge qbr-XXX)层。e.g.

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

如上图,Linux Bridge qbr-XXX 就是 Neutron Security Group 的底层支撑。当用户为指定 Port 设定一系列 Security Group Rules 时,Neutron 实际上是通过 CLI 方式调用操作系统的 iptables 指令为 qbr-xxx 上的 Tap 设备配置了相应的 iptables rules。这就是所谓的 “安全层”。

那么问题来了:为什么不能直接在 OvS br-int 上为虚拟机 Port(Tap 设备)设定安全组规则?非得莫名的增加一个安全层?这是因为 OvS Bridge 本身不支持对连接到其自身的 Tap 设备使用 iptables。

直到,OvS 2.5 提出了 “基于流表” 的 Security Group 特性,它应用 Linux 内核的 conntrack(CT)模块实现了对网络连接状态识别和处理。OpenStack 则从 M 版开始应用 OvS 这一新特性,支持 openvswitch securitygroup driver,支持使用 OvS 来实现 “有状态防火墙” 功能。可以通过修改 Neutron 的 ML2 配置文件来选择 Neutron Security Group 的驱动类型。

  1. # /etc/neutron/plugins/ml2/ml2_conf.ini


  2. [securitygroup]

  3. firewall_driver = openvswitch

当然了,如果选择了 openvswitch securitygroup driver,那么 Neutron 的网络实现模型就不再需要 qbr-XXX 安全层了。相应了少了一层转发之后,网络性能也会有所提高,尤其在安全组规则数量巨大的时候。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

关于 OvS Security Group 的实现细节,这里暂不作过多讨论,感兴趣的小伙伴可以浏览《OVS实现安全组,你需要知道这些!

可用地址对(Allowed address pairs)

Neutron Security Group 不仅仅只会一味的按照安全组规则来对虚拟机进行保护,Security Group 本身的存在(启用)就已经设定了一些安全策略,例如:为了防止虚拟机被 ARP Spoofing(ARP 欺诈)、 DHCP Spoofing 的 Port IP/MAC 地址绑定安全策略。由此,默认情况下,一个 Port 的 IP 地址和 MAC 地址是一一对应且绑定,也就是说对这个 Port 的 IP 地址进行添加、修改、删除都会被 Neutron Security Group 判定为非法行为。这也是之所以在 Neutron 网络环境中无法直接使用 Keepalived 来做虚拟机高可用的原因,这是一个无处安放的 VIP 的问题。

NOTE:ARP 反欺诈有很多方案,将 IP/MAC 地址一一绑定是最直接简单的办法。

但就像上文提到的 Keepalived 例子,一个 Port 具有多个 IP 地址的情况总是存在需求的,Port 资源模型的 fixedips 属性就被设计为一个数组类型(Port 可具有多个 IP 地址,1 个 MAC 地址)。针对这样的场景一般有两个办法:关闭 Port Security(portsecurityenabled,不建议使用)或使用 Allowed address pairs(allowedaddress_pairs)机制。

每个 Port 都具有自己的 allowedaddresspairs 数组,它记录了若干对合法的 IP/MAC 地址映射,以此来支持允许多个 IP 与 Port 连通,从而满足一个 Port 具有多个 IP 地址的需求。

更多可用地址对的详情请浏览官方介绍:https://docs.openstack.org/developer/dragonflow/specs/allowedaddresspairs.html

创建一个 Port

NOTE:Port 是依托于 Network 的,所以首先需要创建一个 Network。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

上图创建了一个简单的 Port:

  • Port 的 Fixed IP 地址会从 Subnet1 的 IP 地址池中随机分配,对应 fixed_ips 属性。

  • 开启了 Port Security,现在 Port 的 IP/MAC 地址一一绑定,对应 portsecurityenabled 属性。

  • VNIC 类型为正常(normal),对应 binding:vnic_type 属性。

  • 没有指定 “设备 ID(deviceid)” 和 “设备所属者(deviceowner)”,所以现在设备的 bindingviftype 状态为 unbound。binding:viftype 除了用来标识 Port 的绑定状态(unbound,boundfailed)之外,还用于标识这个 Port 的 Mechanism 类型(e.g. ovs、bridge、macvtap)。

  • 在创建 Port 的同时可以指定这个 Port 的安全组规则

  1. [root@localhost ~]# openstack port show Port1

  2. +-------------------------+-----------------------------------------------------------------------------+

  3. | Field | Value |

  4. +-------------------------+-----------------------------------------------------------------------------+

  5. | admin_state_up | UP |

  6. | allowed_address_pairs | |

  7. | binding_host_id | |

  8. | binding_profile | |

  9. | binding_vif_details | |

  10. | binding_vif_type | unbound |

  11. | binding_vnic_type | normal |

  12. | created_at | 2019-03-08T03:17:23Z |

  13. | data_plane_status | None |

  14. | description | |

  15. | device_id | |

  16. | device_owner | |

  17. | dns_assignment | None |

  18. | dns_domain | None |

  19. | dns_name | None |

  20. | extra_dhcp_opts | |

  21. | fixed_ips | ip_address='192.168.1.27', subnet_id='96f33568-70cd-47a2-a0b5-a32a853caa11' |

  22. | id | 07995f4e-b6b2-493f-9ce5-b1d945a13807 |

  23. | location | None |

  24. | mac_address | fa:16:3e:0f:ff:74 |

  25. | name | Port1 |

  26. | network_id | e28bd712-352f-439d-88ea-35a994a4a765 |

  27. | port_security_enabled | True |

  28. | project_id | 031cec3e2df143259d302aa1993fd410 |

  29. | propagate_uplink_status | None |

  30. | qos_policy_id | None |

  31. | resource_request | None |

  32. | revision_number | 1 |

  33. | security_group_ids | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad |

  34. | status | DOWN |

  35. | tags | |

  36. | trunk_details | None |

  37. | updated_at | 2019-03-08T03:17:23Z |

  38. +-------------------------+-----------------------------------------------------------------------------+

这里强调一下 “设备 ID(deviceid)” 和 “设备所属者(deviceowner)” 两个字段,它们共同标识了 Port 的绑定实体。比如:Port 绑定到了一台虚拟机,那么 device_id=<instance_uuid>, device_owner=compute:nova。常见的 device_owner 还有:

  • network:dhcp — Neutron DHCP Agent 使用的端口,为 Network 提供 DHCP 服务

  • network:router_interface — Neutron L3 Router Agent 使用的端口,将 Subnet 接入路由器

  • network:router_gateway — Neutron L3 Router Agent 使用的接口,外部网络接入路由器的网管接口

还需要强调一下的是 Port 的类型,Neutron 支持多种类型的 Port,不同类型的 Port 底层可能由不同的 Agent 实现(e.g. ovs-agent、sriov-agent)。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇只需要提供用户预期的 IP/MAC 地址,即可为 Port 添加可用地址对。在可用地址对清单中的 IP/MAC 都可以通过自动或手动的方式应用到这个 Port 所对应的 vNIC(Tap 设备)上。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

还需要注意一点,fixe_ips 属性是一个数值类型,就是说加入一个 Network 下属具有多个 Subnet,那么这个 Port 就可以从多个 Subnets 中获取多个 IP 地址。e.g.

  1. [root@localhost ~]# openstack port create --network Net2 --fixed-ip subnet=Subnet2-1 --fixed-ip subnet=Subnet2-2 --enable-port-security Port2-1

  2. +-------------------------+--------------------------------------------------------------------------------+

  3. | Field | Value |

  4. +-------------------------+--------------------------------------------------------------------------------+

  5. | admin_state_up | UP |

  6. | allowed_address_pairs | |

  7. | binding_host_id | |

  8. | binding_profile | |

  9. | binding_vif_details | |

  10. | binding_vif_type | unbound |

  11. | binding_vnic_type | normal |

  12. | created_at | 2019-03-08T04:21:56Z |

  13. | data_plane_status | None |

  14. | description | |

  15. | device_id | |

  16. | device_owner | |

  17. | dns_assignment | None |

  18. | dns_domain | None |

  19. | dns_name | None |

  20. | extra_dhcp_opts | |

  21. | fixed_ips | ip_address='172.16.100.38', subnet_id='0f15d289-26f2-4c83-9538-fae158bf3153' |

  22. | | ip_address='192.168.100.125', subnet_id='7a2fa4b5-c8ca-48e9-94fb-c74f5e59510f' |

  23. | id | 51383a86-56b5-4907-8bd2-80801351fc1b |

  24. | location | None |

  25. | mac_address | fa:16:3e:df:03:8c |

  26. | name | Port2-1 |

  27. | network_id | 4472e95b-f2e1-4ff5-8bce-429e1997f3cf |

  28. | port_security_enabled | True |

  29. | project_id | 031cec3e2df143259d302aa1993fd410 |

  30. | propagate_uplink_status | None |

  31. | qos_policy_id | None |

  32. | resource_request | None |

  33. | revision_number | 1 |

  34. | security_group_ids | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad |

  35. | status | DOWN |

  36. | tags | |

  37. | trunk_details | None |

  38. | updated_at | 2019-03-08T04:21:57Z |

  39. +-------------------------+--------------------------------------------------------------------------------+

挂载一个 Port

使用 Port 来启动一个虚拟机

  1. openstack server create --flavor cirros256 --image cirros-0.3.4-x86_64-disk --port Port1 VM1

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

需要注意的是,即便 Port 具有多个可用地址对,但虚拟机网卡原生的 IP/MAC 依旧是 Port 的原生 fixedips/macaddress。可用地址对的含义是可以被 Port 使用的 IP/MAC,而非一定会被使用的 IP/MAC。

Port 挂载到虚拟机之后,其自身的信息也会被更新

  1. [root@localhost ~]# openstack port show Port1

  2. +-------------------------+-------------------------------------------------------------------------------------------+

  3. | Field | Value |

  4. +-------------------------+-------------------------------------------------------------------------------------------+

  5. | admin_state_up | UP |

  6. | allowed_address_pairs | ip_address='192.168.1.28', mac_address='fa:16:3e:0f:ff:28' |

  7. | | ip_address='192.168.1.29', mac_address='fa:16:3e:0f:ff:29' |

  8. | binding_host_id | localhost.localdomain |

  9. | binding_profile | |

  10. | binding_vif_details | bridge_name='br-int', datapath_type='system', ovs_hybrid_plug='False', port_filter='True' |

  11. | binding_vif_type | ovs |

  12. | binding_vnic_type | normal |

  13. | created_at | 2019-03-08T03:17:23Z |

  14. | data_plane_status | None |

  15. | description | |

  16. | device_id | 6da255cb-402a-4273-844f-5aad549d65e7 |

  17. | device_owner | compute:nova |

  18. | dns_assignment | None |

  19. | dns_domain | None |

  20. | dns_name | None |

  21. | extra_dhcp_opts | |

  22. | fixed_ips | ip_address='192.168.1.27', subnet_id='96f33568-70cd-47a2-a0b5-a32a853caa11' |

  23. | id | 07995f4e-b6b2-493f-9ce5-b1d945a13807 |

  24. | location | None |

  25. | mac_address | fa:16:3e:0f:ff:74 |

  26. | name | Port1 |

  27. | network_id | e28bd712-352f-439d-88ea-35a994a4a765 |

  28. | port_security_enabled | True |

  29. | project_id | 031cec3e2df143259d302aa1993fd410 |

  30. | propagate_uplink_status | None |

  31. | qos_policy_id | None |

  32. | resource_request | None |

  33. | revision_number | 6 |

  34. | security_group_ids | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad |

  35. | status | ACTIVE |

  36. | tags | |

  37. | trunk_details | None |

  38. | updated_at | 2019-03-08T03:51:05Z |

  39. +-------------------------+-------------------------------------------------------------------------------------------+

  • bindingviftype — 从 unbound 变成 ovs,表示底层使用的是 OvS Mechanism Driver。

  • bindinghostid — Port 对应的 Tap 设备所在的主机,也就是 VM 所在的计算节点。

  • bindingvifdetails — 记录了一些 Port 挂载后的信息,例如 Tap 设备加入的 OvS Bridge 名称,OvS Bridge 的类型。

  • deviceid & deviceowner — 指定绑定 Port 的实体对象(虚拟机)和类型(compute:nova)。

NOTE:从上述信息可以看出,Port 对应的 Tap 设备实际上实在 “绑定” 之后再创建的,Tap 设备的命名规则为 tap[port-uuid]。e.g.

  1. [root@localhost ~]# ovs-vsctl show

  2. ccb13b70-cffb-4a64-97b1-734bf9040abc

  3. Manager "ptcp:6640:127.0.0.1"

  4. is_connected: true

  5. ...

  6. Bridge br-int

  7. Controller "tcp:127.0.0.1:6633"

  8. is_connected: true

  9. fail_mode: secure

  10. ...

  11. Port "tap07995f4e-b6"

  12. tag: 5

  13. Interface "tap07995f4e-b6"

  14. ...

  15. ovs_version: "2.9.0"

使用具有多个 fixed_ips 的 Port 来启动一个虚拟机

  1. [root@localhost ~]# openstack server create --flavor cirros256 --image cirros-0.3.4-x86_64-disk --port Port2-1 VM2

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

奇怪为什么 Port 和虚拟机明明有两个 IP 地址,但 GuestOS 起来后却只看见了一个?其实虚拟机启动获取 IP 地址时是随机挑选一个的,不会把 Port 的所有 IP 都配置上去,当然你也可以手动的将两个 IP 地址配置上去。总的来说这种做法和应用可用地址对的效果差不多,如果你只是单纯的希望虚拟机具有多个 IP,还是建议你使用可用地址对。但如果你希望虚拟机可以实现跨网段中断,那么就可以使用具有多个 Subnet IP 的 Port。

Router

Router 是 Neutron 实现的路由器抽象。

需要注意的是,Router 只是 Neutron 对 Linux 服务器路由功能的抽象,而非 Neutron 实现了一个完整的 vRouter 软件。这之间有着巨大的区别。Neutron Router 对 Linux 路由功能进行了封装,并由 L3 Agent 服务进程负责处理。通常的,L3 Agent 只运行在网络节点中,所以所有的跨网段访问流量、公网访问流量都会流经网络节点。由此,网络节点成为了 Neutron 三层网络的性能瓶颈,为了解决这个问题,Neutron 提出了 DVR(分布式路由器)机制,让每个计算节点都具有三层网络功能。

简而言之,对于上层应用而言,只需要关注 Router 的 端口网关路由表 即可。

NOTE:关于 Linux 路由功能在《Linux 的路由功能》一文中已经介绍过,这里不再赘述。

外部网关

Neutron 语义环境中的 “外部网关” 有两重含义:

  • 实际的外部网关(下文称为物理外部网关),在 Neutron 的管理范畴之外。如下图的 Router_2 上的 Port2。

  • Neutron 的外部网关(下文成为 Neutron 外部网关,以作区别),在 Neutron 的管理范畴之内。如下图 Router_1 上的 Port1。

我们思考一个问题:为什么 Neutron 内部网络需要通过 Router1 再与 Router2 进行连接?而不将内部网络直连 Router_2 然后访问公网呢?首先明确一点,“Neutron 管理的内部网络” 指的是租户网络而非运营商网络。运营商网络当然可以映射到一个直连物理外部网关的物理网络,这是因为云管人员手动的为运营商网络提供了物理网络的 “核心三要素”。同理,如果租户网络希望直连到物理外部网关,那么就需要云管人员提供连接到物理外部网关的 “核心要素(e.g. 外部网关的 IP 地址)”。但是,让云管人员为每个租户网络都手动的 “填入”(实际上并非填入,而是获取) “核心要素” 显然是不明智的,再者物理路由器很可能没有足够的接口数量供租户网络使用。

出于这样的前提,Neutron 在租户网络和外部网关之间引入了一个内部路由层。每个租户都可以创建专属的内部路由层,包含若干个 L3 Router 实例对象。租户可以随意的将租户网络接入内部路由层,然后再由内部路由层统一对接物理外部网关。这样物理路由器的端口就能得到更高效的利用。

NOTE:创建多个 L3 Router 实例对象并非是说需要多个 Linux 服务器,Neutron 通过 qrouter-XXX network namespace 在网络节点上隔离出了多个 “虚拟路由器”。每创建一个 Router,运行在网络节点的 L3 Agent 就会新建一个 qrouter-XXX。由于 qrouter-XXX 具有自己独立的路由表,所以同一租户下不同的 Network 之间允许具有相同 IP 网段(CIDR)的 Subnet,只要这些 Subnet 只要不连接到同一个 Router 上就不会出现 IP 地址重叠的问题。

  1. destination next_hop out_interface

  2. 104.20.110.0/24 182.24.4.1 Port1(182.21.4.6)

这条路由表项的关键信息有三:物理外部网关 IP(182.24.4.1)、Neutron 外部网关 IP(182.21.4.6)以及隐藏信息 —— 运营商网络(外部网络)182.24.4.0/24。

可见,Neutron 内部网络要想连接公网,关键在于如何获取上述 3 个关键信息。办法很简单,只需要创建一个运营商网络(外部网络)即可,例如:182.24.4.0/24。物理外部网关 IP(182.24.4.1)就是运营商网络(外部网络)对应的 Subnet 的 gatewayip 属性,而 Neutron 外部网关 IP(182.21.4.6)就是该 Subnet 分配的一个子网 IP 地址。存放这些信息的 Router 资源模型属性就是 externalgateway_info:

  1. {

  2. "external_gateway_info": {

  3. "enable_snat": true,

  4. "external_fixed_ips": [{

  5. "ip_address": "182.24.4.6",

  6. "subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf"

  7. }],

  8. "network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3"

  9. }

  10. }

  • network_id — 运营商网络(外部网络)

  • subnetid — 运营商网络(外部网络)的 Subnet(其 gatewayip 就是物理外部网关 IP 地址)

  • ip_address — Neutron 外部网关 IP 地址

  • enable_snat — Neutron 外部网关端口是否开启 SNAT 功能

如果开启了 SNAT,Neutron 就会在 qroute-XXX namespace 中添加这样的一条 iptables rule,所有从 qg-426c2bcd-f4(如上图 Port 1)出去的数据包,无论你来自哪里(源 IP 地址)或者要去哪里(目的 IP 地址),数据包的源 IP 地址都会被转换为 172.18.22.210。

  1. [root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f iptables -nvL -t nat

  2. ...


  3. Chain neutron-l3-agent-snat (1 references)

  4. pkts bytes target prot opt in out source destination

  5. 2 142 neutron-l3-agent-float-snat all -- * * 0.0.0.0/0 0.0.0.0/0

  6. 2 142 SNAT all -- * qg-426c2bcd-f4 0.0.0.0/0 0.0.0.0/0 to:172.18.22.210

  7. 0 0 SNAT all -- * * 0.0.0.0/0 0.0.0.0/0 mark match ! 0x2/0xffff ctstate DNAT to:172.18.22.210

NOTE:添加这条 SNAT 的原因是,Linux 作为路由器时仅仅开启了内核的路由转发功能是不够的,还需要添加这么一条 SNAT。那为什么不需要添加 DNAT 呢?因为作为路由器而言,所有的数据包都只是 SNAT(送出去)而已。

新建 Router 和外部网关端口

从上文可一直,当我们希望 Neutron 内部网络连通公网时,就创建一个运营商网络(外部网络),然后使用 L3 Router 将内部网络与运营商网络(外部网络)直连起来即可。e.g.

  • 新建 Router。如果此时用户选择了 “外部网络”,那么 Neutron 会自动的创建 Router1 与 public 直连的外部网关端口。需要注意的是,只有这一种情况下 Neutron 会自动创建路由端口,否则都需要用户手动创建。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

  • 内部网络 Net1 和运营商网络(外部网络)public 通过 Router 直连

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

  • public 有一个端口连接到 Router,类型为 network:router_gateway,IP 为 172.18.22.210(Neutron 外部网关 IP)。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

  • public 的 Subnet public-subnet 具有物理外部网关 IP 地址 172.18.22.1。

  1. [root@localhost ~]# openstack subnet show public-subnet

  2. +-------------------+--------------------------------------+

  3. | Field | Value |

  4. +-------------------+--------------------------------------+

  5. | allocation_pools | 172.18.22.201-172.18.22.210 |

  6. | cidr | 172.18.22.0/24 |

  7. | created_at | 2019-02-13T03:41:09Z |

  8. | description | |

  9. | dns_nameservers | |

  10. | enable_dhcp | False |

  11. | gateway_ip | 172.18.22.1 |

  12. | host_routes | |

  13. | id | 99749410-a6b0-418b-9ebc-fd060e1a746e |

  14. | ip_version | 4 |

  15. | ipv6_address_mode | None |

  16. | ipv6_ra_mode | None |

  17. | location | None |

  18. | name | public-subnet |

  19. | network_id | 282e146e-6948-436f-992c-f2d50588e357 |

  20. | project_id | 031cec3e2df143259d302aa1993fd410 |

  21. | revision_number | 0 |

  22. | segment_id | None |

  23. | service_types | |

  24. | subnetpool_id | None |

  25. | tags | |

  26. | updated_at | 2019-02-13T03:41:09Z |

  27. +-------------------+--------------------------------------+

  • Router 具有两个端口,一个连接 Net1,IP 地址是 Net1 的网关 IP 192.168.1.1;另一个连接 public,IP 是 Neutron 外部网关 IP 172.18.22.210。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

  • Router1 的路由表和网络设备:

  1. [root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f ifconfig

  2. lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536

  3. inet 127.0.0.1 netmask 255.0.0.0

  4. inet6 ::1 prefixlen 128 scopeid 0x10<host>

  5. loop txqueuelen 1000 (Local Loopback)

  6. RX packets 0 bytes 0 (0.0 B)

  7. RX errors 0 dropped 0 overruns 0 frame 0

  8. TX packets 0 bytes 0 (0.0 B)

  9. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0


  10. qg-426c2bcd-f4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500

  11. inet 172.18.22.210 netmask 255.255.255.0 broadcast 172.18.22.255

  12. inet6 fe80::f816:3eff:fe0b:c722 prefixlen 64 scopeid 0x20<link>

  13. ether fa:16:3e:0b:c7:22 txqueuelen 1000 (Ethernet)

  14. RX packets 765265 bytes 36064217 (34.3 MiB)

  15. RX errors 0 dropped 0 overruns 0 frame 0

  16. TX packets 90 bytes 5316 (5.1 KiB)

  17. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0


  18. qr-cf018461-bc: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500

  19. inet 192.168.1.1 netmask 255.255.255.0 broadcast 192.168.1.255

  20. inet6 fe80::f816:3eff:fea5:b2bb prefixlen 64 scopeid 0x20<link>

  21. ether fa:16:3e:a5:b2:bb txqueuelen 1000 (Ethernet)

  22. RX packets 216 bytes 18796 (18.3 KiB)

  23. RX errors 0 dropped 0 overruns 0 frame 0

  24. TX packets 139 bytes 13870 (13.5 KiB)

  25. TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0


  26. [root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f route -nne

  27. Kernel IP routing table

  28. Destination Gateway Genmask Flags MSS Window irtt Iface

  29. 0.0.0.0 172.18.22.1 0.0.0.0 UG 0 0 0 qg-426c2bcd-f4

  30. 172.18.22.0 0.0.0.0 255.255.255.0 U 0 0 0 qg-426c2bcd-f4

  31. 192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 qr-cf018461-bc

其中 qr-XXX 和 qg-YYY 在 qroute-XXX namespace 中用于连接 br-int 和 br-ex,从第二、三条路由表项是由链路层协议发现并创建的,分别是 Net1 和 public 子网的直连路由,由设备 qr-XXX 和 qg-YYY 转发。而第一条路由表项,则是由 Neutron 添加的默认静态路由,Neutron 从 public 的 gateway_ip 获取到物理外部网关 IP 地址,所有非直连路由的子网访问流量,都会使用默认静态路由进行转发。

Router 的路由表

我们知道路由器的路由表项分配静态路由和动态路由,Neutron 中所有的路由表项均为静态路由(不遵循动态路由协议),又细分为:

  • 静态路由

  • 静态默认路由

  • 直连路由

其中只有 “静态路由” 一种类型会被 Neutron 记录在 Router 资源模型的 routes(数值类型)属性中。

  1. MariaDB [neutron]> desc routerroutes;

  2. +-------------+-------------+------+-----+---------+-------+

  3. | Field | Type | Null | Key | Default | Extra |

  4. +-------------+-------------+------+-----+---------+-------+

  5. | destination | varchar(64) | NO | PRI | NULL | |

  6. | nexthop | varchar(64) | NO | PRI | NULL | | # 目的网段( CIDR)

  7. | router_id | varchar(36) | NO | PRI | NULL | | # 下 一 跳 IP(下一个路由器的端口 IP)

  8. +-------------+-------------+------+-----+---------+-------+

后面两种路由表项类型为什么不被持久化呢?因为静态默认路由的信息来自外部网络的 gateway_ip 属性,而直连路由则由链路层协议自动发现、创建。所以不需要额外的持久化。

那么静态路由又是什么路由,为什么要持久化呢?

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

  1. +----------------+-------------+--------------------------------------+

  2. | destination | nexthop | router_id |

  3. +----------------+-------------+--------------------------------------+

  4. | 192.168.1.2/32 | 172.18.22.1 | a1adb970-dba9-49f0-ba4b-4294f0d07f6f |

  5. +----------------+-------------+--------------------------------------+

如上,Router 的静态路由其实就是常规的主机路由或网络路由,使用户自定义的路由表项。但需要注意的是,“下一跳” 表单不能随意乱填,而是限制在 Router 够得着的子网 IP 地址。否则就会 the nexthop is not connected with router

NOTE:在讲述 Subnet 资源模型的章节中你或许会对 Subnet 的 gatewayip 和 hostroutes 属性感到疑惑,通本章的举例,或许你能够更好的理解这些字段所具有的功能和含义。

Floating IP

从功能的角度可以将 Neutron 的 IP 分为:

  • Fixed IP:由租户网络 Subnet 分配,用于虚拟机的内部通讯,创建虚拟机时自动分配。

  • Floating IP:有外部网络分配,用于虚拟机访问公网,需要手动分配。

前文中我们提到过,一般的运营商网络和勾选了 “外部网络” 的运营商网络的主要区别在于后者可以分配 Floating IP,实现的原理就是内部网络与外部网络之间的 Router(qroute-XXX)的 NAT(SNAT&DNAT)功能。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

上图可见,外部网络 public 成为了一个 Floating IP 地址池。创建一个 Floating IP 会相应的创建一条数据库记录:

  1. MariaDB [neutron]> select * from floatingipsG;

  2. *************************** 1. row ***************************

  3. project_id: 031cec3e2df143259d302aa1993fd410

  4. id: 67272b76-493a-4a60-b63a-22e5aefebfc0

  5. floating_ip_address: 172.18.22.204

  6. floating_network_id: 282e146e-6948-436f-992c-f2d50588e357

  7. floating_port_id: 04aceef8-a3b2-46e4-a815-3104a2031ed7

  8. fixed_port_id: NULL

  9. fixed_ip_address: NULL

  10. router_id: NULL

  11. last_known_router_id: NULL

  12. status: DOWN

  13. standard_attr_id: 82

  14. 1 row in set (0.01 sec)

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇 为虚拟机绑定这个 Floating IP 之后,数据库记录会被更改:

  1. MariaDB [neutron]> select * from floatingipsG;

  2. *************************** 1. row ***************************

  3. project_id: 031cec3e2df143259d302aa1993fd410

  4. id: 67272b76-493a-4a60-b63a-22e5aefebfc0

  5. floating_ip_address: 172.18.22.204

  6. floating_network_id: 282e146e-6948-436f-992c-f2d50588e357

  7. floating_port_id: 04aceef8-a3b2-46e4-a815-3104a2031ed7

  8. fixed_port_id: 07995f4e-b6b2-493f-9ce5-b1d945a13807

  9. fixed_ip_address: 192.168.1.27

  10. router_id: a1adb970-dba9-49f0-ba4b-4294f0d07f6f

  11. last_known_router_id: NULL

  12. status: ACTIVE

  13. standard_attr_id: 82

  14. 1 row in set (0.00 sec)

qroute-XXX 中的 iptables rules 也会相同的添加 SNAT、DNAT 规则:

  1. [root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f iptables -nvL -t nat

  2. ...


  3. # 到目的 IP 地址 172.18.22.204 的数据包 DNAT 至 192.168.1.27

  4. Chain neutron-l3-agent-PREROUTING (1 references)

  5. pkts bytes target prot opt in out source destination

  6. ...

  7. 0 0 DNAT all -- * * 0.0.0.0/0 172.18.22.204 to:192.168.1.27

  8. ...


  9. # 从源 IP 地址 192.168.1.27 来的数据包 SNAT 至 172.18.22.204

  10. Chain neutron-l3-agent-float-snat (1 references)

  11. pkts bytes target prot opt in out source destination

  12. 0 0 SNAT all -- * * 192.168.1.27 0.0.0.0/0 to:172.18.22.204

  13. ...

这一切都发生在 qroute-XXX network namespace 中,实现了不同 Router 之间的网络隔离。


关于九州云99Cloud

九州云成立于2012年,是中国早期从事开放基础架构服务的专业公司。公司成立六年,秉承“开源 · 赋能变革”的理念,不断夯实自身实力,先后为政府、金融、运营商、能源、制造业、商业、交通、物流、教育、医疗等各大行业的企业级客户提供高质量的开放基础架构服务。目前拥有国家电网、南方电网广东公司、中国人民银行、中国银联、中国移动、中国电信、中国联通、中国资源卫星、eBay、国际陆港集团、中国人寿、万达信息、东风汽车、诺基亚等众多重量级客户,被用户认可为最值得信赖的合作伙伴。

5月技术周 | 我非要捅穿这 Neutron(二)上层资源模型篇