ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

OpenStack Nova实例生命周期深度解析:Start、Reboot与Lock操作实战

OpenStack Nova实例生命周期深度解析:Start、Reboot与Lock操作实战 搞OpenStack运维的朋友肯定都有过这种体验实例起不来、重启卡住、快照删不掉翻日志翻到怀疑人生。其实这几个问题背后往往都指向同一个根源——你对Nova实例在生命周期里的状态流转不够熟悉。这个OpenStack系列写到第31篇这一次我把实例生命周期里最基础也最容易出岔子的三个操作一次性讲透Start Instance、Nova reboot以及经常被忽略的lock/unlock。这三个操作单独看都不难一条CLI命令就能触发但它们背后涉及Nova的状态机设计、API层的权限校验、Conductor到Compute的RPC链路再到Libvirt驱动层和QEMU进程的交互。任何一个环节没对上表现就是实例创建成功却拉不起来、reboot请求发出去了但虚拟机纹丝不动、或者干脆报一个让你摸不着头脑的Could not acquire lock(s)错误。这篇文章我不会只讲命令怎么敲而是会把每一步的原理、参数含义、日志定位思路都拆开给你看适合刚入门想系统理解Nova的人也适合在生产环境被实例故障折磨已久的运维老手。1. 先把底子铺明白Nova实例状态机与生命周期基础1.1 三个状态字段搞混了必然踩坑Nova在设计上把一个实例的状态拆成了三个不同的字段很多人一开始没搞明白这三个字段的区别后面排查问题就容易被表象误导。这三个字段分别是vm_state、power_state、task_state。vm_state是Nova自己维护的虚拟机的生命周期状态它描述的是这个实例在Nova的认知里处于什么阶段比如ACTIVE、BUILDING、STOPPED、SHELVED、ERROR。这个状态是写进数据库的Nova API在做很多操作校验时看的就是它。power_state则是对底层hypervisor上报的电源状态的描述RUNNING、SHUTDOWN、PAUSED这些值都是从Libvirt那边同步过来的。task_state是最容易忽略的一个字段它表示当前是否有一个异步任务正在运行比如spawning、rebooting、powering-off、deleting。可以这么理解vm_state是业务层面的状态power_state是物理层面的状态task_state是操作过程中间的临时标记。举个典型例子一个实例被nova stop之后数据库里vm_state是STOPPEDpower_state是SHUTDOWN这两个状态是匹配的。但如果直接去底层用virsh把虚拟机destroy了Nova的vm_state还是ACTIVEpower_state在Libvirt上报之后才会变成SHUTDOWN这时候如果不做同步Nova会认为实例仍然在运行中。所有生命周期操作的核心逻辑本质上就是在正确的时间把这三个状态修改到正确的值。1.2 生命周期操作之间的依赖关系Start、Reboot、Lock在Nova的状态机里踩的是完全不同的路径而且它们对实例当前状态的要求也各不相同。Start这个操作要求实例必须处于STOPPED或者SHELVED_OFFLOADED状态如果实例还在ACTIVE状态下执行StartNova API会直接抛Conflict (409)。而Reboot要求实例必须处于ACTIVE状态如果你对一个STOPPED状态的实例执行reboot同样会被拒绝。Lock则是特殊情况它不直接修改vm_state而是在实例记录上打一个locked标记用来在API层挡住那些可能对实例产生破坏性影响的操作。从实际操作顺序来看这三个操作在Keepalive上经常被放在一起比如运维人员习惯性地把stop - start - reboot当作一个重启三连这在Nova的视角里其实是非常重的操作组合。stop会走一遍powering-off的task_state要求guest OS通过ACPI关机然后开始start又走一遍spawning或powering-on。相比之下一个reboot操作只会在rebooting这个task_state上跑一圈开销小得多。理解这层差异之后你会逐渐意识到在Nova里操作实例并不是想怎么组合就怎么组合的状态的合法性校验是第一道关卡。2. Start Instance 操作详解从OpenStack命令行到底层虚拟化驱动2.1 一条Start命令背后的完整调用链路先看日常最常用的命令形式openstack server start instance_id_or_name如果你习惯用老版的Nova client等价命令是nova start server。加上--wait参数可以让CLI一直等到实例真正进入ACTIVE状态才返回这在脚本里特别实用不至于sleep几秒钟然后拿着不准确的状态去做下一步判断。Start操作从API到虚拟化驱动的完整链路大概是这样的nova-api接收POST /v2.1/{project_id}/servers/{server_id}/action请求请求体是{os-start: null}。nova-api从数据库取实例记录校验vm_state是STOPPED或SHELVED_OFFLOADED。nova-api通过RPC调用nova-conductorconductor再把任务转发给该实例所在计算节点上的nova-compute。nova-compute的start_instance方法开始执行它要做的事情包含调用network_api重新为实例配置网络、调用driver.power_on启动虚拟机、等待Libvirt上报运行状态。最后把数据库里实例的task_state清掉vm_state迁移到ACTIVEpower_state更新为RUNNING。其中第4步是真正的重头戏。driver.power_on在Libvirt驱动里实际上会执行domain.create()然后在创建之前会重新把网络接口VIF和云硬盘Volume都接回去。你可以从nova-compute服务的日志里看到这段过程从Starting instance...一直到Instance is running中间夹杂着各种关于network、volume的操作记录。2.2 为什么实例启动这么慢网络和存储是主要耗时点生产环境里一个实例从Start到进入可用状态耗时长短跟底层规模密切相关。一个只挂了系统盘、没有额外云硬盘的实例如果在同一个分布式网络中虚拟机的tap设备都已经提前创建好启动过程可能只需要几秒到十几秒。但如果你挂载了几块大容量Cinder云硬盘并且用的还是后端为LVM或者iSCSI的存储方案那么Start操作在driver.power_on之前还要执行attach_volume这会等待存储后端把块设备映射到计算节点然后由Libvirt的qemu进程以设备方式挂载进去。这个流程一旦网络延迟升高或者存储节点负载暴涨几十秒甚至几分钟就出去了。这里就涉及到一个非常常见的排障点当执行openstack server start后实例长时间停留在ACTIVE但power_state已经不是RUNNING或者卡在BUILDING或者spawning这种状态不要只盯着nova-compute看先检查网络节点和存储节点是不是有异常。我曾经遇到过一例实例启动超时的案例最后定位到是计算节点上bridge的MAC地址表出了问题虚拟机domain都已经被Libvirt创建了但tap设备没有正确接入bridge导致虚拟机压根无法与外网通信而Nova认为资源已经分配完毕状态自然也就没有继续推进。2.3 启动实例时Scheduler真的会介入吗一个很关键但容易被误解的问题是openstack server start的时候nova-scheduler到底还参不参与决策答案是不参与。Scheduler只在实例创建、迁移、热迁移等需要选节点的场景下才会被调用。Start操作的目标节点是已经确定的因为实例在数据库中本来就记录了host字段对应的计算节点是固定的。也就是说实例在哪个计算节点启动几乎完全依赖于该节点上nova-compute的状态。所以如果实例启动失败最先应该去查的就是目标计算节点上的服务状况。比如计算节点的内存超分策略配置有问题、memory_ratio设置得过大导致系统可用内存不足、CPU pinning规则和实际CPU拓扑不匹配、或者Libvirt连接不稳定所有这些异常都会导致Start操作无法完成。另一个常见情况是计算节点在维护期间被nova-compute搞挂了实例数据库里记录的host还是那个节点但该节点已经无法提供服务这时候Start就会一直卡住日志里反复出现Failed to connect to libvirt之类的错误。2.4 从日志到数据库Start失败的排查三板斧排查Start失败我一般的操作顺序是先看openstack server show server输出的fault字段这个字段包含最近一次API层能感知到的错误摘要很多时候问题就写在这一行。然后看目标计算节点上的nova-compute日志路径一般在/var/log/nova/nova-compute.log搜索实例ID从Starting instance...开始往下翻直到看到Exception或ERROR关键字。如果Nova日志里没有明显异常再进底层看Libvirt状态比如virsh list --all确认domain是否已经被创建virsh dumpxml domain检查XML配置是否正确尤其是CPU型号、启动磁盘、VIF类型这几个关键项。结合我自己的实战经验第2步其实能解决大部分问题。很多Start失败不是Nova的逻辑问题而是底层QEMU创建进程时报错比如XML配置里引用了不存在的镜像路径、磁盘格式不正确、或者/dev/kvm不可用导致无法开启硬件虚拟化。这些错误信息会原样封装进Nova-compute的日志里找到最底层那行libvirtError通常就是根因。3. Nova reboot软重启与硬重启的细节与坑3.1 soft reboot不是简单地下个重启命令openstack server reboot server默认执行的是软重启也就是soft reboot。在Libvirt驱动里软重启的实现方式是调用domain.reboot()这个接口会向QEMU发送ACPI重启信号要求guest OS内的init系统友好地关闭所有服务然后重新引导。你可以在虚拟机里执行reboot命令之后观察宿主机的磁盘信息如果是通过Libvirt发出的重启guest OS里的日志一般会记录到一次正常的系统关机和启动过程。但这里有一个非常不为人知的细节domain.reboot()在Libvirt内部的实现对于一些没有ACPI支持的guest OS可能只会做一次优雅关机然后就停在那里。如果虚拟机配置里没有开启ACPI比如某些精简Linux发行版镜像为了缩减体积关闭了ACPIguest OS根本收不到系统总线上的重启信号虚拟机就会停留在一种系统关了一部分但进程没死的诡异状态实例一直处于rebooting状态半天不动。所以如果你发现一个实例软重启后长时间卡住virsh domstate又显示running除了怀疑guest OS有问题之外第一个要查的就是镜像里ACPI有没有启用以及Libvirt domain XML里featuresacpi//features这个标签是否存在。3.2 hard reboot的风控什么时候才该用硬重启对应的是openstack server reboot --hard server在底层实现上相当于对虚拟机先执行强制断电再重新上电启动。Libvirt驱动的实现逻辑是domain.destroy()然后domain.create()destroy()对于QEMU进程来说就是无条件终止guest OS内的所有未落盘数据都会丢失所以不到万不得已不要在生产环境对数据库这类服务使用hard reboot。那么什么时候该用我的判断标准很简单如果guest OS已经完全无响应比如ping不通、SSH连不上、控制台也输出不了内容这种情况软重启大概率也没有任何效果因为ACPI信号发进去guest根本不会处理。此时再等下去只会白白延长故障时间果断硬重启。另一种情况是软重启已经失败Nova本身在reboot_instance的代码里也会有软重启超时后自动fallback到硬重启的逻辑但这取决于具体版本和配置不要完全依赖它关键时刻自己判断更靠谱。hard reboot还有一个值得留意的点它本质上相当于把QEMU进程杀掉再重新拉起来所以网络设备、磁盘设备会全部重新初始化。如果实例挂载了云硬盘并且存储后端在detach/attach过程中有状态一致性要求比如某些老版本的LVM后端在非正常卸载后需要做fsck那么硬重启之后实例的启动速度会明显变慢甚至可能出现一个小概率的启动失败。这些现象在文档里不会写但运维中见得多心里有数就行。3.3 reboot过程中的状态流转与并发保护reboot操作在Nova里的task_state是rebooting从API发出到执行完成这中间如果运维手滑又发了一个其他操作比如同时发了resize或snapshot那么Nova的并发保护机制会拒绝后者这在业务上是合理的你不能在一台正在重启的机器上做热迁移或者生成快照底层资源的状态是混乱的。理解状态流转还有一个实际价值当你用脚本批量对多台实例执行reboot时每台实例的reboot请求会进入nova-compute的工作队列而task_staterebooting是防止同一实例被重复发起重启的关键。我在写自动化工具时习惯在每个reboot请求发出后立刻轮询openstack server show的输出重点不是看status而是看task_state是否又变回了空值。只有task_state为空并且vm_stateACTIVE才说明这一轮reboot已经彻底结束。这个小技巧能帮你避免很多脚本层面的误判。举一个具体的踩坑例子。某次生产变更我写了一个遍历所有实例执行reboot的脚本一开始只判断了status字段结果发现status在reboot过程中变成了ACTIVE但task_state仍然是一大串很长的字符串脚本误以为reboot完成继续往下执行后续操作导致一部分实例收到了重复的reboot请求。从那次之后我再也没有用单一状态字段做过并发判断状态机这种东西必须组合着看。4. Nova lock/unlock很多人忽略的防误删利器4.1 lock实例后究竟锁住了什么nova lock这个操作在OpenStack社区里的定位非常明确它是Nova API层的一个轻量级保护机制防止实例被意外删除或执行一些破坏性操作。老方法用nova lock server新版本的OpenStackCLI也支持openstack server lock server。实例被锁定后数据库里的locked字段会被置为True此后所有跟删除、迁移、快照、resize、rebuild等相关的API请求在走到nova-api校验层时都会检查这个锁定标记并直接拒绝。这就有意思了因为很多人以为lock了实例之后啥都不能干其实不是。Start、Stop、Reboot这些生命周期的操作是不受lock限制的也就是说你锁定的实例照样可以正常开关机和重启。这个设计的逻辑是锁定的目的是防止误操作删除或改动配置而不是防止对实例做基本的启停管理。如果你想要的是那种禁止一切操作的锁Nova的lock是做不到的得靠其他的资源管理策略。4.2 locked状态与重建、删除、快照的冲突实际操作里最常见的case是实例被锁定然后你执行openstack server delete serverAPI直接返回403 Forbidden这其实就是在提醒你实例处于保护状态。如果你确定需要删除必须先把锁解开命令是nova unlock server管理员还可以用nova unlock --force server强制解锁想了解和管理这个状态的运维同学一定要记清楚这个细节。顺带说一个我在多租户环境中遇到的场景经常有用户创建完实例之后手动执行nova lock结果过了几个月要删除实例的时候怎么都删不掉然后开ticket找管理员。这个问题的本质不是安全问题而是用户对lock语义的理解有偏差。处理这种问题的时候管理员可以先openstack server show server看一眼locked字段确认之后用nova unlock --force server解锁然后再让用户去删除。这种情况在生产环境一个月能碰到好几起所以我把lock和unlock单独拎出来讲也是想提醒大家lock不是银弹用之前想清楚用途用之后也要记得公布规范的解锁流程。4.3 结合Start和Reboot一起用锁定状态下的运维套路运维上一套比较稳妥的组合拳是对核心数据库实例执行nova lock同时在变更窗口期内照常使用openstack server reboot --hard server重启实例再配合openstack server start启动实例。这样既保证了实例不会被某个手滑的操作误删又不影响正常的重启和维护。但有一点要特别强调locked状态对Libvirt层是不生效的。也就是说锁只是Nova API层面的行为约束如果你在计算节点上直接用virsh destroy去关掉对应的QEMU进程Lock是完全不知道的Nova只会通过后续的周期任务发现实例失联。所以在做底层的强制操作时一定要先评估实例的锁状态和业务影响不要觉得有了lock就万事大吉。同样的道理也适用于热迁移和跨节点迁移lock不会阻止你在底层直接移动或者破坏实例的磁盘文件。另外一个值得记录的经验是lock和reboot联动的并发问题。在Nova的高版本代码里API层对locked实例的校验顺序很多时候是放在状态校验之前的也就是先检查锁定再判断状态。这意味着即使实例处于ACTIVE状态如果它被锁了某些操作也会直接被拒。所以如果脚本里连续执行了lock、reboot、unlock要注意操作顺序对执行结果的影响我建议lock之后等实例状态稳定再执行其他操作不要一口气串起来。5. 常见问题排查技巧从热词场景到实战故障速查5.1 实例起不来日志又显示could not acquire lock(s)怎么办这个报错是OpenStack运维圈一个非常经典的热词。你在nova-compute.log或者libvirtd.log里看到could not acquire lock(s)时多数情况是Libvirt在尝试对磁盘镜像或者云硬盘加锁时失败。为什么会失败常见的原因有两类一是同一个磁盘设备被两个QEMU进程同时打开了这在共享存储环境中特别容易出现尤其是当你的实例被意外调度到两台计算节点上同时启动的时候二是Lock Manager的锁文件目录权限问题Libvirt默认把锁文件放在/var/lib/libvirt/lockd/files/下如果这个目录被误改权限或者磁盘空间满了也会导致锁获取失败。既然Lock报错了最直接的做法是先对实例做一次openstack server stop然后去计算节点上确认没有残留的QEMU进程在访问同一块磁盘用ps aux | grep qemu检查一下再用virsh list --all确认domain是否已经不存在。清理干净之后重新openstack server start绝大多数情况下就能恢复。如果你用的是Kolla部署的容器化环境还要记得lockd目录是在宿主机上挂载挂入容器的不要把宿主机路径和容器路径搞混。顺带说一个重要经验could not acquire lock(s)不一定意味着你的实例坏了很多时候它只是上一个没被清理干净的进程留下的锁文件残留。如果你确认没有其他进程在用同一块磁盘直接把锁文件删掉当然前提是确认实例已经停止就能解决。但这属于脏修复用之前要反复确认实例处于真正停止的状态如果实例还在运行你强行删锁会导致两个进程同时操作磁盘数据损坏的风险非常高。5.2 多架构虚拟机和QEMU启动失败的特殊场景近两年ARM架构的实例需求越来越大通过QEMU部署多架构虚拟机的场景也越来越多。在OpenStack里跑ARM实例通常需要底层计算节点支持x86模拟ARM也就是用qemu-system-aarch64这类二进制文件配合固件启动。如果你在Start一个ARM实例时报错不要先急着怀疑Nova的代码先检查两样东西一是计算节点上有没有对应的qemu-system-aarch64可执行文件二是Libvirt的domain XML里os标签下的loader和firmware路径是否正确。大多数多架构实例起不来的原因无非是镜像不够纯净、内核和initrd不配套、或者虚拟机的CPU类型和架构不匹配。排查的时候直接看QEMU进程的error输出往往是最快的然后进virsh edit检查一下XML里的CPU型号和machine type比如virt-4.0这种。如果对QEMU命令行不太熟建议先在宿主机上手动执行一次QEMU命令确认命令行本身没问题再用它反推Libvirt的XML配置怎么改。这个思路在目前的多架构部署场景里基本能解决90%以上的启动类问题。5.3 重启后网络不通别忘了检查端口绑定和DHCP实例reboot之后网络不通也是一个高频问题很多人第一时间想到的是安全组或者路由规则但在Nova的场景里还要加一步检查实例对应的Neutron端口与本机的绑定关系。在openstack server reboot --hard之后实例的VIF底层会重新plug一遍但极端情况下比如计算节点neutron-agent和nova-compute的顺序发生变化或者openvswitch的流表被刷掉端口可能没有重新绑到正确的bridge上。快速定位方法是这样在计算节点上执行ip link看tap设备是否存在然后virsh dumpxml里找到对应的interface typebridge配置确认target dev和本机实际网卡对得上。如果对不上先从neutron侧删除端口再重建试试或者重启neutron-openvswitch-agent服务。这种问题跟运维平台的联动关系比较大多踩几次坑自然就有感觉了。综合来说Start、Reboot、Lock这三个操作虽然基础但每一个都在Nova状态机的设计里占据着独特的位置。把这几个操作吃透你在处理日常运维故障时的排查思路就会清晰很多。我自己在跑生产环境的过程中最大的体会是要学会用状态机视角去看待实例而不是停留在命令层面。每一条指令的本质都是状态迁移的触发条件顺着状态变化的脉络去排查问题往往比淹死在日志里高效得多。下一篇我们可以接着聊Live Migration和Resize这类更高级的操作那些操作对状态机的依赖更深理解了本篇的基础下篇会更轻松。
返回列表