9ee30cd9b5
You can now say: systemd.containers.foo.config = { services.openssh.enable = true; services.openssh.ports = [ 2022 ]; users.extraUsers.root.openssh.authorizedKeys.keys = [ "ssh-dss ..." ]; }; which defines a NixOS instance with the given configuration running inside a lightweight container. You can also manage the configuration of the container independently from the host: systemd.containers.foo.path = "/nix/var/nix/profiles/containers/foo"; where "path" is a NixOS system profile. It can be created/updated by doing: $ nix-env --set -p /nix/var/nix/profiles/containers/foo \ -f '<nixos>' -A system -I nixos-config=foo.nix The container configuration (foo.nix) should define boot.isContainer = true; to optimise away the building of a kernel and initrd. This is done automatically when using the "config" route. On the host, a lightweight container appears as the service "container-<name>.service". The container is like a regular NixOS (virtual) machine, except that it doesn't have its own kernel. It has its own root file system (by default /var/lib/containers/<name>), but shares the Nix store of the host (as a read-only bind mount). It also has access to the network devices of the host. Currently, if the configuration of the container changes, running "nixos-rebuild switch" on the host will cause the container to be rebooted. In the future we may want to send some message to the container so that it can activate the new container configuration without rebooting. Containers are not perfectly isolated yet. In particular, the host's /sys/fs/cgroup is mounted (writable!) in the guest.
137 lines
4.1 KiB
Nix
137 lines
4.1 KiB
Nix
{ config, pkgs, ... }:
|
|
|
|
with pkgs.lib;
|
|
|
|
{
|
|
options = {
|
|
|
|
boot.isContainer = mkOption {
|
|
type = types.bool;
|
|
default = false;
|
|
description = ''
|
|
Whether this NixOS machine is a lightweight container running
|
|
in another NixOS system.
|
|
'';
|
|
};
|
|
|
|
systemd.containers = mkOption {
|
|
type = types.attrsOf (types.submodule (
|
|
{ config, options, name, ... }:
|
|
{
|
|
options = {
|
|
|
|
root = mkOption {
|
|
type = types.path;
|
|
description = ''
|
|
The root directory of the container.
|
|
'';
|
|
};
|
|
|
|
config = mkOption {
|
|
description = ''
|
|
A specification of the desired configuration of this
|
|
container, as a NixOS module.
|
|
'';
|
|
};
|
|
|
|
path = mkOption {
|
|
type = types.path;
|
|
example = "/nix/var/nix/profiles/containers/webserver";
|
|
description = ''
|
|
As an alternative to specifying
|
|
<option>config</option>, you can specify the path to
|
|
the evaluated NixOS system configuration, typically a
|
|
symlink to a system profile.
|
|
'';
|
|
};
|
|
|
|
};
|
|
|
|
config = mkMerge
|
|
[ { root = mkDefault "/var/lib/containers/${name}";
|
|
}
|
|
(mkIf options.config.isDefined {
|
|
path = (import ../../lib/eval-config.nix {
|
|
modules =
|
|
let extraConfig =
|
|
{ boot.isContainer = true;
|
|
security.initialRootPassword = "!";
|
|
networking.hostName = mkDefault name;
|
|
};
|
|
in [ extraConfig config.config ];
|
|
prefix = [ "systemd" "containers" name ];
|
|
}).config.system.build.toplevel;
|
|
})
|
|
];
|
|
}));
|
|
|
|
default = {};
|
|
example = literalExample
|
|
''
|
|
{ webserver =
|
|
{ root = "/containers/webserver";
|
|
path = "/nix/var/nix/profiles/webserver";
|
|
};
|
|
database =
|
|
{ root = "/containers/database";
|
|
config =
|
|
{ config, pkgs, ... }:
|
|
{ services.postgresql.enable = true;
|
|
services.postgresql.package = pkgs.postgresql92;
|
|
};
|
|
};
|
|
}
|
|
'';
|
|
description = ''
|
|
A set of NixOS system configurations to be run as lightweight
|
|
containers. Each container appears as a service
|
|
<literal>container-<replaceable>name</replaceable></literal>
|
|
on the host system, allowing it to be started and stopped via
|
|
<command>systemctl</command> .
|
|
'';
|
|
};
|
|
|
|
};
|
|
|
|
|
|
config = {
|
|
|
|
systemd.services = mapAttrs' (name: container: nameValuePair "container-${name}"
|
|
{ description = "Container '${name}'";
|
|
|
|
wantedBy = [ "multi-user.target" ];
|
|
|
|
unitConfig.RequiresMountsFor = [ container.root ];
|
|
|
|
preStart =
|
|
''
|
|
mkdir -p -m 0755 ${container.root}/etc
|
|
if ! [ -e ${container.root}/etc/os-release ]; then
|
|
touch ${container.root}/etc/os-release
|
|
fi
|
|
'';
|
|
|
|
serviceConfig.ExecStart =
|
|
"${config.systemd.package}/bin/systemd-nspawn -M ${name} -D ${container.root} --bind-ro=/nix ${container.path}/init";
|
|
|
|
preStop =
|
|
''
|
|
pid="$(cat /sys/fs/cgroup/systemd/machine/${name}.nspawn/system/tasks 2> /dev/null)"
|
|
if [ -n "$pid" ]; then
|
|
# Send the RTMIN+3 signal, which causes the container
|
|
# systemd to start halt.target.
|
|
echo "killing container systemd, PID = $pid"
|
|
kill -RTMIN+3 $pid
|
|
# Wait for the container to exit. We can't let systemd
|
|
# do this because it will send a signal to the entire
|
|
# cgroup.
|
|
for ((n = 0; n < 180; n++)); do
|
|
if ! kill -0 $pid 2> /dev/null; then break; fi
|
|
sleep 1
|
|
done
|
|
fi
|
|
'';
|
|
}) config.systemd.containers;
|
|
|
|
};
|
|
} |