[转]Neutron(二)上层资源模型篇

204 阅读37分钟

Neutron 的资源模型

Neutron 资源模型官方文档:developer.openstack.org/api-ref/net…

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 租户无法管理这个物理网络的生命周期

创建运营商网络

创建运营商网络有两个要点:一是只能有管理员创建,二是需要手动填写 “核心三要素”。
在这里插入图片描述
上图为通过 admin 创建一个 VLAN 类型的运营商网络,填写的三个表单就对应了 “核心三要素” 并存储到数据库中 networks 表的 provider:network_ type、provider:physical_network、provider:segmentation_id 字段中。“核心三要素” 中的 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.

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

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。

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

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

  • 只能由管理员创建

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

    • 非隧道网络类型:需要传入 Physical Network Mapping(provider:physical_network)字段值,描述 Neutron Network 接入运营商网络的 “网卡(br-ethX)”。
    • 隧道网络类型:只需要保证节点上已经定义好了与运营商网络对应的 Tunnel 端口 IP 地址并且三层网络通信无障碍即可。

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

创建租户网络

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

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

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

[ml2]
...
tenant_network_types = vlan,vxlan
mechanism_drivers = openvswitch,linuxbridge

[securitygroup]
firewall_driver = openvswitch

[ovs]
datapath_type = system
bridge_mappings = public:br-ex,
tunnel_bridge = br-tun
local_ip = 172.18.22.200

[ml2_type_flat]
flat_networks = public,

[ml2_type_vlan]
network_vlan_ranges = public:3001:4000,

[ml2_type_geneve]
vni_ranges = 1:1000

[ml2_type_gre]
tunnel_id_ranges = 1:1000

[ml2_type_vxlan]
vni_ranges = 1:1000

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

创建外部网络

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

在这里插入图片描述

实际上所谓的外部网络和运营商网络的底层实现并无区别,不同之处在于外部网络具有分配 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, end IP]。若没有配置则以 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.

