使用 Cinder 的新 Attach/Detach API

https://blueprints.launchpad.net/nova/+spec/cinder-new-attach-apis

使 Nova 使用 Cinder 的新 attach/detach API。

问题描述

在尝试实现 Cinder 多重挂载并尝试使所有驱动程序都能正常工作的实时迁移时,已经清楚 Cinder 和 Nova 的交互不够清晰,这导致了两个项目之间交互的错误和问题,以及在尝试演进这种交互时出现的问题。

让我们创建一个 Nova 和 Cinder 之间的新干净接口。

您可以在这里查看有关新 Cinder API 的详细信息:https://specs.openstack.org/openstack/cinder-specs/specs/ocata/add-new-attach-apis.html

用例

主要的 API 操作包括

  • 将卷附加到实例,包括在生成实例期间,以及调用 os-brick 将卷后端(可选)连接到 hypervisor。连接是可选的,因为当主机到卷后端存在共享连接时,后端可能已经附加了。

  • 从实例分离卷,包括(可选)调用 os-brick 将卷从 hypervisor 主机断开连接。

  • 实时迁移实例,涉及在启动实时迁移之前在目标主机上设置卷连接,然后在实时迁移完成后删除源主机连接。如果回滚,则删除目标主机连接。

  • 迁移和调整大小从这个新的视角来看,与实时迁移非常相似。

  • 撤离,我们知道旧主机不再运行,我们需要将卷附加到新主机。

  • 搁置,我们希望卷在逻辑上附加到实例,但我们也需要在实例卸载时将其从主机分离。

  • 附加/分离附加到搁置实例的卷

  • 使用交换卷在两个不同的 Cinder 后端之间迁移卷。

特别是,请注意

  • 卷附加特定于主机 uuid、实例 uuid 和卷 uuid

  • 当卷标记为 multi_attach=True 时,您可以对同一卷进行多次附加,到不同的实例(在同一主机或不同主机上)。

  • 对于相同的实例 uuid 和卷 uuid,即使 multi_attach=False,您也可以在两个不同的主机上拥有连接。这通常用于移动虚拟机时。

  • 主机上的卷连接可以与其他连接到相同卷后端的卷共享,具体取决于所选驱动程序。因此,需要小心删除该连接,不要错误地添加两个连接,也不要过早地删除正在使用的连接。Cinder 需要向 Nova 提供额外的信息,特别是对于每个附加项,如果连接是共享的,如果是,则该连接当前与谁共享。

提议的变更

Cinder 现在具有两种不同的 attach/detach API 流。我们需要一种在不影响现有实例的情况下从旧 API 切换到新 API 的方法。

首先,我们需要确定何时使用新 API 是安全的。我们需要配置 Cinder v3 API,并且该端点应该具有微版本 v3.27。此外,我们应该仅在所有 nova-compute 节点都升级后才使用新 API。我们可以通过查找与我们添加对新 Cinder API 的支持相关的最低服务版本来检测这一点。请注意,这意味着我们可能需要增加服务版本,以便我们可以显式检测对新 Cinder API 的支持。

如果允许使用新 API,我们可以将其用于所有新的附加项。在添加新附加项时,我们

  • (api) 调用 attachment_create,不带 connector,在 API 调用返回之前。BDM 记录使用 attachment_id 更新。请注意,如果卷不是 multi_attach=True,它只会允许将一个 instance_uuid 与每个卷关联。虽然长期目标是启用 multi_attach,但此规范不会附加到任何具有 multi_attach=True 的卷。虽然我们仍然可以对该卷进行单个附加,但由于依赖 cinder 来限制卷的附加数量,为了安全起见,在 Nova 中完全实现该支持之前,我们不应允许任何附加项,如果 multi_attach=True。

  • (compute) 获取 connector 信息并使用它来调用 attachment_update。API 现在返回所有需要提供给 os-brick 以附加卷后端的信息,以及如何将 VM 附加到该连接到卷后端的连接方式。

  • (compute) 在实际连接到卷之前,我们需要等待卷准备就绪并完全配置。如果等待卷准备就绪超时,我们在这里失败并删除附加项。如果这是实例的首次启动,这将使实例进入 ERROR 状态。如果卷准备就绪,我们可以继续进行附加过程。

  • (compute) 使用 os-brick 连接到卷后端。如果有任何错误,请尝试调用 os-brick disconnect(以双重检查是否已完全清理),然后删除 Cinder 中的附加项。如果在回滚过程中出现任何问题,请将实例置于 ERROR 状态。

  • (compute) 现在后端已连接,并且卷已准备就绪,我们可以以通常的方式将后端连接附加到 VM。

