Cinder Volume Active/Active 支持 - 作业分发

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

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

其中一个原因是,我们没有一个概念来定义处理相同存储后端的一组节点。

此规范向 Cinder 引入了集群的概念,旨在为 Cinder 的 API 和调度器节点提供一种将作业分发到具有 Active/Active 配置的高可用性部署中的 Volume 节点的方法。

问题描述

目前 cinder-volume 服务仅接受 Active/Passive 高可用性配置,并且当前作业分发机制不允许将多个服务分组在一个集群中,其中作业可以排队由这些节点中的任何一个处理。

作业当前使用基于主题的消息队列进行分发,该队列由 volume_topicscheduler_topicbackup_topic 前缀与主机名以及(如果节点是多后端节点,例如 cinder-volume.localhost@lvm)后端名称连接而成,这是将作业发送到 Volume 节点的机制,无论处理作业的节点的物理地址如何,从而简化了故障转移过程。

所选解决方案必须向后兼容,并允许新的 Active/Active 配置有效地发送作业。

在 Active/Active 配置中,可以有多个 Volume 服务 - 这并非一直都是强制性的,因为故障可能会导致我们只有 1 个活动服务 - 具有不同的 host 配置值,这些值可以互换地接受处理相同存储后端的作业。

用例

具有严格要求、SLA 或其他原因希望其云始终运行或具有更高吞吐量要求的操作员将希望能够配置其部署为 Active/Active 配置。

提议的变更

基本机制

为了提供一种将作业分发到一组节点机制,我们将添加一个新的 cluster 配置选项,该选项将唯一标识共享相同存储后端并因此可以互换地接受相同 Volume 作业的一组 Volume 节点。

host 选项不同,此新的配置选项允许在多个 Volume 节点上具有相同的值,唯一的条件是共享相同值的节点也必须共享相同的存储后端,并且它们也必须共享相同的配置。

默认情况下 cluster 配置选项将未定义,但当给定一个字符串值时,将在消息代理上创建一个新的主题队列,以分发旨在用于该集群的作业,形式为 cinder-volume.cluster@backend,类似于现有的主机主题队列 cinder-volume.host@backend

重要的是要注意,cluster 配置选项不是 host 选项的替代品,因为两者将同时存在于服务中,并且对于 Active-Active 配置而言必须存在。

为了能够确定 RPC 调用者必须发送操作的主题队列,我们将向当前具有我们用于非 Active/Active 配置的 host 字段的任何资源 DB 表中添加 cluster_name 字段。 这样,我们就不需要检查 DB 或在内存中保留缓存来确定该服务包含在哪个集群中(如果它位于集群中)。

一旦集群主题队列上接收 RPC 调用的基本机制到位,如果资源位于集群中(由 cluster_name 资源字段中的值指示),操作将逐步移动以支持 Active-Active。

选择这种渐进式方法而不是一蹴而就的方法的原因是减少添加新错误的可能,并通过仅回滚特定补丁来促进快速修复。

此解决方案清楚地区分了独立服务和属于集群的服务,以及属于集群的资源。

为了便于将服务包含到集群中,Volume 管理器将在 cluster 值从未定义变为具有值时检测到,然后通过填充 cluster_name 字段将所有现有资源包含到集群中。

拥有两个消息队列,一个用于集群,一个用于服务,在未来可能有用,如果我们想要添加可以针对集群内的特定服务的操作。

心跳

在 Active/Passive 配置中,只要我们没有从服务收到有效的 heartbeat,存储后端服务就会停止工作,如果收到 heartbeat,则会启动。这些 heartbeat 在 DB 的 services 表中报告。

在 Active/Active 配置中,如果构成集群的任何服务都没有有效的 heartbeat,则服务将停止工作,如果至少有一个有效的 heartbeat,则会启动。

服务将继续以它们现在正在执行的方式报告它们的 heartbeat,并且调度器将负责区分单个服务和集群服务,并按集群名称汇总后者。

REST API 影响 中所述,API 将能够显示集群信息以及每个集群的状态(启动或停止),基于属于该集群的服务。

禁用

对于位于集群中的服务,此新机制将更改“禁用工作单元”从服务到集群。 这意味着一旦所有通过调度器进行的作业都已移动以支持 Active-Active 配置,我们将无法禁用属于集群的单个服务,并且必须禁用整个集群。 对于非集群服务,禁用将像往常一样工作。

禁用集群将阻止调度器在筛选和加权期间考虑该集群,因此其所有服务,并且该服务仍然可以访问所有不通过调度器进行的作业。

有理由认为,有时我们需要清空节点以将其从集群中删除,但此规范及其实现不会添加任何新的机制来实现此目的。 因此,应使用现有的机制(使用 SIGTERM)来执行 cinder volume 服务的正常关闭。

当前的正常关闭机制将确保在等待正在进行的作业完成后停止之前,不会从消息队列接收到任何新的作业。

重要的是要记住,正常关闭有一个超时时间,如果操作时间超过配置值,将强制停止操作。 配置选项称为 graceful_shutdown_timeout,位于 [DEFAULT] 部分,默认值为 60 秒;因此,如果认为这对于我们的用例来说不够长,我们应该在我们的部署中配置它。

功能

所有 Volume 服务都会定期向调度器报告其功能,以使其了解其统计信息,以便它们可以做出明智的决策,确定在何处执行操作。

与服务状态报告类似,我们需要防止在更新此信息时并发访问数据结构。 幸运的是,我们在调度器上将此信息存储在 Python 字典中,并且由于我们对 RPC 服务器使用 eventlet 执行器,因此我们不必担心使用锁,执行器的固有行为将防止并发访问字典。 因此,无需进行任何更改即可对数据结构进行独占访问。