$ openstack server create -h
...
[--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.

$ openstack subnet create -h
...
[--subnet-pool <subnet-pool> | --use-prefix-delegation USE_PREFIX_DELEGATION | --use-default-subnet-pool]

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

在这里插入图片描述

# 记录了一个 SubnetPool 的子网网段划分规则
MariaDB [neutron]>  select * from subnetpools\G;
*************************** 1. row ***************************
       project_id: 031cec3e2df143259d302aa1993fd410
               id: 5bbd5cab-c6a0-4851-b2d7-1137742e6adf
             name: shared-default-subnetpool-v4
       ip_version: 4
default_prefixlen: 26
    min_prefixlen: 8
    max_prefixlen: 32
           shared: 1
    default_quota: NULL
             hash: 27e49575-31f7-4012-b57b-55c990cb6d82
 address_scope_id: NULL
       is_default: 1
 standard_attr_id: 1
 
# 记录了一个 SubnetPool 的子网网段信息
MariaDB [neutron]> select * from  subnetpoolprefixes;
+----------------+--------------------------------------+
| cidr           | subnetpool_id                        |
+----------------+--------------------------------------+
| 192.168.1.0/24 | 5bbd5cab-c6a0-4851-b2d7-1137742e6adf |
+----------------+--------------------------------------+

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 官方示例文档:docs.openstack.org/newton/netw…

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

创建 Subnet

在这里插入图片描述
创建 Subnet 可以手动输入你预期的网段,也可以应用 SubnetPools 资源模型,直接选择一个 IP 地址资源池。显然后者要来的更加简便一些,只需要通过 “网络掩码” 表单来描述你想要的 IP 地址数量即可(e.g. NETMASK=24 即 2**8 == 256 个 IP 地址)。
在这里插入图片描述
创建 Subnet 时你还可以 “手动输入网络地址”,该表单对应 Subnet 资源模型的 allocation_pools 属性,所以 “手动输入网络地址” 是无法和 SubnetPools 共存的,只有通过手动指定网段的方式才可以使用。“主机路由” 对应 host_routes 属性,格式正如上文所说:[目标网络, 下一跳 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 维持 “表面上” 的一比一关系吧。
在这里插入图片描述

NOTE:虽然 Network 下属有多个 Subnet,但为整个 Network 服务的 DHCP 仍然只有一个。e.g.
在这里插入图片描述

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 属性。

Port 与 Network、Subnet 的关系如下图
在这里插入图片描述

Neutron 安全组(Security Group)

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


在这里插入图片描述

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

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

[securitygroup]
firewall_driver = openvswitch

当然了,如果选择了 openvswitch securitygroup driver,那么 Neutron 的网络实现模型就不再需要 qbr-XXX 安全层了。相应了少了一层转发之后,网络性能也会有所提高,尤其在安全组规则数量巨大的时候。
在这里插入图片描述
在这里插入图片描述
关于 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 资源模型的 fixed_ips 属性就被设计为一个数组类型(Port 可具有多个 IP 地址,1 个 MAC 地址)。针对这样的场景一般有两个办法:关闭 Port Security(port_security_enabled,不建议使用)或使用 Allowed address pairs(allowed_address_pairs)机制。
在这里插入图片描述
每个 Port 都具有自己的 allowed_address_pairs 数组,它记录了若干对合法的 IP/MAC 地址映射,以此来支持允许多个 IP 与 Port 连通,从而满足一个 Port 具有多个 IP 地址的需求。

更多可用地址对的详情请浏览官方介绍:docs.openstack.org/developer/d…

创建一个 Port

NOTE:Port 是依托于 Network 的,所以首先需要创建一个 Network。
在这里插入图片描述
上图创建了一个简单的 Port:

  • Port 的 Fixed IP 地址会从 Subnet1 的 IP 地址池中随机分配,对应 fixed_ips 属性。
  • 开启了 Port Security,现在 Port 的 IP/MAC 地址一一绑定,对应 port_security_enabled 属性。
  • VNIC 类型为正常(normal),对应 binding:vnic_type 属性。
  • 没有指定 “设备 ID(device_id)” 和 “设备所属者(device_owner)”,所以现在设备的 binding_vif_type 状态为 unbound。binding:vif_type 除了用来标识 Port 的绑定状态(unbound,bound_failed)之外,还用于标识这个 Port 的 Mechanism 类型(e.g. ovs、bridge、macvtap)。
  • 在创建 Port 的同时可以指定这个 Port 的安全组规则
[root@localhost ~]# openstack port show Port1
+-------------------------+-----------------------------------------------------------------------------+
| Field                   | Value                                                                       |
+-------------------------+-----------------------------------------------------------------------------+
| admin_state_up          | UP                                                                          |
| allowed_address_pairs   |                                                                             |
| binding_host_id         |                                                                             |
| binding_profile         |                                                                             |
| binding_vif_details     |                                                                             |
| binding_vif_type        | unbound                                                                     |
| binding_vnic_type       | normal                                                                      |
| created_at              | 2019-03-08T03:17:23Z                                                        |
| data_plane_status       | None                                                                        |
| description             |                                                                             |
| device_id               |                                                                             |
| device_owner            |                                                                             |
| dns_assignment          | None                                                                        |
| dns_domain              | None                                                                        |
| dns_name                | None                                                                        |
| extra_dhcp_opts         |                                                                             |
| fixed_ips               | ip_address='192.168.1.27', subnet_id='96f33568-70cd-47a2-a0b5-a32a853caa11' |
| id                      | 07995f4e-b6b2-493f-9ce5-b1d945a13807                                        |
| location                | None                                                                        |
| mac_address             | fa:16:3e:0f:ff:74                                                           |
| name                    | Port1                                                                       |
| network_id              | e28bd712-352f-439d-88ea-35a994a4a765                                        |
| port_security_enabled   | True                                                                        |
| project_id              | 031cec3e2df143259d302aa1993fd410                                            |
| propagate_uplink_status | None                                                                        |
| qos_policy_id           | None                                                                        |
| resource_request        | None                                                                        |
| revision_number         | 1                                                                           |
| security_group_ids      | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad                                        |
| status                  | DOWN                                                                        |
| tags                    |                                                                             |
| trunk_details           | None                                                                        |
| updated_at              | 2019-03-08T03:17:23Z                                                        |
+-------------------------+-----------------------------------------------------------------------------+

这里强调一下 “设备 ID(device_id)” 和 “设备所属者(device_owner)” 两个字段,它们共同标识了 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)。
在这里插入图片描述
只需要提供用户预期的 IP/MAC 地址,即可为 Port 添加可用地址对。在可用地址对清单中的 IP/MAC 都可以通过自动或手动的方式应用到这个 Port 所对应的 vNIC(Tap 设备)上。
在这里插入图片描述

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

[root@localhost ~]# openstack port create --network Net2 --fixed-ip subnet=Subnet2-1 --fixed-ip subnet=Subnet2-2 --enable-port-security Port2-1
+-------------------------+--------------------------------------------------------------------------------+
| Field                   | Value                                                                          |
+-------------------------+--------------------------------------------------------------------------------+
| admin_state_up          | UP                                                                             |
| allowed_address_pairs   |                                                                                |
| binding_host_id         |                                                                                |
| binding_profile         |                                                                                |
| binding_vif_details     |                                                                                |
| binding_vif_type        | unbound                                                                        |
| binding_vnic_type       | normal                                                                         |
| created_at              | 2019-03-08T04:21:56Z                                                           |
| data_plane_status       | None                                                                           |
| description             |                                                                                |
| device_id               |                                                                                |
| device_owner            |                                                                                |
| dns_assignment          | None                                                                           |
| dns_domain              | None                                                                           |
| dns_name                | None                                                                           |
| extra_dhcp_opts         |                                                                                |
| fixed_ips               | ip_address='172.16.100.38', subnet_id='0f15d289-26f2-4c83-9538-fae158bf3153'   |
|                         | ip_address='192.168.100.125', subnet_id='7a2fa4b5-c8ca-48e9-94fb-c74f5e59510f' |
| id                      | 51383a86-56b5-4907-8bd2-80801351fc1b                                           |
| location                | None                                                                           |
| mac_address             | fa:16:3e:df:03:8c                                                              |
| name                    | Port2-1                                                                        |
| network_id              | 4472e95b-f2e1-4ff5-8bce-429e1997f3cf                                           |
| port_security_enabled   | True                                                                           |
| project_id              | 031cec3e2df143259d302aa1993fd410                                               |
| propagate_uplink_status | None                                                                           |
| qos_policy_id           | None                                                                           |
| resource_request        | None                                                                           |
| revision_number         | 1                                                                              |
| security_group_ids      | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad                                           |
| status                  | DOWN                                                                           |
| tags                    |                                                                                |
| trunk_details           | None                                                                           |
| updated_at              | 2019-03-08T04:21:57Z                                                           |
+-------------------------+--------------------------------------------------------------------------------+

挂载一个 Port

使用 Port 来启动一个虚拟机

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

在这里插入图片描述
需要注意的是,即便 Port 具有多个可用地址对,但虚拟机网卡原生的 IP/MAC 依旧是 Port 的原生 fixed_ips/mac_address。可用地址对的含义是可以被 Port 使用的 IP/MAC,而非一定会被使用的 IP/MAC。

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

[root@localhost ~]# openstack port show Port1
+-------------------------+-------------------------------------------------------------------------------------------+
| Field                   | Value                                                                                     |
+-------------------------+-------------------------------------------------------------------------------------------+
| admin_state_up          | UP                                                                                        |
| allowed_address_pairs   | ip_address='192.168.1.28', mac_address='fa:16:3e:0f:ff:28'                                |
|                         | ip_address='192.168.1.29', mac_address='fa:16:3e:0f:ff:29'                                |
| binding_host_id         | localhost.localdomain                                                                     |
| binding_profile         |                                                                                           |
| binding_vif_details     | bridge_name='br-int', datapath_type='system', ovs_hybrid_plug='False', port_filter='True' |
| binding_vif_type        | ovs                                                                                       |
| binding_vnic_type       | normal                                                                                    |
| created_at              | 2019-03-08T03:17:23Z                                                                      |
| data_plane_status       | None                                                                                      |
| description             |                                                                                           |
| device_id               | 6da255cb-402a-4273-844f-5aad549d65e7                                                      |
| device_owner            | compute:nova                                                                              |
| dns_assignment          | None                                                                                      |
| dns_domain              | None                                                                                      |
| dns_name                | None                                                                                      |
| extra_dhcp_opts         |                                                                                           |
| fixed_ips               | ip_address='192.168.1.27', subnet_id='96f33568-70cd-47a2-a0b5-a32a853caa11'               |
| id                      | 07995f4e-b6b2-493f-9ce5-b1d945a13807                                                      |
| location                | None                                                                                      |
| mac_address             | fa:16:3e:0f:ff:74                                                                         |
| name                    | Port1                                                                                     |
| network_id              | e28bd712-352f-439d-88ea-35a994a4a765                                                      |
| port_security_enabled   | True                                                                                      |
| project_id              | 031cec3e2df143259d302aa1993fd410                                                          |
| propagate_uplink_status | None                                                                                      |
| qos_policy_id           | None                                                                                      |
| resource_request        | None                                                                                      |
| revision_number         | 6                                                                                         |
| security_group_ids      | 12519ba0-d1f1-46e3-a0b2-48e6639ee8ad                                                      |
| status                  | ACTIVE                                                                                    |
| tags                    |                                                                                           |
| trunk_details           | None                                                                                      |
| updated_at              | 2019-03-08T03:51:05Z                                                                      |
+-------------------------+-------------------------------------------------------------------------------------------+
  • binding_vif_type — 从 unbound 变成 ovs,表示底层使用的是 OvS Mechanism Driver。
  • binding_host_id — Port 对应的 Tap 设备所在的主机,也就是 VM 所在的计算节点。
  • binding_vif_details — 记录了一些 Port 挂载后的信息,例如 Tap 设备加入的 OvS Bridge 名称,OvS Bridge 的类型。
  • device_id & device_owner — 指定绑定 Port 的实体对象(虚拟机)和类型(compute:nova)。

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

[root@localhost ~]# ovs-vsctl show
ccb13b70-cffb-4a64-97b1-734bf9040abc
    Manager "ptcp:6640:127.0.0.1"
        is_connected: true
...
    Bridge br-int
        Controller "tcp:127.0.0.1:6633"
            is_connected: true
        fail_mode: secure
...
        Port "tap07995f4e-b6"
            tag: 5
            Interface "tap07995f4e-b6"
...            
    ovs_version: "2.9.0"

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

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

在这里插入图片描述
在这里插入图片描述
奇怪为什么 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 内部网络需要通过 Router_1 再与 Router_2 进行连接?而不将内部网络直连 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 地址重叠的问题。

如上图,Router_1 对接 Router_2 的方式很简单,只需在 Router_1 添加一条路由表项即可。e.g.

destination          next_hop     out_interface
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。

NOTE:此处的运营商网络(外部网络)182.24.4.0/24 不是上图中的外部网络(公网),而是勾选了 “外部网络” 的运营商网络。

在这里插入图片描述

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

{
	"external_gateway_info": {
		"enable_snat": true,
		"external_fixed_ips": [{
			"ip_address": "182.24.4.6",
			"subnet_id": "b930d7f6-ceb7-40a0-8b81-a425dd994ccf"
		}],
		"network_id": "ae34051f-aa6c-4c75-abf5-50dc9ac99ef3"
	}
}
  • network_id — 运营商网络(外部网络)
  • subnet_id — 运营商网络(外部网络)的 Subnet(其 gateway_ip 就是物理外部网关 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。

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

Chain neutron-l3-agent-snat (1 references)
 pkts bytes target     prot opt in     out     source               destination
    2   142 neutron-l3-agent-float-snat  all  --  *      *       0.0.0.0/0            0.0.0.0/0
    2   142 SNAT       all  --  *      qg-426c2bcd-f4  0.0.0.0/0            0.0.0.0/0            to:172.18.22.210
    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 会自动创建路由端口,否则都需要用户手动创建。
    在这里插入图片描述
  • 内部网络 Net1 和运营商网络(外部网络)public 通过 Router 直连
    在这里插入图片描述
  • public 有一个端口连接到 Router,类型为 network:router_gateway,IP 为 172.18.22.210(Neutron 外部网关 IP)。
    在这里插入图片描述
  • public 的 Subnet public-subnet 具有物理外部网关 IP 地址 172.18.22.1。

[root@localhost ~]# openstack subnet show public-subnet
+-------------------+--------------------------------------+
| Field             | Value                                |
+-------------------+--------------------------------------+
| allocation_pools  | 172.18.22.201-172.18.22.210          |
| cidr              | 172.18.22.0/24                       |
| created_at        | 2019-02-13T03:41:09Z                 |
| description       |                                      |
| dns_nameservers   |                                      |
| enable_dhcp       | False                                |
| gateway_ip        | 172.18.22.1                          |
| host_routes       |                                      |
| id                | 99749410-a6b0-418b-9ebc-fd060e1a746e |
| ip_version        | 4                                    |
| ipv6_address_mode | None                                 |
| ipv6_ra_mode      | None                                 |
| location          | None                                 |
| name              | public-subnet                        |
| network_id        | 282e146e-6948-436f-992c-f2d50588e357 |
| project_id        | 031cec3e2df143259d302aa1993fd410     |
| revision_number   | 0                                    |
| segment_id        | None                                 |
| service_types     |                                      |
| subnetpool_id     | None                                 |
| tags              |                                      |
| updated_at        | 2019-02-13T03:41:09Z                 |
+-------------------+--------------------------------------+
  • Router 具有两个端口,一个连接 Net1,IP 地址是 Net1 的网关 IP 192.168.1.1;另一个连接 public,IP 是 Neutron 外部网关 IP 172.18.22.210。
    在这里插入图片描述
  • Router1 的路由表和网络设备:
[root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f ifconfig
lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

qg-426c2bcd-f4: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.22.210  netmask 255.255.255.0  broadcast 172.18.22.255
        inet6 fe80::f816:3eff:fe0b:c722  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:0b:c7:22  txqueuelen 1000  (Ethernet)
        RX packets 765265  bytes 36064217 (34.3 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 90  bytes 5316 (5.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

qr-cf018461-bc: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.1.1  netmask 255.255.255.0  broadcast 192.168.1.255
        inet6 fe80::f816:3eff:fea5:b2bb  prefixlen 64  scopeid 0x20<link>
        ether fa:16:3e:a5:b2:bb  txqueuelen 1000  (Ethernet)
        RX packets 216  bytes 18796 (18.3 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 139  bytes 13870 (13.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

[root@localhost ~]# ip netns exec qrouter-a1adb970-dba9-49f0-ba4b-4294f0d07f6f route -nne
Kernel IP routing table
Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface
0.0.0.0         172.18.22.1     0.0.0.0         UG        0 0          0 qg-426c2bcd-f4
172.18.22.0     0.0.0.0         255.255.255.0   U         0 0          0 qg-426c2bcd-f4
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(数值类型)属性中。

MariaDB [neutron]> desc routerroutes;
+-------------+-------------+------+-----+---------+-------+
| Field       | Type        | Null | Key | Default | Extra |
+-------------+-------------+------+-----+---------+-------+
| destination | varchar(64) | NO   | PRI | NULL    |       |
| nexthop     | varchar(64) | NO   | PRI | NULL    |       |    # 目的网段( CIDR) 
| router_id   | varchar(36) | NO   | PRI | NULL    |       |    # 下 一 跳 IP(下一个路由器的端口 IP)
+-------------+-------------+------+-----+---------+-------+

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

那么静态路由又是什么路由,为什么要持久化呢?
在这里插入图片描述

+----------------+-------------+--------------------------------------+
| destination    | nexthop     | router_id                            |
+----------------+-------------+--------------------------------------+
| 192.168.1.2/32 | 172.18.22.1 | a1adb970-dba9-49f0-ba4b-4294f0d07f6f |
+----------------+-------------+--------------------------------------+

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

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

Floating IP

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

  • Fixed IP:由租户网络 Subnet 分配,用于虚拟机的内部通讯,创建虚拟机时自动分配。
  • Floating IP:有外部网络分配,用于虚拟机访问公网,需要手动分配。

前文中我们提到过,一般的运营商网络和勾选了 “外部网络” 的运营商网络的主要区别在于后者可以分配 Floating IP,实现的原理就是内部网络与外部网络之间的 Router(qroute-XXX)的 NAT(SNAT&DNAT)功能。
在这里插入图片描述
上图可见,外部网络 public 成为了一个 Floating IP 地址池。创建一个 Floating IP 会相应的创建一条数据库记录:

MariaDB [neutron]> select * from floatingips\G;
*************************** 1. row ***************************
          project_id: 031cec3e2df143259d302aa1993fd410
                  id: 67272b76-493a-4a60-b63a-22e5aefebfc0
 floating_ip_address: 172.18.22.204
 floating_network_id: 282e146e-6948-436f-992c-f2d50588e357
    floating_port_id: 04aceef8-a3b2-46e4-a815-3104a2031ed7
       fixed_port_id: NULL
    fixed_ip_address: NULL
           router_id: NULL
last_known_router_id: NULL
              status: DOWN
    standard_attr_id: 82
1 row in set (0.01 sec)

在这里插入图片描述
在这里插入图片描述
为虚拟机绑定这个 Floating IP 之后,数据库记录会被更改:

MariaDB [neutron]> select * from floatingips\G;
*************************** 1. row ***************************
          project_id: 031cec3e2df143259d302aa1993fd410
                  id: 67272b76-493a-4a60-b63a-22e5aefebfc0
 floating_ip_address: 172.18.22.204
 floating_network_id: 282e146e-6948-436f-992c-f2d50588e357
    floating_port_id: 04aceef8-a3b2-46e4-a815-3104a2031ed7
       fixed_port_id: 07995f4e-b6b2-493f-9ce5-b1d945a13807
    fixed_ip_address: 192.168.1.27
           router_id: a1adb970-dba9-49f0-ba4b-4294f0d07f6f
last_known_router_id: NULL
              status: ACTIVE
    standard_attr_id: 82
1 row in set (0.00 sec)

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

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

# 到目的 IP 地址 172.18.22.204 的数据包 DNAT 至 192.168.1.27
Chain neutron-l3-agent-PREROUTING (1 references)
 pkts bytes target     prot opt in     out     source               destination
    ...
    0     0 DNAT       all  --  *      *       0.0.0.0/0            172.18.22.204        to:192.168.1.27
...

# 从源 IP 地址 192.168.1.27 来的数据包 SNAT 至 172.18.22.204
Chain neutron-l3-agent-float-snat (1 references)
 pkts bytes target     prot opt in     out     source               destination
    0     0 SNAT       all  --  *      *       192.168.1.27         0.0.0.0/0            to:172.18.22.204
...

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

转载请注明作者:JmilkFan 范桂飓