RFC-0210:虚拟化功能路由

RFC-0210:虚拟化功能路由
状态已接受
区域
  • 虚拟化
说明

定义如何在平台和产品之间路由虚拟化功能。

问题
Gerrit 更改
作者
审核人
提交日期(年-月-日)2022-11-07
审核日期(年-月-日)2023-02-23

摘要

本文档介绍了如何将创建虚拟机的能力路由到产品会话中。相应地,本文档还将介绍 Fuchsia 平台中包含的一组虚拟化功能和 API,以及产品提供的组件。

设计初衷

目前,所有虚拟化组件都由 Fuchsia 核心平台提供,包括:

  • 用于模拟虚拟机和虚拟设备的组件
  • 提供特定客机操作系统二进制文件的软件包
  • 将客机特定功能与产品集成的组件

这有一些明显的缺点。通过将所有客机特定组件放置在平台中,我们使得产品无法以产品特定的方式集成虚拟化。此外,许多客机将保留状态并保存用户数据,我们需要确保此用户数据与其他所有用户数据一样经过加密和保护。本文档介绍了一种与产品无关的设计,但大部分设计将以工作站产品的要求作为激励性示例。

此提案的一个明确目标是使虚拟化能够作为 Fuchsia 平台功能提供。这意味着,我们将向产品会话公开一组功能,使产品能够利用虚拟化机制,而无需将虚拟化所需的所有低级别特权功能路由到会话本身。这部分基于 RFC-0092 - 会话RFC-0194 - 附录:会话中的指南。 如需了解详情,请参阅安全 注意事项

利益相关方

哪些人会关心此 RFC 是否被接受?(此部分是可选的,但建议填写。)

辅导员

由 FEC 指定的人员,负责引导此 RFC 完成 RFC 流程。

审核人

  • abdulla
  • alerad
  • dahastin
  • jsankey
  • aaronwood
  • ypomortsev

咨询对象

列出应审核 RFC 但无需批准的人员。

共同化

此计划的草稿已在虚拟化、组件框架和安全方面的利益相关方之间共同化。

术语库

  • 虚拟机管理器 (vmm):负责模拟和驱动虚拟机的组件。
  • 虚拟设备:负责模拟单个设备(例如块、网络等)的组件。
  • vsock:一种准虚拟化套接字设备,允许在客机和主机之间建立 0 配置套接字连接。通常用作客机的控制平面。

背景

Fuchsia 上的虚拟机由 vmm(虚拟机管理器)组件驱动。vmm 组件本身管理着多个实现虚拟设备的子组件(例如 virtio_block.cmvirtio_net.cm 等),但本文档将整个组件组简称为 vmmvmm 将为虚拟机提供基本资源(内存、vcpus)和虚拟设备。vmm 组件与客机无关,没有任何客机操作系统特定的逻辑。每个 vmm 组件实例都由特定于客机操作系统的 GuestManager 组件(例如 TerminaGuestManager)管理。GuestManager。包含客机生命周期和任何客机特定服务或功能的更高级别逻辑。GuestManager 负责提供所有启动资源(内核、ramdisk、块设备等)以及客机配置,例如要提供哪些设备、要提供多少虚拟 CPU 或多少内存。

目前,Fuchsia 上支持的每个客机操作系统都完全使用静态组件路由进行建模。例如,TerminaGuestManager 组件(为工作站上的 Linux 终端提供支持的客机)具有由核心分片提供的静态子 vmm 组件。有一些路由允许会话中的 Linux 终端组件连接到核心领域中的 TerminaGuestManager,还有一些路由允许客机在图形 shell 中创建窗口(使用 virtio-wayland)。

显示当前功能路由的示意图

存储

GuestManager 组件负责打开 vmm 所需的所有文件和设备;vmm 本身仅接收文件、块设备或其他有状态功能的句柄。TerminaGuestManager 将通过在 CFv2 data 存储功能提供的目录中创建文件来初始化有状态分区。其他只读有状态分区通过打开 TerminaGuestManager 软件包本身中包含的映像的 blob 文件来提供。

由于 TerminaGuestManager 是核心领域中的一个组件,因此将提供给它的 data 存储功能也来自核心领域。这意味着,在工作站等产品上,如果存在基于用户身份验证因素加密的单独账号卷,则客机数据存储将不会位于此卷上,即使它可能包含敏感用户数据也是如此。此外,如果我们设想一个多用户系统,那么在核心领域中拥有单个组件意味着单个 vmm 实例和数据将在所有账号之间共享,这不是一个可行的解决方案。

