基于Trait的端口调度以实现动态网络

OpenStack 最初是为虚拟机和虚拟网络而构建的系统。Ironic 与 OpenStack 网络交互的最复杂部分之一是将“虚拟接口”(代表与网络的连接的概念)映射到服务器上的物理/Ironic 端口或端口组,以使该连接成为现实。

如今,Ironic 具有极其基本的映射规则,在机器上具有大量网络或网卡(以及许多其他情况)时,这些规则是不充分的。

问题描述

从 2025.1 版本开始,Ironic 使用以下模型调度端口

  • 需要匹配或未定义的物理网络

  • 优先选择定义了物理网络的端口或端口组

  • 优先选择(静态、预配置的)端口组而不是端口

  • 优先选择启用了 PXE 的端口

然而,这种方法存在根本限制,即使用这种模型,我们无法

  • 动态组装端口组

  • 对特定端口、端口组或设备类进行提示或加权。

  • 避免其他特定类型的接口或网络。

例如,操作员可能拥有一台机器,该机器具有

  • 2 个“绿色”网络设备

  • 4 个“紫色”网络设备

即使流量可能可以使用这些六个接口中的任何一个,但最好由特定设备处理。这通常是为了提高性能,例如使用更高速度的网络适配器或 DPU 来提高该网络的性能。

这并不意味着我们希望 Ironic 完全理解和解析所有这些细节或所有硬件和网络组合,但是,我们需要向操作员提供他们需要执行这些映射的工具。

提议的变更

概述

为了允许操作员模拟这些复杂的网络情况,我们建议实施一个新的 yaml 配置文件,其键将是自定义 trait,其值将是配置,用于控制网络端口的连接和配置。当 Ironic 执行具有这些 trait 的部署时,Ironic 将根据配置的规则执行网络连接。

对于使用由 Nova 提供的 Ironic 的用户,他们将能够配置自定义 flavor,这些 flavor 将将所需的 trait 传输到 Ironic 以执行网络映射。Trait 已经以这种方式用于在部署时修改 deploy_templates 中的部署过程。

最终,这需要在数据模型和调度方面取得进展,但总体希望是促进基于 flavor 的模型以启用此映射,以便利用 Nova 的用户可以请求裸机节点,而 Ironic 会完成必要的工作。 此外,在使用这些模式时,将不支持直接提示接口附件的端口。有关更多信息,请参阅 Alternatives 部分。

里程碑一:单端口映射

作为第一个里程碑,Ironic 将为预配置的端口组和单个端口实现此功能。

例如,如果操作员有一个名为 fast_purple 的 flavor,具有 trait CUSTOM_FAST_PURPLE_NETWORK,他们可以配置任何标记为“purple”的网络以使用与特定供应商关联的端口。 操作员还可以有一个 flavor fast_green,它具有 trait CUSTOM_FAST_GREEN_NETWORK,这将相反地将标记为“green”的网络连接到与供应商关联的端口。

在实施时,重要的是要确保在采取任何配置操作之前,可以解析配置、节点、现有端口和请求的网络。 避免在确定无法满足请求之前,网络配置正在进行的情况。

里程碑二:动态端口组

作为第二个里程碑,我们将添加将单个端口动态创建为端口组的附加能力。

例如,如果我有一个名为 double_purple 的 flavor,它具有 trait CUSTOM_BOND_TWO_PURPLE,操作员可以配置此 trait 以执行操作,将两个现有端口连接在一起,形成连接到标记为 purple 的网络的动态端口组。 此外,他们可以有一个名为 double_green 的 flavor,它执行相同的操作,但针对标记为 green 的网络。 在两种情况下,当节点退出 ACTIVE 状态时,动态组装的端口组将被拆解。

动态端口组将与现有的端口组模型分离,该模型当前将端口组视为节点的静态属性。

端口建模

为了启用更动态的端口分配模型,我们需要一些附加字段,该字段可以由使用此功能的用户利用,以便在过滤逻辑中做出决策。 最初,我们将向 Port 对象添加 vendorclass 字段。 对于里程碑二,我们将向 Port 对象添加 available_for_dynamic_portgroup,它可以用于选择 Port 参与或退出动态端口组功能。

在实施过程中可能会识别出其他字段,但意图是过滤提供端口对象作为过滤逻辑的一部分。

请求处理的总体流程

