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 設定的 Socket 連線的半虛擬化 Socket 裝置。通常用於做為訪客的控制平面。

背景

Fuchsia 上的虛擬機器由 vmm (虛擬機器管理員) 元件驅動。vmm 元件本身會管理多個子元件,這些子元件會實作虛擬裝置 (例如 virtio_block.cmvirtio_net.cm 等),不過本文件會將整個元件群組簡稱為 vmmvmm 會為虛擬機器提供基本資源 (記憶體、vCPU) 和虛擬裝置。vmm 元件不受訪客作業系統影響,也不含任何訪客作業系統專屬邏輯。每個 vmm 元件執行個體都由特定於客體作業系統的 GuestManager 元件管理,例如 TerminaGuestManagerGuestManager。包含訪客生命週期和任何訪客專屬服務或功能的較高層級邏輯。GuestManager 負責提供所有啟動資源 (核心、RAM 磁碟、區塊裝置等) 以及訪客設定,例如要提供哪些裝置、提供多少虛擬 CPU 或提供多少記憶體。

目前,Fuchsia 支援的每個訪客作業系統都會完全使用靜態元件路徑進行建模。舉例來說,TerminaGuestManager 元件 (工作站上 Linux 終端機的輔助作業系統) 具有核心區塊提供的靜態子項 vmm 元件。其中有些路徑可讓工作階段中的 Linux 終端機元件連線至核心領域中的 TerminaGuestManager,有些路徑則可讓訪客在圖形殼層中建立視窗 (使用 virtio-wayland)。

顯示目前能力轉送的圖表

儲存空間

GuestManager 元件負責開啟 vmm 所需的所有檔案和裝置;vmm 本身只會接收檔案、區塊裝置或其他具狀態功能的句柄。TerminaGuestManager 會在 CFv2 data 儲存空間能力提供的目錄中建立檔案,藉此初始化有狀態的分區。其他唯讀有狀態區隔是由開啟 TerminaGuestManager 套件本身所包含的映像檔 blobfs 檔案提供。

由於 TerminaGuestManager 是核心領域中的元件,因此提供給它的 data 儲存空間能力也來自核心領域。這表示在工作站等產品中,如果有根據使用者驗證因素加密的獨立帳戶磁碟區,訪客資料儲存空間就不會位於這個磁碟區,即使可能含有敏感的使用者資料也一樣。此外,如果我們假設為多使用者系統,在核心領域中擁有單一元件,就表示單一 vmm 例項和資料會在所有帳戶間共用,這並非可行解決方案。

設計

vmm 元件需要多項特權功能,因此建議將其保留在核心中。此外,由於 vmm 提供啟動虛擬機器的機制,因此 vmm 中並沒有任何產品專屬政策或行為。從核心中擷取 GuestManager 元件是可行的,因為這些是產品專屬的套件,可提供僅與特定產品相關的邏輯和客體作業系統。將這些元件移出 core 後,我們就能讓產品自由地以產品專屬方式自訂虛擬化整合方式,而無須處理虛擬機器模擬作業的低階詳細資料。

我們將推出新的核心分片,讓工作階段可在 CFv2 集合中建立 vmm 元件例項,但任何狀態都必須由用戶端提供及管理。接著,我們會提供使用能力轉送功能啟動虛擬機器的功能,而非使用每個 GuestManager 元件的靜態子項 vmm.cm。這樣一來,我們就能將 GuestManager 元件移至工作階段,而無須將 vmm 本身移至工作階段。換句話說,我們保留 vmm,這是 core 中不區分產品和訪客的值,並將特定產品的 GuestManagers 移至工作階段。

為此,我們將引入新的 VmmLauncher 元件,用於處理建立 vmm 元件。這個元件會公開 vmm 公開的相同 GuestLifecycle 通訊協定。差異在於,VmmLauncher 不會直接實作 GuestLifecycle,而是會為每個 GuestLifecycle 連線建立新的 vmm 例項,並將 FIDL 管道的伺服器端轉送至新元件。GuestLifecycle 通訊協定用於初始化 VM,然後開始執行。

顯示建議能力轉送的圖表

/// 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 通訊協定可讓 VM 存取執行階段服務。

/// 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 引數手動新增至建構作業。每個核心分片都支援單一訪客。此提案中並未要求變更運作方式,但由於 vmm 和相關裝置負責處理進入訪客管理員分片的大部分能力路徑,因此可大幅簡化元件路由。

日後,我們可能會變更啟動 GuestManager 元件的開發用途。debianzircon 等簡易訪客除了 GuestLifecycle 之外,不需要任何功能,因此我們可以支援將這些元件啟動至各自的集合,以減少需要支援虛擬化工作流程的自訂核心領域分片數量。

安全性考量

vmm 需要一些特權功能才能正常運作 (例如 HypervisorResource、某些 /dev 目錄)。這項設計可避免將這些特權功能公開給工作階段。

這項設計與先前不同的是,先前我們提供的通訊協定代表單一 VM 執行個體 (例如 fuchsia.virtualization.TerminaGuestManager),現在則會提供使用 fuchsia.virtualization.GuestManager 通訊協定建立任意數量 VM 執行個體的能力。這項能力僅會透過 vmm 元件公開,且我們不會允許工作階段在包含 vmm 例項的集合中啟動任意元件。

只要 GuestLifecycle 管道關閉,工作階段建立的所有訪客都會停止運作,確保使用者工作階段停止後,不會有任何訪客繼續執行。

為避免在 vmm 執行個體之間洩漏狀態,可變動目錄功能會從與每個執行個體相關聯的對應 GuestManager 元件,動態轉送至 vmm 執行個體,而非靜態轉送共用目錄能力。這也讓工作階段能夠以一致的方式管理及保護具有狀態的 VM 資料。

說明文件

產品如何使用虛擬化功能的詳細資訊,將記載於 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 ...
};

由於我們目前不需要任何額外功能,因此建議改用現有的一組通訊協定。