diff options
Diffstat (limited to 'nixos/modules/virtualisation')
-rw-r--r-- | nixos/modules/virtualisation/container-config.nix | 71 | ||||
-rw-r--r-- | nixos/modules/virtualisation/containers.nix | 91 | ||||
-rw-r--r-- | nixos/modules/virtualisation/docker-image.nix | 67 | ||||
-rw-r--r-- | nixos/modules/virtualisation/docker.nix | 1 | ||||
-rw-r--r-- | nixos/modules/virtualisation/libvirtd.nix | 35 | ||||
-rw-r--r-- | nixos/modules/virtualisation/nixos-container.pl | 90 | ||||
-rw-r--r-- | nixos/modules/virtualisation/openvswitch.nix | 117 |
7 files changed, 343 insertions, 129 deletions
diff --git a/nixos/modules/virtualisation/container-config.nix b/nixos/modules/virtualisation/container-config.nix index b81f97f2b4e..a7e8953827a 100644 --- a/nixos/modules/virtualisation/container-config.nix +++ b/nixos/modules/virtualisation/container-config.nix @@ -18,76 +18,7 @@ with lib; # Shut up warnings about not having a boot loader. system.build.installBootLoader = "${pkgs.coreutils}/bin/true"; - # Provide a root login prompt on /var/lib/root-login.socket that - # doesn't ask for a password. This socket can only be used by root - # on the host. - systemd.sockets.root-login = - { description = "Root Login Socket"; - wantedBy = [ "sockets.target" ]; - socketConfig = - { ListenStream = "/var/lib/root-login.socket"; - SocketMode = "0600"; - Accept = true; - }; - }; - - systemd.services."root-login@" = - { description = "Root Login %i"; - environment.TERM = "linux"; - serviceConfig = - { Type = "simple"; - StandardInput = "socket"; - ExecStart = "${pkgs.socat}/bin/socat -t0 - \"exec:${pkgs.shadow}/bin/login -f root,pty,setsid,setpgid,stderr,ctty\""; - TimeoutStopSec = 1; # FIXME - }; - restartIfChanged = false; - }; - - # Provide a daemon on /var/lib/run-command.socket that reads a - # command from stdin and executes it. - systemd.sockets.run-command = - { description = "Run Command Socket"; - wantedBy = [ "sockets.target" ]; - socketConfig = - { ListenStream = "/var/lib/run-command.socket"; - SocketMode = "0600"; # only root can connect - Accept = true; - }; - }; - - systemd.services."run-command@" = - { description = "Run Command %i"; - environment.TERM = "linux"; - serviceConfig = - { Type = "simple"; - StandardInput = "socket"; - TimeoutStopSec = 1; # FIXME - }; - script = - '' - #! ${pkgs.stdenv.shell} -e - source /etc/bashrc - read c - eval "command=($c)" - exec "''${command[@]}" - ''; - restartIfChanged = false; - }; - - systemd.services.container-startup-done = - { description = "Container Startup Notification"; - wantedBy = [ "multi-user.target" ]; - after = [ "multi-user.target" ]; - script = - '' - if [ -p /var/lib/startup-done ]; then - echo done > /var/lib/startup-done - fi - ''; - serviceConfig.Type = "oneshot"; - serviceConfig.RemainAfterExit = true; - restartIfChanged = false; - }; + systemd.services.systemd-remount-fs.enable = false; }; diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index 7f545a9d303..d62340f2c79 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -10,7 +10,7 @@ let isExecutable = true; src = ./nixos-container.pl; perl = "${pkgs.perl}/bin/perl -I${pkgs.perlPackages.FileSlurp}/lib/perl5/site_perl"; - inherit (pkgs) socat; + inherit (pkgs) utillinux; }; # The container's init script, a small wrapper around the regular @@ -32,7 +32,10 @@ let fi fi - exec "$1" + # Start the regular stage 1 script, passing the bind-mounted + # notification socket from the host to allow the container + # systemd to signal readiness to the host systemd. + NOTIFY_SOCKET=/var/lib/private/host-notify exec "$1" ''; system = config.nixpkgs.system; @@ -168,17 +171,18 @@ in preStart = '' - mkdir -p -m 0755 $root/var/lib + # Clean up existing machined registration and interfaces. + machinectl terminate "$INSTANCE" 2> /dev/null || true - # Create a named pipe to get a signal when the container - # has finished booting. - rm -f $root/var/lib/startup-done - mkfifo -m 0600 $root/var/lib/startup-done + if [ "$PRIVATE_NETWORK" = 1 ]; then + ip link del dev "ve-$INSTANCE" 2> /dev/null || true + fi ''; script = '' mkdir -p -m 0755 "$root/etc" "$root/var/lib" + mkdir -p -m 0700 "$root/var/lib/private" if ! [ -e "$root/etc/os-release" ]; then touch "$root/etc/os-release" fi @@ -205,12 +209,16 @@ in fi ''} + # Run systemd-nspawn without startup notification (we'll + # wait for the container systemd to signal readiness). + EXIT_ON_REBOOT=1 NOTIFY_SOCKET= \ exec ${config.systemd.package}/bin/systemd-nspawn \ --keep-unit \ -M "$INSTANCE" -D "$root" $extraFlags \ --bind-ro=/nix/store \ --bind-ro=/nix/var/nix/db \ --bind-ro=/nix/var/nix/daemon-socket \ + --bind=/run/systemd/notify:/var/lib/private/host-notify \ --bind="/nix/var/nix/profiles/per-container/$INSTANCE:/nix/var/nix/profiles" \ --bind="/nix/var/nix/gcroots/per-container/$INSTANCE:/nix/var/nix/gcroots" \ --setenv PRIVATE_NETWORK="$PRIVATE_NETWORK" \ @@ -222,12 +230,6 @@ in postStart = '' - # This blocks until the container-startup-done service - # writes something to this pipe. FIXME: it also hangs - # until the start timeout expires if systemd-nspawn exits. - read x < $root/var/lib/startup-done - rm -f $root/var/lib/startup-done - if [ "$PRIVATE_NETWORK" = 1 ]; then ifaceHost=ve-$INSTANCE ip link set dev $ifaceHost up @@ -242,23 +244,41 @@ in preStop = '' - machinectl poweroff "$INSTANCE" + machinectl poweroff "$INSTANCE" || true ''; restartIfChanged = false; #reloadIfChanged = true; # FIXME - serviceConfig.ExecReload = pkgs.writeScript "reload-container" - '' - #! ${pkgs.stdenv.shell} -e - SYSTEM_PATH=/nix/var/nix/profiles/system - echo $SYSTEM_PATH/bin/switch-to-configuration test | \ - ${pkgs.socat}/bin/socat unix:$root/var/lib/run-command.socket - - ''; + serviceConfig = { + ExecReload = pkgs.writeScript "reload-container" + '' + #! ${pkgs.stdenv.shell} -e + ${nixos-container}/bin/nixos-container run "$INSTANCE" -- \ + bash --login -c "/nix/var/nix/profiles/system/bin/switch-to-configuration test" + ''; - serviceConfig.SyslogIdentifier = "container %i"; + SyslogIdentifier = "container %i"; - serviceConfig.EnvironmentFile = "-/etc/containers/%i.conf"; + EnvironmentFile = "-/etc/containers/%i.conf"; + + Type = "notify"; + + NotifyAccess = "all"; + + # Note that on reboot, systemd-nspawn returns 10, so this + # unit will be restarted. On poweroff, it returns 0, so the + # unit won't be restarted. + Restart = "on-failure"; + + # Hack: we don't want to kill systemd-nspawn, since we call + # "machinectl poweroff" in preStop to shut down the + # container cleanly. But systemd requires sending a signal + # (at least if we want remaining processes to be killed + # after the timeout). So send an ignored signal. + KillMode = "mixed"; + KillSignal = "WINCH"; + }; }; # Generate a configuration file in /etc/containers for each @@ -292,5 +312,30 @@ in environment.systemPackages = [ nixos-container ]; + # Start containers at boot time. + systemd.services.all-containers = + { description = "All Containers"; + + wantedBy = [ "multi-user.target" ]; + + unitConfig.ConditionDirectoryNotEmpty = "/etc/containers"; + + serviceConfig.Type = "oneshot"; + + script = + '' + res=0 + shopt -s nullglob + for i in /etc/containers/*.conf; do + AUTO_START= + source "$i" + if [ "$AUTO_START" = 1 ]; then + systemctl start "container@$(basename "$i" .conf).service" || res=1 + fi + done + exit $res + ''; # */ + }; + }; } diff --git a/nixos/modules/virtualisation/docker-image.nix b/nixos/modules/virtualisation/docker-image.nix new file mode 100644 index 00000000000..13b861dc988 --- /dev/null +++ b/nixos/modules/virtualisation/docker-image.nix @@ -0,0 +1,67 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + pkgs2storeContents = l : map (x: { object = x; symlink = "none"; }) l; + +in { + # Create the tarball + system.build.dockerImage = import ../../lib/make-system-tarball.nix { + inherit (pkgs) stdenv perl xz pathsFromGraph; + + contents = []; + extraArgs = "--owner=0"; + storeContents = [ + { object = config.system.build.toplevel + "/init"; + symlink = "/bin/init"; + } + ] ++ (pkgs2storeContents [ pkgs.stdenv ]); + }; + + boot.postBootCommands = + '' + # After booting, register the contents of the Nix store in the Nix + # database. + if [ -f /nix-path-registration ]; then + ${config.nix.package}/bin/nix-store --load-db < /nix-path-registration && + rm /nix-path-registration + fi + + # nixos-rebuild also requires a "system" profile and an + # /etc/NIXOS tag. + touch /etc/NIXOS + ${config.nix.package}/bin/nix-env -p /nix/var/nix/profiles/system --set /run/current-system + + # Set virtualisation to docker + echo "docker" > /run/systemd/container + ''; + + + # docker image config + require = [ + ../installer/cd-dvd/channel.nix + ../profiles/minimal.nix + ../profiles/clone-config.nix + ]; + + boot.isContainer = true; + + # Iptables do not work in docker + networking.firewall.enable = false; + + services.openssh.enable = true; + + # Socket activated ssh presents problem in docker + services.openssh.startWhenNeeded = false; + + # Allow the user to login as root without password + security.initialRootPassword = ""; + + # Some more help text. + services.mingetty.helpLine = + '' + + Log in as "root" with an empty password. + ''; +} diff --git a/nixos/modules/virtualisation/docker.nix b/nixos/modules/virtualisation/docker.nix index a0aa6135326..1ce066cdc73 100644 --- a/nixos/modules/virtualisation/docker.nix +++ b/nixos/modules/virtualisation/docker.nix @@ -59,6 +59,7 @@ in config = mkIf cfg.enable (mkMerge [ { environment.systemPackages = [ pkgs.docker ]; + users.extraGroups.docker.gid = config.ids.gids.docker; } (mkIf cfg.socketActivation { diff --git a/nixos/modules/virtualisation/libvirtd.nix b/nixos/modules/virtualisation/libvirtd.nix index d7d700d8841..318460f4c2c 100644 --- a/nixos/modules/virtualisation/libvirtd.nix +++ b/nixos/modules/virtualisation/libvirtd.nix @@ -7,6 +7,7 @@ with lib; let cfg = config.virtualisation.libvirtd; + vswitch = config.virtualisation.vswitch; configFile = pkgs.writeText "libvirtd.conf" '' unix_sock_group = "libvirtd" unix_sock_rw_perms = "0770" @@ -56,6 +57,20 @@ in ''; }; + virtualisation.libvirtd.onShutdown = + mkOption { + type = types.enum ["shutdown" "suspend" ]; + default = "suspend"; + description = + '' + When shutting down / restarting the host what method should + be used to gracefully halt the guests. Setting to "shutdown" + will cause an ACPI shutdown of each guest. "suspend" will + attempt to save the state of the guests ready to restore on boot. + ''; + }; + + }; @@ -73,12 +88,17 @@ in { description = "Libvirt Virtual Machine Management Daemon"; wantedBy = [ "multi-user.target" ]; - after = [ "systemd-udev-settle.service" ]; + after = [ "systemd-udev-settle.service" ] + ++ optional vswitch.enable "vswitchd.service"; - path = - [ pkgs.bridge_utils pkgs.dmidecode pkgs.dnsmasq + path = [ + pkgs.bridge_utils + pkgs.dmidecode + pkgs.dnsmasq pkgs.ebtables - ] ++ optional cfg.enableKVM pkgs.qemu_kvm; + ] + ++ optional cfg.enableKVM pkgs.qemu_kvm + ++ optional vswitch.enable vswitch.package; preStart = '' @@ -152,7 +172,12 @@ in ${pkgs.libvirt}/etc/rc.d/init.d/libvirt-guests start || true ''; - postStop = "${pkgs.libvirt}/etc/rc.d/init.d/libvirt-guests stop"; + postStop = + '' + export PATH=${pkgs.gettext}/bin:$PATH + export ON_SHUTDOWN=${cfg.onShutdown} + ${pkgs.libvirt}/etc/rc.d/init.d/libvirt-guests stop + ''; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; diff --git a/nixos/modules/virtualisation/nixos-container.pl b/nixos/modules/virtualisation/nixos-container.pl index c6a5ecde9e3..7403a42f0f1 100644 --- a/nixos/modules/virtualisation/nixos-container.pl +++ b/nixos/modules/virtualisation/nixos-container.pl @@ -7,7 +7,7 @@ use File::Slurp; use Fcntl ':flock'; use Getopt::Long qw(:config gnu_getopt); -my $socat = '@socat@/bin/socat'; +my $nsenter = "@utillinux@/bin/nsenter"; # Ensure a consistent umask. umask 0022; @@ -17,25 +17,30 @@ umask 0022; sub showHelp { print <<EOF; Usage: nixos-container list - nixos-container create <container-name> [--config <string>] [--ensure-unique-name] + nixos-container create <container-name> [--system-path <path>] [--config <string>] [--ensure-unique-name] [--auto-start] nixos-container destroy <container-name> nixos-container start <container-name> nixos-container stop <container-name> + nixos-container status <container-name> nixos-container login <container-name> nixos-container root-login <container-name> nixos-container run <container-name> -- args... - nixos-container set-root-password <container-name> <password> nixos-container show-ip <container-name> + nixos-container show-host-key <container-name> EOF exit 0; } +my $systemPath; my $ensureUniqueName = 0; +my $autoStart = 0; my $extraConfig; GetOptions( "help" => sub { showHelp() }, "ensure-unique-name" => \$ensureUniqueName, + "auto-start" => \$autoStart, + "system-path=s" => \$systemPath, "config=s" => \$extraConfig ) or exit 1; @@ -122,17 +127,13 @@ if ($action eq "create") { push @conf, "PRIVATE_NETWORK=1\n"; push @conf, "HOST_ADDRESS=$hostAddress\n"; push @conf, "LOCAL_ADDRESS=$localAddress\n"; + push @conf, "AUTO_START=$autoStart\n"; write_file($confFile, \@conf); close($lock); print STDERR "host IP is $hostAddress, container IP is $localAddress\n"; - mkpath("$root/etc/nixos", 0, 0755); - - my $nixosConfigFile = "$root/etc/nixos/configuration.nix"; - writeNixOSConfig $nixosConfigFile; - # The per-container directory is restricted to prevent users on # the host from messing with guest users who happen to have the # same uid. @@ -141,10 +142,21 @@ if ($action eq "create") { $profileDir = "$profileDir/$containerName"; mkpath($profileDir, 0, 0755); - system("nix-env", "-p", "$profileDir/system", - "-I", "nixos-config=$nixosConfigFile", "-f", "<nixpkgs/nixos>", - "--set", "-A", "system") == 0 - or die "$0: failed to build initial container configuration\n"; + # Build/set the initial configuration. + if (defined $systemPath) { + system("nix-env", "-p", "$profileDir/system", "--set", $systemPath) == 0 + or die "$0: failed to set initial container configuration\n"; + } else { + mkpath("$root/etc/nixos", 0, 0755); + + my $nixosConfigFile = "$root/etc/nixos/configuration.nix"; + writeNixOSConfig $nixosConfigFile; + + system("nix-env", "-p", "$profileDir/system", + "-I", "nixos-config=$nixosConfigFile", "-f", "<nixpkgs/nixos>", + "--set", "-A", "system") == 0 + or die "$0: failed to build initial container configuration\n"; + } print "$containerName\n" if $ensureUniqueName; exit 0; @@ -155,7 +167,11 @@ my $profileDir = "/nix/var/nix/profiles/per-container/$containerName"; my $gcRootsDir = "/nix/var/nix/gcroots/per-container/$containerName"; my $confFile = "/etc/containers/$containerName.conf"; if (!-e $confFile) { - exit 0 if $action eq "destroy"; + if ($action eq "destroy") { + exit 0; + } elsif ($action eq "status") { + print "gone\n"; + } die "$0: container ‘$containerName’ does not exist\n" ; } @@ -169,6 +185,22 @@ sub stopContainer { or die "$0: failed to stop container\n"; } +# Return the PID of the init process of the container. +sub getLeader { + my $s = `machinectl show "$containerName" -p Leader`; + chomp $s; + $s =~ /^Leader=(\d+)$/ or die "unable to get container's main PID\n"; + return int($1); +} + +# Run a command in the container. +sub runInContainer { + my @args = @_; + my $leader = getLeader; + exec($nsenter, "-t", $leader, "-m", "-u", "-i", "-n", "-p", "--", @args); + die "cannot run ‘nsenter’: $!\n"; +} + if ($action eq "destroy") { die "$0: cannot destroy declarative container (remove it from your configuration.nix instead)\n" unless POSIX::access($confFile, &POSIX::W_OK); @@ -190,6 +222,10 @@ elsif ($action eq "stop") { stopContainer; } +elsif ($action eq "status") { + print isContainerRunning() ? "up" : "down", "\n"; +} + elsif ($action eq "update") { my $nixosConfigFile = "$root/etc/nixos/configuration.nix"; @@ -214,28 +250,14 @@ elsif ($action eq "login") { } elsif ($action eq "root-login") { - exec($socat, "unix:$root/var/lib/root-login.socket", "-,echo=0,raw"); + runInContainer("su", "root", "-l"); } elsif ($action eq "run") { shift @ARGV; shift @ARGV; - my $pid = open(SOCAT, "|-", $socat, "-t0", "-", "unix:$root/var/lib/run-command.socket") or die "$0: cannot start $socat: $!\n"; - print SOCAT join(' ', map { "'$_'" } @ARGV), "\n"; - flush SOCAT; - waitpid($pid, 0); - close(SOCAT); -} - -elsif ($action eq "set-root-password") { - # FIXME: don't get password from the command line. - my $password = $ARGV[2] or die "$0: no password given\n"; - my $pid = open(SOCAT, "|-", $socat, "-t0", "-", "unix:$root/var/lib/run-command.socket") or die "$0: cannot start $socat: $!\n"; - print SOCAT "passwd\n"; - print SOCAT "$password\n"; - print SOCAT "$password\n"; - flush SOCAT; - waitpid($pid, 0); - close(SOCAT); + # Escape command. + my $s = join(' ', map { s/'/'\\''/g; "'$_'" } @ARGV); + runInContainer("su", "root", "-l", "-c", "exec " . $s); } elsif ($action eq "show-ip") { @@ -244,6 +266,12 @@ elsif ($action eq "show-ip") { print "$1\n"; } +elsif ($action eq "show-host-key") { + my $fn = "$root/etc/ssh/ssh_host_ecdsa_key.pub"; + exit 1 if ! -f $fn; + print read_file($fn); +} + else { die "$0: unknown action ‘$action’\n"; } diff --git a/nixos/modules/virtualisation/openvswitch.nix b/nixos/modules/virtualisation/openvswitch.nix new file mode 100644 index 00000000000..c1579d94657 --- /dev/null +++ b/nixos/modules/virtualisation/openvswitch.nix @@ -0,0 +1,117 @@ +# Systemd services for openvswitch + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.virtualisation.vswitch; + +in + +{ + + options = { + + virtualisation.vswitch.enable = mkOption { + type = types.bool; + default = false; + description = + '' + Enable Open vSwitch. A configuration + daemon (ovs-server) will be started. + ''; + }; + + + virtualisation.vswitch.package = mkOption { + type = types.package; + default = pkgs.openvswitch; + description = + '' + Open vSwitch package to use. + ''; + }; + + }; + + config = mkIf cfg.enable (let + + # Where the communication sockets live + runDir = "/var/run/openvswitch"; + + # Where the config database live (can't be in nix-store) + stateDir = "/var/db/openvswitch"; + + # The path to the an initialized version of the database + db = pkgs.stdenv.mkDerivation { + name = "vswitch.db"; + unpackPhase = "true"; + buildPhase = "true"; + buildInputs = with pkgs; [ + cfg.package + ]; + installPhase = + '' + ensureDir $out/ + ''; + }; + + in { + + environment.systemPackages = [ cfg.package ]; + + boot.kernelModules = [ "tun" "openvswitch" ]; + + boot.extraModulePackages = [ cfg.package ]; + + systemd.services.ovsdb = { + description = "Open_vSwitch Database Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "systemd-udev-settle.service" ]; + wants = [ "vswitchd.service" ]; + path = [ cfg.package ]; + restartTriggers = [ db cfg.package ]; + # Create the config database + preStart = + '' + mkdir -p ${runDir} + mkdir -p /var/db/openvswitch + chmod +w /var/db/openvswitch + if [[ ! -e /var/db/openvswitch/conf.db ]]; then + ${cfg.package}/bin/ovsdb-tool create \ + "/var/db/openvswitch/conf.db" \ + "${cfg.package}/share/openvswitch/vswitch.ovsschema" + fi + chmod -R +w /var/db/openvswitch + ''; + serviceConfig.ExecStart = + '' + ${cfg.package}/bin/ovsdb-server \ + --remote=punix:${runDir}/db.sock \ + --private-key=db:Open_vSwitch,SSL,private_key \ + --certificate=db:Open_vSwitch,SSL,certificate \ + --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert \ + --unixctl=ovsdb.ctl.sock \ + /var/db/openvswitch/conf.db + ''; + serviceConfig.Restart = "always"; + serviceConfig.RestartSec = 3; + postStart = + '' + ${cfg.package}/bin/ovs-vsctl --timeout 3 --retry --no-wait init + ''; + + }; + + systemd.services.vswitchd = { + description = "Open_vSwitch Daemon"; + bindsTo = [ "ovsdb.service" ]; + after = [ "ovsdb.service" ]; + path = [ cfg.package ]; + serviceConfig.ExecStart = ''${cfg.package}/bin/ovs-vswitchd''; + }; + + }); + +} |