diff options
author | Benjamin Staffin <benley@gmail.com> | 2019-03-24 18:59:09 -0400 |
---|---|---|
committer | Danylo Hlynskyi <abcz2.uprola@gmail.com> | 2019-03-25 00:59:09 +0200 |
commit | c94005358c185d8262814a5b59b2b4185183bd95 (patch) | |
tree | 730f4f61f6d7a7fbb2c20e1ad5d0abd4ee530252 /nixos | |
parent | 0ee682da538489e032ccbdf7ba243a919677718c (diff) | |
download | nixlib-c94005358c185d8262814a5b59b2b4185183bd95.tar nixlib-c94005358c185d8262814a5b59b2b4185183bd95.tar.gz nixlib-c94005358c185d8262814a5b59b2b4185183bd95.tar.bz2 nixlib-c94005358c185d8262814a5b59b2b4185183bd95.tar.lz nixlib-c94005358c185d8262814a5b59b2b4185183bd95.tar.xz nixlib-c94005358c185d8262814a5b59b2b4185183bd95.tar.zst nixlib-c94005358c185d8262814a5b59b2b4185183bd95.zip |
NixOS: Run Docker containers as declarative systemd services (#55179)
* WIP: Run Docker containers as declarative systemd services * PR feedback round 1 * docker-containers: add environment, ports, user, workdir options * docker-containers: log-driver, string->str, line wrapping * ExecStart instead of script wrapper, %n for container name * PR feedback: better description and example formatting * Fix docbook formatting (oops) * Use a list of strings for ports, expand documentation * docker-continers: add a simple nixos test * waitUntilSucceeds to avoid potential weird async issues * Don't enable docker daemon unless we actually need it * PR feedback: leave ExecReload undefined
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/virtualisation/docker-containers.nix | 233 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/docker-containers.nix | 29 |
4 files changed, 264 insertions, 0 deletions
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 0f3c9d0c5627..dc571602581b 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -880,6 +880,7 @@ ./virtualisation/container-config.nix ./virtualisation/containers.nix ./virtualisation/docker.nix + ./virtualisation/docker-containers.nix ./virtualisation/ecs-agent.nix ./virtualisation/libvirtd.nix ./virtualisation/lxc.nix diff --git a/nixos/modules/virtualisation/docker-containers.nix b/nixos/modules/virtualisation/docker-containers.nix new file mode 100644 index 000000000000..7cf871cc3bac --- /dev/null +++ b/nixos/modules/virtualisation/docker-containers.nix @@ -0,0 +1,233 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.docker-containers; + + dockerContainer = + { name, config, ... }: { + + options = { + + image = mkOption { + type = types.str; + description = "Docker image to run."; + example = "library/hello-world"; + }; + + cmd = mkOption { + type = with types; listOf str; + default = []; + description = "Commandline arguments to pass to the image's entrypoint."; + example = literalExample '' + ["--port=9000"] + ''; + }; + + entrypoint = mkOption { + type = with types; nullOr str; + description = "Overwrite the default entrypoint of the image."; + default = null; + example = "/bin/my-app"; + }; + + environment = mkOption { + type = with types; attrsOf str; + default = {}; + description = "Environment variables to set for this container."; + example = literalExample '' + { + DATABASE_HOST = "db.example.com"; + DATABASE_PORT = "3306"; + } + ''; + }; + + log-driver = mkOption { + type = types.str; + default = "none"; + description = '' + Logging driver for the container. The default of + <literal>"none"</literal> means that the container's logs will be + handled as part of the systemd unit. Setting this to + <literal>"journald"</literal> will result in duplicate logging, but + the container's logs will be visible to the <command>docker + logs</command> command. + + For more details and a full list of logging drivers, refer to the + <link xlink:href="https://docs.docker.com/engine/reference/run/#logging-drivers---log-driver"> + Docker engine documentation</link> + ''; + }; + + ports = mkOption { + type = with types; listOf str; + default = []; + description = '' + Network ports to publish from the container to the outer host. + </para> + <para> + Valid formats: + </para> + <itemizedlist> + <listitem> + <para> + <literal><ip>:<hostPort>:<containerPort></literal> + </para> + </listitem> + <listitem> + <para> + <literal><ip>::<containerPort></literal> + </para> + </listitem> + <listitem> + <para> + <literal><hostPort>:<containerPort></literal> + </para> + </listitem> + <listitem> + <para> + <literal><containerPort></literal> + </para> + </listitem> + </itemizedlist> + <para> + Both <literal>hostPort</literal> and + <literal>containerPort</literal> can be specified as a range of + ports. When specifying ranges for both, the number of container + ports in the range must match the number of host ports in the + range. Example: <literal>1234-1236:1234-1236/tcp</literal> + </para> + <para> + When specifying a range for <literal>hostPort</literal> only, the + <literal>containerPort</literal> must <emphasis>not</emphasis> be a + range. In this case, the container port is published somewhere + within the specified <literal>hostPort</literal> range. Example: + <literal>1234-1236:1234/tcp</literal> + </para> + <para> + Refer to the + <link xlink:href="https://docs.docker.com/engine/reference/run/#expose-incoming-ports"> + Docker engine documentation</link> for full details. + ''; + example = literalExample '' + [ + "8080:9000" + ] + ''; + }; + + user = mkOption { + type = with types; nullOr str; + default = null; + description = '' + Override the username or UID (and optionally groupname or GID) used + in the container. + ''; + example = "nobody:nogroup"; + }; + + volumes = mkOption { + type = with types; listOf str; + default = []; + description = '' + List of volumes to attach to this container. + + Note that this is a list of <literal>"src:dst"</literal> strings to + allow for <literal>src</literal> to refer to + <literal>/nix/store</literal> paths, which would difficult with an + attribute set. There are also a variety of mount options available + as a third field; please refer to the + <link xlink:href="https://docs.docker.com/engine/reference/run/#volume-shared-filesystems"> + docker engine documentation</link> for details. + ''; + example = literalExample '' + [ + "volume_name:/path/inside/container" + "/path/on/host:/path/inside/container" + ] + ''; + }; + + workdir = mkOption { + type = with types; nullOr str; + default = null; + description = "Override the default working directory for the container."; + example = "/var/lib/hello_world"; + }; + + extraDockerOptions = mkOption { + type = with types; listOf str; + default = []; + description = "Extra options for <command>docker run</command>."; + example = literalExample '' + ["--network=host"] + ''; + }; + }; + }; + + mkService = name: container: { + wantedBy = [ "multi-user.target" ]; + after = [ "docker.service" "docker.socket" ]; + requires = [ "docker.service" "docker.socket" ]; + serviceConfig = { + ExecStart = concatStringsSep " \\\n " ([ + "${pkgs.docker}/bin/docker run" + "--rm" + "--name=%n" + "--log-driver=${container.log-driver}" + ] ++ optional (! isNull container.entrypoint) + "--entrypoint=${escapeShellArg container.entrypoint}" + ++ (mapAttrsToList (k: v: "-e ${escapeShellArg k}=${escapeShellArg v}") container.environment) + ++ map (p: "-p ${escapeShellArg p}") container.ports + ++ optional (! isNull container.user) "-u ${escapeShellArg container.user}" + ++ map (v: "-v ${escapeShellArg v}") container.volumes + ++ optional (! isNull container.workdir) "-w ${escapeShellArg container.workdir}" + ++ map escapeShellArg container.extraDockerOptions + ++ [container.image] + ++ map escapeShellArg container.cmd + ); + ExecStartPre = "-${pkgs.docker}/bin/docker rm -f %n"; + ExecStop = "${pkgs.docker}/bin/docker stop %n"; + ExecStopPost = "-${pkgs.docker}/bin/docker rm -f %n"; + + ### There is no generalized way of supporting `reload` for docker + ### containers. Some containers may respond well to SIGHUP sent to their + ### init process, but it is not guaranteed; some apps have other reload + ### mechanisms, some don't have a reload signal at all, and some docker + ### images just have broken signal handling. The best compromise in this + ### case is probably to leave ExecReload undefined, so `systemctl reload` + ### will at least result in an error instead of potentially undefined + ### behaviour. + ### + ### Advanced users can still override this part of the unit to implement + ### a custom reload handler, since the result of all this is a normal + ### systemd service from the perspective of the NixOS module system. + ### + # ExecReload = ...; + ### + + TimeoutStartSec = 0; + TimeoutStopSec = 120; + Restart = "always"; + }; + }; + +in { + + options.docker-containers = mkOption { + default = {}; + type = types.attrsOf (types.submodule dockerContainer); + description = "Docker containers to run as systemd services."; + }; + + config = mkIf (cfg != []) { + + systemd.services = mapAttrs' (n: v: nameValuePair "docker-${n}" (mkService n v)) cfg; + + virtualisation.docker.enable = true; + + }; + +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 69510c1420fa..a5acf78a8839 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -59,6 +59,7 @@ in dhparams = handleTest ./dhparams.nix {}; dnscrypt-proxy = handleTestOn ["x86_64-linux"] ./dnscrypt-proxy.nix {}; docker = handleTestOn ["x86_64-linux"] ./docker.nix {}; + docker-containers = handleTestOn ["x86_64-linux"] ./docker-containers.nix {}; docker-edge = handleTestOn ["x86_64-linux"] ./docker-edge.nix {}; docker-preloader = handleTestOn ["x86_64-linux"] ./docker-preloader.nix {}; docker-registry = handleTest ./docker-registry.nix {}; diff --git a/nixos/tests/docker-containers.nix b/nixos/tests/docker-containers.nix new file mode 100644 index 000000000000..972552735202 --- /dev/null +++ b/nixos/tests/docker-containers.nix @@ -0,0 +1,29 @@ +# Test Docker containers as systemd units + +import ./make-test.nix ({ pkgs, lib, ... }: { + name = "docker-containers"; + meta = { + maintainers = with lib.maintainers; [ benley ]; + }; + + nodes = { + docker = { pkgs, ... }: + { + virtualisation.docker.enable = true; + + virtualisation.dockerPreloader.images = [ pkgs.dockerTools.examples.nginx ]; + + docker-containers.nginx = { + image = "nginx-container"; + ports = ["8181:80"]; + }; + }; + }; + + testScript = '' + startAll; + $docker->waitForUnit("docker-nginx.service"); + $docker->waitForOpenPort(8181); + $docker->waitUntilSucceeds("curl http://localhost:8181|grep Hello"); + ''; +}) |