ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenStack源码阅读指南:以Nova为主线掌握服务骨架与RPC调试

OpenStack源码阅读指南:以Nova为主线掌握服务骨架与RPC调试 简介OpenStack技术源码模块解读.docx 是一份面向云计算平台开发者和运维人员的解决方案型精简笔记旨在帮助读者从 Nova 项目入手理解 OpenStack 各服务模块化、松耦合的整体架构。资源包仅含 1 个 docx 文档大小约 759KB体量轻、内容集中可作为阅读博文后的对照手册也适合快速检索关键结论。目前已有 105 人学习使用。文档先概览 Keystone、Glance、Nova、Cinder、Neutron 等核心服务及其依赖关系再以 Nova 为重点围绕 setup.cfg 中的服务入口、console_scripts 启动脚本、cmd 与 compute 等目录功能、api/rpcapi/manager 三层交互进行源码级拆解同时介绍搭建 all-in-one 开发环境、使用 pdb 设置断点跟踪创建虚拟机流程的调试方法。最后以 Cinder、Glance、Neutron 为例说明它们与 Nova 的调用关系帮助读者把从一个组件获得的阅读经验迁移到 OpenStack 其他项目减少在大规模代码库中摸索的盲目感适合作为系统学习 OpenStack 源码的起点资料。1. OpenStack源码怎么读先抓住Nova这条主线OpenStack组件多到让人无从下口但绝大多数服务的骨架都遵循同一套模式。以Nova为例从setup.cfg的console_scripts找出服务入口再沿着api.py、rpcapi.py、manager.py三个模块把一次RPC调用追完你基本就掌握了所有OpenStack服务的阅读方法。这里用创建虚拟机这条完整链路把nova-api、nova-conductor、nova-scheduler、nova-compute之间的协作拆开讲清楚断点打在哪个文件、参数走到哪一层、调度结果怎么传。适合刚把OpenStack环境跑起来、准备深入源码的人也适合被线上诡异调用链折磨的运维。与其跟着教程装完环境就结束不如拿Nova当标本做一次源码解剖后面的Cinder、Neutron代码结构会给你一种似曾相识的感觉这才是解决OpenStack二次开发问题的根本方案。2. 从setup.cfg到console_scripts定位Nova的服务入口2.1 setuptools和console_scripts是源码地图OpenStack所有项目都是标准Python项目用setuptools做包管理和分发。想快速知道一个项目包含哪些服务不要漫无目的翻目录直接看根目录下的setup.cfg。其中console_scripts这一段的每一条都对应一个系统里的可执行命令和一个入口函数。比如Nova安装后会生成21个可执行程序每个都对应一个nova/cmd/xxx.py模块下的main函数。[entry_points] console_scripts nova-api nova.cmd.api:main nova-conductor nova.cmd.conductor:main nova-scheduler nova.cmd.scheduler:main nova-compute nova.cmd.compute:main上面这段是典型的Nova入口声明具体版本会略有差异。以nova-compute为例它对应的源码就是nova/cmd/compute.py打开这个文件找到main函数就是整个计算节点服务的启动入口。我一般会先在main里打个断点看服务启动时到底加载了哪些配置、注册了哪些信号处理。这一步比盯日志直观得多。顺便说一句很多手把手教你搭建openstack云平台的教程只讲到服务起来而setup.cfg这一段才是真正值得抄的作业。2.2 用ctags和vim搭一个能跳转的源码环境在虚拟机里读源码没有图形界面pycharm跑不起来的情况很常见。我用的是vim加ctags组合。先安装ctags在Nova源码根目录生成索引sudo apt-get install -y exuberant-ctags cd /opt/stack/nova ctags -R .然后在vim中打开任意文件把光标移到类名或函数名上按Ctrl-]就能跳转按Ctrl-o回退。如果你习惯输入补全可以再配个jedi-vim。这个组合跟踪跨模块调用时非常顺手尤其是从rpcapi.py跳到manager.py这样的大跳转比grep按关键字搜快很多。源码阅读工具不需要多花哨能跳转、能搜索、能折叠就已经解决80%的效率问题。2.3 用命令行而不是systemd启动服务断点才能生效很多朋友从openstack安装教程里学到的启停方式都是systemctl start nova-api。这样一切正常但打进的Python断点永远不会停下来因为systemd把stdin吞掉了pdb交互无门。想要用pdb跟踪必须手动在命令行前台执行。以nova-api为例su -s /bin/sh nova -c nova-api --config-file /etc/nova/nova.conf在你要停的代码位置先插入断点import pdb pdb.set_trace()之后调用创建虚拟机API请求到达这个位置时命令行就会弹出(Pdb)提示符。此时可以用s单步进入、n单步跳过、print(变量名)直接看参数内容。这套方法对所有OpenStack服务通用是源码跟踪的基本功。提示如果使用DevStack安装的环境服务默认由systemd管理调试前先设置USE_SYSTEMD0重跑stack.sh禁用systemd否则手动启动会与systemd抢占端口。2.4 服务入口与承担职责速查下表列出了Nova核心服务常见的入口模块与主要职责方便你在看某一层代码时快速定位。服务命令入口模块职责nova-apinova/cmd/api.py处理RESTful API请求做参数校验与policy校验nova-conductornova/cmd/conductor.py数据库访问代理同时承担部分跨服务编排逻辑nova-schedulernova/cmd/scheduler.py执行过滤与权重计算选出目标计算节点nova-computenova/cmd/compute.py调用hypervisor driver创建、销毁虚拟机这里有一个容易混淆的点目录结构和组件并不是严格对应的。比如nova/compute/目录下的代码不一定都在nova-compute进程里跑nova/compute/api.py就是给nova-api调用的封装库。理解这一点之后再往下看RPC链路思路会清晰很多。3. 拆开api.py、rpcapi.py、manager.py的三层通信骨架3.1 三个核心模块各干各的任何一个OpenStack服务目录只要与业务逻辑相关基本都会出现api.py、rpcapi.py、manager.py这三件套。它们的分工很明确api.py本服务的对外封装层通常被别的服务调用。比如nova/compute/api.py里的create方法实际是nova-api的controller调用的它本身不处理RPC。rpcapi.pyRPC调用的客户端封装把方法名和参数打包成消息通过消息队列发送给目标服务的manager.py。manager.pyRPC服务端实现处理rpcapi.py发来的请求方法名和rpcapi.py里的调用名基本一一对应。这三者的关系可以简单理解为api.py是给别人用的SDKrpcapi.py是发消息的入口manager.py是收消息干活的地方。如果你在某处看到compute_rpcapi.build_and_run_instance(...)那就可以确定下一步要去nova/compute/rpcapi.py找同名方法再顺着它跳转到nova/compute/manager.py里对应的接收方法。这种推断方式在整个OpenStack源码库里都成立是比任何架构图都可靠的路标。3.2 同步call与异步cast差在哪儿创建虚拟机过程中nova-api第一次向conductor发RPC使用的是cast而conductor向scheduler发RPC使用的是call。这俩的区别直接决定了调用线程是否要阻塞等待。看nova/conductor/rpcapi.py中的build_instances方法典型代码是这样def build_instances(self, context, instances, image_meta, ...): version 5.0 if not self.client.can_send_version(version): version 4.0 return self.client.cast( context, self.make_msg(build_instances, instancesinstances, image_metaimage_meta, ...), versionversion)cast表示把消息发出去就立即返回不等待远端执行结果所以nova-api进程很快就能响应客户端“虚拟机创建中”。而conductor的select_destinations用的是callreturn self.client.call( context, self.make_msg(select_destinations, ...), versionversion)call是同步远程调用conductor会一直阻塞等scheduler把宿主机列表算出来。一个任务需要拿到返回值才能继续时用call不需要返回值只是通知对方干活时用cast。这个判断逻辑在阅读任何OpenStack RPC代码时都成立。如果你在日志里看到CALL和CAST关键字也能立刻明白当前这一步是否会卡住调用方。3.3 追踪build_instances的现场实操为了把上面这套东西串起来我们实际追踪一次。先确定要从哪个任务入手我建议选“创建一台虚拟机”因为它会串起api、conductor、scheduler、compute四个进程。在nova/api/openstack/compute/servers.py的create方法加断点注意这里create是server实例的创建不是数据库的create。启动一个终端运行nova-api再开一个终端分别运行nova-conductor和nova-scheduler如果allinone环境直接手动运行三个进程即可。用openstack命令触发创建openstack server create --flavor m1.small --image cirros.vm test-vm当nova-api的断点命中后你会看到请求正在做参数校验。接着使用p打印body变量里的flavor和image信息再一直n到调用compute_api.create的位置。此时不要进入函数内部可以直接回车跳过你会发现执行权很快回到了服务端响应因为后续逻辑已经被cast出去。之后再切到conductor的终端窗口pdb已经停在那里等你了。3.4 数据库操作为什么也走RPC一个容易忽略的细节是nova-compute这类节点本来可以直接访问数据库但在生产环境下会配置use_localFalse让所有数据库更新都通过conductor转发。比如instance.save()最终会变成一次发给conductor的RPC调用而不是本地直接执行SQL。这样设计是为了让计算节点不需要保存数据库连接串和密码提高安全性。你在源码里看到object.save()时要记得它的背后可能藏着一次跨进程RPC。理解这层代理关系后再去看conductor的manager.py里大量_object_*方法就不奇怪了。4. 手把手走一遍创建虚拟机从servers.py到libvirt的spawn4.1 nova-api先做校验和状态落地创建虚拟机的第一个入口是nova/api/openstack/compute/servers.py的create方法。这个方法会做四件事解析请求体、检查项目配额和policy权限、确认flavor和镜像存在、创建数据库中的instance记录并写入building状态。代码走完后调用nova/compute/api.py的create方法。compute/api.py的create里不仅有业务校验还会通过compute_task_api.build_instances把真正的创建任务往下传。这里的compute_task_api就是conductor的api.py模块。你可能注意到了nova-api进程并没有直接调用conductor的方法而是又包了一层。这层封装的意义在于nova-api不关心conductor具体怎么实现只需要拿到一个本地对象调用其方法即可。这也解释了为什么OpenStack服务之间的依赖看起来绕来绕去其实每一层都在做职责收口。4.2 conductor把调度请求同步抛给schedulerconductor收到build_instances的RPC后先进入nova/conductor/manager.py的build_instances方法。它首先调用_schedule_instances该方法内部通过scheduler_client.select_destinations去问nova-scheduler要宿主机列表。scheduler_client位于nova/scheduler/client/__init__.py它不像其他服务有api.py而是直接封装了一个SchedulerQueryClient。这个client会调用scheduler_rpcapi.select_destinations最终以call同步等待scheduler返回。在scheduler这一侧nova/scheduler/manager.py的select_destinations会调用驱动模块。默认驱动是filter_scheduler对应filter_scheduler.py。它的工作分三步先通过host_manager收集所有计算节点的资源信息然后依次跑filters把不满足条件的节点剔除最后对剩余节点用weighers计算权值选出最合适的一组返回。关键是这两类配置决定了调度行为[DEFAULT] scheduler_driver filter_scheduler [filter_scheduler] enabled_filters RetryFilter, AvailabilityZoneFilter, RamFilter, DiskFilter, ComputeCapabilitiesFilter, ImagePropertiesFilter weight_classes nova.scheduler.weights.ram.RAMWeigherenabled_filters里每一项都会在调度循环中被执行顺序就是过滤顺序。RAMWeigher决定内存越大权重越高最终选择权重值最大的节点。你可以把这里当成调优入口比如想限制某类CPU就加一个ComputeFilter或自定义filter类。值得注意的是过滤和加权是两回事过滤是硬条件不满足直接淘汰加权是软偏好只影响选中的优先级。4.3 scheduler返回conductor再异步通知computescheduler返回宿主机的host列表后控制权回到conductor的build_instances方法。由于可能同时创建多台虚拟机conductor会遍历host列表对每个host循环调用compute_rpcapi.build_and_run_instance。这里使用的是cast异步调用conductor不用等nova-compute把虚拟机真正创建完成直接把消息发给消息队列本轮任务结束。紧接着就是nova-compute登场。它在nova/compute/manager.py里找到build_and_run_instance方法这个方法先做资源预留、网络准备、块设备映射等一系列前置工作然后调用driver.spawn。这里的driver就是虚拟化驱动的抽象层nova/virt/driver.py定义了统一接口而nova/virt/libvirt/driver.py是大家最常碰到的实现。spawn方法的内部逻辑可以用下面几行伪代码概括# nova/virt/libvirt/driver.py 简化示意 def spawn(self, context, instance, image_meta, ...): disk_info self._create_image(context, instance, ...) # 拉取镜像并准备根磁盘 xml self._get_guest_xml(context, instance, disk_info, ...) # 生成libvirt domain XML self._host.write_instance_config(xml) # 定义domain self._host.get_guest(instance).launch() # 启动domain每一行都不是简单的一句调用拉镜像需要走Glance生成XML要拼接CPU拓扑、内存、磁盘总线、虚拟网卡等信息启动domain会返回vif和metadata。真正要深入研究时建议在spawn入口和launch调用后各打一个断点对比前后instance的状态迁移。你会发现building到active的切换就发生在launch成功后这比看日志里的状态变化直观得多。4.4 调试时最容易翻车的三个点第一断点没生效。先确认服务是不是被systemd启动的必须手动前台执行。第二改了代码不生效。Python的__pycache__会缓存旧字节码改动nova源码后建议先删pyc文件再启动find /opt/stack/nova -name *.pyc -delete第三RPC调用超时。同步call如果对端scheduler卡住conductor会一直等。此时去scheduler日志里看有没有异常而不是在conductor上反复重启。理解了这三条基本上就能顺利跟踪一次完整的创建流程。如果再遇到挂卷或创建网络失败处理方法完全一样先找到对应的rpcapi调用再跳转到manager方法最后到driver实现里打点。5. 把openstack-workflow序列图变成自己的源码排障手册阅读OpenStack源码时最忌讳的是按进程从头追到尾。进程初始化、消息循环、心跳线程这些内容非常绕而且与业务逻辑无关。更好的做法是以“任务”为单位画一张属于自己的调用链序列图。如果你读过int32bit的openstack-workflow项目会发现他把创建虚拟机、挂载卷、创建网络这些典型流程都画成了序列图。你不需要完整复刻只要用自己的话整理成一份带断点位置的排障手册效果立竿见影。我建议用表格维护每条链路的断点清单类似于这样阶段进程断点文件断点方法关注变量apinova-apiservers.pycreatebody, contextconductornova-conductormanager.pybuild_instancesinstances, request_specschedulernova-schedulerfilter_scheduler.pyselect_destinationshosts, weightscomputenova-computelibvirt/driver.pyspawninstance, guest_xml每次排障前先查表定位到对应阶段再启动对应服务并打上断点。这样做的好处是不用每次都从API入口一路单步走几十分钟直接跳到最可疑的那一段。另一个实用技巧是给pdb设置“自动跟踪目标”。比如你想观察build_and_run_instance的入参又不想每次停稳后手动打印可以在pdb命令行里用display instance.uuid之后每次单步执行只要instance.uuid发生变化pdb就会自动打印新值。这个功能对跟踪状态迁移特别有效能帮你快速发现instance从building切到active的具体代码行。最后建议把调试过程中看到的RPC调用方法名也记录到手册对应行。比如nova-conductor那行可以补上build_instances(cast)和select_destinations(call)这样你看到日志里的CALL和CAST时能直接对应到源码行为。手册越细后面排查线上问题时定位越快几分钟就能锁定是调度层的问题还是hypervisor层的问题。本文还有配套的精品资源点击获取
返回列表