将 Inspector 合并到 Ironic

https://storyboard.openstack.org/#!/story/2010275

本规范建议将 ironic-inspector 项目(Inspector)完全合并到主 Ironic 项目中,使其所有的 API 功能成为 Bare Metal API 的一部分,并弃用独立的该项目。

问题描述

Inspector 起源于 2014 年年中 Ironic 中未合并的一批补丁,最初名为 ironic-discoverd。Ironic 团队认为这些补丁,特别是自动发现的想法,超出了 Bare Metal 项目的范围。在 2015 年初,Ironic 团队同意将已经建立并运行的项目纳入其管理范围,并将名称更改为 ironic-inspector。随着时间的推移,Inspector 获得并又失去了自己的核心评审团队,同时变得越来越符合 Ironic 社区的标准实践。

快进到 2023 年,社区中仍然记得这个故事的人已经很少了。“为什么 Inspector 要与 Ironic 分开?” 是新来者常问的问题。当前状况存在一些问题

维护负载。

一个很好的例子是 SQLAlchemy 2.0 的过渡。由于 Inspector 有一个单独的数据库,我们需要做两次工作。

运行负载。

又一个服务 - 又一个问题。它需要定期安装、保护和更新。

扩展/HA 问题。

Ironic 在设计时就考虑到了水平扩展和高可用性;Inspector 则没有。在某种程度上,这是有道理的:Inspector 仅偶尔使用。尽管如此,在非常大的部署中(例如 CERN),即使是很少使用的服务,其可用性也足够重要。

通过引入消息总线(RabbitMQ)和成员资格跟踪(ZooKeeper/etcd)在很大程度上缓解了这个问题。大部分 - 因为该解决方案依赖于通常存在于 OpenStack 中的两个服务,但不存在于诸如 Bifrost 或 Metal3 [1] 等独立解决方案中。

性能问题。

由于 Inspector 有一个单独的数据库,它必须维护自己的节点表并定期与 Ironic 同步。这效率低下,并可能导致可扩展性问题。

同样,每个检查钩子(请参阅 术语表)都必须向 Ironic 发出一些 API 请求以更新信息。包含多个钩子的管道可能会产生明显的流量。

资源利用率。

Inspector 使用 Python 编写,具有不可忽略的 RAM 占用量,而大多数时间却几乎什么也不做。

可发现性。

由于 Inspector 有自己的文档、API 参考和客户端,因此更难发现有关它的信息。

术语表

带内检查

通过使用带有 IPA(ironic-python-agent)的 ramdisk 进行硬件检查。另一种选择是带外检查,其中通过 BMC 收集数据,而无需启动节点。

术语 内省 在 Inspector 代码库和文档中用作同义词。

检查数据

IPA 在检查期间收集并发送回 Inspector(未来 - 到 Ironic)的所有信息的通用术语。

清单

广义上 - 检查数据的同义词。狭义上 - 硬件 清单,由 IPA 定义并由其硬件管理器返回。

检查收集器

负责在 ramdisk 端收集检查数据的 IPA 插件。 default 收集 清单。目前,收集器独立于硬件管理器,但这可能会在未来改变。

处理钩子

Inspector(未来 - Ironic)插件,用于处理检查数据并更新节点。允许操作员通过配置处理钩子管道来微调检查过程。

本文档引入了术语 检查 钩子,以避免在更大的 Ironic 环境中产生歧义。

检查钩子和收集器可能对应于彼此。例如, extra-hardware 收集器有一个对应的 extra_hardware 钩子。

托管/非托管检查

当 Ironic 完全设置启动环境时(无论是 (i)PXE 还是虚拟介质启动),我们谈论的是托管检查。当使用 Inspector 的 (i)PXE 环境(通常是单独的 dnsmasq 安装)时,检查是非托管的。

非托管检查一直是 Inspector 的默认设置很长时间。引入托管检查是为了支持虚拟介质,首先也是最重要的。通过提议的合并,托管检查将成为默认模式,而非托管检查将需要显式配置。