设计

vmm 组件保留在核心中是可取的,因为它需要多个特权功能。此外,由于 vmm 提供了启动虚拟机的机制,因此 vmm 中已经没有特定于产品的政策或行为。从核心中提取 GuestManager 组件是可取的,因为这些是特定于产品的软件包,它们带来了仅与特定产品相关的逻辑和客机操作系统。通过将这些组件移出 core,我们可以让产品自由地以产品特定的方式自定义虚拟化的集成方式,而无需处理虚拟机模拟的低级别详细信息。

我们将引入一个新的核心分片,允许会话在 CFv2 集合中创建 vmm 组件实例,但任何状态都必须由客户端提供和管理。然后,我们将提供使用功能路由启动虚拟机的能力,而不是使用 vmm.cm(每个 GuestManager 组件的静态子项)。这样,我们就可以将 GuestManager 组件移到会话中,而无需将 vmm 本身也移到会话中。换句话说,我们将与产品和客机无关的 vmm 保留在 core 中,并将特定于产品的 GuestManagers 移到会话中。

为此,我们将引入一个新的 VmmLauncher 组件来处理创建 vmm 组件。此组件将公开与 vmm 公开的 GuestLifecycle 协议相同的协议。不同之处在于,VmmLauncher 不会直接实现 GuestLifecycle,而是会为每个 GuestLifecycle 连接创建一个新的 vmm 实例,并将 FIDL 通道的服务器端转发到新组件。GuestLifecycle 协议用于初始化虚拟机,然后启动执行。

显示建议的功能路由的示意图

/// The guest control plane allows for creating, starting, and stopping the guest.
protocol GuestLifecycle {
    /// Create a VMM configured with the provided config. This instantiates all
    /// devices and loads the kernel without starting the VCPU or device dispatch
    /// loops.
    ///
    /// `Create` must not be called after a call to `Run` until `Stop` is called.
    /// Once a guest has been stopped with a call to `Stop`, then `Create` may be
    /// called again to re-initialize the guest.
    Create(resource struct {
        guest_config GuestConfig;
    }) -> (struct {}) error GuestError;

    /// Binds to the Guest protocol for an initialized guest.
    ///
    /// This operation must be called between `Create` and `Stop`, otherwise
    /// the provided channel will be immediately closed.
    Bind(resource struct {
        guest server_end:Guest;
    }) -> (struct {}) error GuestError;

    /// Start the VCPU and device dispatch loops. This will not return until the
    /// dispatch loops exit. On a clean shutdown (either guest or client initiated)
    /// this will return success.
    ///
    /// If forced to stop by the guest manager calling stop, a SHUTDOWN_FORCED
    /// error will be returned. This will also return any runtime error that forces
    /// the guest to stop.
    ///
    /// `Run` must only be called after a call to `Create`. Once a guest is stopped with
    /// a call to `Stop`, then `Run` may not be called again until `Create` is called
    /// to re-initialize the guest. Notably, we do not support calling `Stop`
    /// and then `Run` directly; the call to `Create` after `Stop` is a requirement.
    Run() -> (struct {}) error GuestError;

    /// Stop a running VMM. Returns once the dispatch loops have stopped. After
    /// Stop returns, `Create` and then `Run` can be called again.
    Stop() -> ();
};

type GuestError = strict enum {
    /// Catch all VMM error.
    INTERNAL_ERROR = 1;

    /// A device endpoint was requested via the guest client API, but the device isn't enabled.
    DEVICE_NOT_PRESENT = 2;

    /// The config failed VMM validation for reasons such as a missing required field.
    BAD_CONFIG = 3;

    /// The VMM failed to initialize the guest object, usually due to capability routing issues
    /// or memory layout problems.
    GUEST_INITIALIZATION_FAILURE = 4;

    /// The VMM failed to initialize a device.
    DEVICE_INITIALIZATION_FAILURE = 5;

    /// The VMM failed to start a device, usually because the device component returned a failure.
    DEVICE_START_FAILURE = 6;

    /// Two or more devices have attempted to register overlapping memory ranges.
    DEVICE_MEMORY_OVERLAP = 7;

    /// Failed to connect to a required service. Check the routing in the manifest.
    FAILED_SERVICE_CONNECT = 8;

    /// Failed to add a public service.
    DUPLICATE_PUBLIC_SERVICES = 9;

    /// General error when loading the guest kernel.
    KERNEL_LOAD_FAILURE = 10;

    /// Error when starting a VCPU.
    VCPU_START_FAILURE = 11;

    /// A VCPU encountered a fatal error while running.
    VCPU_RUNTIME_FAILURE = 12;

    /// The VMM was asked to run before it was created.
    NOT_CREATED = 13;

    /// A VMM is already running. The VMM must be stopped and a new VMM must be created before it
    /// can be run again.
    ALREADY_RUNNING = 14;

    /// A running VMM was forced to stop by the VMM controller.
    CONTROLLER_FORCED_HALT = 15;
};

