nixos/lxd: add virtual-machine support, image and module

+2
nixos/doc/manual/release-notes/rl-2311.section.md
···
- Support for WiFi6 (IEEE 802.11ax) and WPA3-SAE-PK was enabled in the `hostapd` package, along with a significant rework of the hostapd module.
+
- LXD now supports virtual machine instances to complement the existing container support
+
## New Services {#sec-release-23.11-new-services}
- [MCHPRS](https://github.com/MCHPR/MCHPRS), a multithreaded Minecraft server built for redstone. Available as [services.mchprs](#opt-services.mchprs.enable).
+20
nixos/maintainers/scripts/lxd/lxd-container-image-inner.nix
···
+
# Edit this configuration file to define what should be installed on
+
# your system. Help is available in the configuration.nix(5) man page
+
# and in the NixOS manual (accessible by running ‘nixos-help’).
+
+
{ config, pkgs, lib, ... }:
+
+
{
+
imports =
+
[
+
# Include the default lxd configuration.
+
../../../modules/virtualisation/lxc-container.nix
+
# Include the container-specific autogenerated configuration.
+
./lxd.nix
+
];
+
+
networking.useDHCP = false;
+
networking.interfaces.eth0.useDHCP = true;
+
+
system.stateVersion = "21.05"; # Did you read the comment?
+
}
-95
nixos/maintainers/scripts/lxd/lxd-image-inner.nix
···
-
# Edit this configuration file to define what should be installed on
-
# your system. Help is available in the configuration.nix(5) man page
-
# and in the NixOS manual (accessible by running ‘nixos-help’).
-
-
{ config, pkgs, lib, ... }:
-
-
{
-
imports =
-
[ # Include the default lxd configuration.
-
../../../modules/virtualisation/lxc-container.nix
-
# Include the container-specific autogenerated configuration.
-
./lxd.nix
-
];
-
-
# networking.hostName = mkForce "nixos"; # Overwrite the hostname.
-
# networking.wireless.enable = true; # Enables wireless support via wpa_supplicant.
-
-
# Set your time zone.
-
# time.timeZone = "Europe/Amsterdam";
-
-
# The global useDHCP flag is deprecated, therefore explicitly set to false here.
-
# Per-interface useDHCP will be mandatory in the future, so this generated config
-
# replicates the default behaviour.
-
networking.useDHCP = false;
-
networking.interfaces.eth0.useDHCP = true;
-
-
# Configure network proxy if necessary
-
# networking.proxy.default = "http://user:password@proxy:port/";
-
# networking.proxy.noProxy = "127.0.0.1,localhost,internal.domain";
-
-
# Select internationalisation properties.
-
# i18n.defaultLocale = "en_US.UTF-8";
-
# console = {
-
# font = "Lat2-Terminus16";
-
# keyMap = "us";
-
# };
-
-
# Enable the X11 windowing system.
-
# services.xserver.enable = true;
-
-
# Configure keymap in X11
-
# services.xserver.layout = "us";
-
# services.xserver.xkbOptions = "eurosign:e";
-
-
# Enable CUPS to print documents.
-
# services.printing.enable = true;
-
-
# Enable sound.
-
# sound.enable = true;
-
# hardware.pulseaudio.enable = true;
-
-
# Enable touchpad support (enabled default in most desktopManager).
-
# services.xserver.libinput.enable = true;
-
-
# Define a user account. Don't forget to set a password with ‘passwd’.
-
# users.users.alice = {
-
# isNormalUser = true;
-
# extraGroups = [ "wheel" ]; # Enable ‘sudo’ for the user.
-
# };
-
-
# List packages installed in system profile. To search, run:
-
# $ nix search wget
-
# environment.systemPackages = with pkgs; [
-
# vim # Do not forget to add an editor to edit configuration.nix! The Nano editor is also installed by default.
-
# wget
-
# firefox
-
# ];
-
-
# Some programs need SUID wrappers, can be configured further or are
-
# started in user sessions.
-
# programs.mtr.enable = true;
-
# programs.gnupg.agent = {
-
# enable = true;
-
# enableSSHSupport = true;
-
# };
-
-
# List services that you want to enable:
-
-
# Enable the OpenSSH daemon.
-
# services.openssh.enable = true;
-
-
# Open ports in the firewall.
-
# networking.firewall.allowedTCPPorts = [ ... ];
-
# networking.firewall.allowedUDPPorts = [ ... ];
-
# Or disable the firewall altogether.
-
# networking.firewall.enable = false;
-
-
# This value determines the NixOS release from which the default
-
# settings for stateful data, like file locations and database versions
-
# on your system were taken. It’s perfectly fine and recommended to leave
-
# this value at the release version of the first install of this system.
-
# Before changing this value read the documentation for this option
-
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
-
system.stateVersion = "21.05"; # Did you read the comment?
-
}
+3 -3
nixos/maintainers/scripts/lxd/lxd-image.nix nixos/maintainers/scripts/lxd/lxd-container-image.nix
···
-
{ lib, config, pkgs, ... }:
+
{ lib, pkgs, ... }:
{
imports = [
···
system.activationScripts.config = ''
if [ ! -e /etc/nixos/configuration.nix ]; then
mkdir -p /etc/nixos
-
cat ${./lxd-image-inner.nix} > /etc/nixos/configuration.nix
-
sed 's|../../../modules/virtualisation/lxc-container.nix|<nixpkgs/nixos/modules/virtualisation/lxc-container.nix>|g' -i /etc/nixos/configuration.nix
+
cat ${./lxd-container-image-inner.nix} > /etc/nixos/configuration.nix
+
${lib.getExe pkgs.gnused} 's|../../../modules/virtualisation/lxc-container.nix|<nixpkgs/nixos/modules/virtualisation/lxc-container.nix>|g' -i /etc/nixos/configuration.nix
fi
'';
+20
nixos/maintainers/scripts/lxd/lxd-virtual-machine-image-inner.nix
···
+
# Edit this configuration file to define what should be installed on
+
# your system. Help is available in the configuration.nix(5) man page
+
# and in the NixOS manual (accessible by running ‘nixos-help’).
+
+
{ config, pkgs, lib, ... }:
+
+
{
+
imports =
+
[
+
# Include the default lxd configuration.
+
../../../modules/virtualisation/lxd-virtual-machine.nix
+
# Include the container-specific autogenerated configuration.
+
./lxd.nix
+
];
+
+
networking.useDHCP = false;
+
networking.interfaces.eth0.useDHCP = true;
+
+
system.stateVersion = "23.05"; # Did you read the comment?
+
}
+27
nixos/maintainers/scripts/lxd/lxd-virtual-machine-image.nix
···
+
{ lib, pkgs, ... }:
+
+
{
+
imports = [
+
../../../modules/virtualisation/lxd-virtual-machine.nix
+
];
+
+
virtualisation.lxc.templates.nix = {
+
enable = true;
+
target = "/etc/nixos/lxd.nix";
+
template = ./nix.tpl;
+
when = ["create" "copy"];
+
};
+
+
# copy the config for nixos-rebuild
+
system.activationScripts.config = ''
+
if [ ! -e /etc/nixos/configuration.nix ]; then
+
mkdir -p /etc/nixos
+
cat ${./lxd-virtual-machine-image-inner.nix} > /etc/nixos/configuration.nix
+
${lib.getExe pkgs.gnused} 's|../../../modules/virtualisation/lxd-virtual-machine.nix|<nixpkgs/nixos/modules/virtualisation/lxd-virtual-machine.nix>|g' -i /etc/nixos/configuration.nix
+
fi
+
'';
+
+
# Network
+
networking.useDHCP = false;
+
networking.interfaces.enp5s0.useDHCP = true;
+
}
+6 -124
nixos/modules/virtualisation/lxc-container.nix
···
{ lib, config, pkgs, ... }:
-
with lib;
-
let
-
templateSubmodule = { ... }: {
-
options = {
-
enable = mkEnableOption (lib.mdDoc "this template");
-
-
target = mkOption {
-
description = lib.mdDoc "Path in the container";
-
type = types.path;
-
};
-
template = mkOption {
-
description = lib.mdDoc ".tpl file for rendering the target";
-
type = types.path;
-
};
-
when = mkOption {
-
description = lib.mdDoc "Events which trigger a rewrite (create, copy)";
-
type = types.listOf (types.str);
-
};
-
properties = mkOption {
-
description = lib.mdDoc "Additional properties";
-
type = types.attrs;
-
default = {};
-
};
-
};
-
};
-
-
toYAML = name: data: pkgs.writeText name (generators.toYAML {} data);
-
cfg = config.virtualisation.lxc;
-
templates = if cfg.templates != {} then let
-
list = mapAttrsToList (name: value: { inherit name; } // value)
-
(filterAttrs (name: value: value.enable) cfg.templates);
-
in
-
{
-
files = map (tpl: {
-
source = tpl.template;
-
target = "/templates/${tpl.name}.tpl";
-
}) list;
-
properties = listToAttrs (map (tpl: nameValuePair tpl.target {
-
when = tpl.when;
-
template = "${tpl.name}.tpl";
-
properties = tpl.properties;
-
}) list);
-
}
-
else { files = []; properties = {}; };
-
-
in
-
{
+
in {
imports = [
-
../installer/cd-dvd/channel.nix
-
../profiles/clone-config.nix
-
../profiles/minimal.nix
+
./lxc-instance-common.nix
];
options = {
virtualisation.lxc = {
-
templates = mkOption {
-
description = lib.mdDoc "Templates for LXD";
-
type = types.attrsOf (types.submodule (templateSubmodule));
-
default = {};
-
example = literalExpression ''
-
{
-
# create /etc/hostname on container creation. also requires networking.hostName = "" to be set
-
"hostname" = {
-
enable = true;
-
target = "/etc/hostname";
-
template = builtins.toFile "hostname.tpl" "{{ container.name }}";
-
when = [ "create" ];
-
};
-
# create /etc/nixos/hostname.nix with a configuration for keeping the hostname applied
-
"hostname-nix" = {
-
enable = true;
-
target = "/etc/nixos/hostname.nix";
-
template = builtins.toFile "hostname-nix.tpl" "{ ... }: { networking.hostName = \"{{ container.name }}\"; }";
-
# copy keeps the file updated when the container is changed
-
when = [ "create" "copy" ];
-
};
-
# copy allow the user to specify a custom configuration.nix
-
"configuration-nix" = {
-
enable = true;
-
target = "/etc/nixos/configuration.nix";
-
template = builtins.toFile "configuration-nix" "{{ config_get(\"user.user-data\", properties.default) }}";
-
when = [ "create" ];
-
};
-
};
-
'';
-
};
-
-
privilegedContainer = mkOption {
-
type = types.bool;
+
privilegedContainer = lib.mkOption {
+
type = lib.types.bool;
default = false;
description = lib.mdDoc ''
Whether this LXC container will be running as a privileged container or not. If set to `true` then
···
${config.nix.package.out}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system
'';
-
system.build.metadata = pkgs.callPackage ../../lib/make-system-tarball.nix {
-
contents = [
-
{
-
source = toYAML "metadata.yaml" {
-
architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.system)) 0;
-
creation_date = 1;
-
properties = {
-
description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.system}";
-
os = "${config.system.nixos.distroId}";
-
release = "${config.system.nixos.codeName}";
-
};
-
templates = templates.properties;
-
};
-
target = "/metadata.yaml";
-
}
-
] ++ templates.files;
-
};
-
# TODO: build rootfs as squashfs for faster unpack
system.build.tarball = pkgs.callPackage ../../lib/make-system-tarball.nix {
extraArgs = "--owner=0";
···
ProtectKernelTunables=no
NoNewPrivileges=no
LoadCredential=
-
'' + optionalString cfg.privilegedContainer ''
+
'' + lib.optionalString cfg.privilegedContainer ''
# Additional settings for privileged containers
ProtectHome=no
ProtectSystem=no
···
})
];
-
# Allow the user to login as root without password.
-
users.users.root.initialHashedPassword = mkOverride 150 "";
-
-
system.activationScripts.installInitScript = mkForce ''
+
system.activationScripts.installInitScript = lib.mkForce ''
ln -fs $systemConfig/init /sbin/init
'';
-
-
# Some more help text.
-
services.getty.helpLine =
-
''
-
-
Log in as "root" with an empty password.
-
'';
-
-
# Containers should be light-weight, so start sshd on demand.
-
services.openssh.enable = mkDefault true;
-
services.openssh.startWhenNeeded = mkDefault true;
-
-
# As this is intended as a standalone image, undo some of the minimal profile stuff
-
environment.noXlibs = false;
-
documentation.enable = true;
-
documentation.nixos.enable = true;
-
services.logrotate.enable = true;
};
}
+104
nixos/modules/virtualisation/lxc-image-metadata.nix
···
+
{ lib, config, pkgs, ... }:
+
+
let
+
templateSubmodule = {...}: {
+
options = {
+
enable = lib.mkEnableOption "this template";
+
+
target = lib.mkOption {
+
description = "Path in the container";
+
type = lib.types.path;
+
};
+
template = lib.mkOption {
+
description = ".tpl file for rendering the target";
+
type = lib.types.path;
+
};
+
when = lib.mkOption {
+
description = "Events which trigger a rewrite (create, copy)";
+
type = lib.types.listOf (lib.types.str);
+
};
+
properties = lib.mkOption {
+
description = "Additional properties";
+
type = lib.types.attrs;
+
default = {};
+
};
+
};
+
};
+
+
toYAML = name: data: pkgs.writeText name (lib.generators.toYAML {} data);
+
+
cfg = config.virtualisation.lxc;
+
templates = if cfg.templates != {} then let
+
list = lib.mapAttrsToList (name: value: { inherit name; } // value)
+
(lib.filterAttrs (name: value: value.enable) cfg.templates);
+
in
+
{
+
files = map (tpl: {
+
source = tpl.template;
+
target = "/templates/${tpl.name}.tpl";
+
}) list;
+
properties = lib.listToAttrs (map (tpl: lib.nameValuePair tpl.target {
+
when = tpl.when;
+
template = "${tpl.name}.tpl";
+
properties = tpl.properties;
+
}) list);
+
}
+
else { files = []; properties = {}; };
+
+
in {
+
options = {
+
virtualisation.lxc = {
+
templates = lib.mkOption {
+
description = "Templates for LXD";
+
type = lib.types.attrsOf (lib.types.submodule templateSubmodule);
+
default = {};
+
example = lib.literalExpression ''
+
{
+
# create /etc/hostname on container creation
+
"hostname" = {
+
enable = true;
+
target = "/etc/hostname";
+
template = builtins.writeFile "hostname.tpl" "{{ container.name }}";
+
when = [ "create" ];
+
};
+
# create /etc/nixos/hostname.nix with a configuration for keeping the hostname applied
+
"hostname-nix" = {
+
enable = true;
+
target = "/etc/nixos/hostname.nix";
+
template = builtins.writeFile "hostname-nix.tpl" "{ ... }: { networking.hostName = "{{ container.name }}"; }";
+
# copy keeps the file updated when the container is changed
+
when = [ "create" "copy" ];
+
};
+
# copy allow the user to specify a custom configuration.nix
+
"configuration-nix" = {
+
enable = true;
+
target = "/etc/nixos/configuration.nix";
+
template = builtins.writeFile "configuration-nix" "{{ config_get(\"user.user-data\", properties.default) }}";
+
when = [ "create" ];
+
};
+
};
+
'';
+
};
+
};
+
};
+
+
config = {
+
system.build.metadata = pkgs.callPackage ../../lib/make-system-tarball.nix {
+
contents = [
+
{
+
source = toYAML "metadata.yaml" {
+
architecture = builtins.elemAt (builtins.match "^([a-z0-9_]+).+" (toString pkgs.system)) 0;
+
creation_date = 1;
+
properties = {
+
description = "${config.system.nixos.distroName} ${config.system.nixos.codeName} ${config.system.nixos.label} ${pkgs.system}";
+
os = "${config.system.nixos.distroId}";
+
release = "${config.system.nixos.codeName}";
+
};
+
templates = templates.properties;
+
};
+
target = "/metadata.yaml";
+
}
+
] ++ templates.files;
+
};
+
};
+
}
+30
nixos/modules/virtualisation/lxc-instance-common.nix
···
+
{lib, ...}:
+
+
{
+
imports = [
+
./lxc-image-metadata.nix
+
+
../installer/cd-dvd/channel.nix
+
../profiles/clone-config.nix
+
../profiles/minimal.nix
+
];
+
+
# Allow the user to login as root without password.
+
users.users.root.initialHashedPassword = lib.mkOverride 150 "";
+
+
# Some more help text.
+
services.getty.helpLine = ''
+
+
Log in as "root" with an empty password.
+
'';
+
+
# Containers should be light-weight, so start sshd on demand.
+
services.openssh.enable = lib.mkDefault true;
+
services.openssh.startWhenNeeded = lib.mkDefault true;
+
+
# As this is intended as a standalone image, undo some of the minimal profile stuff
+
environment.noXlibs = false;
+
documentation.enable = true;
+
documentation.nixos.enable = true;
+
services.logrotate.enable = true;
+
}
+46
nixos/modules/virtualisation/lxd-virtual-machine.nix
···
+
{ config, lib, pkgs, ... }:
+
+
let
+
serialDevice =
+
if pkgs.stdenv.hostPlatform.isx86
+
then "ttyS0"
+
else "ttyAMA0"; # aarch64
+
in {
+
imports = [
+
./lxc-instance-common.nix
+
+
../profiles/qemu-guest.nix
+
];
+
+
config = {
+
system.build.qemuImage = import ../../lib/make-disk-image.nix {
+
inherit pkgs lib config;
+
+
partitionTableType = "efi";
+
format = "qcow2-compressed";
+
copyChannel = true;
+
};
+
+
fileSystems = {
+
"/" = {
+
device = "/dev/disk/by-label/nixos";
+
autoResize = true;
+
fsType = "ext4";
+
};
+
"/boot" = {
+
device = "/dev/disk/by-label/ESP";
+
fsType = "vfat";
+
};
+
};
+
+
boot.growPartition = true;
+
boot.loader.systemd-boot.enable = true;
+
+
# image building needs to know what device to install bootloader on
+
boot.loader.grub.device = "/dev/vda";
+
+
boot.kernelParams = ["console=tty1" "console=${serialDevice}"];
+
+
virtualisation.lxd.agent.enable = lib.mkDefault true;
+
};
+
}
+1 -1
nixos/modules/virtualisation/lxd.nix
···
"kernel.keys.maxkeys" = 2000;
};
-
boot.kernelModules = [ "veth" "xt_comment" "xt_CHECKSUM" "xt_MASQUERADE" ]
+
boot.kernelModules = [ "veth" "xt_comment" "xt_CHECKSUM" "xt_MASQUERADE" "vhost_vsock" ]
++ optionals (!config.networking.nftables.enable) [ "iptable_mangle" ];
};
}
+36 -4
nixos/release.nix
···
);
# An image that can be imported into lxd and used for container creation
-
lxdImage = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
lxdContainerImage = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
with import ./.. { inherit system; };
···
modules =
[ configuration
versionModule
-
./maintainers/scripts/lxd/lxd-image.nix
+
./maintainers/scripts/lxd/lxd-container-image.nix
];
}).config.system.build.tarball)
);
# Metadata for the lxd image
-
lxdMeta = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
lxdContainerMeta = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
with import ./.. { inherit system; };
···
modules =
[ configuration
versionModule
-
./maintainers/scripts/lxd/lxd-image.nix
+
./maintainers/scripts/lxd/lxd-container-image.nix
+
];
+
}).config.system.build.metadata)
+
+
);
+
+
# An image that can be imported into lxd and used for container creation
+
lxdVirtualMachineImage = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
+
with import ./.. { inherit system; };
+
+
hydraJob ((import lib/eval-config.nix {
+
inherit system;
+
modules =
+
[ configuration
+
versionModule
+
./maintainers/scripts/lxd/lxd-virtual-machine-image.nix
+
];
+
}).config.system.build.qemuImage)
+
+
);
+
+
# Metadata for the lxd image
+
lxdVirtualMachineImageMeta = forMatchingSystems [ "x86_64-linux" "aarch64-linux" ] (system:
+
+
with import ./.. { inherit system; };
+
+
hydraJob ((import lib/eval-config.nix {
+
inherit system;
+
modules =
+
[ configuration
+
versionModule
+
./maintainers/scripts/lxd/lxd-virtual-machine-image.nix
];
}).config.system.build.metadata)
+5 -5
nixos/tests/lxd/container.nix
···
import ../make-test-python.nix ({ pkgs, lib, ... } :
let
-
lxd-image = import ../../release.nix {
+
releases = import ../../release.nix {
configuration = {
# Building documentation makes the test unnecessarily take a longer time:
documentation.enable = lib.mkForce false;
···
};
};
-
lxd-image-metadata = lxd-image.lxdMeta.${pkgs.stdenv.hostPlatform.system};
-
lxd-image-rootfs = lxd-image.lxdImage.${pkgs.stdenv.hostPlatform.system};
+
lxd-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+
lxd-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
in {
-
name = "lxd";
+
name = "lxd-container";
meta = with pkgs.lib.maintainers; {
-
maintainers = [ patryk27 ];
+
maintainers = [ patryk27 adamcstephens ];
};
nodes.machine = { lib, ... }: {
+1
nixos/tests/lxd/default.nix
···
container = import ./container.nix {inherit system pkgs;};
nftables = import ./nftables.nix {inherit system pkgs;};
ui = import ./ui.nix {inherit system pkgs;};
+
virtual-machine = import ./virtual-machine.nix { inherit system pkgs; };
}
+64
nixos/tests/lxd/virtual-machine.nix
···
+
import ../make-test-python.nix ({ pkgs, lib, ... }:
+
+
let
+
releases = import ../../release.nix {
+
configuration = {
+
# Building documentation makes the test unnecessarily take a longer time:
+
documentation.enable = lib.mkForce false;
+
+
# Our tests require `grep` & friends:
+
environment.systemPackages = with pkgs; [busybox];
+
};
+
};
+
+
lxd-image-metadata = releases.lxdVirtualMachineImageMeta.${pkgs.stdenv.hostPlatform.system};
+
lxd-image-disk = releases.lxdVirtualMachineImage.${pkgs.stdenv.hostPlatform.system};
+
+
instance-name = "instance1";
+
in {
+
name = "lxd-virtual-machine";
+
+
meta = with pkgs.lib.maintainers; {
+
maintainers = [adamcstephens];
+
};
+
+
nodes.machine = {lib, ...}: {
+
virtualisation = {
+
diskSize = 4096;
+
+
cores = 2;
+
+
# Ensure we have enough memory for the nested virtual machine
+
memorySize = 1024;
+
+
lxc.lxcfs.enable = true;
+
lxd.enable = true;
+
};
+
};
+
+
testScript = ''
+
def instance_is_up(_) -> bool:
+
status, _ = machine.execute("lxc exec ${instance-name} --disable-stdin --force-interactive /run/current-system/sw/bin/true")
+
return status == 0
+
+
machine.wait_for_unit("sockets.target")
+
machine.wait_for_unit("lxd.service")
+
machine.wait_for_file("/var/lib/lxd/unix.socket")
+
+
# Wait for lxd to settle
+
machine.succeed("lxd waitready")
+
+
machine.succeed("lxd init --minimal")
+
+
with subtest("virtual-machine image can be imported"):
+
machine.succeed("lxc image import ${lxd-image-metadata}/*/*.tar.xz ${lxd-image-disk}/nixos.qcow2 --alias nixos")
+
+
with subtest("virtual-machine can be launched and become available"):
+
machine.succeed("lxc launch nixos ${instance-name} --vm --config limits.memory=512MB --config security.secureboot=false")
+
with machine.nested("Waiting for instance to start and be usable"):
+
retry(instance_is_up)
+
+
with subtest("lxd-agent is started"):
+
machine.succeed("lxc exec ${instance-name} systemctl is-active lxd-agent")
+
'';
+
})