OpenStack多版本混布导致nova-compute启动卡死的排查与修复

发布时间:2026/10/15 0:31:58
OpenStack多版本混布导致nova-compute启动卡死的排查与修复
说实话OpenStack环境里最磨人的故障不是那种直接狂刷ERROR的而是进程明明活着、却什么也不干的类型。我今天要聊的就是这个标题里的场景——openstack多版本部署导致nova-compute启动卡死。我前后在几套环境里碰到过类似情况表现形式基本一致计算节点上systemctl status openstack-nova-compute显示 active进程也在但宿主机始终加不进新虚拟机控制节点看计算资源一直处于异常。它不像崩溃那样有一个明确的退出码也不像配置错误那样启动几秒内就报错。就是卡住没有任何有效日志让人无从下手。这类问题的坑在于它不会主动告诉你“我因为版本不匹配所以起不来了”而是把所有异常都吞在内部重试里以一种极其静默的方式消耗你的排查时间。这篇文章就围绕这个故障类型从现象、根因、排查链路到修复方案完整走一遍结合我自己的实操经验尽量把每一步为什么这么做、卡在哪、怎么验证都讲透。适合正在做OpenStack版本升级、混合部署、或者时不时遇到“服务活着但实际不可用”问题的运维、平台开发和架构师参考。1. 现象复盘一个毫无报错的“卡死”1.1 现场情形服务状态与表象先说说我遇到的典型场景。一次版本升级过程中控制节点和部分计算节点已经升到新版但还有几个计算节点因为资源池排期问题没来得及升级处于旧版本。就在这个“混合运行”的阶段某一个旧计算节点的 nova-compute 服务被重启过——可能因为是内核更新、断电恢复、或者有人手动重启——然后它就再也回不来了。从表面看systemctl status显示active (running)进程 PID 存在端口也在监听。但你去控制节点执行openstack compute service list会看到该计算节点处于down状态nova hypervisor-list里也看不到它。更诡异的是你登录到该计算节点直接手动执行nova-compute的前台启动命令日志停在一个“看起来很正常”的地方就不动了没有报错没有退出CtrlC 才能结束。1.2 卡死与崩溃的本质区别崩溃类故障有一个明确退出码系统会把错误堆栈打出来定位起来相对快。卡死则完全不同它意味着进程的主线程还在但被某个等待条件阻塞住了可能是等待网络返回、等待数据库锁、等待消息队列回包。这种等待如果没设超时或者重试机制有问题就会无限持续下去。在多版本部署环境下nova-compute 卡死的特殊性在于它往往不是操作系统层面的问题而是OpenStack各组件之间版本协商失败后代码进入了“重试—失败—再重试”的循环。这个循环之所以看起来像卡死是因为重试间隔会指数退避时间越拉越长最长可能达到几分钟甚至几十分钟。你观察它的表象时恰好处于一个漫长的退避等待窗口里自然看不到任何日志输出。2. 为什么多版本混布会精准卡住nova-compute根因拆解2.1 nova-compute启动期的关键依赖链要理解卡死点得先搞清楚 nova-compute 启动时都干了什么。简化后的启动链路大概是这样的解析/etc/nova/nova.conf与 rootwrap 配置加载 oslo.config 配置对象初始化 oslo.messaging创建与 RabbitMQ 的长连接声明自己的 topic 队列连接数据库查询 cell mapping、service 记录拿到本节点的 UUID 和身份信息初始化 virt driver比如 libvirt driver连上本机 qemu向 placement 服务注册 resource provider并上报各类 inventory 资源启动周期任务与 servicegroup 心跳向 conductor 注册服务能力。多版本混布环境下以上任何一步都可能因为版本不匹配而卡住。但根据我的排查经验真正高发且具有“静默”特征的主要集中在三个环节。2.2 三个最容易形成“静默阻塞”的环节第一个是 RPC 队列声明冲突。旧版 nova-compute 使用的 oslo.messaging 版本与新版控制节点上声明的 exchange、queue 元数据不一致时RabbitMQ 会拒绝 queue 声明请求。oslo.messaging 内部对这类异常的处理逻辑是重试而不是直接抛错。如果恰好遇到队列被其他版本组件以不同参数声明过旧进程会陷入反复声明、反复被拒绝的循环。第二个是 placement API 接口版本不匹配。nova-compute 启动时会上报 resource provider 的 inventory这个动作依赖 placement 的 REST API。新版 placement 对请求体中的字段要求更严格比如要求inventories里必须携带保留字段、要求 PUT 请求必须判空等。旧版 nova-compute 发送的请求格式不满足新版要求placement 会返回 400 或 409。nova-compute 的 HTTP 客户端在收到这类响应时会进入连接池重试逻辑。问题在于 reconnection 的退避时间很长而且不会向日志写入一条明确的“placement 返回 400”记录。第三个是数据库 schema 迁移不一致。控制节点升级时通常会在共享数据库中执行nova-manage db sync将 schema 升到新版本。此时旧计算节点上的 nova-compute 代码去查新表结构时会遇到新版本才有的列或约束。特别是查询时如果涉及到新加的 NOT NULL 列数据库直接报错而 oslo_db 的会话重试机制会在后台反复建立新连接表现就是进程卡在建连接池和重试的路上。2.3 最容易中招的三种多版本场景根据我遇到和帮朋友排查过的案例以下三种场景最容易触发 nova-compute 卡死滚动升级中间态一部分计算节点已经升级到新版本另一部分还是旧版本且头尾相差两个release以上。这种情况下旧计算节点与新版conductor之间的RPC接口兼容性就已出现风险。控制节点先升级、计算节点长期滞后这是最容易忽略的。控制节点和数据库升级后旧计算节点里的nova代码在启动时大量依赖新版schema和新版placement接口卡死概率最高。版本回退后schema残留升级后发现新版不稳定把计算节点回退到旧版本但数据库已经跑过新版迁移脚本。旧代码启动时遇到不认识的字段或表就可能陷入数据库重试泥潭。3. 复现排查链路从日志尾部到线程栈再到边界服务3.1 第一步定位最后一条有效日志排查卡死问题第一件事不是看配置而是看日志最后到底停在哪。这一步的目标是缩小阻塞范围判断是卡在网络初始化、数据库连接、还是virt driver加载。tail -200 /var/log/nova/nova-compute.log实战中我看到的典型“最后一条日志”有几种2024-XX-XX 10:23:45.123 INFO nova.compute.manager [req-xxx] Loading compute driver libvirt.LibvirtDriver或2024-XX-XX 10:23:50.456 INFO nova.compute.manager [req-xxx] Connecting to neutron或干脆没有任何新日志只有很早期的Starting nova-compute这一行。日志停在不同位置对应的嫌疑点不同。停在“Loading compute driver”之前大概率卡在RPC或数据库初始化阶段停在“Connecting to neutron”附近则多半是neutron接口版本协商出了问题。这一步不需要做任何修改只是帮自己划定一个“从哪开始挖”的范围。3.2 第二步抓取线程堆栈确认阻塞点日志不是全部真相。很多时候进程卡住的位置藏在线程栈里尤其Python服务靠日志判断可能完全错方向。这里我强烈推荐py-spy它能直接dump出Python进程的线程栈不用重启服务不打断进程。pip install py-spy py-spy dump --pid $(pgrep -f nova-compute | head -1)输出会列出所有线程当前的调用栈。重点关注处于waiting、sleeping、locked状态的线程尤其是主线程和周期任务线程。我遇到过几个非常有代表性的栈栈底部停在oslo_messaging._drivers.impl_rabbit._consume说明卡在RabbitMQ消费/重连逻辑栈底部停在requests.sessions.Session.send配合urllib3.util.retry.Retry说明卡在API请求重试栈底部停在oslo_db.sqlalchemy.enginefacade._transaction_scope配合QueuePool说明卡在数据库连接池等待。看到这些栈基本就能把问题定性了不是“系统假死”而是在等待某个外部依赖再结合第一步日志位置就能锁定到底是哪个外部依赖。3.3 第三步横向检查conductor、placement与消息队列确认阻塞方向后需要到边界服务侧做交叉验证。比如怀疑卡在RabbitMQ就去查队列状态和连接来源怀疑卡在placement就去看placement API的访问日志怀疑卡在conductor就去查conductor是否有处理该请求的记录。检查RabbitMQ队列和消费者rabbitmqctl list_queues name consumers messages对比正常节点和卡死节点的队列数量。多版本场景下常见异常是旧计算节点所需的队列没有消费者或者队列元数据与正常节点不一致。还可以直接看RabbitMQ的连接列表确认这个计算节点的连接是否处于blocked状态。检查placement侧journalctl -u apache2 -u httpd | grep -i resource-provider或直接手动模拟一次上报curl -i -X PUT \ -H Content-Type: application/json \ -H X-Auth-Token: token \ -d {name:test,uuid:uuid,parent_provider_uuid:null} \ https://placement-endpoint/placement/resource_providers如果返回400或409而正常节点返回201基本可以确认是API版本协商出问题。3.4 第四步数据库schema与版本一致性校验最后一步也是最能一锤定音的就是核对所有相关组件版本和数据库迁移版本。# 查看各节点nova版本 nova-manage version pip show nova 2/dev/null || rpm -q openstack-nova-compute # 查看数据库schema版本 nova-manage db version nova-manage api_db version我遇到过一次非常经典的例子控制节点nova-manage db version显示database migration is up to date但某个计算节点上pip show nova显示的是低于该schema版本对应的代码。新旧代码混跑新库旧码角色错位一目了然。这一步做完基本就能下结论了。4. 修复实操让nova-compute恢复正常启动的完整方案4.1 场景A计算节点版本滞后直接对齐版本如果你确认是计算节点版本滞后导致的问题最省事的方案就是把它升级到与控制节点一致的版本。注意这里要统一的不只是nova主包还有它的依赖库。以某次我用源码安装方式排障的过程为例# 在计算节点上停服务 systemctl stop openstack-nova-compute libvirtd # 从控制节点导出完全相同的nova版本信息 pip freeze | grep -E ^(nova|oslo|neutron|placement) /tmp/req.txt # 在计算节点上安装对齐版本 pip install -r /tmp/req.txt --upgrade systemctl start libvirtd openstack-nova-compute源码安装环境通常更多依赖库版本比如oslo.messaging、oslo.db、openstacksdk这些都要一起对齐。如果是RPM部署则yum install openstack-nova-compute-版本号 --enablerepoxxx升级后别急着看服务状态先观察日志尾部是否出现了新的初始化进展。我这里给个建议用 journald 和日志文件双通道同时跟踪因为systemd服务在前台日志可能被吞掉一部分。journalctl -u openstack-nova-compute -f tail -f /var/log/nova/nova-compute.log只要日志开始出现“Loading compute driver”“Connecting to neutron”之后的下一步说明阻塞解除。4.2 场景B无法立即统一版本时的临时拉起方案现实项目中经常有“现在不能全部升级但服务得先跑起来”的尴尬。如果没法立刻统一版本可以尝试用配置参数和故障隔离手段把服务先拉起来。首先把RPC超时时间调大避免启动期的正常慢速协商被误判为超时中断[DEFAULT] rpc_response_timeout 300 [oslo_messaging_rabbit] rabbit_retry_interval 1 rabbit_retry_backoff 2 rabbit_max_retries 0其次清理掉该节点在RabbitMQ里残留的队列。多版本场景下旧队列可能被新控制节点用不同参数声明过导致队列规范冲突。建议先禁用compute服务再手动清除队列openstack compute service set --disable host nova-compute rabbitmqctl list_queues | grep compute.host rabbitmqctl delete_queue compute.host.uuid systemctl restart openstack-nova-compute openstack compute service set --enable host nova-compute注意删除队列前必须确认该队列没有其他节点正在消费否则会影响该节点上的存量实例心跳上报。保守做法是先把服务禁用再删队列恢复后再启用。如果卡在placement API版本协商还可以在 nova.conf 里指定placement端点时使用带/placement显式路径的 internal 接口。部分场景下旧版nova-compute通过service catalog拿到的placement URL格式不对需要手动指定[placement] auth_url https://keystone/v3 region_name RegionOne valid_interfaces internal timeout 60这套临时方案的目标不是消除版本差异而是绕过导致卡死的等待点。它有一定的风险比如存量实例迁移、疏散、节点维护等操作期间可能遇到不一致行为所以只能作为短期应急手段长期仍要回到版本统一这条路。4.3 场景C版本回退产生的schema残留清理如果你经历了“升级—回退”的过程那么问题往往不只是代码版本不匹配数据库里还残留了新版schema。旧代码启动时可能因为查询了新列、新表而反复触发数据库异常。处理这个场景的原则是先确认该计算节点是否还有实例需要保留。如果有先迁移或疏散实例如果没有可以直接清掉service表记录让nova-compute当成全新节点加入。# 查看计算节点对应的service id openstack compute service list --host host # 删除该计算节点的service记录 openstack compute service delete service-id之后清理数据库里与该节点相关的残留resource provider数据# 在控制节点上进入nova_api库 mysql -u root -p nova_api DELETE FROM resource_providers WHERE uuid node-uuid; DELETE FROM inventories WHERE resource_provider_id id;再重启nova-compute它会把该节点视为一个新的计算节点重新创建resource provider并上报。这样做的代价是原有节点上以resource provider维度保存的一些资源记录会消失但正常情况下不影响存量实例计数因为实例属于cell数据库不在nova_api库。如果数据库整体迁移版本和代码版本存在系统性偏差就必须做更严苛的处理先用备份恢复数据库到迁移前状态再按正确顺序重新执行迁移。# 控制节点操作务必先在维护窗口执行 mysqldump nova_api /tmp/nova_api_backup.sql mysqldump nova_cell0 /tmp/nova_cell0_backup.sql恢复后重新执行nova-manage api_db sync nova-manage db sync nova-manage db online_data_migrations这里我踩过一个坑只记得恢复nova_api忘记了nova_cell0和cell数据库。结果控制节点正常计算节点依旧起不来。所以备份恢复时所有nova相关的库都要一起处理cell库尤其关键。5. 防复发多版本升级场景下如何安全部署不踩坑5.1 升级顺序与组件依赖版本矩阵nova-compute卡死这件事本质上不是“计算节点自己”的问题而是整个OpenStack控制面和计算面版本同步性的问题。所以防复发的手段也应该从架构和流程层面处理而不是只修那一台机器。多版本升级时必须坚持“先控制面后计算面”的滚动顺序。控制节点先把nova-api、nova-conductor、nova-scheduler、placement全部升级完数据库迁移跑完确认线上无异常再开始动计算节点。计算节点最好按资源池分批升级比如每次只升一个集群的几个节点观察一段时间再继续。这里给一张我常用的升级版本检查表每次升级前逐项比对检查项命令/方法通过标准nova代码版本nova-manage version所有节点显示一致数据库迁移状态nova-manage db version与控制节点一致依赖库版本pip freeze/rpm -qaoslo.messaging等关键依赖一致配置差异diff /etc/nova/nova.conf除host、ip外无差异placement接口可用性手动curl上报返回正常HTTP码neutron扩展协商nova-compute日志正常打印已加载扩展列表5.2 配置版本与配置文件一起管除了二进制包版本配置文件的版本差异同样会造成启动阻塞。我见过最隐蔽的情况是计算节点上的nova.conf里还有一个旧版本的transport_url指向一个早已被回收的RabbitMQ集群控制节点新版里已经切到新集群。旧计算节点启动时连不上旧MQ消息队列连接一卡就是几分钟。所以强烈建议把配置文件和部署脚本一起纳入版本管理。用Ansible等配置管理工具统一下发nova.conf不要在节点上手动改配置。在group_vars里显式声明版本变量nova_version: 2023.1 rpc_transport_url: rabbit://nova:****mq01:5672,mq02:5672 placement_endpoint: https://controller/placement这样每次升级配置变更跟随代码变更一起发布降低因为节点间配置漂移引发的启动问题。5.3 关键超时与重试参数的设置多版本环境里不设超时或者超时设置得极短都会放大卡死问题。超时太长会掩盖真实故障超时太短则会让正常的协商过程被误杀。我自己在混合版本期间用的参数如下[DEFAULT] rpc_response_timeout 300 rpc_conn_pool_size 30 [oslo_messaging_rabbit] rabbit_retry_interval 1 rabbit_retry_backoff 2 rabbit_max_retries 0 [placement] timeout 60 [database] retry_interval 5 max_retries 10 connection_recycle_time 600rabbit_max_retries 0表示无限重试配合指数退避可以让偶发网络问题时服务自动恢复。但要注意无限重试在多版本场景下也可能掩盖真正不可恢复的版本冲突。所以这个参数更适合已经确认“版本没大问题只是网络抖动”的时候用。5.4 事后验证与常态化巡检修复完成后不能只盯一次启动成功就完事。nova-compute启动成功和完全可用之间还有一段距离需要在控制节点侧做几个验证# 检查计算服务是否已UP openstack compute service list # 确认hypervisor资源已经上报 openstack hypervisor list openstack hypervisor show host # 尝试在该节点创建一台测试虚拟机 openstack server create --flavor m1.small --image cirros --availability-zone nova:host test-vm除此之外建议把版本一致性检查做成定时任务。比如每周执行一次脚本自动比对所有计算节点的nova-manage version和控制节点的差异差异大于一个小版本就告警。多版本混布最大的风险不是“有差异”而是差异存在了很长时间而没人发现最后一次偶发重启直接把问题引爆。多版本部署本身不是错滚动升级也不可避免会有一段混合期。真正危险的是没有意识到混合期各组件的兼容边界没有给comute节点一个“如果协商失败该怎么办”的兜底策略。这篇内容里的排查方法、修复命令和参数建议都是我实际用过且验证有效的希望能帮你少走几趟弯路。