对于分离

  • (compute) 如果 BDM 中设置了 attachment_id,我们使用新的分离流程,否则我们回退到旧的分离流程。新的流程是…

  • (api) 正常的检查以查看请求是否有效

  • (compute) 从 VM 分离卷,如果失败则在此处停止请求

  • (compute) 调用 os-brick 从卷后端断开连接

  • (compute) 如果成功,则调用 attachment_remove。如果出现错误,我们添加一个实例故障并将实例置于错误状态。

如上所述,我们可以使用 BDM 中 attachment_id 的存在来确定附加项是使用新流程还是旧流程进行的。从长远来看,我们希望将所有现有附加项迁移到新的样式附加项,但这留给以后的规范。

实时迁移

在实时迁移期间,我们首先通过确保卷已附加到源主机和目标主机来启动该过程。当卷为 multi_attach=False 时,并且我们即将开始实时迁移 VM1 时,您会遇到如下情况

+-------------------+   +-------------------+
|                   |   |                   |
| +------------+    |   | +--------------+  |
| |VM1 (active)|    |   | |VM1 (inactive)|  |
| +---+--------+    |   | +--+-----------+  |
|     |             |   |    |              |
|     | Host 1      |   |    |  Host 2      |
+-------------------+   +-------------------+
      |                      |
      +-----------+----------+
                  |
                  |
     +---------------------------+
     |            |              |
     |  +---------+---------+    |
     |  | VolA              |    |
     |  +-------------------+    |
     |                           |
     |    Cinder Backend 1       |
     |                           |
     +---------------------------+

请注意,在 cinder 中,我们将为这个 multi_attach=False 卷最终得到两个附加项

  • 附加项 1:VolA,VM1,主机 1

  • 附加项 2:VolA,VM1,主机 2

从逻辑上讲,我们对同一个非 multi-attach 卷有两个附加项。这两个附加项都与 vm1 相关,但对于实时迁移期间的源主机和目标主机,都有一个附加项。请注意,这两个附加项都与相同的实例 uuid 相关联,这就是为什么即使 multi_attach=False,也允许这两个附加项的原因。

如果实时迁移成功,我们将删除附加项 1(即源主机附加项,主机 1),并只剩下附加项 2(即目标主机附加项,主机 2)。如果在源主机上 os-brick 断开连接时出现任何故障,我们将实例置于 ERROR 状态,并且不在 Cinder 中删除附加项。我们这样做是为了向操作员发出信号,表明需要手动修复某些内容。我们还将迁移置于错误状态,就像即使回滚干净也会发生的那样。

如果实时迁移由于中止或类似原因而失败,我们将执行上述操作的相反操作。我们尝试在主机 2 上 os-brick 断开连接。如果成功,我们删除附加项 2,否则将实例置于 ERROR 状态。如果回滚成功,我们将回到一个附加项,但在这种情况下,它是附加项 1。

因此,对于在 BDM 中具有 attachment_id 的卷,我们遵循 Cinder 的这种新的 API 调用流程

  • (destination) 获取 connector,并创建新的附加项

  • (destination) 附加卷后端

  • (source) 启动实时迁移

如果实时迁移成功

  • (source) 调用 os-brick 断开连接

  • (source) 如果成功,则删除附加项,否则将实例置于 ERROR 状态