为了使 Nova 利用此模型,请求的总体流程需要采取以下形式

  1. 通过设置 instance_uuid 来保留节点。

  2. 节点的期望 instance_info 已设置。 这应该可以使用与设置 instance_uuid 相同的 patch 操作来完成。 结果是,节点的 instance_info 值具有一个 trait 字段,该字段包含从 flavor 定义的 trait 列表。

  3. Vif 附件发生,并触发内部逻辑以动态组装端口组 影响映射。 Ironic 将利用这段时间在节点上采取任何准备工作,例如配置网卡或组装动态端口组。

  4. Nova 随后能够查找节点的端口和端口组,并生成所需的 user metadata,这些 metadata 必须在部署命令提交时提交。

  5. 然后将通过 set_provision_state API deploy 节点。

  6. 网络接口将采取行动来配置端口/端口组(这些端口/端口组已更新以反映动态映射#3 中的内容)。 它仅期望与 neutron 接口一起工作,但将来可以使用其他接口。

值得庆幸的是,Nova 已经执行了我们需要能够执行这些逻辑的完全过程。

一个独立的 user 也可以遵循完全相同的过程,或者如果愿意,可以直接请求特定的 VIF 到端口组或端口映射,这些映射由他们手动管理。 再次,此规范的总体目标是自动且精简的集成用例。 将独立用例与此功能区分开来的主要区别是,独立用户能够在预先做出这些决定并更改其他资源。 而 Nova 用户在集成上下文中则无法做到,因为他们无法提前知道他们可能被映射到哪台机器,也无法以任何其他方式影响它,而只能通过今天提供的方式。

Trait 映射

一个初始的提议模型是使 trait 到 action 的映射成为部署到 conductor 的静态 YAML 配置文件。 该文件的位置将可配置。

对于可以作用于任何数量的端口的操作,将使用所有匹配表达式的端口。 对于只能作用于单个端口的操作,将使用第一个匹配表达式的端口。 如果未配置最小或最大值,则任何数量的端口均有资格。

如果没有 YAML 文件,或者没有 trait 匹配,我们将回退到以前的行为。

YAML 结构/语法

<trait_name>:
  - action: <action_name>
    filter: <filter_expression>
    min_count: <min_count>
  - action: <action_name>
    filter: <filter_expression>
    max_count: <max_count>
<trait_name>:
  - action: <action_name>
    filter: <action_name>
...

trait_name 经过验证是一个有效的 trait,这意味着它必须以 CUSTOM_ 开头,少于 255 个字符,并且仅包含字母数字字符或下划线。

action_nameActions 中列出的有效操作

filter_expressionFilters 中定义的表达式

min_countmax_count 是可选字段
  • min_count(如果设置),定义了必须匹配的端口的最小数量才能执行该操作

  • max_count(如果设置),定义了将作用的端口的最大数量。 将匹配的前 max_count 个端口传递给该操作。

可以列出任意数量的 trait,并且可以在其下方列出任意数量的 action/filter 对。 trait 及其关联的 action/filter 对按特定顺序处理:首先应用 trait 名称,按字母顺序(例如,使用 trait CUSTOM_NETWORK_XCUSTOM_NETWORK_Y 进行部署将导致首先评估 CUSTOM_NETWORK_X action/filter 对,然后应用 CUSTOM_NETWORK_Y action/filter 对)。 action/filter/count 集按 trait 名称下列表的顺序进行评估。

操作

在里程碑一,我们预计将实现以下两个操作

  • attach_port(连接第一个匹配的端口)

  • attach_portgroup(连接第一个匹配的静态定义的端口组)

在里程碑二,我们预计将实现一个额外的操作

  • group_and_attach_ports(创建匹配端口的动态端口组并连接它们)

Ironic 可能会在未来添加更多操作。

过滤器

表达式

  • 比较器:- ==(等于)- !=(不等于)- >=(大于或等于)- >(大于)- <=(小于或等于)- <(小于)- =~(前缀匹配,等效于 python 的 string.startswith()

  • 布尔运算符:- &&(逻辑 AND)- ||(逻辑 OR)

  • 可以使用括号 () 以及布尔运算符对表达式进行分组。

简单表达式由三个部分组成:- 要比较的值(例如 port.vendor)- 要应用的比较器- 用于比较的字符串

例如 port.vendor == 'fastNIC'network.tag == 'fastNET'

要过滤的对象

Ironic 将提供对一些网络元数据的访问,以便使用此配置进行比较。它们将包括

一个 port-like 对象,代表一个端口或静态端口组,包含(至少)

  • address

  • class

  • physical_network

  • vendor

  • is_port (bool)

  • is_portgroup (bool)

一个 network-like 对象,代表虚拟接口(vif)上的网络对象,包含(至少)

  • name

  • tags

  • (更多待定)

在实施时,Ironic 可用的其他对象/字段可能会根据可用性和适用性添加。完整的列表将在实施时明确记录。

示例配置

完成两个里程碑后的 Ironic 示例配置

---
CUSTOM_TRAIT_NAME:
  - action: bond_ports
    filter: port.vendor == 'vendor_string'
    min_count: 2
CUSTOM_DIRECT_ATTACH_A_PURPLE_TO_STORAGE:
  - action: attach_port
    filter: port.vendor == 'purple' && network.name == 'storage':
CUSTOM_BOND_PURPLE_BY_2:
  - action: group_and_attach_ports
    filter: port.vendor == 'purple'
    max_count: 2
CUSTOM_BOND_GREEN_STORAGE_TO_STORAGE_BY_2:
  - action: group_and_attach_ports
    filter: port.vendor == 'green' && port.class == 'storage' && ( network.name =~ 'storage' or network.tags =~ 'storage' )
    max_count: 2
    min_count: 2
CUSTOM_USE_PHYSNET_A_OR_B:
  - action: attach_port
    filter: port.physical_network == 'fabric_a' && network.tag == 'a'
  - action: attach_port
    filter: port.physical_network == 'fabric_b' && network.tag == 'b'

过滤不需要的网络

因此,在这个模型中,为了保持一致性,并帮助防止 Ironic 服务或端口结构的使用跨越到可能具有特定用途的不需要的网络结构,例如专用的存储网络结构,我们将添加一个新的选项到 ironic.conf 和围绕内部网络附件的过滤行为。

此选项将在 [DEFAULT] 配置部分中,并命名为 ironic_network_attachment_filter,它将接受类似于可以为 flavor 映射设置的过滤器值。

未来增强

此功能的显而易见的次要未来增强包括

  • 指定应用于单个节点的默认规则(例如 node.driver_info.default_network_rule=CUSTOM_MANUAL_NETWORKING_EXPLICIT)

  • 为一组节点指定默认规则集(例如,所有节点的默认设置,或给定 resource_class 的所有节点)

  • 支持非集成 OpenStack 用例

  • 使用动态端口映射功能,用于系统网络,例如 service_networkcleaning_network

  • 在检查中填充新的 Port.class 和/或 Port.vendor 字段

  • 将 port.extra/portgroup.extra 字段的内容暴露给表达式

虽然这些都不作为 MVP 的一部分实现,但它们是许多用例所需的次要增强功能。它们包含在这里,以完善 Ironic 中网络映射的完整愿景。

此更改还为对节点执行其他预配置操作打开了大门,尽管此规范仅限于网络映射和配置。

备选方案

提出的总体思路将两个基本需求结合在一起,即动态关联具有部署时操作的绑定,并以尽可能减少对 Nova 当前建模方式的侵入性来实现。

这是因为许多运营商拥有利用裸机环境的多租户环境,并且他们希望对网络有更精确的控制。

为了达到相同的灵活性水平,我们最终必须在 Nova 中实现类似的想法,但由于缺乏对通用虚拟机适用性,这不太可能实现。

或者,对于混合工作负载来说,更昂贵和破坏性的选择是,运营商可能需要努力从他们的运营图中移除 Nova,或者直接编写自己的逻辑来促进这些类型的配置,或者直接提前编排配置。对于深度投资于融合多租户平台的运营商来说,这两种方法可能都不理想。

提前直接关联 - 类似的功能

在过去,将 VIF 与特定端口或端口组配对的功能是在变更 I82efa90994325aa37cca865920d656f510a691b2 中添加的,但是 Ironic API 表面的已知用户没有利用此功能,并且对于一般使用情况而言。

这在技术上允许用户明确了解细节并请求映射,但这需要对基础设施的高度了解,对于非特权用户来说是不合适的。

作为这项工作的一部分,预计不会更改直接映射。

也就是说,此功能虽然在解决“虚拟接口”到“物理端口”的映射方面有所相关,但它也需要提前配置,这对于接近部署的操作来说很大程度上不适用。

数据模型影响

为了促进这一点,我们需要在端口和端口组上添加新的字段值,以启用更详细的运营商与对象的交互,以便运营商能够轻松识别和标记端口/端口组以进行过滤。

对于 Port 对象,最终对于 API

里程碑 1

  • “vendor” - 字符串字段 - 32 个字符

  • “class” - 字符串字段 - 80 个字符

里程碑 2

  • “available_for_dynamic_portgroup” - 布尔值 - 默认 False

对于 Portgroup 对象,最终对于 API

里程碑 1

  • “class” - 字符串字段 - 字符串字段 - 80 个字符

  • “physical_network” - 字符串字段 - 64 个字符 - 只读于 API 消费者,将反映成员端口上设置的 physical_network。

里程碑 2

  • “dynamic_portgroup” - 布尔值 - 只读于 API 消费者,默认 False,但在内部设置为 True,以便 Ironic 知道何时必须拆除端口组。

这些字段的默认状态将为空,无需数据迁移。实施者可以选择在里程碑一的 DB 迁移中添加所有字段,以防止运营商需要两次迁移。

状态机影响

REST API 影响

数据模型影响 中列出的字段将使用 Ironic 对象标准的约定在端口和端口组 API 中暴露。

因此,REST API 微版本也需要递增。

所有端口组的成员必须具有相同的物理网络。

如果端口需要更改其 physical_network,则其所属的任何现有端口组都必须被拆除或删除。

客户端 (CLI) 影响

“openstack baremetal” CLI

baremetal 客户端需要修改以包含额外的字段。

“openstacksdk”

openstacksdk 需要修改以包含额外的字段。

RPC API 影响

端口和端口组的对象将使用新字段进行更新。

驱动程序 API 影响

Nova 驱动程序影响

请求处理的总体流程 中,我们规定了为了使此新功能正常工作所需的运行顺序。我们目前认为,virt 驱动程序中的代码,按照今天的编写方式,可以促进这一点。

Ramdisk 影响

安全影响

其他最终用户影响

可扩展性影响

为聚合添加额外的配置,随着网络布局的变化可能需要更新,这对于可扩展性来说不是理想的。鼓励运营商使用带有前缀匹配和网络标记的表达式语法来创建可以应用于大型环境中的“N”个网络配置的配置。

性能影响

在这种模型下,vif 附加操作将采取一些额外的每请求操作,这些操作将由运营商提供的规则定义。换句话说,将评估规则,并且根据规则的复杂性,可能会触发额外的查询,例如到 Neutron,以帮助填充完成请求操作所需的信息。

其他部署者影响

开发人员影响

实现

负责人

主要负责人
  • Jay Faulkner <jay at gr-oss.io, IRC: JayF)

  • Clif Houck <clif at gr-oss.io, IRC: clif)

