尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

OpenStack源码解读:从Nova入手掌握核心架构与调试技巧

OpenStack源码解读:从Nova入手掌握核心架构与调试技巧 简介这份《OpenStack技术源码模块解读》面向云计算开发与运维人员、源码阅读爱好者以及希望从IaaS层理解OpenStack架构的中高级学习者帮助解决组件繁杂、源码入门无从下手的问题。资源以Nova项目为主线系统梳理OpenStack从最初Nova与Swift两大组件逐步拆分出Cinder、Glance、Neutron、Ironic等服务的演进脉络并深入讲解Nova的调度器、计算节点、API服务器与数据库接口等核心架构。内容涵盖setup.cfg中console_scripts入口函数定位、api.py与rpcapi.py及manager.py三大模块的职责划分、cmd与db等目录结构规律以及all-in-one环境搭建、pdb断点调试等实用方法并延伸至Cinder、Glance、Neutron与Nova的交互关系。压缩包共1个docx文件约759KB结构紧凑便于通读。目前已有105人学习适合作为理解OpenStack整体设计、快速迁移到其他组件源码的入门参考。1. 从 Nova 源码切入为什么读懂一个项目就能摸清 OpenStack 的骨架很多人第一次打开 OpenStack 源码仓库看到几十个组件、上百个目录第一反应是“这玩意儿怎么下手”。我当初也一样clone 完 Nova 之后在根目录愣了半天不知道该从哪个文件开始看。后来踩了不少坑才想明白一件事OpenStack 虽然组件繁杂但所有服务的骨架脉络几乎一模一样——api.py 负责对外封装、rpcapi.py 负责 RPC 客户端调用、manager.py 负责真正的服务端逻辑。你只要把 Nova 这一条线吃透再去看 Cinder、Neutron、Glance会发现结构惊人地一致。这份《OpenStack 技术源码模块解读》文档就是以 Nova 为例从组件介绍、环境搭建、调试工具到创建虚拟机的完整调用链路一步步拆解源码结构。适合已经接触过 OpenStack 部署、想进一步深入源码的运维和开发人员。如果你还在纠结“源码从哪看起”这份材料能帮你省掉大量瞎翻的时间。2. 读源码前的环境准备调试工具链与项目骨架认知2.1 代码阅读工具的选择与配置OpenStack 全部基于 Python 开发标准 setuptools 管理项目。阅读源码首先需要一套趁手的工具。图形界面下 PyCharm 确实好用但实际生产环境中OpenStack 部署在虚拟机或远程服务器上通常没有图形界面。这时候 vim 是首选但裸装 vim 看大型项目基本没法用——没有代码跳转、没有符号搜索跟看 txt 没区别。常见做法是配置一套支持代码导航的 vim 环境。GitHub 上 int32bit/dotfiles 这个仓库提供了一套 vim、zsh、git、tmux 的配置文件直接 clone 下来按 README 安装即可。核心依赖是 ctags 和 cscope前者生成符号索引支持跳转定义后者支持函数调用关系搜索。安装完之后在项目根目录执行# 生成 ctags 索引-R 递归--python-kinds 指定 Python 符号类型 ctags -R --python-kinds-i --fieldsniazS --extrasq . # 生成 cscope 索引-b 只构建不进入界面-q 生成倒排索引加速查询 cscope -Rbq # 在 vim 中加载索引后Ctrl] 跳转定义Ctrlt 返回ctags 的--python-kinds-i排除 import 语句产生的符号避免索引膨胀--extrasq让标签带限定名比如nova.compute.api.API.create也能被搜到。cscope 的-q生成倒排索引文件大项目下查询速度差别很明显。配置好之后在 vim 里把光标放在某个函数调用上按Ctrl]就能跳到定义Ctrlt跳回来读代码效率提升非常明显。注意ctags 和 cscope 的索引文件需要在代码更新后重新生成否则跳转结果会过时。我一般会在 shell 里加个 alias切分支后手动跑一遍。2.2 从 setup.cfg 定位服务入口拿到一个 OpenStack 项目第一步不是急着翻代码而是搞清楚这个项目到底有哪些可执行程序、各自的入口在哪。答案就在项目根目录的setup.cfg文件里。这个文件是 setuptools 的配置其中console_scripts段定义了所有服务的入口点。以 Nova 为例setup.cfg的console_scripts列出了 21 个可执行程序包括nova-api、nova-compute、nova-conductor、nova-scheduler等。每一行的格式是服务名 模块路径:函数名。比如[entry_points] console_scripts nova-api nova.cmd.api:main nova-compute nova.cmd.compute:main nova-conductor nova.cmd.conductor:main nova-scheduler nova.cmd.scheduler:main这意味着nova-compute安装后的入口函数是nova/cmd/compute.py模块里的main函数。想知道某个服务怎么初始化、加载了哪些配置、启动了哪些管理器直接从对应的cmd/*.py文件开始追就行。这个规律对所有 OpenStack 项目都适用——Cinder 看cinder/cmd/Neutron 看neutron/cmd/套路完全一样。2.3 搭建 all-in-one 调试环境与 pdb 断点实战Python 是动态类型语言很多参数类型光看代码根本看不出来必须实际跑起来跟踪。所以一个 all-in-one 的 OpenStack 开发测试环境是刚需。RDO 的 Packstack quickstart 是比较省事的方案一条命令拉起全套服务喜欢折腾的可以用 DevStack从源码部署改代码即时生效更适合深度调试。环境有了之后调试工具选 pdb 就够了不用上 IDE 的远程调试。使用方法极其简单在你想设断点的地方插入两行import pdb; pdb.set_trace()然后在命令行直接运行服务不能通过 systemd 启动否则 pdb 的交互终端拿不到标准输入。比如想跟踪创建虚拟机的过程在nova/api/openstack/compute/servers.py的create方法里打上断点# nova/api/openstack/compute/servers.py class ServersController(wsgi.Controller): def create(self, req, body): import pdb; pdb.set_trace() # 断点API 入口 context req.environ[nova.context] # ... 后续参数校验和 policy 检查然后用命令行启动 nova-api# 直接前台运行pdb 断点才会生效 nova-api --config-file /etc/nova/nova.conf调用创建虚拟机的 API 后nova-api 进程会停在断点处弹出 pdb shell。此时用sstep into进入函数内部nnext执行下一行p 变量名打印变量值l查看当前代码上下文。通过这种方式可以一个函数一个函数地跟踪整个调用链路比静态读代码直观得多。提示pdb 调试完成后记得删掉断点代码否则服务会一直卡住。我习惯用grep -rn pdb.set_trace全局搜一遍确认清理干净。3. Nova 通用骨骼脉络api.py、rpcapi.py、manager.py 三角关系3.1 目录按功能划分而非按服务划分OpenStack 项目的目录结构有一个容易让人误解的地方它并不是严格按照服务组件来划分目录的。以 Nova 为例compute目录并不一定只在 nova-compute 节点上运行而是把所有和虚拟机操作相关的功能实现都放在里面同样scheduler目录的代码也不全在 scheduler 服务节点上跑但都是调度相关的逻辑。这个设计思路需要先理解否则看代码时会困惑“为什么这个函数在 compute 目录里却被 nova-api 调用”。目录划分的依据是功能领域不是部署位置。除了compute和schedulerNova 还有几个关键目录目录职责典型调用方cmd各服务启动脚本main 函数所在地systemd / 命令行db数据库访问封装driver 为 sqlalchemyapi / managerconf配置项声明全局imageGlance 接口封装computenetwork网络服务接口封装computevolumeCinder client 封装computevirt各 hypervisor 驱动实现computeobjects对象模型封装实体 CRUD 和版本控制全局policiespolicy 校验实现api这套目录结构在 Cinder 里几乎一一对应cinder/volume对应nova/virtcinder/scheduler对应nova/schedulercinder/db对应nova/db。所以吃透 Nova 的目录划分逻辑换到 Cinder 就是换个名字的事。3.2 api.py / rpcapi.py / manager.py 的三角分工每个服务目录下通常都有三个核心模块api.py、rpcapi.py、manager.py。这三个文件构成了 OpenStack 服务间通信的基本骨架。api.py是供其它组件调用的封装库。注意它通常不会被本模块调用。比如nova/compute/api.py调用方是 nova-api 服务的 controller而不是 nova-compute 自己。它封装的是“对计算资源进行操作”的高层接口比如创建虚拟机、删除虚拟机、调整规格等。rpcapi.py是 RPC 请求的客户端封装。当需要跨进程调用另一个服务时通过它发起 RPC 请求。它定义了远程方法的名称和参数序列化方式底层走的是 oslo.messaging。manager.py才是真正的服务端实现也是 RPC 请求的接收入口。它里面实现的方法通常和rpcapi.py里的方法一一对应——rpcapi 里声明“我要调用 build_instances”manager 里就有一个build_instances方法来处理这个请求。以虚拟机关机为例调用关系是这样的# nova/compute/api.py — 供 nova-api 调用的封装 class API: def stop(self, context, instance): # 参数校验、policy 检查 self._record_action_start(context, instance, instance_actions.STOP) # 通过 rpcapi 发起 RPC 调用 self.compute_rpcapi.stop_instance(context, instance) # nova/compute/rpcapi.py — RPC 客户端封装 class ComputeAPI: def stop_instance(self, ctxt, instance): # 构造 RPC 调用参数 cctxt self.client.prepare(serverinstance.host) # cast 异步调用不等待返回 cctxt.cast(ctxt, stop_instance, instanceinstance) # nova/compute/manager.py — RPC 服务端实现 class ComputeManager: def stop_instance(self, context, instance): # 真正的关机逻辑 self.driver.power_off(instance)这个三角关系在所有 OpenStack 服务里反复出现。看懂了这一层再去看 Cinder 的cinder/volume/api.py、cinder/volume/rpcapi.py、cinder/volume/manager.py结构完全一致。3.3 以创建虚拟机为例的完整调用链路静态看代码结构还不够真正理解 OpenStack 的方式是追踪一个任务的完整执行过程。创建虚拟机是最典型的场景涉及 nova-api、nova-conductor、nova-scheduler、nova-compute 四个服务的协作。S1nova-api 接收请求。入口在nova/api/openstack/compute/servers.py的create方法。它检查参数和 policy 后调用nova/compute/api.py的create方法。后者创建数据库记录、检查配额然后调用compute_task_api的build_instances方法。compute_task_api是 conductor 的 api.py它直接调用conductor_compute_rpcapi的build_instances最终落到nova/conductor/rpcapi.py# nova/conductor/rpcapi.py def build_instances(self, context, instances, image, filter_properties, admin_password, injected_files, requested_networks, security_groups, block_device_mapping, legacy_bdmTrue): # cast 异步调用发完即返回 cctxt self.client.prepare() cctxt.cast(context, build_instances, ...)cast是异步调用nova-api 发完 RPC 请求就返回了虚拟机状态变为building用户请求得到响应。S2nova-conductor 接管。进程跳到nova/conductor/manager.py的build_instances方法。它先调用_schedule_instances后者通过scheduler_client的select_destinations方法向 nova-scheduler 发起 RPC 调用。注意这里用的是call而不是cast# nova/scheduler/rpcapi.py def select_destinations(self, ctxt, request_spec, filter_properties): # call 同步调用阻塞等待返回 cctxt self.client.prepare(versionversion) return cctxt.call(ctxt, select_destinations, request_specrequest_spec, filter_propertiesfilter_properties)call是同步调用nova-conductor 会阻塞等待 nova-scheduler 返回结果。S3nova-scheduler 调度。nova/scheduler/manager.py的select_destinations方法调用 driver 的调度算法。默认是filter_scheduler对应nova/scheduler/filter_scheduler.py。它先通过host_manager拿到所有计算节点信息然后用 filters 过滤掉不满足条件的节点比如资源不足、不在同一可用区剩下的节点通过 weigh 方法计算权值选权值最高的作为候选返回。S4回到 nova-conductor。拿到调度结果后build_instances循环调用compute_rpcapi的build_and_run_instance方法向 nova-compute 发起异步 RPC 调用。nova-conductor 任务结束。S5nova-compute 执行创建。入口在nova/compute/manager.py的build_and_run_instance方法。它调用 driver 的spawn方法driver 就是各种 hypervisor 的实现在nova/virt/目录下。以 libvirt 为例对应nova/virt/libvirt/driver.py的spawn方法负责拉取镜像创建根磁盘、生成 XML 文件、define domain、启动 domain。至此虚拟机创建完成。整个链路中还有一个容易忽略的细节所有数据库操作比如instance.save()和 update如果配置use_local为 falsenova-compute 不会直接访问数据库而是向 nova-conductor 发起 RPC 调用由 conductor 代理完成数据库更新。这个设计是为了安全——计算节点不直接持有数据库凭证。4. 避坑与排查读源码时最容易翻车的几个地方4.1 pdb 断点不生效服务直接卡死现象在代码里插了pdb.set_trace()通过 systemd 启动服务后服务卡住不响应但终端没有任何 pdb 交互界面。原因systemd 启动的服务没有绑定到当前终端pdb 的标准输入输出被重定向到了 journal你根本看不到交互提示。解决调试时必须通过命令行前台运行服务比如nova-api --config-file /etc/nova/nova.conf不要用systemctl start nova-api。调试完记得删掉断点代码用grep -rn pdb.set_trace全局确认。4.2 混淆 api.py 的调用方追错进程现象在nova/compute/api.py里打断点以为会跳到 nova-compute 进程结果发现断点是在 nova-api 进程里触发的。原因api.py是供其它组件调用的封装库不是本服务的实现。nova/compute/api.py的调用方是 nova-api 的 controller不是 nova-compute。解决记住分工——api.py给外部用rpcapi.py发 RPC 请求manager.py处理 RPC 请求。想看 nova-compute 的实际逻辑断点应该打在nova/compute/manager.py里。4.3 RPC 调用 cast 和 call 搞混流程对不上现象跟踪创建虚拟机流程时发现 nova-api 发完请求就返回了但后面 conductor 和 scheduler 的日志还在继续输出时间线对不上。原因cast是异步调用发完即返回call是同步调用阻塞等待结果。nova-api 到 conductor 用的是 castconductor 到 scheduler 用的是 call。解决看rpcapi.py里用的是cast还是call就能判断调用方是否等待返回。异步调用意味着调用方任务结束后续流程在另一个进程里继续。4.4 目录结构按功能划分找错文件位置现象想找 nova-compute 的启动逻辑在nova/compute/目录下翻了半天没找到 main 函数。原因启动脚本在nova/cmd/目录下不在nova/compute/。nova/compute/放的是计算功能实现不是服务启动入口。解决找入口看setup.cfg的console_scripts找功能实现看对应功能目录找 RPC 服务端逻辑看manager.py。4.5 数据库操作不走本地追不到 SQL现象在 nova-compute 里跟踪instance.save()期望看到 SQL 执行结果发现只是发了个 RPC 请求就结束了。原因use_local配置为 false 时数据库操作由 nova-conductor 代理nova-compute 不直接访问数据库。解决如果要跟踪数据库操作断点应该打在nova/conductor/manager.py里对应的数据库代理方法上而不是 nova-compute 的代码里。5. 从 Nova 迁移到 Cinder验证源码阅读方法论的实战技巧读完 Nova 的创建虚拟机流程后怎么验证自己真的掌握了这套方法论最直接的方式是拿 Cinder 练手看创建数据卷的流程对比两者的骨架是否一致。Cinder 的入口在cinder/api/v2/volumes.py的create方法对应 Nova 的servers.py的create。它会调用cinder/volume/api.py的create方法对应 Nova 的nova/compute/api.py。然后通过cinder/volume/rpcapi.py向cinder/volume/manager.py发起 RPC 调用对应 Nova 的 conductor 和 compute 之间的 RPC 链路。调度部分在cinder/scheduler/下和nova/scheduler/结构一致。具体验证步骤# 1. 查看 Cinder 的服务入口 grep -A 20 console_scripts setup.cfg # 2. 对比目录结构 ls cinder/volume/ cinder/scheduler/ cinder/db/ # 3. 在创建数据卷的 API 入口打断点 # cinder/api/v2/volumes.py 的 VolumeController.create 方法 import pdb; pdb.set_trace() # 4. 命令行启动 cinder-api cinder-api --config-file /etc/cinder/cinder.conf # 5. 调用创建数据卷 API跟踪调用链路对比下来会发现几个关键差异点Cinder 的调度器叫cinder/scheduler/filter_scheduler.py和 Nova 的nova/scheduler/filter_scheduler.py名字一样但过滤条件不同Cinder 的 driver 在cinder/volume/drivers/下对应 Nova 的nova/virt/Cinder 没有 conductor 的独立进程数据库操作直接在cinder/db/api.py里完成这是和 Nova 最大的架构差异。我自己的习惯是每读一个新组件先画一张调用链路图标注每个环节用的是 cast 还是 call、在哪个进程里执行、断点应该打在哪里。画完图再实际跑一遍 pdb 跟踪验证图和代码是否一致。这个方法帮我省了很多瞎翻代码的时间也避免了对流程的想当然。从那以后我每次读新组件源码都强制先跑一遍 setup.cfg 确认入口再画调用链路图最后用 pdb 验证。这套流程走下来基本不会出现“看了半天不知道代码在哪执行”的情况。希望帮到你。本文还有配套的精品资源点击获取
返回列表