Add support for NixOS inside containers

This commit is contained in:
Robert Hensing 2019-03-05 19:41:54 +01:00
parent d63026ce42
commit 35a309097a
10 changed files with 228 additions and 6 deletions

View file

@ -0,0 +1,11 @@
{
docker-compose.services.webserver = { pkgs, ... }: {
nixos.useInit = true;
nixos.configuration.services.nginx.enable = true;
nixos.configuration.services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
service.useHostStore = true;
service.ports = [
"8000:80" # host:container
];
};
}

View file

@ -0,0 +1,2 @@
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
import <nixpkgs> {}

View file

@ -0,0 +1,40 @@
/*
DISCLAIMER
This uses a somewhat hidden feature in NixOS, which is the
"runner". It's a script that's available on systemd services
that lets you run the service independently from systemd.
However, it was clearly not intended for public consumption
so please use it with care.
It does not support all features of systemd so you are on
your own if you use it in production.
One known issue is that the script does not respond to docker's
SIGTERM shutdown signal.
*/
{
docker-compose.services.webserver = { config, pkgs, ... }: {
nixos.configuration = {config, pkgs, ...}: {
boot.isContainer = true;
services.nginx.enable = true;
services.nginx.virtualHosts.localhost.root = "${pkgs.nix.doc}/share/doc/nix/manual";
system.build.run-nginx = pkgs.writeScript "run-nginx" ''
#!${pkgs.bash}/bin/bash
PATH='${config.systemd.services.nginx.environment.PATH}'
echo nginx:x:${toString config.users.users.nginx.uid}:${toString config.users.groups.nginx.gid}:nginx web server user:/var/empty:/bin/sh >>/etc/passwd
echo nginx:x:${toString config.users.groups.nginx.gid}:nginx >>/etc/group
${config.systemd.services.nginx.runner}
'';
};
service.command = [ config.nixos.build.run-nginx ];
service.useHostStore = true;
service.ports = [
"8000:80" # host:container
];
};
}

View file

@ -0,0 +1,2 @@
# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH
import <nixpkgs> {}

View file

@ -12,6 +12,8 @@ let
./modules/service/docker-compose-service.nix ./modules/service/docker-compose-service.nix
./modules/service/host-store.nix ./modules/service/host-store.nix
./modules/service/host.nix ./modules/service/host.nix
./modules/service/nixos.nix
./modules/service/nixos-init.nix
]; ];
argsModule = { argsModule = {

View file

@ -0,0 +1,24 @@
/*
NixOS configuration to for running a mostly normal systemd-based
NixOS in Docker.
*/
{ pkgs, lib, ...}: {
# imports = [
# # This profile doesn't seem to work well.
# (pkgs.path + "/nixos/modules/profiles/docker-container.nix")
# This one works, but can not be imported here due because imports can not depend on pkgs.
# (pkgs.path + "/nixos/modules/profiles/minimal.nix")
# ];
boot.isContainer = true;
boot.specialFileSystems = lib.mkForce {};
networking.hostName = "";
services.journald.console = "/dev/console";
systemd.services.systemd-logind.enable = false;
systemd.services.console-getty.enable = false;
systemd.sockets.nix-daemon.enable = false;
systemd.services.nix-daemon.enable = false;
}

View file

@ -36,7 +36,12 @@ in
service.volumes = mkOption { service.volumes = mkOption {
type = listOf types.unspecified; type = listOf types.unspecified;
default = []; default = [];
description = ""; description = dockerComposeRef "volumes";
};
service.tmpfs = mkOption {
type = listOf types.str;
default = [];
description = dockerComposeRef "tmpfs";
}; };
service.build.context = mkOption { service.build.context = mkOption {
type = nullOr str; type = nullOr str;
@ -52,6 +57,11 @@ in
default = null; default = null;
description = dockerComposeKitchenSink; description = dockerComposeKitchenSink;
}; };
service.tty = mkOption {
type = nullOr bool;
default = null;
description = dockerComposeKitchenSink;
};
service.environment = mkOption { service.environment = mkOption {
type = attrsOf (either str int); type = attrsOf (either str int);
default = {}; default = {};
@ -130,6 +140,11 @@ in
default = null; default = null;
description = dockerComposeRef "network_mode"; description = dockerComposeRef "network_mode";
}; };
service.stop_signal = mkOption {
type = nullOr str;
default = null;
description = dockerComposeRef "stop_signal";
};
}; };
config.build.service = { config.build.service = {
@ -166,6 +181,12 @@ in
inherit (config.service) network_mode; inherit (config.service) network_mode;
} // lib.optionalAttrs (config.service.restart != null) { } // lib.optionalAttrs (config.service.restart != null) {
inherit (config.service) restart; inherit (config.service) restart;
} // lib.optionalAttrs (config.service.stop_signal != null) {
inherit (config.service) stop_signal;
} // lib.optionalAttrs (config.service.tmpfs != []) {
inherit (config.service) tmpfs;
} // lib.optionalAttrs (config.service.tty != null) {
inherit (config.service) tty;
} // lib.optionalAttrs (config.service.working_dir != null) { } // lib.optionalAttrs (config.service.working_dir != null) {
inherit (config.service) working_dir; inherit (config.service) working_dir;
}; };