虽然很少见,但我们可能会在 Volume 服务之间出现一致性问题,其中不同的调度器不会对给定的后端拥有相同的信息。

当每个给定后端只有 1 个 Volume 服务报告时,这种情况不会发生,因为接收到的功能报告始终是最新的,并且所有调度器服务都保持同步。 但是现在我们有多个 Volume 服务报告相同的后端,我们可能会在不同的调度器上以不同的顺序接收来自不同 Volume 服务的两个报告,从而导致每个调度器上的数据不同。

我们无法保证所有调度器都将在其内部结构中存储相同的功能的原因是,功能报告可以在不同的服务上以不同的顺序处理。 顺序在几乎所有阶段都已保留,Volume 服务按特定顺序报告,消息代理保留此顺序,甚至以相同的顺序传递,但当每个服务处理它们时,我们可能会在不同的调度器服务上以不同的顺序执行 greenthreads,从而导致每个服务上的数据不同。

这种情况可能可以忽略,因为它非常罕见并且差异很小,但为了调度器服务上后端功能的一致性,我们将会在 Volume 服务在发送到调度器之前对功能进行时间戳,而不是像现在这样在调度器上进行时间戳。 然后,我们将让调度器丢弃任何比数据结构中的旧版本旧的功能。

通过进行此更改,我们促进了与功能报告相关的新功能,例如功能缓存。 由于功能收集通常是一项昂贵的操作,并且在 Active-Active 配置中,我们将有多个 Volume 服务以相同的频率为相同的后端请求相同的功能,我们可以考虑功能缓存作为降低后端收集成本的解决方案。

备选方案

一种替代的作业分发方法是保留主题队列不变,并将作业分发逻辑移动到调度器。

调度器将接收作业,然后将其发送到属于同一集群且未停止的 Volume 服务之一。

此方法有一个问题,那就是我们可能会将作业发送到 heartbeat 已过期但仍处于停止状态的节点,或者在从队列中获取作业之前已停止的节点。 在这些情况下,我们将最终得到一个未被任何人处理的作业,并且我们需要等待节点恢复或调度器需要从队列中检索该消息并将其发送到另一个活动节点。

一种替代的 heartbeat 方法是所有服务使用 cluster@backend 而不是像现在这样使用 host@backend 报告,只要我们有有效的 heartbeat,就知道该服务已启动。

我相信即使我们需要修改 DB 表,发送独立 heartbeat 也是一种更优的解决方案,原因如下:

  • 更高的信息粒度:我们可以报告哪些服务启动/停止,哪些节点启动/停止。

  • 这将有助于清理无法恢复的失败节点。 虽然清理不属于此规范,但最好记住它并尽可能地促进它。

作业分发的另一种替代方案,是此规范先前版本中提出的解决方案,即使用 host 配置选项作为等效于 cluster 的选项,并添加一个新的 node 配置选项,该选项将用于标识单个节点。

使用这种解决方案可能会导致对集群概念的误解,而直接使用集群概念则没有这个问题,它不允许像一次性更改这样的渐进式解决方案,并且我们无法发送消息到单个 Volume 服务,因为我们只有主机消息主题队列。

有一系列补丁显示了 节点替代机制 的实现,可以作为更详细的解释。

另一种可能性是允许在集群内禁用单个服务,而不是必须禁用整个集群,这是我们可以在完成所有其他操作后采取的措施。 为此,我们将使用 cinder-volume 服务上的正常主机消息队列来接收在管理器上启用/禁用集群的消息,这将触发集群主题队列的启动/停止。 但这并非易事,因为它需要我们能够从 cinder volume 管理器停止和启动集群主题的客户端(它在服务级别管理),并且能够在接受新的启用请求以再次启动消息客户端之前等待完全停止。

数据模型影响

最终结果

将添加一个新的 clusters 表,其中包含以下字段

  • id:集群的唯一标识符

  • name:集群的名称,用于在

    与使用 `host` 配置选项的方式相同。这来自 `cluster` 配置选项。

  • binary:目前它始终为“cinder-volume”,但当我们添加备份时

    它也将接受“cinder-backup”。

  • disabled:用于支持禁用集群。

  • disabled_reason:与服务表中的相同。

  • race_preventer:该字段将用于防止可能发生的竞态条件,

    如果同时启动 2 个新服务,并且两者都尝试同时创建集群条目,则可能会发生这种情况。

一个 `cluster_name` 字段将被添加到现有的 `services`、`volumes` 和 `consistencygroups` 表中。

REST API 影响

服务列表将在使用适当的微版本时返回 `cluster_name` 字段。

将向 `clusters` 端点添加一个新的端点,用于列出(详细和汇总)、显示和更新操作,以及它们各自的策略。

安全影响

通知影响

其他最终用户影响

性能影响

如果我们在 SQL 查询中使用 exist 来实现心跳的聚合,而不是检索所有心跳并在调度器上进行聚合,则可以忽略不计。

其他部署者影响

开发人员影响

实现

负责人

主要负责人

Gorka Eguileor (geguileo)

其他贡献者

Michal Dulko (dulek) Scott DAngelo (scottda) 欢迎任何人提供帮助

工作项

  • 添加新的 `clusters` 表和相关操作。

  • 添加集群版本对象。

  • 修改作业分发以使用新的 `cluster` 配置选项。

  • 更新服务 API 并添加新的 `clusters` 端点。

  • 更新 cinder-client 以支持新的端点和服务的新的字段。

  • 将操作迁移到主动-主动模式。

依赖项

测试

针对新 API 行为的单元测试。

文档影响

此规范对 API 进行了更改,还添加了一个新的配置选项,需要对其进行文档记录。

参考资料