资源提供者 - 基础模型

https://blueprints.launchpad.net/nova/+spec/resource-providers

该蓝图部分解决了 Nova 假定所有资源都由单个计算节点提供的问题,引入了一个新的概念——资源提供者,它将允许 Nova 准确地跟踪和预留资源,无论资源是由单个计算节点、共享资源池还是某种外部资源提供服务提供的。

注意

请注意,此处描述的大部分工作已在 Mitaka 中完成。 剩余的唯一工作项是创建 AllocationItem

问题描述

在云部署中,有许多资源可能被用户消耗。 一些资源类型由计算节点提供;这些类型的资源包括 CPU、内存、PCI 设备和本地临时磁盘。 然而,其他类型的资源并非由计算节点提供,而是由某些外部资源池提供。 这种资源的一个例子是 Ceph 或 NFS 共享提供的共享存储池。

不幸的是,由于遗留原因,Nova 仅将资源视为由计算节点提供。 资源的跟踪假定资源是由计算节点提供的,因此在报告某些资源的用法时,Nova 会通过简单地在数据库中的所有计算节点上求和来计算资源用法和可用性。 这最终会导致许多问题 [1],导致用法和容量金额不正确。

用例

作为选择使用共享存储解决方案来存储实例临时磁盘的部署者,我希望 Nova 和 Horizon 报告正确的用法和容量信息。

提议的变更

我们建议在 Nova 中引入新的数据库表和对象模型,以存储有关各种资源的提供者/容量信息的清单,以及可以存储该清单的用法/分配信息的表结构。

此蓝图有意不在这些新的数据库表中插入记录。 这些表将通过后续的 compute-node-inventorycompute-node-allocationsgeneric-resource-pools 蓝图中的工作填充。

我们还需要一个查找表来查找系统中各种资源提供者的 ID。 我们将此查找表称为 resource_providers

CREATE TABLE resource_providers (
    id INT UNSIGNED NOT NULL AUTOINCREMENT PRIMARY KEY,
    uuid CHAR (36) NOT NULL,
    name VARCHAR(200) NOT NULL CHARACTER SET utf8,
    generation INT NOT NULL,
    can_host INT NOT NULL,
    UNIQUE INDEX (uuid)
);

generationcan_host 字段是内部实现字段,分别允许进行原子分配操作,并告诉调度器资源提供者是否可以作为实例着陆的目标(提示:资源池永远不能是实例的目标)。

inventories 表记录了特定资源提供者提供的特定资源的数量

CREATE TABLE inventories (
    id INT UNSIGNED NOT NULL AUTOINCREMENT PRIMARY KEY,
    resource_provider_id INT UNSIGNED NOT NULL,
    resource_class_id INT UNSIGNED NOT NULL,
    total INT UNSIGNED NOT NULL,
    reserved INT UNSIGNED NOT NULL,
    min_unit INT UNSIGNED NOT NULL,
    max_unit INT UNSIGNED NOT NULL,
    step_size INT UNSIGNED NOT NULL,
    allocation_ratio FLOAT NOT NULL,
    INDEX (resource_provider_id),
    INDEX (resource_class_id)
);

reserved 字段将存储资源提供者“预留”用于对其资源进行非托管消耗的数量。 这里的“非托管”指的是 Nova(或最终分离的调度器)不参与从提供者分配某些资源。 例如,假设一个计算节点想要为宿主机预留一些 RAM,从而减少计算节点将其广告为容量的 RAM 量。 另一个例子是,想象一个共享资源池,其中一些磁盘空间被 Nova 实例以外的事物消耗。 或者,进一步地,一个包含 IPv4 地址池的 Neutron 路由网络,但 Nova 实例可能不会被分配池中的前 5 个 IP 地址。

allocation_ratio 字段将存储提供者愿意容忍的特定资源类别的“超卖”比率。 此信息当前仅存储在 compute_nodes 表中的 cpu_allocation_ratioram_allocation_ratio 字段中。

min_unitmax_unit 字段将存储资源的“限制”信息。 这些信息对于确保请求的资源多于或少于可以作为单个单元提供的资源不会被接受是必要的。

注意

min_unit、max_unit 和 allocation_ratio 的协同工作方式

例如,假设某个计算节点有两个四核 Xeon 处理器,提供总共 8 个物理核心。 即使云管理员将 cpu_allocation_ratio 设置为 16(默认值),该计算节点也无法接受需要超过 8 个 vCPU 的实例的请求。 因此,虽然计算节点上可能有 128 个总 vCPU 可用,但 min_unit 将设置为 1,max_unit 将设置为 8,以防止资源与请求的不良匹配。

step_size 是可以请求的资源的可分割单位金额的表示,如果请求的金额大于 `min_unit` 值

例如,假设操作员希望确保用户只能以 10G 增量请求磁盘资源,且不能小于 5G 或大于 1TB。 对于 DISK_GB 资源类,操作员会将共享存储池的清单设置为 min_unit 为 5,max_unit 为 1000,step_size 为 10。 这将允许请求 5G 磁盘空间以及 10G 和 20G 磁盘空间,但不能请求 6、7 或 8GB 磁盘空间。 另一个例子是,如果操作员将特定计算节点上的 VCPU 清单记录设置为 min_unit 为 1,max_unit 为 16,step_size 为 2,这意味着用户只能消耗 1 个 vCPU,但如果用户请求超过一个 vCPU,则该数字必须能被 2 整除,最高为 16。