View file

@ -0,0 +1,39 @@
/*
Invokes the NixOS init system in the container.
*/
{ config, lib, pkgs, ... }:
let
inherit (lib) types;
in
{
options = {
nixos.useInit = lib.mkOption {
type = types.bool;
default = false;
description = ''
When enabled, call the NixOS init system.
Configure NixOS with <code>nixos.configuration</code>.
'';
};
};
config = lib.mkIf (config.nixos.useInit) {
nixos.configuration.imports = [
../nixos/container-systemd.nix
(pkgs.path + "/nixos/modules/profiles/minimal.nix")
];
service.command = [ "${config.nixos.build.toplevel}/init" ];
service.environment.container = "docker";
service.volumes = [
"/sys/fs/cgroup:/sys/fs/cgroup:ro"
];
service.tmpfs = [
"/tmp"
"/run"
"/run/wrappers"
];
service.stop_signal = "SIGRTMIN+3";
service.tty = true;
};
}

View file

@ -0,0 +1,60 @@
/*
Generic options for adding NixOS modules and evaluating the NixOS
configuration. The methods for installing NixOS in the image are
defined elsewhere.
*/
{ pkgs, lib, config, ... }:
let
inherit (lib) types;
inherit (types) attrs;
in
{
options = {
nixos.configuration = lib.mkOption {
type = with types; coercedTo unspecified (a: [a]) (listOf unspecified);
default = {};
description = ''
Modules to add to the NixOS configuration.
This option is unused by default, because not all images use NixOS.
One way to use this is to enable <code>nixos.useInit</code>, but the
NixOS configuration can be used in other ways.
'';
};
nixos.build = lib.mkOption {
type = attrs;
readOnly = true;
description = ''
NixOS build products from <code>config.system.build</code>, such as <code>toplevel</code> and <code>etc</code>.
This option is unused by default, because not all images use NixOS.
One way to use this is to enable <code>nixos.useInit</code>, but the
NixOS configuration can be used in other ways.
'';
};
nixos.evaluatedConfig = lib.mkOption {
type = attrs;
readOnly = true;
description = ''
Evaluated NixOS configuration, to be read by service-level modules.
This option is unused by default, because not all images use NixOS.
One way to use this is to enable <code>nixos.useInit</code>, but the
NixOS configuration can be used in other ways.
'';
};
};
config = {
nixos.build = builtins.addErrorContext "while evaluating the service nixos configuration" (
pkgs.nixos config.nixos.configuration
);
nixos.configuration = { config, ... }: { system.build.theConfig = config; };
nixos.evaluatedConfig = config.nixos.build.theConfig;
};
}

View file

@ -2,8 +2,8 @@
let let
# To make some prebuilt derivations available in the vm # To make some prebuilt derivations available in the vm
preEval = import ../../src/nix/eval-composition.nix { preEval = modules: import ../../src/nix/eval-composition.nix {
modules = [ ../../examples/minimal/arion-compose.nix ]; inherit modules;
inherit pkgs; inherit pkgs;
}; };
in in
@ -31,14 +31,35 @@ in
virtualisation.pathsInNixDB = [ virtualisation.pathsInNixDB = [
# Pre-build the image because we don't want to build the world # Pre-build the image because we don't want to build the world
# in the vm. # in the vm.
preEval.config.build.dockerComposeYaml (preEval [ ../../examples/minimal/arion-compose.nix ]).config.build.dockerComposeYaml
(preEval [ ../../examples/full-nixos/arion-compose.nix ]).config.build.dockerComposeYaml
(preEval [ ../../examples/nixos-unit/arion-compose.nix ]).config.build.dockerComposeYaml
pkgs.stdenv pkgs.stdenv
]; ];
}; };
testScript = '' testScript = ''
$machine->fail("curl localhost:8000"); $machine->fail("curl localhost:8000");
$machine->succeed("docker --version"); $machine->succeed("docker --version");
subtest "minimal", sub {
$machine->succeed("cp -r ${../../examples/minimal} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d"); $machine->succeed("cp -r ${../../examples/minimal} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d");
$machine->waitUntilSucceeds("curl localhost:8000"); $machine->waitUntilSucceeds("curl localhost:8000");
$machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work");
$machine->waitUntilFails("curl localhost:8000");
};
subtest "full-nixos", sub {
$machine->succeed("cp -r ${../../examples/full-nixos} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d");
$machine->waitUntilSucceeds("curl localhost:8000");
$machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work");
$machine->waitUntilFails("curl localhost:8000");
};
subtest "nixos-unit", sub {
$machine->succeed("cp -r ${../../examples/nixos-unit} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d");
$machine->waitUntilSucceeds("curl localhost:8000");
$machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work");
$machine->waitUntilFails("curl localhost:8000");
};
''; '';
} }