PXE 过滤器

Inspector 分离的 PXE 环境与 Neutron 共存的一种方式,是通过限制它为哪些 MAC 地址提供服务。Inspector 有两种实现

  • iptables 使用防火墙打开/关闭对 DHCP 服务器的访问

  • dnsmasq 更新 dnsmasq 主机文件,其中包含要提供/拒绝服务的 MAC 地址

随着时间的推移, dnsmasq PXE 过滤器被发现更具可扩展性和灵活性(例如,它支持带有 DHCP 中继的主干/叶架构),但代价是仅支持一种 DHCP 服务器实现。

本规范建议仅迁移 dnsmasq PXE 过滤器。如果看到需求, iptables 过滤器将遵循(弃用发布说明将提及它)。

自动发现

自动注册新节点的过程。当配置了非托管检查时,置备网络上的未知节点将启动到 IPA ramdisk,进行检查并在 Ironic 中注册。

此操作默认禁用。

提议的变更

Inspector 功能将逐步迁移到 Ironic 代码库中,主要迁移到 ironic.drivers.modules.inspector 包中。我们将尽量避免对设计进行根本性的改变,但有以下例外

  • 将定义不明确的 检查数据 分割为正式定义且版本化的(尽管不是 API 微版本意义上的) 清单 和自由格式的插件数据(受 IPA 中的检查收集器和 Inspector/Ironic 中的处理钩子的影响 - 请参阅 术语表)。

    清单将不会被任何检查钩子修改,从而减少了对未处理的检查数据 API 的需求(Inspector 具有此 API)。

  • 避免处理数据中的文档记录不完善的内部数据格式。例如,Inspector 生成字段 interfacesall_interfaces,这些字段不基于清单中的 interfaces 集合。

  • 将迁移的 PXE 过滤器拆分为一个新的脚本,以避免将其(以及其背后的 dnsmasq 实例)与 Ironic 进程耦合。这样,它可以单独扩展。

  • 重构检查(以前的处理)钩子,使其命名更明显,组合性更好。

    在入口点名称中始终使用连字符而不是下划线。

    默认配置中将运行更少的钩子。

  • 始终使用术语 检查 而不是 内省

为了保持本规范的大小合理并保持我的理智(相对而言),此处省略了检查规则。它们相对来说很简单,但需要大量的解释,并且可以独立实现。

备选方案

保持 Inspector 分离。

支持它的论点包括

  • 通过拥有一个单独的进程来更好地利用 CPU 核心。这应该更好地通过允许每个物理主机使用多个 conductor 来解决。这样的解决方案也将使更密集的运行和没有 Inspector 的部署受益。

  • 更易于管理(即更小的)代码库。然而,Inspector 中很多代码仅仅是因为它是一个独立的工程而存在。这包括大部分数据库代码、一些 API 端点和节点同步例程。

不要迁移一些主要功能。

这将阻碍迁移,并阻止我们最终弃用 Inspector。

不要迁移 PXE 过滤器。

这将使在 Neutron 存在的情况下自动发现变得不可能。自动发现是一个经常被要求的特性(示例请求)。

数据模型影响

清单表

当不可用对象存储服务时,添加一个新表来存储检查数据

class NodeInventory(Base):
    """Represents an inventory of a baremetal node."""
    __tablename__ = 'node_inventory'
    __table_args__ = (
        Index('inventory_node_id_idx', 'node_id'),
        table_args())
    id = Column(Integer, primary_key=True)
    inventory_data = Column(db_types.JsonEncodedDict(mysql_as_long=True))
    plugin_data = Column(db_types.JsonEncodedDict(mysql_as_long=True))
    node_id = Column(Integer, ForeignKey('nodes.id'), nullable=True)

在这里, inventory_data 包含 IPA 定义的清单(请参阅 术语表),而 plugin_data 包含各种收集器返回的辅助数据或由检查钩子生成的辅助数据。

