From ba88db3cd332e439dd2090b64abb7b9942b5fc94 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 19 Mar 2014 19:55:05 +0100 Subject: Add support for imperative container management MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The command nixos-container can now create containers. For instance, the following creates and starts a container named ‘database’: $ nixos-container create database The configuration of the container is stored in /var/lib/containers//etc/nixos/configuration.nix. After editing the configuration, you can make the changes take effect by doing $ nixos-container update database The container can also be destroyed: $ nixos-container destroy database Containers are now executed using a template unit, ‘container@.service’, so the unit in this example would be ‘container@database.service’. --- nixos/modules/virtualisation/containers.nix | 166 +++++++++++++----------- nixos/modules/virtualisation/nixos-container.sh | 106 +++++++++++++-- 2 files changed, 185 insertions(+), 87 deletions(-) (limited to 'nixos/modules') diff --git a/nixos/modules/virtualisation/containers.nix b/nixos/modules/virtualisation/containers.nix index e718398815d2..573a3d1c5848 100644 --- a/nixos/modules/virtualisation/containers.nix +++ b/nixos/modules/virtualisation/containers.nix @@ -42,13 +42,6 @@ in { options = { - root = mkOption { - type = types.path; - description = '' - The root directory of the container. - ''; - }; - config = mkOption { description = '' A specification of the desired configuration of this @@ -103,9 +96,7 @@ in }; config = mkMerge - [ { root = mkDefault "/var/lib/containers/${name}"; - } - (mkIf options.config.isDefined { + [ (mkIf options.config.isDefined { path = (import ../../lib/eval-config.nix { modules = let extraConfig = @@ -126,12 +117,10 @@ in example = literalExample '' { webserver = - { root = "/containers/webserver"; - path = "/nix/var/nix/profiles/webserver"; + { path = "/nix/var/nix/profiles/webserver"; }; database = - { root = "/containers/database"; - config = + { config = { config, pkgs, ... }: { services.postgresql.enable = true; services.postgresql.package = pkgs.postgresql92; @@ -153,78 +142,76 @@ in config = { - systemd.services = mapAttrs' (name: cfg: - let - # FIXME: interface names have a maximum length. - ifaceHost = "c-${name}"; - ifaceCont = "ctmp-${name}"; - ns = "net-${name}"; - in - nameValuePair "container-${name}" { - description = "Container '${name}'"; - - wantedBy = [ "multi-user.target" ]; + systemd.services."container@" = + { description = "Container '%I'"; - unitConfig.RequiresMountsFor = [ cfg.root ]; + unitConfig.RequiresMountsFor = [ "/var/lib/containers/%I" ]; path = [ pkgs.iproute ]; - preStart = + environment.INSTANCE = "%I"; + + script = '' - mkdir -p -m 0755 ${cfg.root}/etc - if ! [ -e ${cfg.root}/etc/os-release ]; then - touch ${cfg.root}/etc/os-release + root="/var/lib/containers/$INSTANCE" + mkdir -p -m 0755 "$root/etc" + if ! [ -e "$root/etc/os-release" ]; then + touch "$root/etc/os-release" fi mkdir -p -m 0755 \ - /nix/var/nix/profiles/per-container/${name} \ - /nix/var/nix/gcroots/per-container/${name} - '' + "/nix/var/nix/profiles/per-container/$INSTANCE" \ + "/nix/var/nix/gcroots/per-container/$INSTANCE" - + optionalString (cfg.root != "/var/lib/containers/${name}") '' - ln -sfn "${cfg.root}" "/var/lib/containers/${name}" - '' + SYSTEM_PATH=/nix/var/nix/profiles/system + if [ -f "/etc/containers/$INSTANCE.conf" ]; then + . "/etc/containers/$INSTANCE.conf" + fi - + optionalString cfg.privateNetwork '' # Cleanup from last time. - ip netns del ${ns} 2> /dev/null || true - ip link del ${ifaceHost} 2> /dev/null || true - ip link del ${ifaceCont} 2> /dev/null || true - - # Create a pair of virtual ethernet devices. On the host, - # we get ‘c- /dev/null || true + ip link del $ifaceHost 2> /dev/null || true + ip link del $ifaceCont 2> /dev/null || true + + if [ "$PRIVATE_NETWORK" = 1 ]; then + # Create a pair of virtual ethernet devices. On the host, + # we get ‘c-" >&2 + echo "Usage: $0 create [--config ]" >&2 + echo " $0 update " >&2 + echo " $0 destroy " >&2 + echo " $0 login " >&2 echo " $0 root-shell " >&2 + echo " $0 set-root-password " >&2 } -args="`getopt --options '' -l help -- "$@"`" +args="`getopt --options '' -l help -l config: -- "$@"`" eval "set -- $args" +extraConfigFile= while [ $# -gt 0 ]; do case "$1" in (--help) usage; exit 0;; + (--config) shift; extraConfigFile=$1;; (--) shift; break;; (*) break;; esac @@ -28,26 +34,104 @@ getContainerRoot() { fi } -if [ $action = login ]; then +container="$1" +if [ -z "$container" ]; then usage; exit 1; fi +shift - container="$1" - if [ -z "$container" ]; then usage; exit 1; fi - shift +if [ $action = create ]; then + + confFile="/etc/containers/$container.conf" + root="/var/lib/containers/$container" + + if [ -e "$confFile" -o -e "$root/nix" ]; then + echo "$0: container ‘$container’ already exists" >&2 + exit 1 + fi + + profileDir="/nix/var/nix/profiles/per-container/$container" + mkdir -m 0755 -p "$root/etc/nixos" "$profileDir" + + config=" +{ config, pkgs, ... }: + +with pkgs.lib; + +{ boot.isContainer = true; + security.initialRootPassword = mkDefault \"!\"; + networking.hostName = mkDefault \"$container\"; + networking.useDHCP = false; + imports = [ $extraConfigFile ]; +}" + configFile="$root/etc/nixos/configuration.nix" + echo "$config" > "$configFile" + + nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '' --set -A system + + # Allocate a new /8 network in the 10.233.* range. + network="$(sed -e 's/.*_ADDRESS=10\.233\.\(.*\)\..*/\1/; t; d' /etc/containers/*.conf | sort -n | tail -n1)" + if [ -z "$network" ]; then network=0; else : $((network++)); fi + + hostAddress="10.233.$network.1" + localAddress="10.233.$network.2" + echo "host IP is $hostAddress, container IP is $localAddress" >&2 + + cat > "$confFile" <&2 + systemctl start "container@$container.service" + +elif [ $action = update ]; then getContainerRoot - exec @socat@/bin/socat "unix:$root/var/lib/login.socket" -,echo=0,raw + configFile="$root/etc/nixos/configuration.nix" + profileDir="/nix/var/nix/profiles/per-container/$container" -elif [ $action = root-shell ]; then + nix-env -p "$profileDir/system" -I "nixos-config=$configFile" -f '' --set -A system - container="$1" - if [ -z "$container" ]; then usage; exit 1; fi - shift + echo "reloading container@$container.service..." >&2 + systemctl reload "container@$container.service" + +elif [ $action = destroy ]; then getContainerRoot + confFile="/etc/containers/$container.conf" + if [ ! -w "$confFile" ]; then + echo "$0: cannot destroy declarative container (remove it from your configuration.nix instead)" + exit 1 + fi + + if systemctl show "container@$container.service" | grep -q ActiveState=active; then + echo "stopping container@$container.service..." >&2 + systemctl stop "container@$container.service" + fi + + rm -f "$confFile" + +elif [ $action = login ]; then + + getContainerRoot + exec @socat@/bin/socat "unix:$root/var/lib/login.socket" -,echo=0,raw + +elif [ $action = root-shell ]; then + + getContainerRoot exec @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - +elif [ $action = set-root-password ]; then + + password="$1" + if [ -z "$password" ]; then usage; exit 1; fi + + # FIXME: not very secure. + getContainerRoot + (echo "passwd"; echo "$password"; echo "$password") | @socat@/bin/socat "unix:$root/var/lib/root-shell.socket" - + else echo "$0: unknown action ‘$action’" >&2 exit 1 -- cgit 1.4.1