虚拟访客设备角色标记

https://blueprints.launchpad.net/nova/+spec/virt-device-tagged-attach-detach

这将提供一种机制,允许用户为分配给其访客的设备标记一个特定的角色。该标签将与设备的硬件地址匹配,并通过元数据服务/cloud-init 将此映射暴露给访客操作系统。

问题描述

创建具有多个网络设备或磁盘驱动器的虚拟机是很常见的。创建实例的租户用户通常会为每个设备设想一个特定的角色。例如,特定的磁盘可能打算用于 Oracle 数据库存储,或作为 Squid Web 缓存存储等。 同样,可能存在特定的网络接口,旨在由在访客中运行的网络服务应用程序使用。

创建实例的租户用户没有明确的方法将每个设备的预期用途传达给在访客操作系统内运行的应用程序。

可能看起来可以通过租户用户知道的某些方面来识别设备,然后使用 cloud-init / 元数据服务来提供映射给访客。例如,MAC 地址可能用于识别网卡,或者磁盘设备名称字符串可能用于识别磁盘。然后用户将设置一个元数据标签。例如

# nova boot \
    --image mywebappimage \
    --flavor m1.large \
    --meta oracledata=vda \
    --meta apachefrontend=02:10:22:32:33:22 \
    mywebapp

问题在于,由于 Nova 试图隐藏访客硬件设置的尽可能多的细节,因此租户用户很难知道每个设备的唯一标识符是什么。例如,虽然在使用模拟网卡时,可以在启动实例之前知道 MAC 地址,但在使用 PCI 分配的设备时,这是不可用的。

另一种方法似乎是根据设备在访客中出现的顺序来识别设备。例如,访客中的应用程序可以设置为使用第三个 PCI 网卡,或 SCSI 总线上的第二个磁盘。 这个问题是,Nova 和底层虚拟机管理程序都无法保证访客中的设备顺序。 幸运的是,磁盘在 nova 引导命令行上列出的顺序,通常与 Linux 分配设备字母的顺序匹配,但不能保证长期如此。

用例

租户用户需要向访客实例提供信息,以识别用于所需访客应用程序角色的设备。

例如,租户用户希望指示 Oracle 数据库使用特定的 SCSI 磁盘进行其数据存储,因为他们已配置该磁盘使用专为高吞吐量构建的 cinder 卷。或者,他们可能希望指示 NFV 应用程序从特定的网络接口处理数据,因为该接口连接到第二个访客中的接口,该接口正在发送所需的网络流量。

租户需要能够将此标识信息提供给访客操作系统,而无需了解特定的虚拟机管理程序将如何配置虚拟硬件。

提议的变更

该方案是扩展 REST API,以便在向访客实例添加磁盘或网络接口时,可以传递一个不透明的字符串“tag”。

在启动访客时,Nova 将确定与用户请求的设备对应的 PCI、USB、SCSI 地址,并创建一个元数据文件,该文件将用户提供的标签映射到虚拟机管理程序分配的设备地址。

此元数据文件将通过 cloud-init 或元数据服务提供。

当访客操作系统镜像启动时,它将读取此元数据文件以确定需要用于实例中特定应用程序服务的设备。访客操作系统如何执行此操作不在本规范的范围内。Nova 只是定义了一个文件格式和一组它将包含的信息,访客操作系统和/或应用程序可以以其首选的方式使用这些信息。 在这方面目前没有标准,因此文件格式是一个全新的设计。

例如,考虑用户创建了一个具有许多网卡和块设备连接的新实例。这些设备可以被标记,如下所示

nova boot \
    --image mywebappimage \
    --flavor m1.large \
    --nic net-id=12345,tag=nfvfunc1 \
    --nic net-id=56789,tag=nfvfunc2 \
    --block-device volume_id=12345,bus=scsi,tag=oracledb \
    --block-device volume_id=56789,bus=virtio,tag=squidcache \
    mynfvapp

然后 Nova 可以自动生成一个元数据文件,该文件包含以下内容,基于 Nova libvirt 驱动程序为访客实例报告的信息

{
  "devices": [
    {
      "type": "nic",
      "bus": "pci",
      "address": "0000:00:02.0",
      "mac": "01:22:22:42:22:21",
      "tags": ["nfvfunc1"]
    },
    {
      "type": "nic",
      "bus": "pci",
      "address": "0000:00:03.0",
      "mac": "01:22:22:42:22:21",
      "tags": ["nfvfunc2"]
    },
    {
      "type": "disk",
      "bus": "scsi",
      "address": "1:0:2:0",
      "serial": "disk-vol-2352423",
      "tags": ["oracledb"]
    },
    {
      "type": "disk",
      "bus": "pci",
      "address": "0000:00:07.0",
      "serial": "disk-vol-24235252",
      "tags": ["squidcache"]
    }
  ]
}