其他贡献者
  • CID <cid at gr-oss.io, IRC: cid)

工作项

预工作

  1. 在开始之前,记录如何在 devstack 中设置测试环境以测试高级网络。请参阅 测试

里程碑一

  1. 在对象和 DBAPI 层添加 Port、Portgroup 的新字段。

  2. 将字段添加到 REST API,增加微版本。此时不要在 API 级别暴露动态端口组相关字段。

  3. 创建逻辑来处理过滤器并与源端口、端口组匹配。

  4. 记录新功能、配置文件和语法,并创建一个示例配置

里程碑二

  1. 将先前添加的动态端口组相关字段暴露给 REST API,增加微版本。

  2. 添加在飞行中创建端口组的操作 (group_and_attach_ports)

  3. 添加逻辑以从匹配项动态创建端口和端口组,并将该新端口组选择用于动态端口组。

  4. 添加逻辑,在拆除裸机节点 VIF 时,在删除 vif 时拆除动态端口组。

  5. 更新文档以引用新功能和操作。创建一个示例配置和围绕动态端口组的使用案例。

依赖项

测试

实施者需要在贡献者指南中添加一个 devstack 配置,指示如何获取一个功能齐全的 devstack 安装,并提供某种方式来配置单独的网络以进行调度。devstack 中可能已经存在此功能,或者可能需要添加一些功能;可以使用 CI 作业作为模板(例如 networking-baremetal-multitenant-vlans)。

请务必明确测试以下情况
  • 无法满足的部署请求

  • 节点维护后的配置恢复正常工作

  • 节点救援后的配置恢复正常工作

升级和向后兼容性

不适用。

文档影响

应提供此功能的全面文档,包括
  • 特定的示例常见用例和配置

  • 我们 yaml 配置文件的完整指定模式,以及与此规范中类似的方式,可用于表达式的可用值的完整列表。

  • 如何迁移现有的使用 physical_network 映射的安装以进行动态映射。

参考资料

待定