公开给客户端进行配置的参数集包含在 GuestConfig FIDL 表中。这样便可以配置机器形状(vcpu 和内存)以及哪些虚拟设备可用。

type GuestConfig = resource table {
    /// Type of kernel to load.
    1: kernel_type KernelType;
    /// File to load the kernel from.
    2: kernel client_end:fuchsia.io.File;
    /// File to load the initial RAM disk from.
    3: ramdisk client_end:fuchsia.io.File;
    /// File to load the dtb overlay for a Linux kernel from.
    4: dtb_overlay client_end:fuchsia.io.File;
    /// Kernel command-line to use.
    5: cmdline string:MAX;
    /// Additional kernel command-lines to append to the main command-line.
    6: cmdline_add vector<string:MAX>:MAX;
    /// The number of CPUs to provide to a guest.
    7: cpus uint8;
    /// Amount of guest memory required, in bytes. This value may be rounded up
    /// depending on the system configuration.
    8: guest_memory uint64;
    /// A list of block devices to give a guest. Cannot be changed from the
    /// command-line.
    9: block_devices vector<BlockSpec>:MAX_BLOCK_DEVICES;
    /// A list of specifications for network devices.
   10: net_devices vector<NetSpec>:MAX_NET_DEVICES;
    /// Optional virtio-wl device.
   11: wayland_device WaylandDevice;
    /// Optional virtio-magma device.
   12: magma_device MagmaDevice;
    /// Whether to add a default network device.
   13: default_net bool;
    /// Enable virtio-balloon.
   14: virtio_balloon bool;
    /// Enable virtio-console.
   15: virtio_console bool;
    /// Enable virtio-gpu.
   16: virtio_gpu bool;
    /// Enable virtio-rng.
   17: virtio_rng bool;
    /// Enable virtio-vsock.
   18: virtio_vsock bool;
    /// Enable virtio-sound.
   19: virtio_sound bool;
    /// Enable input streams (capture) for virtio-sound.
   20: virtio_sound_input bool;
    /// Host ports to listen for guest initiated vsock connections on. This can be
    /// used for simplicity if a Listener is known at config creation time, or if a
    /// Listener must be available at the moment of guest creation for timing
    /// reasons.
    /// To add a Listener after a guest starts, see HostVsockEndpoint::Listen.
   21: vsock_listeners vector<Listener>:MAX;
};

Guest 协议提供对虚拟机运行时服务的访问权限。

/// A `Guest` provides access to services of a guest instance.
protocol Guest {
    /// Get a guest console.
    ///
    /// The details regarding what output is produced and what input is accepted
    /// are determined by each guest, but will typically be a read/write socket
    /// with a shell.
    ///
    /// Returns ZX_ERR_UNAVAILABLE if the guest has no configured console.
    GetConsole() -> (resource struct {
        socket zx.handle:SOCKET;
    }) error zx.status;

    /// Get the socket for low-level guest debug logs.
    ///
    /// The details regarding what output is produced and what input is accepted
    /// are determined by each guest, but will typically be a read-only socket
    /// with the guest kernel's serial logs.
    GetSerial() -> (resource struct {
        socket zx.handle:SOCKET;
    }) error zx.status;

    /// Get the vsock endpoint for the guest.
    ///
    /// This endpoint can be used to register listeners for guest initiated
    /// connections, and to initiate connections from a client. If listeners need
    /// to be registered before the guest starts so that they are immediately
    /// available, set them via the `GuestConfig` instead of using this protocol.
    ///
    /// Returns error VSOCK_NOT_PRESENT if the guest was started without a vsock
    /// device.
    GetHostVsockEndpoint(resource struct {
        endpoint server_end:HostVsockEndpoint;
    }) -> (struct {}) error GuestError;