在此示例中,我们提供了有关设备的几个信息

  • 提供了设备信息类型。当前为“nic”或“disk”。将来将提供其他类型。

  • 设备连接的汇流排。可以是“pci”、“scsi”、“usb”、“ide”等。基本上就是说明如何解释设备地址。对于容器或平台板集成设备,汇流排可能是“none”。

  • 设备地址。地址的格式因汇流排而异,但将是 PCI 地址或 SCSI 地址、USB 端口或 IDE 通道等。

  • 如果类型==nic,则网络设备的 MAC 地址。

  • 如果类型==disk 且已设置磁盘驱动器序列号字符串,则磁盘驱动器序列号字符串。

  • 如果类型==nic 且虚拟机管理程序支持显式设备名称(例如容器),则网络设备名称。

  • 如果类型==disk 且虚拟机管理程序支持显式设备名称(例如容器),则磁盘设备名称。

  • 相同标签可能针对不同设备类型出现多次

  • 如果虚拟机管理程序提供映射到相同后端的两个设备,则相同的标签可能出现在两者中。 这是 Xen HVM 访客的情况,其中单个块设备通过 Xen 准虚拟磁盘和 IDE 模拟磁盘暴露。访客选择使用哪个。

  • 虽然语法支持为每个设备设置多个标签,但初始实现仅允许单个标签。 语法只是允许将来扩展,以防需要。

请注意,并非所有架构都支持 PCI 汇流排,例如 armv7 和 s390 不支持,因此如果访客操作系统希望可移植,则不能假定它将获得特定类型的设备。 因此,对于设备寻址,仅考虑“bus”属性是强制性的,如果数据不可用,则可以省略“address”属性。网络设备始终具有“mac”属性。磁盘设备将具有“serial”属性,如果磁盘具有关联的唯一序列号。

报告给访客操作系统的将视为必须在未来的 Nova 版本中以向后兼容的方式维护的稳定 API。 因此,数据将符合正式的 JSON 模式,该模式将仅追加,以确保未来的兼容性。

{
  "$schema": "https://schema.json.js.cn/schema#",
  "id": "https://openstack.org/schemas/nova/metadata/device-role-tagging/1.0",
  "definitions": {
    "nonedevicebus": {
      "type": "object",
      "properties": {
        "bus": {
          "type": "string",
          "pattern": "none"
        }
      },
      "required": [ "bus" ]
    },
    "pcidevicebus": {
      "type": "object",
      "properties": {
        "bus": {
          "type": "string",
          "pattern": "pci"
        },
        "address": {
          "type": "string",
          "pattern": "[a-f0-9]{4}:[a-f0-9]{2}:[a-f0-9]{2}.[a-f0-9]"
        }
      },
      "required": [ "bus" ]
    },
    "usbdevicebus": {
      "type": "object",
      "properties": {
        "bus": {
          "type": "string",
          "pattern": "usb"
        },
        "address": {
          "type": "string",
          "pattern": "[a-f0-9]+:[a-f0-9]+"
        }
      },
      "required": [ "bus" ]
    },
    "scsidevicebus": {
      "type": "object",
      "properties": {
        "bus": {
          "type": "string",
          "pattern": "scsi"
        },
        "address": {
          "type": "string",
          "pattern": "[a-f0-9]+:[a-f0-9]+:[a-f0-9]+:[a-f0-9]+"
        }
      },
      "required": [ "bus" ]
    },
    "idedevicebus": {
      "type": "object",
      "properties": {
        "bus": {
          "type": "string",
          "pattern": "ide"
        },
        "address": {
          "type": "string",
          "pattern": "[0-1]:[0-1]"
        }
      },
      "required": [ "bus" ]
    },
    "anydevicebus": {
      "type": "object",
      "oneOf": [
        { "$ref": "#/definitions/pcidevicebus" },
        { "$ref": "#/definitions/usbdevicebus" },
        { "$ref": "#/definitions/idedevicebus" },
        { "$ref": "#/definitions/scsidevicebus" },
        { "$ref": "#/definitions/nonedevicebus" }
      ]
    },
    "nicdevice": {
      "type": "object",
      "properties": {
        "mac": {
          "type": "string"
        }
        "devname": {
          "type": "string"
        }
      },
      "required": ["mac"],
      "additionalProperties": {
        "allOf": [
          { "$ref": "#/definitions/anydevicebus" }
        ]
      }
    },
    "diskdevice": {
      "type": "object",
      "properties": {
        "serial": {
         "type": "string"
        },
        "path": {
          "type": "string"
        }
      },
      "additionalProperties": {
        "allOf": [
          { "$ref": "#/definitions/anydevicebus" }
        ]
      }
    }
  },

  "type": "object",

  "properties": {
    "devices": {
      "type": "array",
      "items": {
        "type": [
          { "$ref": "#/definitions/nicdevice" },
          { "$ref": "#/definitions/diskdevice" }
        ]
      }
    }
  }
}

