将 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 地址
随着时间的推移,
dnsmasqPXE 过滤器被发现更具可扩展性和灵活性(例如,它支持带有 DHCP 中继的主干/叶架构),但代价是仅支持一种 DHCP 服务器实现。本规范建议仅迁移
dnsmasqPXE 过滤器。如果看到需求,iptables过滤器将遵循(弃用发布说明将提及它)。- 自动发现
自动注册新节点的过程。当配置了非托管检查时,置备网络上的未知节点将启动到 IPA ramdisk,进行检查并在 Ironic 中注册。
此操作默认禁用。
提议的变更¶
Inspector 功能将逐步迁移到 Ironic 代码库中,主要迁移到 ironic.drivers.modules.inspector 包中。我们将尽量避免对设计进行根本性的改变,但有以下例外
将定义不明确的 检查数据 分割为正式定义且版本化的(尽管不是 API 微版本意义上的) 清单 和自由格式的插件数据(受 IPA 中的检查收集器和 Inspector/Ironic 中的处理钩子的影响 - 请参阅 术语表)。
清单将不会被任何检查钩子修改,从而减少了对未处理的检查数据 API 的需求(Inspector 具有此 API)。
避免处理数据中的文档记录不完善的内部数据格式。例如,Inspector 生成字段
interfaces和all_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 WAIT 到 INSPECTING 的转变,这可能是一个错误。
REST API 影响¶
添加一个新的 API 端点来获取清单和可选的插件数据
GET /v1/nodes/{node}/inventory返回一个 JSON 对象,其中包含两个键:
inventory和plugin_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 参考。