一个 OpenStack 访问请求在各组件之间的调用过程

阅读数:14374 2014 年 10 月 31 日

OpenStack 各个组件之间的关系

OpenStack 是一整套资源管理软件的集合,也是当前最热的开源虚拟化管理软件之一,有一个全球 139 个国家将近两万开发者参与的开源社区(www.openstack.org)作为支持。OpenStack 项目的目的是快速建设一个稳定可靠的公有云或私有云系统。整个项目涵盖了计算,存储,网络以及前端展现等关于云管理的全部方面,包含了众多子项目,其中主要的子项目有:

  • OpenStack Compute (code-name Nova) 计算服务
  • OpenStack Networking (code-name Neutron) 网络服务
  • OpenStack Object Storage (code-name Swift) 对象存储服务
  • OpenStack Block Storage (code-name Cinder) 块设备存储服务
  • OpenStack Identity (code-name Keystone) 认证服务
  • OpenStack Image Service (code-name Glance) 镜像文件服务
  • OpenStack Dashboard (code-name Horizon) 仪表盘服务
  • OpenStack Telemetry (code-name Ceilometer) 告警服务
  • OpenStack Orchestration (code-name Heat) 流程服务
  • OpenStack Database (code-name Trove) 数据库服务

OpenStack 的各个服务之间通过统一的 REST 风格的 API 调用,实现系统的松耦合。下图是 OpenStack 各个服务之间 API 调用的概览,其中实线代表 client 的 API 调用,虚线代表各个组件之间通过 rpc 调用进行通信。松耦合架构的好处是,各个组件的开发人员可以只关注各自的领域,对各自领域的修改不会影响到其他开发人员。不过从另一方面来讲,这种松耦合的架构也给整个系统的维护带来了一定的困难,运维人员要掌握更多的系统相关的知识去调试出了问题的组件。所以无论对于开发还是维护人员,搞清楚各个组件之间的相互调用关系是怎样的都是非常必要的。

从 nova-client 入手

nova-client 是一个命令行的客户端应用,终端用户可以从 nova-client 发起一个 api 请求到 nova-api,nova-api 服务会转发该请求到相应的组件上。同时,nova-api 支持对 cinder、neutron 的请求转发,也就是你可以在 nova-client 直接向 cinder,neutron 发送请求。

我们可以在调用 nova-client 增加 --debug 选项打印更多的 debug 消息,通过这些 debug 信息可以了解到如果我们需要发起一个完整的业务层面上请求,都需要跟那些服务打交道。

以 boot 一个新实例为例子,以下是执行代码以及 debug 输出:

[tagett@stack-01 devstack]$ nova --debug boot t3 --flavor m1.nano --image  
44c37b90-0ec3-460a-bdf2-bd8bb98c9fdf --nic net-id=b745b2c6-db16-40ab-8ad7-af6da0e5e699

…

REQ: curl -i 'http://cloudcontroller:5000/v2.0/tokens'

REQ: curl -i 'http://cloudcontroller:8774/v2/d7beb7f28e0b4f41901215000339361d/images/44c37b90-0ec3-460a-bdf2-bd8bb98c9fdf'

REQ: curl -i 'http://cloudcontroller:8774/v2/d7beb7f28e0b4f41901215000339361d/flavors/m1.nano'

REQ: curl -i 'http://cloudcontroller:8774/v2/d7beb7f28e0b4f41901215000339361d/servers' -X POST 
-H "Accept: application/json" -H "Content-Type: application/json" -H "User-Agent: 
python-novaclient" -H "X-Auth-Project-Id: admin" -H "X-Auth-Token:
 {SHA1}15d9e554b7456f1043732bb8df72d1521c5f6aa1" -d '{"server":
 {"name": "t3", "imageRef": "44c37b90-0ec3-460a-bdf2-bd8bb98c9fdf", 
"flavorRef": "42", "max_count": 1, "min_count":
 1, "networks": [{"uuid": "b745b2c6-db16-40ab-8ad7-af6da0e5e699"}]}}'

从以上 debug 输出我们可以清楚看到,执行一个 boot 新实例的操作需要发送如下几个 api 请求:

  1. 向 keystone 发送请求,获取租户(d7beb7f28e0b4f41901215000339361d)的认证 token
  2. 通过拿到的 token,向 nova-api 服务发送请求,验证 image 是否存在
  3. 通过拿到的 token,向 nova-api 服务发送请求,验证创建的 favor 是否存在
  4. 请求创健一个新的 instance,需要的元数据信息通过包含在请求 body 中

nova-client 帮我们把需要的全部请求放到一起,而最重要的就是 4。如果用户想自己通过 rest api 直接发送 http 请求的话,可以直接使用 4,当然,前提是先通过调用 keystone 服务得到认证 token。

下面结合代码重点叙述一下 4 的请求数据流动在整个 stack 中的过程。

图 1 创建新实例时的请求在 OpenStack 中各组件之间的调用

上图是一个全局的流程图,图中每个服务是一个单独的进程实例,他们之间通过 rpc 调用(广播或者调用)另一个服务。nova-api 服务是一个 wsgi 服务实例,创建新 instance 的入口代码是在 nova /api/openstack/compute/servers.py,处理函数为:

def create(self, req, body):
    """Creates a new server for a given user."""