实现将由几个部分组成。将在 nova/virt/metadata.py 中定义一组 Python 类,这些类能够表示由上述 JSON 模式描述的数据,并生成符合标准的 JSON 文档。

虚拟驱动程序将被扩展,以使用与每个实例相关联的数据填充这些类的实例。初始实现将针对 Libvirt 驱动程序完成,但是,鼓励其他虚拟驱动程序维护者提供相同的功能。

元数据 API 将被扩展为能够报告与访客实例关联的此数据。这对于网络配置来说是一个鸡生蛋的问题。依赖元数据服务的访客需要执行最小的网络配置才能到达元数据服务并从 Nova 获取信息。然后他们可以根据设备标签信息重新配置网络。

配置驱动程序生成器将被扩展为能够包含与访客实例关联的此 JSON 数据。这是首选方法,因为访客需要依赖标签来配置网络,因为它没有鸡和蛋的情况。

将来 QEMU 将能够通过固件直接导出元数据,因此它将可以直接从启动的早期阶段获得。预计这将作为未来的可选传输方式使用。

在 Nova 工作范围之外,将创建一个简单的工具,该工具可以解析此元数据文件并在 udev 数据库中设置标签。预计 cloud-init 将触发此工具。因此(Linux)应用程序/操作系统不需要直接理解此 Nova JSON 格式。相反,他们可以简单地查询 udev 以获取具有特定标签的设备的详细信息。这避免了应用程序需要处理无数不同的设备汇流排类型或寻址格式。

Xen HVM 具有双磁盘设备的示例

{
  "devices": [
    {
      "type": "nic",
      "bus": "xen",
      "address": "0",
      "mac": "01:22:22:42:22:21",
      "tags": ["nfvfunc1"]
    },
    {
      "type": "nic",
      "bus": "xen",
      "address": "1",
      "mac": "01:22:22:42:22:21",
      "tags": ["nfvfunc2"]
    },
    {
      "type": "disk",
      "bus": "ide",
      "address": "0:0",
      "serial": "disk-vol-123456",
      "tags": ["oracledb"]
    },
    {
      "type": "disk",
      "bus": "xen",
      "address": "0",
      "path": "/dev/xvda",
      "serial": "disk-vol-123456",
      "tags": ["oracledb"]
    }
    {
      "type": "disk",
      "bus": "ide",
      "address": "0:1",
      "serial": "disk-vol-789321",
      "tags": ["squidcache"]
    },
    {
      "type": "disk",
      "bus": "xen",
      "address": "1",
      "path": "/dev/xvdb",
      "serial": "disk-vol-789321",
      "tags": ["squidcache"]
    }
  ]
}

关于此 Xen 示例的一些注意事项。

  • 这里有两个逻辑磁盘,Xen 已将其作为 IDE 和 Xen 准虚拟磁盘暴露。

  • 对于 Xen 准虚拟磁盘,Xen 还可以提供固定的访客路径。

  • Xen 汇流排上的设备的地址只是一个整数,它映射到 XenBus 命名空间。

LXC 容器的示例

{
  "devices": [
    {
      "type": "nic",
      "bus": "none",
      "mac": "01:22:22:42:22:21",
      "devname": "eth0",
      "tags": ["nfvfunc1"]
    },
    {
      "type": "nic",
      "bus": "none",
      "mac": "01:22:22:42:22:21",
      "devname": "eth1",
      "tags": ["nfvfunc2"]
    },
    {
      "type": "disk",
      "bus": "none",
      "serial": "disk-vol-2352423",
      "path": "/dev/sda",
      "tags": ["oracledb"]
    },
    {
      "type": "disk",
      "bus": "none",
      "serial": "disk-vol-24235252",
      "path": "/dev/sdb",
      "tags": ["squidcache"]
    }
  ]
}

关于此 LXC 示例的一些注意事项

  • 容器不会将设备汇流排导出到访客,因为它们不模拟硬件。因此,“bus”为“none”,没有相应的“address”。

  • 容器能够提供固定的磁盘路径和 NIC 设备名称

备选方案

许多面临此问题的人要求 Nova 允许他们在创建磁盘和/或网络接口时指定固定的 PCI 地址。在传统的数据中心虚拟化世界中,这将是一个可接受的要求,但是云的目标是将租户用户与访客硬件配置的细节隔离。 这种配置需要了解底层虚拟机管理程序的详细知识,租户用户既无法获得这些知识,也不应该期望学习这些知识。鉴于此,允许租户用户通过 REST API 控制访客设备寻址被认为是不合适的。

