Initial version of a docker target

This commit is contained in:
Sander van der Burg 2020-07-03 00:04:00 +02:00 committed by Sander van der Burg
parent 5f976340af
commit 2451baf0bc
11 changed files with 475 additions and 5 deletions

View File

@ -32,6 +32,14 @@ Currently, the following process managers are supported:
* `systemd`: [systemd](https://www.freedesktop.org/wiki/Software/systemd)
* `launchd`: [launchd](https://www.launchd.info)
* `cygrunsrv`: Cygwin's [cygrunsrv](http://web.mit.edu/cygwin/cygwin_v1.3.2/usr/doc/Cygwin/cygrunsrv.README)
It can also work with the following solutions that are technically not
categorized as process managers (but can still be used as such):
* `docker`: [Docker](https://docker.com) is technically more than just a process
manager, but by sharing the host's network, Nix store, and bind mounting
relevant state directories, it can also serve as a process manager with
similar functionality as the others described above.
* `disnix`: Technically [Disnix](https://github.com/svanderburg/disnix) is not
a process manager but it is flexible enough to start daemons and arrange
activation ordering. This target backend is not designed for managing

View File

@ -10,7 +10,7 @@
let
createManagedProcess = import ../../nixproc/create-managed-process/agnostic/create-managed-process-universal.nix {
inherit pkgs runtimeDir tmpDir forceDisableUserChange processManager;
inherit pkgs runtimeDir stateDir tmpDir forceDisableUserChange processManager;
};
in
{

View File

@ -10,7 +10,7 @@
let
createManagedProcess = import ../../nixproc/create-managed-process/agnostic/create-managed-process-universal.nix {
inherit pkgs runtimeDir tmpDir forceDisableUserChange processManager;
inherit pkgs runtimeDir stateDir tmpDir forceDisableUserChange processManager;
};
webappExpr = if webappMode == "foreground" then ./webapp-fg.nix

View File

@ -20,7 +20,7 @@ let
};
in
rec {
webapp = rec {
/* webapp = rec {
port = 5000;
dnsName = "webapp.local";
@ -36,5 +36,5 @@ rec {
webapps = [ webapp ];
inherit port;
} {};
};
};*/
}

View File

@ -1,4 +1,4 @@
{pkgs, runtimeDir, tmpDir, forceDisableUserChange ? false, processManager ? null}:
{pkgs, runtimeDir, tmpDir, stateDir, forceDisableUserChange ? false, processManager ? null}:
let
basePackages = [
@ -92,6 +92,15 @@ let
inherit (pkgs) stdenv writeTextFile daemon;
inherit createProcessScript runtimeDir tmpDir forceDisableUserChange basePackages;
};
createDockerContainer = import ../docker/create-docker-container.nix {
inherit (pkgs) stdenv;
};
generateDockerContainer = import ../agnostic/generate-docker-container.nix {
inherit (pkgs) stdenv writeTextFile dockerTools findutils glibc dysnomia;
inherit createDockerContainer basePackages runtimeDir stateDir forceDisableUserChange createCredentials;
};
in
import ./create-managed-process.nix {
inherit processManager;
@ -105,5 +114,6 @@ import ./create-managed-process.nix {
else if processManager == "launchd" then generateLaunchdDaemon
else if processManager == "cygrunsrv" then generateCygrunsrvParams
else if processManager == "disnix" then generateProcessScript
else if processManager == "docker" then generateDockerContainer
else throw "Unknown process manager: ${processManager}";
}

View File

@ -0,0 +1,111 @@
{ createDockerContainer, dockerTools, stdenv, writeTextFile, findutils, glibc, dysnomia, basePackages, runtimeDir, stateDir, forceDisableUserChange, createCredentials }:
{ name
, description
, initialize
, daemon
, daemonArgs
, instanceName
, pidFile
, foregroundProcess
, foregroundProcessArgs
, path
, environment
, directory
, umask
, nice
, user
, dependencies
, credentials
, overrides
, postInstall
}:
# umask unsupported
# nice unsupported
let
generateForegroundWrapper = import ./generate-foreground-wrapper.nix {
inherit stdenv writeTextFile;
};
cmd = if foregroundProcess != null
then
if initialize == null
then [ foregroundProcess ] ++ foregroundProcessArgs
else
let
wrapper = generateForegroundWrapper ({
wrapDaemon = false;
executable = foregroundProcess;
inherit name initialize runtimeDir stdenv;
} // stdenv.lib.optionalAttrs (instanceName != null) {
inherit instanceName;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
});
in
[ wrapper ] ++ foregroundProcessArgs
else
let
wrapper = generateForegroundWrapper ({
wrapDaemon = true;
executable = daemon;
inherit name initialize runtimeDir stdenv;
} // stdenv.lib.optionalAttrs (instanceName != null) {
inherit instanceName;
} // stdenv.lib.optionalAttrs (pidFile != null) {
inherit pidFile;
});
in
[ wrapper ] ++ daemonArgs;
# Remove the Nix store references so that these Nix store paths won't end up in the image.
# Instead, we mount the host system's Nix store so that the software is still accessible inside the container.
cmdWithoutContext = map (arg: builtins.unsafeDiscardStringContext arg) cmd;
_path = basePackages ++ path;
_environment = {
PATH = builtins.concatStringsSep ":" (map(package: "${package}/bin" ) _path);
} // environment;
credentialsSpec = if credentials == {} || forceDisableUserChange then null else createCredentials credentials;
_user = if forceDisableUserChange then null else user;
dockerImage = dockerTools.buildImage (stdenv.lib.recursiveUpdate {
inherit name;
tag = "latest";
runAsRoot = ''
${dockerTools.shadowSetup}
${stdenv.lib.optionalString (credentialsSpec != null) ''
export PATH=$PATH:${findutils}/bin:${glibc.bin}/bin
${dysnomia}/bin/dysnomia-addgroups ${credentialsSpec}
${dysnomia}/bin/dysnomia-addusers ${credentialsSpec}
''}
${stdenv.lib.optionalString forceDisableUserChange ''
groupadd -r nogroup
useradd -r nobody -g nogroup -d /dev/null
''}
'';
config = {
Cmd = cmdWithoutContext;
} // stdenv.lib.optionalAttrs (_environment != {}) {
Env = map (varName: "${varName}=${toString (builtins.getAttr varName _environment)}") (builtins.attrNames _environment);
} // stdenv.lib.optionalAttrs (directory != null) {
WorkingDir = directory;
} // stdenv.lib.optionalAttrs (_user != null) {
User = _user;
};
} overrides.image or {});
in
createDockerContainer (stdenv.lib.recursiveUpdate {
inherit name dockerImage postInstall cmd;
dockerImageTag = "${name}:latest";
useHostNixStore = true;
useHostNetwork = true;
mapStateDirVolume = stateDir;
} overrides.container or {})

