持久化 libvirt 实例存储元数据

https://blueprints.launchpad.net/nova/+spec/libvirt-instance-storage

目前 libvirt 临时存储布局主要基于计算节点的本地配置推断得出。这在某些情况下存在问题。在极端情况下,它最近导致了几个严重的安全漏洞。它还使得在同一安装中的不同计算节点之间,或安装后随着时间的推移,难以或不可能改变存储配置。通过显式持久化特定实例的存储元数据,我们使其配置明确且易于理解,从而降低了安全问题的风险。我们还能够明确地表示单个安装中的多种存储配置。虽然我们没有进行必要的更改来正确处理多种存储配置,但通过明确地描述它们,我们为未来支持此功能的工作奠定了基础。

问题描述

libvirt 临时存储代码存在一些问题

  • 随着每个新的后端类型(LVM/RBD/ploop)的添加,代码已经超出了其原始设计,但从未被重新设计以适应这些截然不同的模型。

  • 存储布局从计算节点上的两个配置变量推断得出:libvirt.images_type 和 use_cow_images。如果在具有这些配置变量不同值的另一个计算节点上创建实例,或者在这些配置变量的值发生更改之前创建实例,则会对其进行错误处理。这最好会导致失败。最坏情况下,它会创建一个安全漏洞。

  • imagebackend 代码使用一个方法,cache(),来创建来自 glance 镜像和来自模板(即空白文件系统或交换磁盘)的磁盘。然后,不同的后端会以不同的方式处理这些磁盘。写入镜像缓存由各个后端完成,由于其不同的性质,它们会以不同的方式使用镜像缓存。为此,后端必须区分 glance 镜像和模板,但接口不允许它们直接这样做。Raw 后端从传递给其模板函数的参数中 grep ‘image_id’。LVM 后端使用 ‘ephemeral_size’。Ploop 后端使用 ‘context’ 和 ‘image_id’,并独立获取 glance 元数据。cache() 接口需要更改以反映其用法。

  • cache() 接口未向后端提供有关其正在导入的磁盘镜像的任何元数据。因此,它必须通过启发式方法推断或检查它。这两种方法都容易出错并可能导致潜在的安全漏洞。cache() 的替代方案必须允许后端提前确定其正在导入的磁盘的格式和大小。

  • Raw 后端命名不当,因为使用 Raw 后端的磁盘可能是 raw 或 qcow2。Raw 后端首先检查其正在导入的磁盘(参见上述问题),然后将其格式写入名为 disk.info 的本地文件。存储磁盘格式意味着在启动时无需检查磁盘,从而防止了严重的安全性缺陷。但是,我们可以做得更好。disk.info 在 Qcow2 和 Raw 后端之间使用不一致,并且其他后端根本不使用它。它还仅限于单个计算节点,因此无法在迁移期间用于确定存储布局。

用例

开发者:通过创建一个单一、规范、持久的磁盘元数据存储库来减少错误,特别是安全错误。

开发者:通过消除理解不足的启发式方法和紧密耦合来支持未来的后端开发工作。

开发者:通过将磁盘布局存储在每个实例而不是每个计算节点,从而支持未来开发功能以

  • 在磁盘布局之间迁移。

  • 实施不同的每实例存储策略(例如,SSD 与旋转磁盘)。

  • 跟踪升级期间磁盘布局的升级过程。

提议的变更

我们需要进行 2 处更改

1 将 cache() 方法拆分为 2 个单独的方法

create_from_image(image_id) 和 create_from_func(func, size)。

2 在创建磁盘之前创建一个磁盘布局的持久记录。这

至少包括:正在使用的后端、磁盘格式和大小。此持久记录必须是可扩展的,以便将来,例如,我们可以为 qcow2 磁盘指定多个本地存储位置,并在它们之间进行选择。

第一次更改涉及对 libvirt 的 imagebackend 模块进行大量重构。这应该以最少的函数更改来实现。

第二次更改取决于第一次更改。在第一次更改到位后,我们在创建磁盘时拥有足够的上下文来了解其大小、格式和应该放置的位置。我们将实现库代码,该代码将持久化此信息以供磁盘使用,然后调用相关的后端。它将存储在 virt 驱动程序特定的字段中,我们将将其添加到 BlockDeviceMapping:driver_info。更高级别的代码将将其视为不透明的 blob。libvirt 驱动程序将将其视为序列化的版本对象:LibvirtDiskMetadata。LibvirtDiskMetadata 最初将包含上述元数据。

备选方案

无疑还有其他实现此目的的方法。但是,重要的是驱动程序应该对实例的存储布局有一个持久的、明确的记录。我选择了这个。

数据模型影响

将添加一个 driver_info 列到 block_device_mapping 表中,类型为 Text。该列将允许为空,并且最初将为空。它将不会被索引,也不会有任何相关的约束。

当对磁盘执行任何操作并且发现它为空时,驱动程序将填充该列。它最初将采用基于驱动程序当前行为的值。

REST API 影响

安全影响

此更改不会直接影响安全性,但通过简化一个代码区域,该区域一直是几个严重安全漏洞的来源,它应该间接提高安全性。具体来说,通过确保我们始终知道虚拟磁盘的格式,我们不应该执行不安全的格式检查。

通知影响

其他最终用户影响

性能影响

在读取驱动程序的块设备映射时,我们需要额外拉取 driver_info,这将增加一些小的开销。更重要的是,通过此更改,驱动程序将在启动和类似操作期间为每个磁盘更新 BlockDeviceMapping 对象。我们应该能够通过将它们批处理到一个 BlockDeviceMappingList 对象中来减少对具有多个磁盘的实例的影响。这将只需要一次往返到 conductor。

其他部署者影响

开发人员影响

该更改在 BlockDeviceMapping 对象中添加了一个 driver_info 字段,并在 libvirt 驱动程序中使用它。其他驱动程序也可能使用此字段,尽管此更改未定义它们应该如何使用它。

实现

负责人

主要负责人

mbooth-9

其他贡献者

工作项

  • 重构 libvirt.imagebackend 以拆分 cache()

  • 在 BlockDeviceMapping 中添加持久元数据存储

依赖项

测试

主要,这不应该引入任何功能上的变化。其目的是为了支持未来的变化。因此,在尽可能大的程度上,所有现有的测试都应该在最少的更改下继续运行。

Tempest 不需要任何更改。

由于更改了内部接口,单元测试可能会发生重大变化,但覆盖的场景应该至少与以前相同。

请注意,Jenkins 当前仅在 gate 中测试 Qcow2 和 Rbd(ceph) 后端。Jenkins 运行的所有当前 libvirt tempest 作业都使用默认的 Qcow2 后端,除了 gate-tempest-dsvm-full-devstack-plugin-ceph,它使用 Rbd。我们还在 Virtuozzo CI 的 check-dsvm-tempest-vz7-exe-minimal 中覆盖了 ploop 后端。这意味着我们当前没有 gate 覆盖 Raw 和 Lvm 后端。

文档影响

参考资料

历史