OpenStack源码学习笔记1
作为已经比较成熟的IAAS开源解决方案,OpenStack已经发布了19个版本,目前稳定版是Stein,并且下一个版本Train也预计在10月发布。可以说,从代码架构角度来说对于初学者来说已经略微复杂,但最核心的组件有以下几个:
- Nova:负责虚拟机相关。
- Glance:负责镜像相关。
- Cinder:负责存储相关。
- Neutron:负责网络相关。
- Keystone:负责鉴权以及服务注册。
大体架构如下图:
预备知识
正式开始学习Openstack源码前,需要一些预备知识,不用特别深入但要知道是做什么的。
虚拟化
虚拟化本身就是一门比较复杂的学问,涉及硬件、操作系统、软件等多个层面知识。这里至少要知道4个名词:
- KVM
- Qemu
- Qemu-KVM
- Libvert
其中KVM在负责对CPU和内存进行虚拟化,Qemu负责对IO进行虚拟化,而Qemu-KVM则是整合了这2者。而Libvert则是提供了一个更加方便的封装,通过Libvertd服务以及virt-*、virsh命令来方便的管理虚拟机。而OpenStack则在此基础上更进一步的封装,通过各种driver插件来管理不同类型的虚拟机。
WSGI
OpenStack的哲学是各个组件通过API以及消息队列建立联系,既然要对外暴露端口,那么其中自然少不了WSGI的存在。
简单来说,WSGI解耦了Web Server和Web Application,可以让开发人员专注于功能实现而不是网络协议的处理上。
Paste Deployment
pastedeploy,这个我也是开始阅读Openstack才学到的新知识,用来建立Server和Application之间的联系。
简单来说就是提供服务发现功能,并且隐藏Application的实现细节的,等开始查看Nova代码时候再详细说明。
通用套路
Openstack的Github主页已经将各个组件拆分,但基本全部遵循着一个相似的结构,比较需要重点关注的文件有3个:
api.py
提供对外访问的接口,可以从这开始入手跟踪各个功能实现。rpcapi.py
封装RPC请求调用,大多数是异步调用。manager.py
各种RPC调用的实现,基本和rpcapi.py
中调用的名称一一对应。
另外需要关注的就是根目录中的setup.cfg
文件,特别是其中的console_scripts
,可以说安装完Openstack后提供的各种命令对应的函数都在这里了,可以说是学习Openstack源码的路标。
此外还有一点,Openstack的目录结构是根据功能划分的,比如Nova中compute目录不一定都是在nova-compute
节点上运行,而是所有和虚拟机创建相关的功能都在这里。
配置加载与路由绑定
这里以stein版的Nova为例,根据架构图,Nova-api是所有虚拟机相关请求的入口,首先看Nova中setup.cfg
文件定义如下:
1 | ……省略…… |
从配置文件可以明显的看出,nova-api对应的文件是nova/cmd/api.py
的main()
函数:
1 | def main(): |
main
函数其实就做了一件事,启动配置文件中设定的WSGI服务,默认情况下配置文件位于/etc/nova/nova.conf
,配置文件中定义了启用哪些服务、Glance、Cinder、Keystone、Libvert、数据库等信息。说到这里简单提一下 oslo_config这个项目,是从Openstack独立出来的专门处理配置文件的基础功能库。
查看WSGIService
的定义,位于nova/service.py
:
1 | class WSGIService(service.Service): |
其中,self.app = self.loader.load_app(name)
就是上面所说的使用Paste Deployment来建立连接的部分,相关代码位于nova/api/wsgi.py
中:
1 | class Loader(object): |
默认情况下,配置文件位于/etc/nova/api-paste.ini
,内容如下:
1 | ############ |
PasteDeploy的配置文件可以包括多个段。每个段包含一个名称和多个键值对。名称由type和name组成,使用冒号分隔。每个键值对占一行,键名和值使用等号分隔。比较常用的type有:
- composite:用来分发请求到其它应用去。其下的键值对use = egg:Paste#urlmap表示使用Paste中的urlmap应用。urlmap是使用路径的前缀来将请求映射到不同的应用去。
- app:基本的WSGI应用,通常的用法是paste.app_factory = <模块名>:<类名>.<类方法>。
- filter-app:过滤器,通过next可以指定下一步交给谁处理,next指定的可以是一个普通的WSGI应用,也可以是另一个过滤器。
- filter:过滤器,与filter-app用法上不同,其他应用中使用filter-with来指定使用此filter。
- pipeline:管道,可以将多个filter和最后一个WSGI应用串联起来。
比如对于metadata
,就直接使用了Paste中的路由,而对于osapi_compute
,则使用nova自己的urlmap_factory
处理。这里以V2版本接口为例,当程序收到请求后转交给了openstack_compute_api_v21_legacy_v2_compatible
,然后经过pipeline定义的各种filter处理后由osapi_compute_app_v21
接手,而这个程序对应的代码就是nova/api/openstack/compute/routes.py
中的APIRouterV21
类的factory
方法,而这个方法本质上就是创建了一个类实例并建立路由和函数之间的映射关系。
这些操作完成后通过使用eventlet创建wsgi.Server
、绑定监听IP和端口,位于nova/wsgi.py
的Server
类中,这里就不贴出代码了。
创建虚拟机
Nova-Api
根据文档,创建虚拟机的行为其实就是向/servers
发送POST请求而已。而上面已经知道在nova/api/openstack/compute/routes.py
中定义了对应的函数是:
1 | ('/servers', { |
而这个server_controller
是位于nova/api/openstack/compute/servers.py
的ServersController
类,其create
方法定义如下:
1 | def create(self, req, body): |
经过一些获取、验证操作后,调用了self.compute_api.create()
方法,这个compute_api
就是nova/compute/api.py
中定义的API
类,create
函数定义如下:
1 |
|
最终,这个函数又调用了self.compute_task_api.schedule_and_build_instances
方法,而这个compute_task_api
就是nova/conductor/api.py
中定义的ComputeTaskAPI
类,方法定义如下:
1 | def schedule_and_build_instances(self, context, build_requests, |
这个函数很简单,就是调用了nova/conductor/rpcapi.py
中ComputeTaskAPI
类的schedule_and_build_instances
方法:
1 | def schedule_and_build_instances(self, context, build_requests, |
cast
代表这个是一个异步调用,schedule_and_build_instances
是调用的方法名。虽然目录从api到compute到conductor`,但上面的所有过程都是在nova-api管控下的。一直到发出这个异步请求为止,nova-api阶段结束,返回响应,虚拟机状态为building。
Nova-Conductor-1
上面说过,manager.py
是对应rpc调用方法的实现,所以在nova/conductor/manager.py
中找到函数定义如下:
1 | def schedule_and_build_instances(self, context, build_requests, |
首先这函数调用_schedule_instances
方法,这个方法中又调用了select_destinations
,而self.query_client
其实是一个SchedulerQueryClient
类实例,位于nova/scheduler/query.py
中,代码很耿直:
1 | def select_destinations(self, context, spec_obj, instance_uuids, |
一看到rpcapi,果不其然在nova/scheduler/rpcapi.py
中找到对应的函数:
1 | def select_destinations(self, ctxt, spec_obj, instance_uuids, |
注意这里使用的call
而不是cast
,所以是一个同步调用,此时nova-conductor会堵塞等待直到nova-scheduler返回。
Nova-Scheduler
根据套路,在nova/scheduler/manager.py
中找到函数定义如下:
1 |
|
这个函数本质上又是一层封装,最终调用的是nova-api.conf
文件中定义的scheduler.driver
,这里以自带的FilterScheduler
为例,位于nova/scheduler/filter_scheduler.py
,入口函数是_schedule
,在这里首先获取全部的host,然后经过_get_sorted_hosts
函数的筛选和权重计算,返回满足条件的主机给nova-conductor。
Nova-Conductor-2
回到nova/conductor/manager.py
的schedule_and_build_instances
方法:
1 | def schedule_and_build_instances(self, context, build_requests, |
拿到主机列表后,由于可能同时创建多台主机,所以使用for循环,进行了一些数据库操作后,进行rpc调用,位于nova/compute/rpcapi.py
:
1 | def build_and_run_instance(self, ctxt, instance, host, image, request_spec, |
Nova-Compute
套路都熟悉了,直接看nova/compute/manager.py
吧,其实最核心函数是_build_and_run_instance
,这里进行镜像、网络资源的准备以及各种验证、状态修改、发送通知等。最后调用对应driver的spawn方法,这里以libvert为例,对应文件是nova/virt/libvirt/driver.py
1 | def spawn(self, context, instance, image_meta, injected_files, |
在这里拉取镜像创建根目录,生成XML(默认在/etc/libvert/qemu目录),定义网络和domain并启动,然后虚拟机状态为running。到此为止,创建虚拟机完成。