View File

@ -0,0 +1,30 @@
{ pkgs ? import <nixpkgs> { inherit system; }
, system ? builtins.currentSystem
, stateDir ? "/var"
, runtimeDir ? "${stateDir}/run"
, logDir ? "${stateDir}/log"
, tmpDir ? (if stateDir == "/var" then "/tmp" else "${stateDir}/tmp")
, forceDisableUserChange ? false
, exprFile
}@args:
let
processesFun = import exprFile;
processesFormalArgs = builtins.functionArgs processesFun;
processesArgs = builtins.intersectAttrs processesFormalArgs (args // {
processManager = "docker";
});
processes = processesFun processesArgs;
in
pkgs.buildEnv {
name = "docker";
paths = map (processName:
let
process = builtins.getAttr processName processes;
in
process.pkg
) (builtins.attrNames processes);
}

View File

@ -0,0 +1,65 @@
{stdenv}:
{ name
, dockerImage
, dockerImageTag
, dockerCreateParameters ? []
, useHostNixStore ? false
, useHostNetwork ? false
, mapStateDirVolume ? null
, cmd ? ""
, storePaths ? []
, dependencies ? []
, postInstall ? ""
}:
let
dockerCreateParametersList =
if builtins.isList dockerCreateParameters then dockerCreateParameters
else if builtins.isAttrs dockerCreateParameters then map (name: { inherit name; value = builtins.getAttr name dockerCreateParameters; }) (builtins.attrNames dockerCreateParameters)
else throw "Unknown type for the dockerCreateParameters";
_dockerCreateParameters = dockerCreateParametersList
++ stdenv.lib.optional useHostNixStore { name = "volume"; value = "/nix/store:/nix/store"; }
++ stdenv.lib.optional useHostNetwork { name = "network"; value = "host"; }
++ stdenv.lib.optional (mapStateDirVolume != null) { name = "volume"; value = "${mapStateDirVolume}:${mapStateDirVolume}"; };
priority = if dependencies == [] then 1
else builtins.head (builtins.sort (a: b: a > b) (map (dependency: dependency.priority) dependencies)) + 1;
in
stdenv.mkDerivation {
inherit name priority;
buildCommand = ''
mkdir -p $out
cat > $out/${name}-docker-settings <<EOF
dockerImage=${dockerImage}
dockerImageTag=${name}:latest
EOF
cat > $out/${name}-docker-createparams <<EOF
${stdenv.lib.concatMapStringsSep "\n" (nameValuePair:
"${if builtins.stringLength nameValuePair.name > 1 then "--" else "-"}${nameValuePair.name}\n"
+ "${toString nameValuePair.value}"
) _dockerCreateParameters}
EOF
echo "${toString priority}" > $out/${name}-docker-priority
${stdenv.lib.optionalString useHostNixStore ''
# Add configuration files with Nix store paths used from the host system so that they will not be garbage collected
${stdenv.lib.optionalString (cmd != "") ''
cat > $out/${name}-docker-cmd <<EOF
${toString cmd}
EOF
''}
${stdenv.lib.optionalString (storePaths != []) ''
cat > $out/${name}-storepaths <<EOF
${stdenv.lib.concatMapStrings (storePath: "${storePath}\n") storePaths}
EOF
''}
''}
${postInstall}
'';
}

