RFC-0210:虚拟化功能路由

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

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

问题
  • 115651
Gerrit 更改
  • 754859
作者
审核人
提交日期(年-月-日)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 将为虚拟机提供基本资源(内存、vCPU)和虚拟设备。vmm 组件独立于客户机,并且没有任何客户机操作系统专用逻辑。每个 vmm 组件实例由特定于客机操作系统的 GuestManager 组件管理,例如 TerminaGuestManagerGuestManager。包含针对访客生命周期的更高级别逻辑以及任何特定于访客的服务或功能。GuestManager 负责提供所有启动资源(内核、ramdisk、块设备等)以及客户机配置,例如要提供哪些设备、要提供多少个虚拟 CPU 或提供多少内存。

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

显示当前功能路由的图表

存储

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

由于 TerminaGuestManager 是核心领域中的组件,因此向其提供的 data 存储功能也是来自核心领域。这意味着在工作站等产品上,如果一个产品有单独的帐号卷(根据用户身份验证因素进行了加密),访客数据存储将不位于此卷上,即使其中可能包含敏感的用户数据也是如此。此外,如果我们想象一个多用户系统,在核心领域中只有一个组件意味着单个 vmm 实例,并且数据将在所有帐号之间共享,这样的解决方案是不可行的。

设计

最好将 vmm 组件置于核心中,因为它需要多项特权功能。此外,由于 vmm 提供了启动虚拟机的机制,因此 vmm 中尚不存在特定于产品的政策或行为。最好从核心中提取 GuestManager 组件,因为这些组件是特定于产品的软件包,可提供仅与特定产品相关的逻辑和客户机操作系统。通过将这些组件移出 core,我们可以让产品自由地以特定于产品的方式自定义虚拟化的集成方式,而无需处理虚拟机模拟的低级细节。

我们将引入一个新的核心分片,以允许会话在 CFv2 集合中创建 vmm 组件实例,但任何状态都必须由客户端提供和管理。然后,我们将提供使用功能路由(而非vmm.cm每个组件的静态子级)启动虚拟机的功能。GuestManager这样,我们就可以将 GuestManager 组件移至会话中,而无需将 vmm 本身移到会话中。换言之,我们在 core 中保留了与产品和客机无关的 vmm,并将特定于产品的 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;
};

实现

我们将实现 VmmLauncher,并在核心中包含一个名为 vmm_launcher.cm 的组件。此组件将管理会将 vmm 组件启动到的 CFv2 集合。然后,系统会更新 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 ...
};

由于我们不需要任何其他功能,因此我们更倾向于仅使用现有的协议集。