在上一篇博文分析完 Neutron Server 的启动过程后,按原先安排应该是接着分析 L2、L3 等服务(agent)的启动过程,但发现它们的启动过程相对于 Neutron Server 来说要简单的多,也比较类似,再过多阐述有点重复的嫌疑;另外也是为了从实践角度出发,对虚拟机创建过程的情景做一个分析,将 Nova、Glance、Neutron 及 Cinder 模块串联起来,其中夹杂对一些关键服务(如 L2、L3)的分析,也不失为一个好的办法。所以,接下来,我们将从虚拟机创建情境入手,一步步来分析涉及的知识点。今天我们先来看下创建虚拟机会用到的网络及子网。
网络及子网在 Neutron 中都属于 Core Resouce,它们的创建入口在 Core Plugin(ML2)中(具体路由分析见上一篇博文 Neutron Server 的启动过程)。还有一点需要特别注意的是,Core Plugin 中的 Resource CRUD 操作一般都会涉及与 Type driver、Mechanism driver、Extension driver 及 RPC 的交互。
接下来对网络及子网的创建也会重点从这 4 点出发来分析。
开始之前,我们先说一下本文一些前提条件:
- L2 Agent 采用的是 Linux Bridge Agent
- 新建的(租户)网络的类型为 VLAN(Enable 了 Admin State)
- 新建(该网络的)子网开启 DHCP,手动设置 CIDR(不从 subnet pool 分配)
- 其余都采用默认值(Extension driver 默认开启了 port security)
创建网络
在 ML2 中,创建网络(create_network 函数)算是最简单的,因为它最主要的事情就是把网络相关信息保存到 Neutron 数据库
当中。
下边我们可以从 5 个部分来分析一下:
- 自身(网络):在 networks 表添加一条记录,记录自己的信息
- Type driver:在 networksegments 表添加一条记录,记录该网络的
类型信息
、segmentation id
- Mechanism driver: 所调用的 create_network_precommit 和 create_network_postcommit 均是直接返回(pass)
- Extension driver: 在 networksecuritybindings 表添加一条记录,记录是否开始 port security(默认开启)
- RPC:ML2 中初始化的 DhcpAgentNotifyAPI(参考上文)订阅(subscribe)了网络创建的事件(AFTER_CREATE,详细参考上文Callback System),所以在创建网络结束后会轮询调用到 DhcpAgentNotifyAPI 中的 _native_event_send_dhcp_notification 函数,但最后还是直接返回而没有其他操作
具体创建流程可以参考博文 OpenStack Neutron ML2 Deep Dive 中的一张示意图:
创建子网
跟创建网络相比,创建子网就要复杂的多了。除了涉及的 Type/Mechanism/Extension 3 个部分基本都是直接返回(pass)外,其余涉及的需要详述的还有:
- IPAM
- Callback System
- Network 调度
- (DHCP)Port 创建
- 网络设备(vlan、tap、veth、bridge)
- Dnsmasq(配置文件及开启进程)
- RPC
下边我们一一详述。
IPAM
IPAM 是 IP Address Management 的缩写,用于 IP/Subnet 的分配(Allocation
)。其实,官方文档对 IPAM 的描述是 Pluggable
,也就是 IPAM 是可插拔的,说白了,IPAM 是要做成支持多个驱动的方式。从配置文件就可以看出来(cfg.CONF.ipam_driver 值默认为 “internal”):
1 | # setup.cfg |
通过这种方式,只要符合接口标准,要写一个我们自己的 IP/Subnet 分配策略驱动就很方便了。可扩展性强。
接下来,我们按官方文档对 IPAM 进行一个简单介绍(具体类名有些跟代码中的不一致,看图就可以了)。
代码结构
跟很多驱动式代码一样,IPAM 也有一个抽象类(IPAMDriver),定义接口;然后有一个实现抽象类接口的类(NeutronIPAM)。最后 Neutron 通过调用该 NeutronIPAM 来实现对 IP/Subnet 管理。请见下图:
Subnet(Allocation Pools)分配
由下图可见,Subnet 的分配是要先经过 IPAM 处理,最后才将其返回的 subnet 信息(只取其中的 allocation pools
)和用户传入的参数一起写入到 subnet 自身的数据库表(subnets、ipallocationpools)中:
在具体代码中,往数据库写入 Subnet 信息最终会调用到 allocate_subnet 函数:
1 | # neutron/db/ipam_pluggable_backend.py |
到这里,我们就很清楚,IPAM 分配 subnet 其实最重要的就是确定 subnet 的可用地址范围(即 allocation pools),而对于其他信息,除了 subnet 自身信息,它都是不处理的(如 dns_nameserver、host_routes)。
IP 分配
由下图可见,IP 的分配是要先经过 IPAM 处理,最后才将其返回的 ip 信息(及 port 信息,稍后分析)写到 subnet 自身的数据库表(ipallocations)中:
Network 调度及网络设备创建
在数据库中创建完 subnet 相关的条目其实只是做了一个记录,并没有实际在宿主机中体现出来。接下来我们重点就在于分析后者。
事件订阅过程说明
我们再来看一下 subnet 创建的代码:
1 | # neutron/plugins/ml2/plugin.py |
而我们在上一篇博文Neutron Server 的启动过程有提到在初始化 ML2 的过程当中会初始化 RPC Notifiers:
1 | # neutron/plugins/ml2/plugin.py |
而在 DhcpAgentNotifyAPI 的初始化中会订阅几个 Core Resources 的事件,下边我们只看一下订阅 AFTER_CREATE 事件的代码:
1 | # neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py |
也就是说,在 Ml2Plugin::create_subnet 函数中的 registry.notify 动作会最终调用到 DhcpAgentNotifyAPI::_native_event_send_dhcp_notification 函数。
下边我们从该函数入手:
1 | # neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py |
其中 _notify_agents
函数定义如下:
1 | # neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py |
这里最终调用的 _notify_agents 函数
的重点在Network 调度(如果存在多个 DHCP Agent,确定该网络应该由哪“几”个 DHCP Agent 来服务)
(上述代码注释中的第 3 步)。下边我们也就从这步着手。
注:对于 ovo_notifier,在其初始化过程中也会订阅这些 Core Resources 的事件,不过这里不是我们关注的重点,大家简单看下订阅的代码就可以了:
1 | # neutron/plugins/ml2/ovo_rpc.py |
Network 调度
而我们在上一篇博文Neutron Server 的启动过程有提到在初始化 ML2 的过程当中会初始化 DHCP(的 network_scheduler
):
1 | # neutron/plugins/ml2/plugin.py |
而在 _notify_agents 函数
中,我们先来看 network 调度部分:
1 | # neutron/api/rpc/agentnotifiers/dhcp_rpc_agent_api.py |
我们先来看 _schedule_network 函数中的第 1 步。
绑定 network 到 DHCP Agents
一图胜千言:
RPC 通知 DHCP Agent “network create” 信息
注:因为我们现在所在还是 Neutron Server 的进程,所以要调用 DHCP Agent(进程),需要通过 RPC。
我们先来看一下 RPC 调用概览图:
下边我们将从上图提到的 DhcpAgent::enable_dhcp_helper
函数的两个点出发来分析。
注:到这里,我们已经进入 DHCP Agent 进程,与 Neutron Server 的通信就要通过 RPC 了。
RPC 获取 Core Plugin 的 network 信息
我们现在在 DhcpAgent::enable_dhcp_helper
函数的第 1 步:
1 | # neutron/agent/dhcp/agent.py |
在上一篇博文 Neutron Server 的启动过程中我们有提到在启动 Neutron Server 服务的过程当中,在最终启动服务的时候启动 Core Plugin(ML2) 的 RPC Listener:
1 | # neutron/plugins/ml2/plugin.py |
所以,在 DhcpAgent::enable_dhcp_helper
函数通过 RPC 调用 Core Plugin RPC 服务获取 network 信息最终会调用到 dhcp_rpc.DhcpRpcCallback()
这个 Endpoint。我们现在来看一下具体调用过程:
1. RPC Client 端
1 | # neutron/agent/dhcp/agent.py |
而 get_network_info
函数定义如下:
1 | # neutron/agent/dhcp/agent.py |
2. RPC Server(Endpoint) 端
1 | # neutron/api/rpc/handlers/dhcp_rpc.py |
DHCP 服务建立过程
DhcpAgent::enable_dhcp_helper
函数的第 2 步就是要为 network 配置 DHCP 了。这部分特别特别重要!代码如下:
1 | # neutron/agent/dhcp/agent.py |
而其中的 call_driver 函数定义如下:
1 | # neutron/agent/dhcp/agent.py |
对于 DhcpAgent::call_driver
函数我们也要分两步来看。
1. 初始化 neutron.agent.linux.dhcp::Dnsmasq
1 | # neutron/agent/linux/dhcp.py |
要特别注意上边初始化的 self.device_manager
。
2. 调用 neutron.agent.linux.dhcp::Dnsmasq 的 enable 函数
这一步是重中之重,该函数主要做了一下事情:
1)初始化 DHCP 相关设备
A)创建一个 DHCP Port(这部分涉及到 Port 的创建,放到本文最后分析)
B)添加 DHCP namespace 及在该 namespace 添加 lo 网卡
C)在宿主机 root namespace 和 DHCP namespace 之间添加一个 Veth Pair;该 Veth 两端网卡分别是 tapXXX 及 ns-XXX(XXX 表示 Port.id 的前 11 位)
D)Metadata(这里暂未开启,后续博文再分析) 及默认路由设置
2)初始化 DHCP 相关配置文件及启动 Dnsmasq 服务
A)生成 DHCP 相关配置文件:leases、host、addn_hosts、opts 文件
B)启动 Dnsmasq 进程
我们会用一个 UML 图来做说明:
最后启动起来的 Dnsmasq 的进程(样例)我们可以用 ps -elf 命令看一下:
1 | stack@ubuntu-controller:~$ ps -elf | grep dnsmasq |
可以看到,该 Dnsmasq 进程正在监听的 interface 正是 DHCP namespace 中的 interface ns-XXX。
Bridge 初始化
在前面,DHCP 相关的 namespace、ns-XXX、tapXXX 设备已经建立完毕,但 DHCP 现在还不能正常工作,因为还差一个(Linux)Bridge。
关于 Brige 创建的代码隐藏的比较深。如果我们不考虑太多细节的话,Bridge 创建的代码流程如下:
**1. **Linux Brige Agent 启动之后会进入一个死循环,不断扫描系统中设备的变化,例如刚刚新建的 DHCP tapXXX 设备:
1 | # neutron/plugins/ml2/drivers/agent/_common_agent.py |
**2. **根据是否新增或更新设备,进行下一步操作。此时是新增了 tapXXX 设备:
1 | # neutron/plugins/ml2/drivers/agent/_common_agent.py |
**3. **RPC call 到 ML2 获取设备信息
1 | # neutron/plugins/ml2/drivers/agent/_common_agent.py |
**4. **创建 Vlan interface/Bridge,并添加 Vlan interface/tapXXX 到 Bridge
1 | # neutron/plugins/ml2/drivers/agent/_common_agent.py |
接下来的 LinuxBridgeManager::plug_interface 函数调用就进入到 neutron.plugins.ml2.drivers.linuxbridge.agent.linuxbridge_neutron_agent::LinuxBridgeManager 这个类了,详细操作过程请见下图:
创建(DHCP)Port
在前边小节“2.2.2.2.2 DHCP 服务建立过程”部分提到的 neutron.agent.linux.dhcp::Dnsmasq::enable 函数首先做的就是通过 RPC Call 调用 Core Plugin 的 RPC Endpoint neutron.api.rpc.handlers.dhcp_rpc::DhcpRpcCallback
的 create_dhcp_port
函数来创建一个 DHCP Port。
这里之所以将 DHCP Port 的创建过程单独拿出来分析,主要是因为 DHCP Port 跟一般的 Port 本质上只在于 device_owner 不一样,所以将其单独抽取出来我们可以借此分析一下 Port 的创建过程;另外,Port 本身是一种 Core Resource(同 Network、Subnet),所以有必要单独拿出来说。
我们先来看一下 DhcpRpcCallback::create_dhcp_port
函数:
1 | # neutron/api/rpc/handlers/dhcp_rpc.py |
最终调用的还是 Ml2Plugin::create_port
函数定义:
1 | # neutron/plugins/ml2/plugin.py |
下边我们就按代码注释中的 3 步来对该函数进行分析。
1. 数据库条目创建Ml2Plugin::create_port
函数会在一下这些数据表中写入条目:
- 在 ports 表添加新 port 的信息条目(id 及 mac 地址在自动生成,除非有作为创建参数传入)
- 利用
IPAM
(这部分我们在前文 IPAM 处已经有介绍) 为该 port 分配 IP 地址(除非有作为创建参数传入),并在 ipamallocations 表添加该 IP 信息条目,之后再在 ipallocations 表该 IP 信息条目(跟 ipamallocations 表的稍有不同) - 在 portsecuritybindings 表添加一个条目
- 在 ml2_port_bindings 表添加一个条目
2. 调用注册事件回调函数
在前边 “2.2.1 事件订阅过程说明”中,我们提到了类 dhcp_rpc_agent_api::DhcpAgentNotifyAPI
在初始化的时候会订阅 Core Resources 的 AFTER_CREATE 事件,所以,当在数据库中创建完 Port 及相关信息条目后,就会调用注册的 DhcpAgentNotifyAPI::_native_event_send_dhcp_notification
函数。这次因为 Network 已经调度过了,所以该函数此次没做什么实质性的东西。
注:除了 DhcpAgentNotifyAPI
和 OVOServerRpcInterface
类在初始化的时候会订阅 port 的 AFTER_CREATE 事件之外,Service Plugin neutron.services.l3_router.l3_router_plugin::L3RouterPlugin
在初始化的时候也会订阅该事件(关于 L3 Agent 我们这里暂时不分析,放到后续博文当中):
1 | # neutron/services/l3_router/l3_router_plugin.py |
3. Security Group 更新
self.notify_security_groups_member_updated(context, result) 最终调用的其实是 SecurityGroupAgentRpcCallbackMixin::security_groups_provider_updated
函数,但该函数是一个空(NOOP)函数(可以看一下函数的注释):
1 | class SecurityGroupAgentRpcCallbackMixin(object): |