From 3fcf647c7baf7462cc0b3ccf34ae2bcc413064b3 Mon Sep 17 00:00:00 2001 From: Danny Canter Date: Wed, 30 Jul 2025 11:14:41 -0700 Subject: [PATCH] Add virtualization support for containers (#377) --- Package.resolved | 2 +- .../Core/ContainerConfiguration.swift | 4 +++ Sources/ContainerClient/Flags.swift | 7 +++++ Sources/ContainerClient/Utility.swift | 2 ++ .../SandboxService.swift | 2 ++ docs/how-to.md | 26 +++++++++++++++++++ 6 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Package.resolved b/Package.resolved index c9208ed5..f71a14e9 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5cec5c196f44a46c11a3df7bd10731692009c1abaedf2fead9926e6e98b3f6a8", + "originHash" : "558d21fffd6782751d52b7edf9cecbd8cf0ab511bb7d120bf134cbd23985d350", "pins" : [ { "identity" : "async-http-client", diff --git a/Sources/ContainerClient/Core/ContainerConfiguration.swift b/Sources/ContainerClient/Core/ContainerConfiguration.swift index 47dfa037..64c5c683 100644 --- a/Sources/ContainerClient/Core/ContainerConfiguration.swift +++ b/Sources/ContainerClient/Core/ContainerConfiguration.swift @@ -47,6 +47,8 @@ public struct ContainerConfiguration: Sendable, Codable { public var resources: Resources = .init() /// Name of the runtime that supports the container public var runtimeHandler: String = "container-runtime-linux" + /// Configure exposing virtualization support in the container. + public var virtualization: Bool = false enum CodingKeys: String, CodingKey { case id @@ -64,6 +66,7 @@ public struct ContainerConfiguration: Sendable, Codable { case platform case resources case runtimeHandler + case virtualization } /// Create a configuration from the supplied Decoder, initializing missing @@ -86,6 +89,7 @@ public struct ContainerConfiguration: Sendable, Codable { platform = try container.decodeIfPresent(ContainerizationOCI.Platform.self, forKey: .platform) ?? .current resources = try container.decodeIfPresent(Resources.self, forKey: .resources) ?? .init() runtimeHandler = try container.decodeIfPresent(String.self, forKey: .runtimeHandler) ?? "container-runtime-linux" + virtualization = try container.decodeIfPresent(Bool.self, forKey: .virtualization) ?? false } public struct DNSConfiguration: Sendable, Codable { diff --git a/Sources/ContainerClient/Flags.swift b/Sources/ContainerClient/Flags.swift index 42e24cfa..98854954 100644 --- a/Sources/ContainerClient/Flags.swift +++ b/Sources/ContainerClient/Flags.swift @@ -148,6 +148,13 @@ public struct Flags { @Option(name: [.customLong("label"), .short], help: "Add a key=value label to the container") public var labels: [String] = [] + + @Flag( + name: [.customLong("virtualization")], + help: + "Expose virtualization capabilities to the container. (Host must have nested virtualization support, and guest kernel must have virtualization capabilities enabled)" + ) + public var virtualization: Bool = false } public struct Progress: ParsableArguments { diff --git a/Sources/ContainerClient/Utility.swift b/Sources/ContainerClient/Utility.swift index 62dba4e2..6f7af413 100644 --- a/Sources/ContainerClient/Utility.swift +++ b/Sources/ContainerClient/Utility.swift @@ -150,6 +150,8 @@ public struct Utility { mounts.append(contentsOf: volumes) config.mounts = mounts + config.virtualization = management.virtualization + if management.networks.isEmpty { config.networks = [ClientNetwork.defaultNetworkName] } else { diff --git a/Sources/Services/ContainerSandboxService/SandboxService.swift b/Sources/Services/ContainerSandboxService/SandboxService.swift index 05dcf89c..9e62e49a 100644 --- a/Sources/Services/ContainerSandboxService/SandboxService.swift +++ b/Sources/Services/ContainerSandboxService/SandboxService.swift @@ -696,6 +696,8 @@ public actor SandboxService { czConfig.sysctl = config.sysctls.reduce(into: [String: String]()) { $0[$1.key] = $1.value } + // If the host doesn't support this, we'll throw on container creation. + czConfig.virtualization = config.virtualization for mount in config.mounts { if try mount.isSocket() { diff --git a/docs/how-to.md b/docs/how-to.md index 11ec9604..91d864cf 100644 --- a/docs/how-to.md +++ b/docs/how-to.md @@ -286,6 +286,32 @@ Use the `--boot` option to see the logs for the virtual machine boot and init pr % +## Expose virtualization capabilities to a container + +> [!NOTE] +> This feature requires a M3 or newer Apple silicon machine and a Linux kernel that supports virtualization. For a kernel configuration that has all of the right features enabled, see https://github.com/apple/containerization/blob/0.5.0/kernel/config-arm64#L602. + +You can enable virtualization capabilities in containers by using the `--virtualization` option of `container run` and `container create`. + +If your machine does not have support for nested virtualization, you will see the following: + +```console +container run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c "dmesg | grep kvm" +Error: unsupported: "nested virtualization is not supported on the platform" +``` + +When nested virtualization is enabled successfully, `dmesg` will show output like the following: + +```console +container run --name nested-virtualization --virtualization --kernel /path/to/a/kernel/with/virtualization/support --rm ubuntu:latest sh -c "dmesg | grep kvm" +[ 0.017245] kvm [1]: IPA Size Limit: 40 bits +[ 0.017499] kvm [1]: GICv3: no GICV resource entry +[ 0.017501] kvm [1]: disabling GICv2 emulation +[ 0.017506] kvm [1]: GIC system register CPU interface enabled +[ 0.017685] kvm [1]: vgic interrupt IRQ9 +[ 0.017893] kvm [1]: Hyp mode initialized successfully +``` + ## Configure container defaults `container` uses macOS user defaults to store configuration settings that persist between sessions. You can customize various aspects of container behavior, including build settings, default images, and network configuration.