Cinder Volume Active/Active 支持 - Manager 本地锁

https://blueprints.launchpad.net/cinder/+spec/cinder-volume-active-active-support

目前 cinder-volume 服务只能以 Active/Passive HA 方式运行。

其中一个原因是 Volume 节点上有多个本地锁,用于对同一资源的特定操作进行互斥。这些锁需要在同一集群的节点之间共享、移除或替换为数据库操作。

本规范提出了一种不同的机制来替换这些本地锁。

问题描述

Volume 节点上存在锁,以防止例如删除正在用于创建另一个 Volume 的 Volume,或附加已经正在附加的 Volume 等情况。

不幸的是,这些锁是本地于节点的,这在仅支持 Active/Passive 配置时有效,但在 Active/Active 配置中,当有多个节点时则不然,因为锁无法保证其他节点上操作的互斥性。

我们有用于不同目的的各种锁,但我们将使用相同的机制来允许它们处理 Active/Active 集群。

锁列表如下

  • ${VOL_UUID}

  • ${VOL_UUID}-delete_volume

  • ${VOL_UUID}-detach_volume

  • ${SNAPSHOT_UUID}-delete_snapshot

我们正在为 Cinder 中的锁定方法(针对 manager 和驱动程序)添加一个抽象层,使用 Tooz,如 Tooz locks for A/A 中解释的那样,它将默认使用本地文件锁,并将在 Active-Active 配置中使用 DLM。

但有些情况下,驱动程序不需要分布式锁即可工作,它们可能只需要本地锁,而我们会强制它们仅为 manager 中的这几个锁安装 DLM,这可能被认为有些极端。

用例

需要始终保持云运行或具有更高吞吐量要求的运营商希望能够配置其部署为 Active/Active 配置,并具有与当前相同的行为。但他们不希望在使用的驱动程序不需要分布式锁即可在 Active-Active 模式下运行的情况下,仅为 4 个锁安装 DLM。

此外,在使 Cinder 能够在 OpenStack 之外作为 SDS 工作,我们不再能够强制要求必须存在 DLM(就像在 OpenStack 中可以做的那样),因此,如果存储后端驱动程序不需要分布式锁,Cinder 能够在 Active-Active 配置中无需 DLM 即可工作,这更有意义。

提议的变更

本规范建议修改 Tooz locks for A/A 引入的行为,针对驱动程序不需要分布式锁的情况。因此,我们将使用驱动程序中的本地文件锁(如果它们使用任何锁),并为 manager 中的锁使用基于 workers 表的锁定机制,该表是由 HA A/A Cleanup specs 引入的。

这种新的锁定机制将是本地锁和分布式锁的混合体。

通过使用类似于本地文件锁的锁定机制,我们将能够模拟相同的行为

  • 确保同一集群的不同节点以及节点本身之间的互斥性。

  • 请求排队。

  • 节点崩溃时的锁释放。

  • 不需要在系统上安装额外的软件。

  • 不需要运营商特定的配置。

为了使用 workers 表确保互斥性,我们需要添加一个新的字段,名为 lock_name,它将存储当前正在 Volume 的 manager 上执行的操作(大多数情况下是方法名称,因为表已经具有资源类型和 UUID),并用于锁定。

新的锁定机制将使用添加的 lock_name 字段来检查 workers 表是否已经为特定节点集群的该 lock_name 资源存在条目,在这种情况下,锁将被获取,我们需要等待并在稍后重试,直到锁被释放 - 行已被删除 - 并且我们可以自己插入该行来获取锁。这意味着,在附加 Volume 的情况下,只有在集群中没有针对 Volume ID 和操作 volume_attach 的 DB 条目时,我们才会继续进行附加操作。

为了确保互斥性,这种锁检查和获取需要是原子的,因此我们将使用与我们删除 API races 时使用的条件更新(比较并交换)相同的方式进行条件插入“锁”。插入查询将根据特定限制进行条件设置,在这种情况下,条件将是 lock_nameclustertype。如果无法插入该行,则锁获取失败,我们需要等待;如果成功插入该行,则我们已获取锁,可以继续执行操作。

这个尝试获取、失败、等待和重试的过程与我们今天使用本地文件锁时完全相同。使用外部同步的 synchronize 方法将尝试使用相对较小的超时时间获取文件锁,如果失败,它将继续重试,直到获取锁为止。

因此,通过模拟与文件锁相同的行为,我们保留了当前操作的排队,并且没有改变我们的 API 行为和一些外部脚本可能依赖的“隐式 API 合同”。

由于我们将使用数据库进行锁定,运营商不需要向其系统添加任何软件或执行特定的系统配置。

重要的是要注意,这些新锁的超时将由清理过程本身处理,因为当不再接收到心跳时,行将被从数据库中删除,从而释放锁并防止资源被卡住。

仔细查看前面提到的 4 个锁,我们可以将它们分为 2 类

  • 操作的资源锁。

    • ${VOL_UUID}-detach_volume - 用于 detach_volume,以防止多次同时分离

    • ${VOL_UUID} - 用于 attach_volume,以防止多次同时附加

  • 防止删除 Volume 创建源的锁(它们由 create_volume 方法创建)

    • ${VOL_UUID}-delete_volume - 用于 delete_volume

    • ${SNAPSHOT_UUID}-delete_snapshot - 用于 delete_snapshot

对于操作资源锁(附加和分离),清理方法已经插入了数据库中的行,因此我们将重用相同的行并有条件地写入 lock_name 字段以检查锁是否可用。

对于防止删除的锁,我们需要自己添加该行,因为清理没有在 workers 表中为这些资源添加行,因为它们不需要任何清理。

备选方案

我们可以使用 DLM,它是本地锁的备用方案,但有些运营商表示担心向其系统和职责添加这种负担,因为他们正在使用不需要 Active-Active 锁的驱动程序,并且更愿意避免将 DLM 添加到他们的系统中。

与其对防止删除资源的锁使用新的锁定机制,我们可以向条件更新(用于防止 API Races 的条件更新)添加一个过滤器,该过滤器将防止我们删除正在用作 Volume 源的 Volume 或 Snapshot,并在尝试删除此类 Volume/Snapshot 时添加适当的响应错误。

数据模型影响

workers 表添加一个新的字符串字段,名为 lock_name

REST API 影响

安全影响

通知影响

其他最终用户影响

性能影响

将本地文件锁更改为数据库调用的微小但必要的性能影响。

其他部署者影响

开发人员影响

实现

负责人

主要负责人

Gorka Eguileor (geguileo)

其他贡献者

欢迎大家参与贡献

工作项

  • workers 表添加 lock_name 字段。

  • 修改 Cinder 的新的锁定方法/装饰器以处理混合行为。

依赖项

HA A/A 的清理:https://review.openstack.org/236977
  • 我们需要新的 workers 表和清理机制。

删除 API Races:https://review.openstack.org/207101/
  • 我们需要到位比较并交换机制,用于 Volume 和 Snapshot 删除,以便我们可以添加所需的过滤器。

测试

新锁定机制的单元测试。

文档影响

这需要正确记录,因为这种锁定机制不适用于所有驱动程序。

参考资料

HA A/A 的通用描述:https://review.openstack.org/#/c/232599/

HA A/A 的清理:https://review.openstack.org/236977

删除 API Races:https://review.openstack.org/207101/