    /// Get the balloon controller endpoint for the guest.
    ///
    /// Returns error BALLOON_NOT_PRESENT if the guest was started without a
    /// balloon device.
    GetBalloonController(resource struct {
        controller server_end:BalloonController;
    }) -> (struct {}) error GuestError;
};

实现

我们将使用核心中的一个组件(称为 vmm_launcher.cm)来实现 VmmLauncher。此组件将管理一个 CFv2 集合,它将向其中启动 vmm 组件。然后,GuestManager 组件将更新 为从其 "parent" 访问 GuestLifecycle 功能,而不是 通过静态子项访问。vmm_launcher.cm 将使用 CFv2 组件层次结构中 /core/virtualization 处的核心分片提供。我们选择将其公开为 virtualization 分片,因为任何支持虚拟化的产品都将使用此分片。

然后,我们将向产品会话公开 GuestLifecycle 协议,并更新工作站产品会话以包含 TerminaGuestManager

工具、诊断和开发者人机工程学

我们需要保留在核心产品上运行客机操作系统的能力。目前,这是通过使用 GN 参数手动添加到 build 的其他核心分片来完成的。每个核心分片都为单个客机提供支持。此提案中的任何内容都不需要更改此工作方式,但它极大地简化了组件路由,因为 vmm 和关联的设备负责将大部分功能路由到客机管理器分片。

对于未来的工作,我们可以更改启动 GuestManager 组件的方式以用于开发目的。简单的客机(如 debianzircon)除了 GuestLifecycle 之外不需要任何功能,因此我们可以支持将这些组件启动到自己的集合中,以减少支持虚拟化工作流所需的自定义核心领域分片的数量。

安全注意事项

vmm 需要一些特权功能才能正常运行(例如 HypervisorResource、一些 /dev 目录)。此设计避免向会话公开这些特权功能。

此设计的一个功能差异是,之前我们提供了一个表示单个虚拟机实例的协议(例如 fuchsia.virtualization.TerminaGuestManager),现在我们将提供使用 fuchsia.virtualization.GuestManager 协议创建任意数量虚拟机实例的功能。此功能仅通过 vmm 组件公开,我们不允许会话将任意组件启动到包含 vmm 实例的集合中。

会话创建的任何客机都将在其 GuestLifecycle 通道关闭时停止,以确保在用户会话停止后没有客机继续运行。

为避免在 vmm 实例之间泄露状态,可变目录功能将从与每个实例关联的相应 GuestManager 组件动态路由到 vmm 实例,而不是例如静态路由共享目录功能。这样,会话还可以以一致的方式管理和保护有状态的虚拟机数据。

文档

有关产品如何使用虚拟化的详细信息将在 fuchsia.dev 上虚拟化文档的新部分中记录。这将包括有关产品会话可以使用的 FIDL 协议集以及任何最佳实践的详细信息。

测试

作为此更改的一部分,所有现有的集成测试都将转换为使用 vmm_launcher 来演示正确的功能。我们还将 探索对 vmm_launcher 进行模糊测试的可能方法,以验证 vmm 实例是否未路由共享的有状态功能。

考虑的替代方案

无需 VmmLauncher 组件

如本文档中所述,vmm_launcher.cm 不会执行任何有趣的操作,它只是响应入站连接请求,在集合中创建组件。这可以直接由组件框架处理,这样我们就可以移除此组件。例如,为了支持这一点,组件框架可以提供一种创建同类组件集合的方法。组件不会在这些集合中创建动态子项,而是在连接到时自动启动。

添加一个定制组件来执行此操作的工作量不大,但如果这是一个常见模式,则考虑直接将其添加到组件框架中可能是有意义的。

添加显式 VmmLauncher 协议

我们可以创建一个新的命名协议供 VmmLauncher 使用,而不是响应与现有 GuestLifecycle 协议的连接来隐式创建新的 vmm 组件。如果我们发现需要扩展 API 占用空间以包含比当前提案更多的功能,则此设计将是合适的。

library fuchsia.virtualization;

@discoverable
protocol VmmLauncher {
    /// Launches a new, uninitialized virtual machine. The bound
    /// `GuestLifecycle` can be used to initialize the VM and start or stop it.
    ///
    /// The VM will be stopped and the component destroyed when the
    ///`GuestLifecycle` channel is closed.
    Launch(resource struct {
        lifecycle server_end:GuestLifecycle;
    });

    /// ... other functionality ...
};

由于我们尚不需要任何其他功能,因此我们更愿意只使用现有协议集。