如问题描述中所述,另一种方法是租户用户通过现有机制手动设置标签来提供用户元数据给访客。但是,这依赖于用户事先知道设备的某些唯一标识属性。在某些情况下这是可能的,但也有许多情况下没有此类信息。

数据模型影响

BlockDeviceMapping 对象(以及关联的表)将获得一个自由格式的字符串属性,名为“tag”。

NetworkRequest 对象(以及关联的表)将获得一个自由格式的字符串属性,名为“tag”。

将来,其他设备类型,例如 PCI 设备或串行端口,也可能会获得类似的“tag”属性。对于初始实现,仅处理磁盘和网络对象。

REST API 影响

块设备映射数据格式将获得一个新的自由格式字符串参数,名为“tag”,可以针对每个磁盘设备进行设置。这将影响启动实例和热添加磁盘的 API。就 Nova 客户端而言,这将显示为 –block-device 标志中的一个新支持的键。例如:

$ nova boot --block-device id=UUID,source=image,tag=database

卷附加 API 同样会在“volumeAttachment”数据字典中获得一个新的自由格式字符串参数,名为“tag”。就 Nova 客户端而言,这将显示为新的标志。例如:

$ nova volume-attach --tag=database INSTANCE-ID VOLUME-ID

服务器创建 API 在每个虚拟接口的“network”数据字典中获得一个新的自由格式字符串参数,名为“tag”。就 Nova 客户端而言,这将显示为 –nic 标志中的一个新支持的键。例如:

$ nova boot --nic net-id=UUID,port-id=UUID,tag=database

接口附加 API 同样会在“interfaceAttachment”数据字典中获得一个新的自由格式字符串参数,名为“tag”。就 Nova 客户端而言,这将显示为新的标志。例如:

$ nova interface-attach UUID --net-id UUID --port-id UUID --tag database

在所有情况下,都需要执行验证以确保提供的“tag”字符串在 (实例、设备类型) 的范围内是唯一的。即,您不能在同一实例上拥有两个具有相同“tag”的网卡,但您可以拥有一个磁盘和一个具有相同“tag”的网卡。

如果未为设备定义标签,则元数据文件中的相应设备条目将不包含任何标签。由于这旨在作为最终用户功能,因此认为 Nova 自动生成标签是不合适的。

这将需要一个新的 API 微版本

安全影响

无,这只是向访客操作系统提供一些用户元数据。

通知影响

其他最终用户影响

在指定虚拟实例的磁盘或网络接口时,将提供新的字段。元数据服务和 cloud-init 将提供一个新的数据文件,其中包含用户标签和地址信息。

性能影响

其他部署者影响

开发人员影响

实现

负责人

主要负责人

Artom Lifshitz

其他贡献者

Daniel Berrange

工作项

  • 为 BlockDeviceMapping 对象定义新属性 (Newton)

  • 为 NetworkRequest 对象定义新属性 (Newton)

  • 为 REST API(s) 中的块设备定义新参数 (Newton)

  • 为 REST API(s) 中的网络请求定义新参数 (Newton)

  • 为 REST API(s) 中网络接口附件定义新参数

  • 为 REST API(s) 中卷附件定义新参数

  • 定义一组类来表示设备元数据 (Newton)

  • 修改元数据 API 以能够提供新的数据文档 (Newton)

  • 修改配置驱动程序生成器以能够包含新的数据文档

  • 修改 libvirt 驱动程序以填充有关具有标签的设备的元数据 (Newton)

  • 修改 Nova 客户端以允许提供额外的 tag 参数 (Newton)

依赖项

将创建一个外部 GIT 存储库,该存储库提供一个能够解析 Nova 标签元数据并在 udev 数据库中设置标签的工具。这并非严格的依赖项,但是一个高度可取的特性,可以促进 Linux 访客使用此标签信息。

Cloud-init 将被增强为在发现 Nova 中的 JSON 标签元数据可用时调用此工具。

测试

Tempest 测试将创建一个具有各种网卡和磁盘的虚拟机,为其分配标签,然后检查面向虚拟机的元数据文件是否存在并包含合理的数据。请注意,它包含的实际数据会根据运行测试的 hypervisor 而变化,因此需要小心确保任何测试的可移植性。

文档影响

API 文档需要更新,以列出允许对磁盘和网络设备使用的新的标签参数。

cloud-init 的用户文档需要描述新提供的元数据文件及其语义。

参考资料

历史

修订版

发布名称

描述

Liberty

引入

Mitaka

重新提出

Newton

已实现使用带标签设备的虚拟机启动

Ocata

重新提议完成实现附加和分离带标签的设备

Pike

重新提议完成在 Ocata 中开始的工作