如果实时迁移由于中止或类似原因而回滚

  • (destination) 调用 os-brick 断开连接

  • (destination) 如果成功,则删除附加项,否则将实例置于 ERROR 状态

迁移

与实时迁移类似,在迁移开始时,我们对源节点和目标节点都有附加项。在调用确认调整大小时,我们在源主机上执行分离,在目标主机上执行 revert resize 和分离。

撤离

当您调用撤离时,并且卷在 BDM 中具有 attachment_id,我们遵循此新的流程

  • (source) 源主机上什么也不发生,假设管理员已经围住了主机,并通过调用 force host down 确认了这一点。

  • (destination) 为任何附加卷创建第二个附加项,该附加项针对此 instance_uuid。

  • (destination) 遵循通常的卷附加流程

  • (destination) 现在删除旧的附加项,以确保 Cinder 清理与该连接相关的任何资源。这类似于我们今天调用 terminate_connection 的方式,除了我们必须在创建新的附加项后调用此项,以确保在整个撤离过程中始终将卷保留给该实例。

  • (operator) 如果源主机永远不会启动,则检测到已被撤离的实例(使用在调用撤离时创建的迁移记录)。这可能会导致 os-brick 未清理某些内容,但这相当安全,并且我们所处的状况与今天没有比糟糕。

搁置和取消搁置

当附加到实例的卷在 BDM 中具有 attachment_id 时,我们遵循对 Cinder API 的这种新的调用流程。请注意,一个实例获取搁置时,可以同时具有旧流程和新流程的卷附加到该实例。

在从旧主机卸载时,我们首先添加一个新的附加项(不设置 connector),然后以通常的方式执行旧附加项的分离。这确保了卷仍然附加到实例,但已安全地从我们正在卸载的主机分离。如果该分离失败,则实例应移动到 ERROR 状态。

同样,在取消搁置时,我们使用 connector 更新现有的附加项,然后继续使用通常的附加卷流程。

交换卷

对于交换卷,我们有一个主机、一个实例、一个设备路径,但有多个卷。

在本节中,我们讨论如果正在交换的卷在 BDM 中存在 attachment_id,因此我们遵循新的流程会发生什么。

首先,是 cinder 调用我们的 API 时的流程,其次是用户调用我们的 API 时的流程。这里涵盖了两种流程

  • 调用 Nova 交换卷 API 以交换 uuid-old 与 uuid-new

    • 新卷可能由用户在 cinder 中创建,并且用户可能发出了 Nova API 调用。

    • 或者,用户可能调用了 Cinder 的 migrate volume API。这意味着 cinder 创建了新卷,并代表用户调用 Nova API。

  • (api) 为卷 uuid-new 创建新的附加项,如果无法创建该附加项则使 API 调用失败

  • (compute) 使用 connector 更新 cinder 附加项 uuid-new

  • (compute) os-brick 连接新卷。如果出现错误,我们像在附加期间失败一样处理此错误,并删除新卷的附加项

  • (compute) Nova 将卷 uuid-old 的内容复制到卷 uuid-new,在 libvirt 中,这是通过 rebase 操作完成的

  • (compute) 一旦复制完成,我们就从实例分离 uuid-old

  • (compute) 更新 BDM,以便 attachment_id 现在指向与 uuid-new 关联的附加项

  • (compute) 一旦旧卷分离,我们就执行 os-brick 断开连接

  • (compute) 如果成功,我们调用 cinder 的 migrate_volume_completion,参数为 (uuid-new, uuid-old)。如果断开连接失败,我们将实例置于 ERROR 状态。

  • (compute) 使用 migrate_volume_completion 返回的内容更新 BDM 中的新 volume-uuid。请注意,如果 cinder 调用了交换,它将删除旧卷,但重命名新卷以具有与旧卷相同的 uuid。如果有人调用 Nova,我们会得到 uuid-new,并更新 BDM 以反映更改。

  • 因此,成功后,我们创建了对新卷的新附加项,并删除了对旧卷的附加项。

