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:一种半虚拟化套接字设备,可在客户机和主机之间建立零配置套接字连接。通常用作 guest 的控制平面。

背景

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

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

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

存储

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

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

设计

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

我们将引入一个新的核心分片,该分片允许会话在 CFv2 集合中创建 vmm 组件实例,但任何状态都必须由客户端提供和管理。然后,我们将提供使用功能路由启动虚拟机的能力,而不是使用每个 GuestManager 组件的静态子级 vmm.cm。这样,我们就可以将 GuestManager 组件移入会话,而无需将 vmm 本身也移入会话。换句话说,我们将 vmm(在 core 中与产品和访客无关)保留在 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 中的额外核心分片来实现的。每个核心分片都支持单个 guest。此提案并未要求更改此工作方式,但它极大地简化了组件路由,因为 VMM 和关联设备负责将大部分功能路由到 Guest 管理器分片。

对于未来的工作,我们可以更改启动 GuestManager 组件的方式,以用于开发目的。像 debianzircon 这样的简单 guest 除了 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 ...
};

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