…   
(instances, resv_id) = self.compute_api.create(context,...

做一些参数验证之后,调用 compute api 的 create 函数(代码在 nova/compute/api.py 中):

@hooks.add_hook("create_instance")
def create(self, context, instance_type,
...
        return self._create_instance(...

创建 instance 对象实例,_create_instance 会调用 compute_task api 的 build_instances 方法对刚创建的 instances 实例进行构建:

self.compute_task_api.build_instances(context, ...

compute_task api 是一个 nova-conductor 服务的 rpc api 请求,处理代码在 nova/conductor/manager.py 中:

def build_instances(self, context, instances, image, filter_properties,
... 
       hosts = self.scheduler_rpcapi.select_destinations(context,
...
       self.compute_rpcapi.build_and_run_instance(context, ...

它做了两件事情:调用 scheduler 的 rpc api 选择在那些主机上创建新实例,并最终通过 rpc 请求 nova-compute 服务去构建和运行新实例。

处理函数在 nova/compute/manager.py 中:

def build_and_run_instance(self, context, instance, image, request_spec,
                 filter_properties, admin_password=None,
                 injected_files=None, requested_networks=None,
                 security_groups=None, block_device_mapping=None,
                 node=None, limits=None):

最终调用配置文件中配置的 hypervisor 类型进行虚拟机的创建和运行,一个实例就这样构建好了。

以下是上面涉及到的服务的主要功能:

  1. nova-api:接受 http 请求,并响应请求,当然还包括请求信息的验证
  2. nova-conductor:与数据库交互,提高对数据库访问的安全性
  3. nova-scheduler:调度服务,决定最终实例要在哪个服务上创建。迁移,重建等都需要通过这个服务
  4. nova-compute:调用虚拟机管理程序,完成虚拟机的创建和运行以及控制

以上基本包含 nova 项目的全部服务,但一个请求有的时候并不需要经过全部的服务。继续看 shelve 一个实例的过程。

[tagett@stack-01 devstack]$ nova --debug shelve t2

REQ: curl -i 'http://cloudcontroller:5000/v2.0/tokens' …

REQ: curl -i 'http://cloudcontroller:8774/v2/d7beb7f28e0b4f41901215000339361d/servers'…

REQ: curl -i 'http://cloudcontroller:8774/v2/d7beb7f28e0b4f41901215000339361d/servers/r'…

…

REQ: curl -i 'http://cloudcontroller:8774/v2/d7beb7f28e0b4f41901215000339361d/servers/
00be783d-bef5-46b1-bfdc-316618c76e92/action' 
-X POST -H "Accept: application/json" -H "Content-Type: application/json" 
-H "User-Agent: python-novaclient" -H "X-Auth-Project-Id: admin" 
-H "X-Auth-Token: {SHA1}0634ea0ef1c3994e1f496c5d8890d32610cf11e9" 
-d '{"shelve": null}'…
  1. 向 keystone 发送请求,获取租户(d7beb7f28e0b4f41901215000339361d)的认证 token
  2. 通过拿到的 token,向 nova-api 服务发送请求,显示该租户的全部服务实例
  3. 通过拿到的 token,向 nova-api 服务发送请求,查询准备 shelve 的实例 uuid 的详细信息
  4. 请求一个 server 操作 action,执行 shelve 操作 (request body 为‘{“shelve”: null}’)

nova-api 返回 http 202,成功接受请求,转为后台进行异步执行。

RESP: [202] CaseInsensitiveDict({'date': 'Thu, 18 Sep 2014 04:03:09 GMT', 
'content-length': '0', 'content-type': 'text/html; 
charset=UTF-8', 'x-compute-request-id': '
req-4be7dc9a-21da-4050-9310-3ee58ca93569'}) RESP BODY: null

上面4中的 shelve 操作代码在 nova/api/openstack/compute/contrib/shelve.py :

@wsgi.action('shelve')
def _shelve(self, req, id, body):
    """Move an instance into shelved mode."""
    context = req.environ["nova.context"]
    auth_shelve(context)

    instance = self._get_instance(context, id)
    try:
        self.compute_api.shelve(context, instance)
    except exception.InstanceIsLocked as e:
        raise exc.HTTPConflict(explanation=e.format_message())

从代码可以看出,nova-api 服务直接调用了 compute_api,代码位于 nova/compute/api.py

    if not self.is_volume_backed_instance(context, instance):
        name = '%s-shelved' % instance['display_name']
        image_meta = self._create_image(context, instance, name,
                'snapshot')
        image_id = image_meta['id']
        self.compute_rpcapi.shelve_instance(context, instance=instance,
                image_id=image_id)
    else:
        self.compute_rpcapi.shelve_offload_instance(context,
                instance=instance)

comput_api 直接调用 rpc 消息请求,所以,直接将消息发送给了 nova-compute 服务,所以最终各个组件之间的调用关系如下:

结论

本文介绍了 OpenStack 项目的所有组件以及组件之间的调用关系,并从 nova-client 入手,结合代码分析了两个具体实例,从实例的 debug 消息分析得出如果我们需要完成一个完整的业务请求,需要调用那些 api 请求;从代码分析,可以得出 api 调用的大致关系。rpc 请求用于实现一个组件内部的各个服务,如 nova 组件中的 nova-api、nova-compute、nova-conductor、nova-scheduler 等。而不同组件之间的调用则是通过 rest api 请求实现,如 nova 组件的某一服务需要调用 cinder 服务,则是在 nova 组件引入改 cinder 服务的 client api,实现 rest-api 请求。

作者简介

乔立勇,IBM 高级系统软件研发工程师,2011 年加入 IBM 中国 Linux 技术中心,一直从事 Linux 虚拟化(KVM)相关方向的研发工作。曾在IBM develowerworks发表多篇关于虚拟化方面的文章,Linux 与虚拟化实验室社区的编辑。现在是 openstack 开源社区一名代码贡献者(launchpad id 'Eli Qiao')


感谢杨赛对本文的策划和审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。