在删除节点时, NodeInventory 对象将被删除。

注意

本表在编写本规范时已经存在。此处包含它仅供完整起见。

节点修改

nodes 表中添加一个新的布尔字段 auto_discovered。从 API 的角度来看,它将是只读的,并用于标记自动发现的节点。

状态机影响

唯一的预期变化是从 INSPECT WAITINSPECTING 的转变,这可能是一个错误。

REST API 影响

添加一个新的 API 端点来获取清单和可选的插件数据

GET /v1/nodes/{node}/inventory

返回一个 JSON 对象,其中包含两个键: inventoryplugin_data

HTTP 状态码

  • 成功时返回 200。

  • 如果未找到节点,则没有记录清单,或者请求版本中不可用 API,则返回 404。

注意

本 API 在编写本规范时已经存在。此处包含它仅供完整起见。

添加一个新的 API 以接受来自 ramdisk 的检查数据

POST /v1/continue_inspection

以与 Inspector 完全相同的方式接受检查数据,即作为至少包含 inventory 字段的 JSON 对象。

唯一的查询参数是 node_uuid - 一个可选的节点 UUID。此参数专为虚拟介质部署设计,以便安全地将节点身份传递给 ramdisk。有关详细信息,请参阅 查找过程

此 API 未经过身份验证,不需要代理令牌,因为检查始终首先在代理启动时发生。

结果取决于请求的 API 版本

  • 在基本 API 版本中,使用 Inspector 兼容模式。生成的 JSON 对象有一个键: uuid (节点的 UUID)。

  • 在新 API 版本中,结果与正常查找 API 相同。在这种情况下,API 会生成并返回一个代理令牌,从而有效地在使用了检查时替换查找。

HTTP 状态码

  • 成功时返回 200。

  • 如果未找到节点,则匹配多个节点,或者节点状态不是 INSPECT WAIT,则返回 404。

    注意

    我们使用相同的通用 HTTP 404 响应,以避免向潜在的入侵者泄露任何信息。

在完整的节点表示中返回 auto_discovered 字段。使用按此字段过滤的能力更新节点列表(GET /v1/nodes)。

查找过程

查找过程比正常的 Ironic 查找更复杂,因为检查的节点可能没有注册任何端口。该过程将尝试找到满足提供的节点 UUID、MAC 地址和 BMC 地址的一个且仅一个节点。

BMC 地址未在数据库中索引,需要进行一些预处理。在启动检查时,BMC 地址将从节点的 driver_info 中收集,解析为 IP 地址,并缓存到 driver_internal_info 中。在查找时,将检查处于 INSPECT WAIT 状态的所有节点的 driver_internal_info

如果未找到或匹配多个节点,将返回不带任何说明的 HTTP 404。将提供详细的日志记录以进行调试。

自动发现

如果启用了自动发现(请参阅 自动发现配置),则查找过程对于完全新节点将略有不同。如果对于数据找不到任何节点(而不是处于无效状态的节点),API 层将创建一个新节点。其余的检查过程与以前相同。

以这种方式创建的节点将把 auto_discovered 字段设置为 True

客户端 (CLI) 影响

所有新的 API 功能都需要公开。

“openstack baremetal” CLI

启动和中止检查。

这里没有变化,使用相同的命令

$ openstack baremetal node set --inspect-interface agent <node>
$ openstack baremetal node inspect <node>
$ openstack baremetal node abort <node>
公开检查数据。

我希望有一个命令来显示库存的某些部分,对其进行过滤等等。目前还不清楚我们应该在客户端还是服务器端(或者根本不)执行此操作。Inspector 有一些关于提取库存部分的评论。我建议在第一次迭代中不要迁移它们。

我们将从迁移最基本的命令开始,即简单地将完整的 JSON 保存到文件或显示它。

$ openstack baremetal node inventory save [--file <file path>] <node>

回调端点将不会通过 CLI 暴露。