View File

@ -23,6 +23,10 @@ rec {
inherit (pkgs) stdenv getopt;
};
docker = import ./docker {
inherit (pkgs) stdenv getopt;
};
launchd = import ./launchd {
inherit (pkgs) stdenv getopt;
};

16
tools/docker/default.nix Normal file
View File

@ -0,0 +1,16 @@
{stdenv, getopt}:
stdenv.mkDerivation {
name = "nixproc-docker-tools";
buildCommand = ''
mkdir -p $out/bin
sed -e "s|/bin/bash|$SHELL|" \
-e "s|@getopt@|${getopt}/bin/getopt|" \
-e "s|@readlink@|$(type -p readlink)|" \
-e "s|@xargs@|$(type -p xargs)|" \
-e "s|@commonchecks@|${../commonchecks}|" \
${./nixproc-docker-switch.in} > $out/bin/nixproc-docker-switch
chmod +x $out/bin/nixproc-docker-switch
'';
}

View File

@ -0,0 +1,226 @@
#!/bin/bash
set -e
shopt -s nullglob
showUsage()
{
cat <<EOF
Usage: $0 [OPTION] PATH
This command repopulates a folder with systemd configuration files and updates
the configuration so that obsolete services will be stoppped and new services
will be started.
Options:
-p, --profile=NAME Name of the Nix profile that stores the sysvinit scripts
(defaults to: processes)
-o, --old-profile=PATH
Path to the previously deployed Nix profile (by default,
it gets auto detected)
--state-dir Changes the directory in which the state of the
processes are stored
--runtime-dir Changes the directory in which the PID files are stored
--log-dir Changes the directory in which the log files are stored
--tmp-dir Changes the directory in which temp files are stored
--force-disable-user-change
Forces to not create users, groups or change user
permissions
--show-trace Shows a trace of the output
-h, --help Shows the usage of this command
Environment:
NIX_STATE_DIR Overrides the location of the Nix state directory
SYSTEMD_TARGET_DIR Directory in which the unit configuration files are
managed (defaults to: /etc/systemd/system)
NIXPROC_STATE_DIR Changes the directory in which the state of the
processes is stored
NIXPROC_RUNTIME_DIR Changes the directory in which the PID files are stored
NIXPROC_LOG_DIR Changes the directory in which log files are stored
NIXPROC_TMP_DIR Changes the directory in which temp files are stored
NIXPROC_FORCE_DISABLE_USER_CHANGE
Forces to not create users, groups or change user
permissions
EOF
}
# Parse valid argument options
PARAMS=`@getopt@ -n $0 -o p:o:h -l profile:,old-profile:,state-dir:,runtime-dir:,log-dir:,tmp-dir:,force-disable-user-change,show-trace,help -- "$@"`
if [ $? != 0 ]
then
showUsage
exit 1
fi
# Evaluate valid options
eval set -- "$PARAMS"
while [ "$1" != "--" ]
do
case "$1" in
-p|--profile)
profile="$2"
;;
-o|--old-profile)
oldProfilePath="$2"
;;
--state-dir)
stateDirArg="--state-dir $2"
;;
--runtime-dir)
runtimeDirArg="--runtime-dir $2"
;;
--log-dir)
logDirArg="--log-dir $2"
;;
--tmp-dir)
tmpDirArg="--tmp-dir $2"
;;
--force-disable-user-change)
forceDisableUserChangeArg="--force-disable-user-change"
forceDisableUserChange=1
;;
--show-trace)
showTraceArg="--show-trace"
;;
-h|--help)
showUsage
exit 0
;;
esac
shift
done
shift
path="$1"
# Validate the given options
source @commonchecks@
checkNixStateDir
checkProfile
composeOldProfilePath
# Build the environment with docker container config files
buildProfile docker
deployContainer()
{
local configDir="$1"
local containerName="$2"
(
source $configDir/$containerName-docker-settings
dockerContainerName="nixproc-$containerName"
# Load the Docker image if it does not exists
if [ "$(docker images -f "reference=$dockerImageTag" | wc -l)" = "1" ]
then
docker load -i $dockerImage
fi
# Create the container if it does not exists yet
if [ "$(docker ps -a -f "name=$dockerContainerName" | wc -l)" = "1" ]
then
(
cat $configDir/*-docker-createparams
echo "--name"
echo "$dockerContainerName"
if [ "$forceDisableUserChange" = "1" ]
then
echo "--user"
id -u
fi
echo "$dockerImageTag"
) | @xargs@ -d '\n' docker create
fi
if [ "$(docker ps -f "name=$dockerContainerName" | wc -l)" = "1" ]
then
docker start $dockerContainerName
fi
)
}
undeployContainer()
{
local configDir="$1"
local containerName="$2"
(
source $configDir/$containerName-docker-settings
dockerContainerName="nixproc-$containerName"
dockerStopTimeout=${dockerStopTimeout:-1}
if [ "$(docker ps -f "name=$dockerContainerName" | wc -l)" = "2" ]
then
docker stop -t $dockerStopTimeout $dockerContainerName
fi
if [ "$(docker ps -a -f "name=$dockerContainerName" | wc -l)" = "2" ]
then
docker rm $dockerContainerName
fi
if [ "$(docker images -f "reference=$dockerImageTag" | wc -l)" = "2" ]
then
docker rmi $dockerImageTag
fi
)
}
# Determine paths of old containers
oldunits=()
if [ -d "$oldProfilePath" ]
then
for i in $oldProfilePath/*-docker-settings
do
currentPath=$(readlink -f "$i")
oldunits+=($currentPath)
done
fi
# Determine paths of new containers
newunits=()
for i in $profilePath/*-docker-settings
do
currentPath=$(readlink -f "$i")
newunits+=($currentPath)
done
if [ "$oldProfilePath" != "" ]
then
# Undeploy obsolete containers
for i in $oldProfilePath/*-docker-settings
do
if ! containsElement "$(readlink -f "$i")" "${newunits[@]}"
then
undeployContainer "$(dirname "$i")" "$(basename "$i" -docker-settings)"
fi
done
fi
# Deploy new containers
for i in $profilePath/*-docker-settings
do
if ! containsElement "$(readlink -f "$i")" "${oldunits[@]}"
then
deployContainer "$(dirname "$i")" "$(basename "$i" -docker-settings)"
fi
done
# Set new profile
setNixProfile