请注意,如果卷是 multi-attach,则交换操作将失败并且不允许。无论是由 Cinder 还是 Nova 启动,情况都将如此。随着时间的推移,我们可能会迁移到使用 attachment_id 而不是卷 id 的 Cinder 的 migrate_volume_completion API。此规范没有研究支持 multi-attach 需要什么,但这个问题似乎值得在此处注意。

备选方案

我们可能会在修复错误方面陷入“打地鼠”的困境。

我们应该以几种方式构建 API 交互。关键的替代方案之一是在 API 中添加大量的状态机复杂性,以便共享连接相关的锁定由 Cinder 在 API 层处理。虽然这使客户端更复杂,但似乎让 Nova 和其他客户端执行上述锁定更简单。

Nova 可以查找 attachment uuid 而不是将其存储在 BDM 中,在主机 uuid 未设置的期间,似乎更安全地存储 attachment uuid 以避免围绕哪个附加项与每个 BDM 关联的任何可能混淆。

在实时迁移期间,我们可以将额外的 attachment_id 存储在迁移数据中,而不是作为 BDM 的一部分。

我们可以继续将 connection_info 保存在 BDM 中,以便在分离卷时使用。虽然这似乎可能有助于避免 Nova 未收到通知的 connection_info 更改引起的问题,但这实际上是一种过早的优化。我们应该与 Cinder 和 os-brick 合作,以正确修复任何此类交互问题,从而帮助所有使用 Cinder 的系统。

数据模型影响

当使用新的 API 流程时,我们不再需要存储 connection_info,因为我们不需要将其传回给 Cinder。相反,我们只存储每个主机附加到的卷的 attachment_id,并且每当我们需要 connection_info 时,我们都从 Cinder 获取它。

当填充 attachment_id 时,我们使用新的流程来执行所有附加或分离操作。如果没有填充,我们使用旧流程。

REST API 影响

Nova 的 REST API 无变更。

安全影响

Nova 不再需要存储卷连接信息,但现在可以随时从 Cinder API 获取。

通知影响

无。

其他最终用户影响

无。

性能影响

不应影响性能。这里的重点是所有驱动程序的稳定性。Nova 和 Cinder 之间的 API 调用可能会略多,但预计不会显著影响性能。

其他部署者影响

要使用这种更稳定的 API 交互以及将依赖于此工作的新功能,必须将 Cinder 升级到支持新 API 的版本。

预计在完成此工作后的两个发布周期内,我们将停止支持旧版本的 Cinder。

开发人员影响

Nova 和 Cinder 的交互应该更容易理解。

实现

负责人

主要负责人

Matt Riedemann

其他贡献者

Lee Yarwood John Griffith

工作项

为了在本周期取得进展,我们需要将这项工作分解为小的补丁。总体策略是,我们最后实现新的连接方式,并且所有其他操作都依赖于 BDM 中包含 attachment_id,在连接代码合并之前,这不会成立。

  • 使用 Cinder v3 API

  • 检测是否包含新的 BDM 支持的微版本

  • 分离新的 BDM/卷连接

  • 重启 / 重建(使用 attachment_id 从 cinder 获取连接信息)

  • 实时迁移

  • 迁移

  • 撤离

  • 搁置和恢复

  • 交换卷

  • 连接(这意味着我们现在公开了所有以前的功能)

请注意,在支持多连接之前还有更多步骤,但这些内容留待未来的规范说明。

  • 将旧的 BDM 迁移到新的 BDM 流程

  • 添加对共享后端连接的显式支持

依赖项

依赖于 Cinder 的工作来添加新的 API。这在 Ocata 中已完成。

测试

我们需要对旧的和新的 Cinder 交互进行功能测试。这可能需要一个 grenade 作业,在升级之前将一个卷连接到实例,以便在升级后可以断开连接。

文档影响

我们需要添加关于更新的 Nova 和 Cinder 交互的良好开发者文档。

参考资料

历史

修订版

发布名称

描述

Pike

引入