过滤自动发现的节点

对于审计目的可能很有用,尤其是在与配置状态过滤结合使用时。

$ openstack baremetal node list --provision-state enroll --auto-discovered

“openstacksdk”

暴露一个获取库存的调用,与现有的 Inspector 调用非常相似。

def get_inventory(self, node):
    """Get inventory for the node.

    :param node: The value can be the name or ID of a node or a
        :class:`~openstack.baremetal.v1.node.Node` instance.
    :returns: inspection data from the most recent successful run.
    :rtype: dict
    """

使用 auto_discovered 过滤更新节点 API。

RPC API 影响

将引入一个新的 RPC 调用来处理检查数据。

def continue_inspection(self, context, node_id, inventory,
                        plugin_data=None):
    """Continue in-band inspection.

    :param context: request context.
    :param node_id: node ID or UUID.
    :param inventory: hardware inventory from the node.
    :param plugin_data: optional plugin-specific data.
    :raises: NodeLocked if node is locked by another conductor.
    :raises: NotFound if node is in invalid state.
    """

收到此调用后,conductor 将获取独占锁,再次检查配置状态并启动一个线程进行进一步处理。

有关更多影响,请参阅 PXE 过滤脚本

驱动程序 API 影响

扩展inspect 接口,添加一个额外的调用

def continue_inspection(self, task, inventory, plugin_data=None):
    """Continue in-band hardware inspection.

    Should not be implemented for purely out-of-band implementations.

    :param task: a task from TaskManager.
    :param inventory: hardware inventory from the node.
    :param plugin_data: optional plugin-specific data.
    :raises: UnsupportedDriverExtension, if the method is not implemented
             by specific inspect interface.
    """

检查钩子

检查钩子是一种新型的 Ironic 插件,与 Inspector 的处理钩子密切相关(请参阅 术语表)。

当前的 Inspector 处理钩子接口如下所示(为提高可读性缩短了文档字符串)

class ProcessingHook(object, metaclass=abc.ABCMeta):

    dependencies = []
    """An ordered list of hooks that must be enabled before this one."""

    def before_processing(self, introspection_data, **kwargs):
        """Hook to run before any other data processing."""

    def before_update(self, introspection_data, node_info, **kwargs):
        """Hook to run before Ironic node update."""

适应 Ironic 的术语、API 和内部结构,这将变为

class InspectionHook(metaclass=abc.ABCMeta):

    dependencies = []
    """An ordered list of hooks that must be enabled before this one."""

    def preprocess(self, task, inventory, plugin_data):
        """Hook to run before the main inspection data processing."""

    def __call__(self, task, inventory, plugin_data):
        """Hook to run to process the inspection data."""

钩子…

  • 必须重写 __call__可以重写其他两个方法。

  • 始终以独占锁在 INSPECTING 配置状态下的节点上运行。

  • 可以修改插件数据,但不应该修改库存。

  • 应该避免preprocess 阶段永久修改节点或任何相关资源。

  • 必须在修改时显式调用 task.node.save()

