Nova 服务优雅关闭¶
https://blueprints.launchpad.net/nova/+spec/nova-services-graceful-shutdown
这是 backlog 规范,提出优雅关闭的设计方案。
Nova 服务无法优雅关闭。当服务停止时,它也会停止所有正在进行的操作,这不仅会中断正在进行的操作,还可能导致实例进入不希望或无法恢复的状态。想法是让服务停止处理新的请求,但在服务终止之前完成正在进行的操作。
问题描述¶
Nova 服务没有优雅关闭的方式意味着它们不会等待正在进行的操作完成。当启动关闭时,服务会等待 RPC 服务器停止,并等待消耗队列中的所有现有请求消息(RPC 调用/cast),但服务不会完成该操作。
每个 Nova compute 服务都有一个运行的 worker,并监听一个 RPC 服务器(topic: compute.<host>)。相同的 RPC 服务器用于新的请求以及正在进行的操作,其他 compute 或 conductor 服务通过它进行通信。当启动关闭时,停止 RPC 服务器意味着它将停止处理新的请求,这没问题,但同时它也会停止正在进行的操作所需的通信。例如,如果正在进行实时迁移,源 compute 和目标 compute 会多次以同步和异步方式相互通信。一旦 compute 服务上的 RPC 服务器停止,它就无法与另一个 compute 通信并导致实时迁移失败。这将导致系统以及实例进入不希望或无法恢复的状态
用例¶
作为操作员,我希望能够优雅地关闭(SIGTERM)Nova 服务,这样就不会影响用户的正在进行的操作,也不会使资源保持可用状态。
作为操作员,我希望即使服务被优雅终止(SIGTERM),也能保持实例和其他资源处于可用状态。
作为操作员,我希望能够在 Nova 服务在 k8s pod 中运行时,充分利用 k8s pod 优雅关闭的优势。
作为用户,我希望在服务被优雅终止(SIGTERM)之前完成正在进行的操作。
提议的变更¶
范围:拟议的解决方案是针对 SIGTERM 信号优雅关闭服务。
优雅关闭基于以下设计原则
当通过 SIGTERM 启动服务关闭时
不处理任何新的请求
新的请求不应丢失。一旦服务重新启动,它应该处理这些请求。
允许正在进行的操作尽快安全地终止,无论是完成还是中止。
正确记录正在进行的操作的状态
保持实例或其他资源处于可用状态
当服务关闭完成时
正确记录未完成的操作。理想情况下,所有正在进行的操作都应该在服务终止之前完成,但如果优雅关闭超时(由于配置的超时,稍后章节中添加超时详细信息),则应该正确记录所有未完成的操作。这将有助于恢复系统或实例。
当服务再次启动时
以正常方式开始处理新的请求。
如果由于关闭启动而未处理请求,则它们将保留在消息代理队列中,并且有多种可能性
请求可能已被该服务的其他 worker 选取。例如,您可以运行多个 Nova scheduler(或 conductor)worker。如果其中一个 worker 正在关闭,则其他 worker 将处理该请求。这对于 Nova compute 来说不是这种情况,Nova compute 对于特定主机始终只有一个 worker。
如果服务只有一个 worker 正在运行,则可以在服务启动后选取请求。
compute 服务有机会在 init_host() 期间清理或恢复实例上的中断操作。采取的行动将取决于任务及其状态。
如果服务长时间处于停止状态,基于 RPC 和消息队列超时,则存在以下可能性
RPC 客户端或服务器将超时调用。
消息代理队列可能会因超时而丢弃消息。
请求和消息的顺序可能会陈旧。
作为优雅关闭的目标,我们需要做两件事
一种停止新请求的方式,但不要中断正在进行的操作。建议通过 RPC 实现这一点。
为服务提供足够的时间来完成操作。作为第一步,建议通过基于时间的等待来实现,稍后通过适当的跟踪机制来实现。
此 backlog 规范建议分多个步骤实现上述目标。每个步骤将作为特定版本的独立规范提出。
已经优雅关闭的 Nova 服务:¶
对于以下服务,它们的优雅关闭由其部署服务器或使用的库处理。
Nova API & Nova metadata API
这些服务使用支持 WSGI 的服务器进行部署。该服务器将确保 Nova API 服务优雅关闭,这意味着它将完成正在进行中的请求并拒绝新的请求。
我使用 uWSGI/mod_proxy_uwsgi(devstack 环境)进行了调查。服务启动时,uWSGI 服务器会为 API 服务预先生成数量的 worker,这些 worker 将以分布式方式处理 API 请求。当通过 SIGTERM 启动关闭时,uWSGI 服务器 SIGTERM 处理程序会检查任何 worker 上是否有正在进行中的请求。它等待所有 worker 完成请求,然后终止每个 worker。一旦所有 worker 都被终止,它将终止 Nova API 服务。
如果在启动关闭后有任何新的请求,它将被拒绝并显示“503 服务不可用”错误。
测试
我测试了两种类型的请求
同步请求:‘openstack server list’
为了观察优雅关闭,我在 server list API 代码中添加了 10 秒的睡眠。
启动 API 请求 ‘request1’:
openstack server list等待 server list 请求到达 Nova API(您可以在 controller 中看到日志)
由于 sleep(10),server list 需要时间才能完成。
启动 Nova API 服务关闭。
启动新的 API 请求 ‘request2’:
openstack server list。此新请求在启动关闭后发出,因此应被拒绝。Nova API 服务将等待,因为 ‘request1’ 未完成。
‘request1’ 将在服务终止之前获得 server list 的响应。
‘request2’ 被拒绝并将收到错误“503 服务不可用”
异步请求:
openstack server pause <server>为了观察优雅关闭,我在 server pause API 代码中添加了 10 秒的睡眠。
启动 API 请求 ‘request1’:
openstack server pause server1等待 pause server 请求到达 Nova API(您可以在 controller 中看到日志)
由于 sleep(10),pause server 需要时间才能完成。
启动 Nova API 服务关闭。
服务将等待,因为 ‘request1’ 未完成。
Nova API 将向 Nova compute 服务发出 RPC cast 并返回。
‘request1’ 已完成,并将响应返回给用户。
Nova API 服务现在已终止。
Nova compute 服务正在操作 pause server 请求。
检查服务器是否已暂停
openstack server list您可以看到服务器已暂停。
Nova console proxy 服务:nova-novncproxy、nova-serialproxy 和 nova-spicehtml5proxy
所有控制台代理服务都作为 websockify.websocketproxy 服务运行。 websockify 库处理 SIGTERM 信号和优雅关闭,这足以满足 Nova 服务的需求。
当用户访问控制台时,websockify 库会在 start_service 中启动一个新进程,并调用 Nova new_websocket_client 。Nova 将授权 token,在主机和端口上创建一个 socket,该 socket 将用于发送数据/帧。之后,用户可以访问控制台。
如果启动了关闭请求,websockify 会处理该信号。首先,它将终止所有子进程,然后引发终止异常,最终调用 Nova close_connection 方法。Nova close_connection 方法首先在 socket 上调用 shutdown() 然后调用 close(),这确保在关闭 socket 之前发送剩余的数据/帧。
这样,用户控制台会话将优雅地终止,他们将收到“已断开连接”消息。服务启动后,用户可以刷新浏览器,如果 token 未过期,控制台将再次启动。
规范 1:通过 RPC 分割新的和正在进行中的请求:¶
RPC 通信是服务完成特定操作的重要组成部分。在关闭期间,我们需要确保保持所需的 RPC 服务器/总线处于启动状态。如果停止 RPC 通信,那么与服务终止没有什么不同。
Nova 实现,并且此规范讨论了很多关于 RPC 服务器 start、stop 和 wait,所以让我们从 oslo.messaging/RPC 资源的角度简要介绍它们,以便以一种简单的方式理解这个提议。你们中的大多数人可能已经知道这些,所以可以跳过此部分。
RPC 服务器
创建和 start()
它将在 oslo.messaging 侧创建所需的资源,例如 dispatcher、consumer、listener 和队列。
它将处理与所需的 exchange 的绑定。
stop()
它将禁用 listener 从队列中选取任何新消息的能力,但将已选取的 message 分发给 dispatcher。
它将删除 consumer。
它不会删除消息代理侧的队列和 exchange。
它不会停止 RPC 客户端向队列发送新消息,但是它们将不会被选取,因为 consumer 和 listener 已停止。
wait()
它将等待线程池完成分发所有已选取的 message。基本上,这将确保在 manager 上调用方法。
按服务分析和所需的 RPC 设计更改
以下服务与 Nova 服务的 RPC 服务器通信。由于它们没有自己的 RPC 服务器,因此不需要更改
Nova API
Nova metadata API
nova-novncproxy
nova-serialproxy
nova-spicehtml5proxy
Nova scheduler:不需要 RPC 更改。
请求处理:Nova scheduler 服务以多个 worker 运行,每个 worker 都有自己的 RPC 服务器,但所有 Nova scheduler worker 都以 fanout 方式侦听相同的 RPC topic 和队列
scheduler。当前,nova.service.py->stop() 调用 RPC 服务器上的 stop() 和 wait()。一旦 RPC 服务器停止,它将停止侦听任何新消息。但这不会影响其他 scheduler worker,它们将继续侦听相同的队列并处理请求。如果任何 scheduler worker 停止,则其他 worker 将处理该请求。
响应处理:每当有 RPC 调用时,oslo.messaging 都会创建一个与唯一 message id 关联的 reply 队列。此 reply 队列将用于将 RPC 调用响应发送给调用者。即使在此 worker 上停止 RPC 服务器,也不会影响 reply 队列。
我们仍然需要保持 worker 处于启动状态,直到通过 reply 队列发送所有响应,为此,我们需要在 scheduler 服务中实现正在进行中的任务跟踪,但这将在步骤 2 中处理。
这样,停止 Nova scheduler worker 不会影响 scheduler 服务上的 RPC 通信。
Nova conductor:不需要 RPC 更改。
Nova conductor 二进制文件是一个无状态服务,可以生成多个 worker 线程。Nova conductor 的每个实例都有自己的 RPC 服务器,但所有 Nova conductor 实例都侦听相同的 RPC topic 和队列
conductor。这允许 conductor 实例充当分布式 worker 池,以便停止单个 conductor 实例不会影响 conductor 实例池的 RPC 通信,从而允许其他可用的 worker 处理请求。每个 cell 都有自己的 conductor 池,这意味着即使停止一个或多个 conductor,RPC 通信仍将继续正常运行。请求和响应处理方式与调度器中提到的方式相同。
注意
本规范不涵盖conductor单worker的情况。这可能需要为conductor设计RPC,但需要进一步调查。
Nova compute:需要更改RPC设计
请求处理:Nova compute在每个主机上运行一个worker,每个主机上的compute都有自己的RPC服务器、监听器和单独的队列。它处理新的请求以及在同一RPC服务器上完成进行中的操作所需的通信。为了实现优雅的关闭,我们需要将新请求和进行中的操作的通信分开。这将通过在compute服务中添加一个新的RPC服务器来完成。
为了便于阅读,我们将为每个RPC服务器使用不同的术语
‘ops RPC服务器’:这将用于新的RPC服务器,它将用于完成进行中的请求,并在关闭期间保持运行。
‘新请求RPC服务器’:这将用于当前的RPC服务器,它用于新的请求,并在关闭期间停止。
‘新请求RPC服务器’每compute一个:此RPC服务器没有更改,但它将用于所有新请求,以便我们可以在关闭期间停止它并停止compute上的新请求。
‘ops RPC服务器’每compute一个
每个compute将有一个新的‘ops RPC服务器’,它将监听一个新的主题
compute-ops.<host>。使用compute-ops名称是因为它主要用于compute操作,但如果需要,可以使用更好的名称。它将使用与‘新请求RPC服务器’相同的传输层/总线和交换机。
它将创建自己的调度器、监听器和队列。
两个RPC服务器都将绑定到相同的端点(相同的compute管理器),以便来自任一服务器的请求都由相同的compute管理器处理。
该服务器主要用于compute-to-compute操作和服务器外部事件。目的是在关闭期间保持此RPC服务器运行,以便完成进行中的操作。
在关闭时,nova.service将等待compute告知它们是否已完成所有任务,以便它可以停止‘ops RPC服务器’并完成关闭。
响应处理:无论请求来自哪个RPC服务器,只要有RPC调用,oslo.messaging都会创建另一个与唯一消息ID关联的回复队列。此回复队列将用于将RPC调用响应发送给调用者。即使RPC服务器在此worker上停止,也不会影响回复队列。
Compute服务工作流程
SIGTERM信号由oslo.service处理,它将调用nova.service上的stop。
nova.service将停止‘新请求RPC服务器’,以便compute不接受任何新请求。‘ops RPC服务器’正在运行且处于活动状态。
nova.service将等待管理器发出信号,指示所有进行中的操作都已完成。
一旦compute向nova.service发出信号,它将停止‘ops RPC服务器’并继续服务关闭。
超时
oslo.service上存在一个现有的 graceful_shutdown_timeout 配置选项,可以为每个服务设置。
该选项用于超时服务停止,并且无论compute是否完成操作,都会停止服务。
RPC 客户端
RPC客户端保持为单例类,该类使用主题
compute.<host>创建,这意味着默认情况下消息将通过‘新请求RPC服务器’发送。如果任何RPC cast/call想要通过‘ops RPC服务器’发送消息,则需要在client.prepare()调用期间将
topic覆盖为compute-ops.<host>。哪些RPC cast/call将使用‘ops RPC服务器’将在实施期间决定,以便我们可以更好地判断哪些方法用于我们希望在关闭期间完成的操作。一个草案列表,我们可以在其中使用‘ops RPC服务器’
注意
此列表是草案,可以在实施期间更改。
迁移
实时迁移
注意
我们将使用‘新请求RPC服务器’用于check_can_live_migrate_destination和check_can_live_migrate_source方法,因为这是compute服务尚未开始live迁移的初始阶段。如果关闭在live迁移请求启动之前启动,则应拒绝迁移。
pre_live_migration()
live_migration()
prep_snapshot_based_resize_at_dest()
remove_volume_connection()
post_live_migration_at_destination()
rollback_live_migration_at_destination()
drop_move_claim_at_destination()
resize方法
冷迁移方法
服务器外部事件
重建实例
validate_console_port() 当控制台已经请求时,如果端口验证请求正在进行中,compute应在关闭之前完成它,以便用户可以获得他们请求的控制台。
基于时间的等待服务完成进行中的操作
注意
基于时间的等待是规范1中的临时解决方案。在规范2中,它将被替换为对进行中的任务的适当跟踪。
为了使优雅的关闭不那么复杂,规范1建议对服务进行可配置的基于时间的等待,以完成其操作。
等待时间应小于全局优雅关闭超时。这样,外部系统或oslo.service就不会在服务等待时间结束之前关闭服务。
它将可配置为每个服务。
默认值建议
compute服务:150秒,考虑到compute上的长期运行操作。
conductor服务:60秒应该足够了。
scheduler服务:60秒应该足够了。
PoC:此PoC显示了规范1提案的工作原理。
此提案将解决的一些特定关闭问题示例
迁移
迁移操作将使用‘ops RPC服务器’。
如果迁移正在进行中,则服务关闭不会终止迁移;而是能够等待迁移完成。
实例启动
实例启动操作将继续使用‘新请求RPC服务器’。否则,我们将无法停止新请求。
如果compute服务中的实例启动请求正在进行中,则关闭将等待compute成功启动它们。
如果在启动关闭后到达新的实例启动请求,则它将保留在队列中,compute将在重新启动后处理它。
任何到达compute的操作都将在服务关闭之前完成。
注意
根据我目前为止的PoC和手动测试,它不需要oslo.messaging方面的任何更改。
规范2:智能跟踪和等待进行中的操作:¶
以下服务的优雅关闭由其部署的服务器或库处理,因此不需要规范2的工作。
Nova API
Nova metadata API
nova-novncproxy
nova-serialproxy
nova-spicehtml5proxy
以下服务需要实施跟踪系统
Nova compute
Nova conductor
Nova scheduler
该提案是使服务等待时间基于跟踪进行中的任务。一旦服务完成任务,它们就可以向nova.service发出信号以继续关闭服务。基本上,这将用基于跟踪器的方法替换上述等待时间方法。
将引入一个任务跟踪器来跟踪进行中的任务。
它将是一个单例对象。
它维护一个‘方法名称’和
request-id列表。如果任务与实例相关,那么我们也可以添加实例UUID,这可以帮助过滤或了解特定实例上的哪些操作正在进行中。唯一的request-id将帮助跟踪对相同方法的多次调用。每当新的请求到达compute时,它将将其添加到任务列表中,并在任务完成后将其删除。对跟踪器的修改将在锁下完成。
一旦启动关闭
任务跟踪器将向跟踪器列表中添加新的任务或拒绝它们。将根据情况做出决定,例如,如果它们对于在关闭期间处理不重要,则拒绝任务。
在关闭期间,任何新的周期性任务都将被拒绝,但正在进行的周期性任务将被完成。
将拒绝和接受哪些任务的精确列表将在实施期间决定。
任务跟踪器将开始记录正在进行中的任务,并在它们完成时记录。基本上,记录关闭期间正在进行的事情的详细视图。
nova.service将等待任务跟踪器完成进行中的任务,直到超时。
RPC服务器停止、等待和任务跟踪器等待流程的示例如下
我们可以发出信号给任务跟踪器开始记录正在进行中的任务。
RPCserver1.stop()
RPCserver1.wait()
manager.finish_tasks():等待管理器完成进行中的任务。
RPCserver2.stop()
RPCserver2.wait()
规范3:Nova操作的安全终止点:¶
在优雅关闭时,所有进行中的操作都应到达其安全终止点,即完成或中止。
这需要根据操作类型和它们所处的阶段来完成。有些操作,例如预复制live迁移、冷迁移、resize、snapshot或shelve_offload可以安全地中止,并带有适当的日志记录和异常类型。用户可以在服务启动后再次请求它们。
有些操作,例如后复制live迁移,如果VM已经移动到目标compute,则很难中止。这可能需要某种方式将VM恢复到源或让它完成。
此规范的范围是调查和审核所有操作,并将它们分类为‘可以中止’和‘等待完成’。据此,优雅关闭需要实施逻辑来中止或继续等待操作完成。
优雅关闭超时:¶
Nova服务超时
oslo.service已经具有一个超时 (graceful_shutdown_timeout),可以为每个服务配置,并用于超时SIGTERM信号处理程序。
oslo.service将基于 graceful_shutdown_timeout 终止Nova服务,即使Nova服务优雅关闭尚未完成。
不会为Nova添加新的可配置超时,而是将使用现有的 graceful_shutdown_timeout。
其默认值为60秒,这对于Nova服务来说太短了。建议为每个Nova服务覆盖其默认值
compute服务:180秒(考虑到长期运行的任务)。
conductor服务:80秒
scheduler服务:80秒
外部系统超时
根据Nova服务的部署方式,可能存在外部系统(例如,Nova在k8s pod上运行)的优雅关闭超时。这可能会影响Nova的优雅关闭,因此我们需要清楚地记录如果存在外部系统超时,则Nova服务的超时 graceful_shutdown_timeout 应相应设置。外部系统超时应高于 graceful_shutdown_timeout,否则外部系统将超时并中断Nova的优雅关闭。
备选方案¶
RPC重新设计的另一种替代方案是处理每个RPC服务器的两个主题。这需要在oslo.messaging框架以及驱动程序实现中进行大量的更改。想法是允许oslo.messaging Target接受多个主题(将主题作为列表)并要求驱动程序为每个主题创建单独的消费者、监听器、调度器和队列。为每个主题创建每个主题绑定到交换机。这还需要oslo.messaging提供一种新的方式让RPC服务器取消订阅特定主题并继续侦听其他主题。我们还需要重新设计RPC服务器stop()和wait()的工作方式。这太复杂了,几乎是重新设计oslo.messaging RPC概念。
另一种替代方案是跟踪并停止来自Nova api或scheduler服务的请求,但这将无法停止所有新请求(compute到compute任务)或让进行中的事情完成。
数据模型影响¶
无
REST API 影响¶
无
安全影响¶
无
通知影响¶
无
其他最终用户影响¶
这应该对最终用户产生积极影响,以便关闭不会停止他们正在进行的操作。
性能影响¶
对正常操作没有影响,但服务关闭将花费更多时间。有一个可配置的超时来控制服务关闭等待时间。
其他部署者影响¶
除了更长的关闭过程外,没有其他影响,但可以配置服务关闭等待时间的适当超时。
开发人员影响¶
无
升级影响¶
添加新的RPC服务器将影响升级。旧的compute将没有监听RPC_TOPIC_OPS主题的新‘ops RPC服务器’,因此我们需要使用RPC版本控制来处理它。如果RPC客户端检测到旧的compute(基于version_cap),它将回退到将消息发送到原始RPC服务器(侦听RPC_TOPIC)。
实现¶
负责人¶
主要负责人
gmaan
其他贡献者
无
功能联络人¶
gmaan
工作项¶
在compute服务上实施‘ops RPC服务器’
使用‘ops RPC服务器’进行我们需要在关闭期间完成的操作,例如compute-to-compute任务和服务器外部事件。
由于升级影响,RPC版本控制。
在服务中实施任务跟踪器以跟踪和报告关闭期间进行中的任务。
依赖项¶
目前没有依赖项,但我们将查看实施期间是否需要对oslo.messaging进行任何更改。
测试¶
我们无法编写tempest测试,因为tempest无法停止服务。
我们可以尝试(使用一些需要时间的长live迁移)在‘post-run’阶段进行一些测试,就像对evacuate测试所做的那样。
将添加单元和功能测试。
文档影响¶
优雅关闭的工作原理将记录在案,以及其他注意事项,例如考虑用于优雅关闭的超时或等待时间。
参考资料¶
PoC
PTG讨论
历史¶
发布名称 |
描述 |
|---|---|
2026.1 Gazpacho |
引入 |