为了跟踪已被某个资源消费者分配和使用的资源,我们需要一个 allocations 表。 此表中的记录将指示已从特定资源提供者分配给给定资源消费者的特定资源的数量

CREATE TABLE allocations (
    id INT UNSIGNED NOT NULL AUTOINCREMENT PRIMARY KEY,
    resource_provider_id INT UNSIGNED NOT NULL,
    consumer_id VARCHAR(64) NOT NULL,
    resource_class_id INT UNSIGNED NOT NULL,
    used INT UNSIGNED NOT NULL,
    INDEX (resource_provider_id, resource_class_id, used),
    INDEX (consumer_id),
    INDEX (resource_class_id)
);

当资源的消费者从提供者声明资源时,将记录插入到 allocations 表中。

注意

consumer_id 字段将是消耗该资源的实体的 UUID。 在 Nova 调度器未来可能被分离以支持不仅仅是计算资源之前,这将始终是 Nova 实例 UUID。 allocations 表由 compute-node-allocations 规范中概述的逻辑填充。

allocations 表中声明一组资源的流程如下所示

BEGIN TRANSACTION;
FOR $RESOURCE_CLASS, $REQUESTED_AMOUNT IN requested_resources:
    INSERT INTO allocations (
        resource_provider_id,
        resource_class_id,
        consumer_id,
        used
    ) VALUES (
        $RESOURCE_PROVIDER_ID,
        $RESOURCE_CLASS,
        $INSTANCE_UUID,
        $REQUESTED_AMOUNT
    );
COMMIT TRANSACTION;

上述问题在于,如果两个线程运行查询并选择相同的资源提供者来放置实例,它们将在对该资源提供者的可用清单进行点时快照后进行选择。 到 COMMIT_TRANSACTION 发生时,一个线程可能已在该资源提供者上声明资源并更改了另一个线程的点时快照视图。 如果另一个线程只是继续并向 allocations 表添加记录,我们最终可能会在宿主机上消耗比宿主机实际可以容纳的更多的资源。 解决此问题的传统方法是在检索资源提供者的清单的点时快照视图时使用 SELECT FOR UPDATE 查询。 但是,在以多写模式运行 MySQL Galera Cluster 时,SELECT FOR UPDATE 语句无法正确支持。 此外,它使用繁重的悲观锁定算法,该算法将选定的记录锁定较长时间。

为了解决这个问题,应用程序可以使用“比较和更新”策略。 在这种方法中,读取线程保存有关点时快照视图的一些信息,并在将写入发送到数据库时,包含来自点时快照视图的数据片段的 WHERE 条件。 写入仅在原始条件成立且另一个线程未在从点时快照读取到尝试写入表中的相同行之间更新查看的行时才会成功(返回受影响的行数 > 0)。

resource_providers.generation 字段使用这种“比较和更新”策略启用对 allocations 表的原子写入。

本质上,以伪代码的形式,这是 generation 字段在“比较和更新”方法中用于声明提供者上资源的流程

deadlock_retry:

    $ID, $GENERATION = SELECT id, generation FROM resource_providers
                       WHERE ( <QUERY_TO_IDENTIFY_AVAILABLE_INVENTORY> );

    BEGIN TRANSACTION;
    FOR $RESOURCE_CLASS, $REQUESTED_AMOUNT IN requested_resources:
        INSERT INTO allocations (
            resource_provider_id,
            resource_class_id,
            consumer_id,
            used
        ) VALUES (
            $RESOURCE_PROVIDER_ID,
            $RESOURCE_CLASS,
            $INSTANCE_UUID,
            $REQUESTED_AMOUNT
        );
    $ROWS_AFFECTED = UPDATE resource_providers
                     SET generation = $GENERATION + 1
                     WHERE generation = $GENERATION;
    IF $ROWS_AFFECTED == 0:
        ROLLBACK TRANSACTION;
        GO TO deadlock_retry;
    COMMIT TRANSACTION;

备选方案

继续使用 compute_nodes 表来存储所有资源用法和容量信息。 问题如下

  • 任何新资源都需要更改数据库模式

  • 我们没有在数据库中指示某些资源在计算节点之间共享

数据模型影响

需要进行许多数据模型更改。

  • 新模型

  • ResourceProvider

  • InventoryItem

  • AllocationItem

  • 所有上述内容的新数据库表

  • 需要的数据库迁移

  • 将以下表添加到模式中

  • resource_providers

  • inventories

  • allocations

REST API 影响

无。

安全影响

无。

通知影响

无。

其他最终用户影响

无。

性能影响

无。

其他部署者影响

无。

开发人员影响

无。

实现

负责人

主要负责人

dstepanenko

其他贡献者

jaypipes

工作项

  • 创建创建 resource_providersinventoriesallocations 表的数据库迁移

  • 创建 nova.objectsResourceProviderInventoryItemAllocationItem 的新模型

在 Mitaka 中,所有这些工作都已完成,除了创建 AllocationItem,它将在 Newton 中完成。

依赖项

  • resource-classes 蓝图工作是这项工作的基础,因为 inventoriesallocations 表中的 resource_class_id 字段引用(逻辑上,而不是通过外键约束)该蓝图规范中引入的资源类概念。

测试

针对迁移和新对象模型的新的单元测试足以满足此规范。

文档影响

无。

参考资料

[1] 与资源使用报告和计算相关的错误

历史

修订版

发布名称

描述

Mitaka

引入

Mitaka (M3)

将 name、generation 和 can_host 字段添加到 resource_providers

Newton

重新提出