默认情况下将运行的有序钩子列表(请参阅 钩子配置

ramdisk-error

如果在检查数据中传递了错误消息,则会提前使检查失败。

architecture

根据库存设置 cpu_arch 属性。

validate_interfaces

验证库存中的接口。有效的接口存储在 plugin_data 中的新键 valid_interfaces 中,并带有额外的字段 pxe_enabled

ports

基于 add_ports / keep_ports 选项创建端口(请参阅 端口创建配置)。需要 validate_interfaces 钩子。使用新的布尔接口字段 is_added 更新 valid_interfaces 集合。

可用可选钩子的列表(改编自现有的 Inspector 钩子)

accelerators

根据可用的加速器设备和配置设置 accelerators 属性。

boot-mode

根据 ramdisk 运行期间的启动模式设置 boot_mode 功能。

cpu-capabilities

根据库存中的 CPU 标志更新功能。

extra-hardware

将从 hardware-detect 工具(列表的列表)收集的额外数据转换为嵌套字典。删除 plugin_data 中的原始 data 字段,并创建新的字段 extra

local-link-connection

使用 LLDP 信息设置端口上的 local_link_connection 字段。可以与 parse-lldp 一起使用。

memory

根据库存设置 memory_mb 属性。

parse-lldp

将二进制 LLDP 信息转换为可读形式,然后将其存储在 plugin_data 中,作为新的 parsed_lldp 字典,接口名称作为键。

https://specs.openstack.org/openstack/ironic-inspector-specs/specs/lldp-reporting.html

pci-devices

使用来自配置的映射更新节点的 PCI 设备功能。

https://specs.openstack.org/openstack/ironic-inspector-specs/specs/generic-pci-resource.html

physical-network

允许根据配置中的 CIDR 映射设置端口的 physical_network 字段。

可以被子类化以实现不同的逻辑。

raid-device

使用两次检查之间的差异来检测新创建的 RAID 设备并将其配置为根设备。

注意

当前实现将设备缓存在节点的 extra 中。我们应该获取旧的库存。

root-device

使用根设备提示来确定根设备并设置 local_gb 属性。

注意

没有东西会设置 cpus 属性。Nova 不再使用它,应该从基本属性中删除。

Nova 驱动程序影响

幸运的是,没有。

Ramdisk 影响

在第一次传递时,ramdisk 不会有任何更改。新的回调 API 将与 Inspector 中的对应 API 完全兼容。

后续更改将使查找和检查互斥:如果检查(至少是其同步查找部分)成功,则响应中将返回令牌和节点数据,并且不需要查找。

安全影响

  • 此更改引入了一个没有身份验证的额外的 API 端点。知道 UUID、MAC 地址或 BMC 地址,入侵者可以接收一些信息以及节点在 INSPECT WAIT 状态下的代理令牌(如果尚未检索)。

    如果禁用或根本未使用带内检查,则没有节点将处于 INSPECT WAIT 状态,因为它不被带外检查实现使用。

其他最终用户影响

没有?

可扩展性影响

具有带内检查的部署的可扩展性影响可能为净积极,因为不再需要 Inspector 与 Ironic 的定期同步。

将 PXE 过滤器作为单独的进程意味着可以单独扩展它们(例如,将它们保持在主动/待机设置中,而 Ironic 的其余部分是主动/主动)。

性能影响

预期的性能影响也是积极的

  • 删除将检查结果从 Inspector 同步到 Ironic 的定期任务。

  • 更高效的数据库查询,用于检查查找和 PXE 过滤器。

将库存存储在数据库中会影响其大小,但对于 Inspector 来说也是如此。但是,在过渡期间,将存在两个库存副本。如果这成为一个问题,操作员可以选择在准备完全切换之前禁用 Ironic 侧的库存存储。

其他部署者影响

钩子配置

[inspector] 部分中的新配置选项

default_hooks

默认运行的检查钩子的逗号分隔列表。在大多数情况下,操作员不会修改此列表。

默认的(有些保守的)钩子集将创建端口并设置 cpu_arch

hooks

要运行的检查钩子的逗号分隔列表。默认为 $default_hooks

注意

此方案允许轻松地在列表的开头或结尾插入钩子,而无需对默认列表进行硬编码,例如:

[inspector]
hooks = my-early-hook,$default_hooks,later-hook-1,later-hook-2

端口创建配置

各种检查钩子将带有自己的配置。最重要的是 [inspector] 部分中的端口创建选项

add_ports

将哪些接口注册为节点的端口。选项

  • all(默认)- 所有有效的接口。

  • active - 只有具有 IP 地址的接口。

  • pxe - 仅 PXE 启动接口。

keep_ports

保留哪些现有的端口。

  • all(默认)- 保留所有端口,不要删除任何内容。

  • present - 删除所有不对应于库存中接口的端口。

  • added - 删除除通过 add_ports 选项选择的端口(只有当 add_ports 未设置为 all 时才有意义)。

磁盘间距配置

我们的分区代码的一个奇怪之处是,local_gb 字段必须小于实际磁盘,否则分区可能会失败。Inspector 通过使 local_gb 减小 1GB 来处理它。这将在以下选项中反映

disk_partitioning_spacing

要保留的空间大小,以 GiB 为单位。默认为 1,设置为 0 以禁用。

自动发现配置

新的 [auto_discovery] 部分将具有以下选项

enabled

布尔字段,默认为 False

driver

用于新注册节点的驱动程序。启用该功能时需要。

注意

Inspector 有几个选项来调整新创建的节点。我相信这种复杂的逻辑应该用检查规则来实现。后续的检查规则规范将进行一些补充。

PXE 过滤脚本

将引入一个新的可执行文件 ironic-pxe-filter,以支持在具有 Neutron 的环境中进行非托管检查。它将被设计为与单独的 dnsmasq 进程一起部署。与 Inspector 中的当前实现不同,该脚本将直接访问 Ironic 数据库以提高效率。

不需要 PXE 过滤的操作员,例如,因为他们只使用托管检查或使用单个 PXE 环境(没有 Neutron),可以选择不运行 ironic-pxe-filter。这适用于 Bifrost 和 Metal3,例如。

这种方法的唯一缺点是知道检查何时开始或结束。Inspector 立即了解它并能够无需进一步延迟即可更新过滤器。定期任务方法将导致对于真实硬件来说可能可以接受的延迟,但对于虚拟机来说将存在问题。

为了克服这个限制,新的可执行文件将具有一个 RPC 服务,当 RPC 传输为 oslo.messaging 时。该服务将使用一个单独的 topic ironic.pxe_filter,并将从处理检查的 conductor 接收广播消息。RPC 调用将是

def update_pxe_filters(self, context, allow=None, deny=None):
    """Update the PXE filter with the given addresses.

    Modifies the allowlist and the denylist with the given addresses.
    The state of addresses that are not mentioned does not change.

    :param allow: MAC addresses to enable in the filters.
    :param deny: MAC addresses to disable in the filters.
    """

对于 JSON RPC 传输,将不执行任何通知。这将记录为已知限制。作为 跨 conductor RPC 工作 的一部分,后续工作最终可能会解除它。

开发人员影响

虽然其他带内检查实现是可能的,但它们可能发生在所提出实现的上游修改中。

实现

负责人

主要负责人

Dmitry Tantsur (IRC: dtantsur, dtantsur@protonmail.com)

其他贡献者

Jakub Jelínek (IRC: kubajj) - 库存 API

工作项

太多了,无法一一列举 - 请参阅 https://storyboard.openstack.org/#!/story/2010275 中的任务。

依赖项

到目前为止还没有。

测试

  • Bifrost 将尽快迁移到新的实现。

  • 将添加 DevStack CI 覆盖率,可能以独立作业中测试的形式出现。

  • 最终,现有的 Inspector 作业将被迁移或删除。

升级和向后兼容性

除了最终弃用 Inspector 本身以及相应的 inspect 接口 之外,该更改在 Ironic 侧是向后兼容的。

迁移将相对容易,但并非没有摩擦。可能的问题

  • 不迁移检查数据。我们可以提供一个工具,我只是不确定是否值得付出努力。可以作为事后补充。

  • 新旧 inspect 接口 的共存。

    • 回调 API 将被设计为通过代理数据到 Inspector 来与旧接口一起工作。这样,操作员可以为两种实现使用相同的回调 URL。

    • 新的 PXE 过滤器脚本也将以相同的方式为两种实现工作。

    • 库存 API 将通过在成功检查时从 Inspector 获取数据来实现旧实现。

文档影响

需要编写大量的文档,或者说从 ironic-inspector 调整文档。将为所有新的 API 端点添加 API 参考。

参考资料