Merge: nixos/oci-containers: support rootless containers & healthchecks (#368565)
This commit is contained in:
@@ -470,6 +470,9 @@
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
||||
- `virtualisation.containers` with backend "podman" now supports rootless containers and `sd_notify(3)`-integration
|
||||
based on container healthchecks.
|
||||
|
||||
- Cinnamon has been updated to 6.4, please check the [upstream announcement](https://www.linuxmint.com/rel_xia_whatsnew.php) for more details.
|
||||
- Following [changes in Mint 22](https://github.com/linuxmint/mintupgrade/commit/f239cde908288b8c250f938e7311c7ffbc16bd59) we are no longer overriding Qt application styles. You can still restore the previous default with `qt.style = "gtk2"` and `qt.platformTheme = "gtk2"`.
|
||||
- Following [changes in Mint 20](https://github.com/linuxmint/mintupgrade-legacy/commit/ce15d946ed9a8cb8444abd25088edd824bfb18f6) we are replacing xplayer with celluloid since xplayer is no longer maintained.
|
||||
|
||||
@@ -17,6 +17,10 @@ let
|
||||
{ name, ... }:
|
||||
{
|
||||
|
||||
config = {
|
||||
podman = mkIf (cfg.backend == "podman") { };
|
||||
};
|
||||
|
||||
options = {
|
||||
|
||||
image = mkOption {
|
||||
@@ -287,6 +291,43 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
podman = mkOption {
|
||||
type = types.nullOr (
|
||||
types.submodule {
|
||||
options = {
|
||||
sdnotify = mkOption {
|
||||
default = "conmon";
|
||||
type = types.enum [
|
||||
"conmon"
|
||||
"healthy"
|
||||
"container"
|
||||
];
|
||||
description = ''
|
||||
Determines how `podman` should notify systemd that the unit is ready. There are
|
||||
[three options](https://docs.podman.io/en/latest/markdown/podman-run.1.html#sdnotify-container-conmon-healthy-ignore):
|
||||
|
||||
* `conmon`: marks the unit as ready when the container has started.
|
||||
* `healthy`: marks the unit as ready when the [container's healthcheck](https://docs.podman.io/en/stable/markdown/podman-healthcheck-run.1.html) passes.
|
||||
* `container`: `NOTIFY_SOCKET` is passed into the container and the process inside the container needs to indicate on its own that it's ready.
|
||||
'';
|
||||
};
|
||||
user = mkOption {
|
||||
default = "root";
|
||||
type = types.str;
|
||||
description = ''
|
||||
The user under which the container should run.
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
default = null;
|
||||
description = ''
|
||||
Podman-specific settings in OCI containers. These must be null when using
|
||||
the `docker` backend.
|
||||
'';
|
||||
};
|
||||
|
||||
pull = mkOption {
|
||||
type =
|
||||
with types;
|
||||
@@ -379,16 +420,20 @@ let
|
||||
${container.imageStream} | ${cfg.backend} load
|
||||
''}
|
||||
${optionalString (cfg.backend == "podman") ''
|
||||
rm -f /run/podman-${escapedName}.ctr-id
|
||||
rm -f /run/${escapedName}/ctr-id
|
||||
''}
|
||||
'';
|
||||
};
|
||||
|
||||
effectiveUser = container.podman.user or "root";
|
||||
dependOnLingerService =
|
||||
cfg.backend == "podman" && effectiveUser != "root" && config.users.users.${effectiveUser}.linger;
|
||||
in
|
||||
{
|
||||
wantedBy = [ ] ++ optional (container.autoStart) "multi-user.target";
|
||||
wants = lib.optional (
|
||||
container.imageFile == null && container.imageStream == null
|
||||
) "network-online.target";
|
||||
wants =
|
||||
lib.optional (container.imageFile == null && container.imageStream == null) "network-online.target"
|
||||
++ lib.optional dependOnLingerService "linger-users.service";
|
||||
after =
|
||||
lib.optionals (cfg.backend == "docker") [
|
||||
"docker.service"
|
||||
@@ -398,9 +443,15 @@ let
|
||||
++ lib.optionals (container.imageFile == null && container.imageStream == null) [
|
||||
"network-online.target"
|
||||
]
|
||||
++ dependsOn;
|
||||
++ dependsOn
|
||||
++ lib.optional dependOnLingerService "linger-users.service";
|
||||
requires = dependsOn;
|
||||
environment = proxy_env;
|
||||
environment = lib.mkMerge [
|
||||
proxy_env
|
||||
(mkIf (cfg.backend == "podman" && container.podman.user != "root") {
|
||||
HOME = config.users.users.${container.podman.user}.home;
|
||||
})
|
||||
];
|
||||
|
||||
path =
|
||||
if cfg.backend == "docker" then
|
||||
@@ -424,9 +475,9 @@ let
|
||||
++ optional (container.entrypoint != null) "--entrypoint=${escapeShellArg container.entrypoint}"
|
||||
++ optional (container.hostname != null) "--hostname=${escapeShellArg container.hostname}"
|
||||
++ lib.optionals (cfg.backend == "podman") [
|
||||
"--cidfile=/run/podman-${escapedName}.ctr-id"
|
||||
"--cgroups=no-conmon"
|
||||
"--sdnotify=conmon"
|
||||
"--cidfile=/run/${escapedName}/ctr-id"
|
||||
"--cgroups=enabled"
|
||||
"--sdnotify=${container.podman.sdnotify}"
|
||||
"-d"
|
||||
"--replace"
|
||||
]
|
||||
@@ -454,13 +505,13 @@ let
|
||||
|
||||
preStop =
|
||||
if cfg.backend == "podman" then
|
||||
"podman stop --ignore --cidfile=/run/podman-${escapedName}.ctr-id"
|
||||
"podman stop --ignore --cidfile=/run/${escapedName}/ctr-id"
|
||||
else
|
||||
"${cfg.backend} stop ${name} || true";
|
||||
|
||||
postStop =
|
||||
if cfg.backend == "podman" then
|
||||
"podman rm -f --ignore --cidfile=/run/podman-${escapedName}.ctr-id"
|
||||
"podman rm -f --ignore --cidfile=/run/${escapedName}/ctr-id"
|
||||
else
|
||||
"${cfg.backend} rm -f ${name} || true";
|
||||
|
||||
@@ -490,6 +541,9 @@ let
|
||||
Environment = "PODMAN_SYSTEMD_UNIT=podman-${name}.service";
|
||||
Type = "notify";
|
||||
NotifyAccess = "all";
|
||||
Delegate = mkIf (container.podman.sdnotify == "healthy") true;
|
||||
User = effectiveUser;
|
||||
RuntimeDirectory = escapedName;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -536,17 +590,46 @@ in
|
||||
|
||||
assertions =
|
||||
let
|
||||
toAssertion =
|
||||
_:
|
||||
{ imageFile, imageStream, ... }:
|
||||
toAssertions =
|
||||
name:
|
||||
{
|
||||
assertion = imageFile == null || imageStream == null;
|
||||
|
||||
message = "You can only define one of imageFile and imageStream";
|
||||
};
|
||||
imageFile,
|
||||
imageStream,
|
||||
podman,
|
||||
...
|
||||
}:
|
||||
[
|
||||
{
|
||||
assertion = imageFile == null || imageStream == null;
|
||||
|
||||
message = "virtualisation.oci-containers.containers.${name}: You can only define one of imageFile and imageStream";
|
||||
}
|
||||
{
|
||||
assertion = cfg.backend == "docker" -> podman == null;
|
||||
message = "virtualisation.oci-containers.containers.${name}: Cannot set `podman` option if backend is `docker`.";
|
||||
}
|
||||
];
|
||||
in
|
||||
lib.mapAttrsToList toAssertion cfg.containers;
|
||||
concatMap (name: toAssertions name cfg.containers.${name}) (lib.attrNames cfg.containers);
|
||||
|
||||
warnings = mkIf (cfg.backend == "podman") (
|
||||
lib.foldlAttrs (
|
||||
warnings: name:
|
||||
{ podman, ... }:
|
||||
let
|
||||
inherit (config.users.users.${podman.user}) linger;
|
||||
in
|
||||
warnings
|
||||
++ lib.optional (podman.user != "root" && linger && podman.sdnotify == "conmon") ''
|
||||
Podman container ${name} is configured as rootless (user ${podman.user})
|
||||
with `--sdnotify=conmon`, but lingering for this user is turned on.
|
||||
''
|
||||
++ lib.optional (podman.user != "root" && !linger && podman.sdnotify == "healthy") ''
|
||||
Podman container ${name} is configured as rootless (user ${podman.user})
|
||||
with `--sdnotify=healthy`, but lingering for this user is turned off.
|
||||
''
|
||||
) [ ] cfg.containers
|
||||
);
|
||||
}
|
||||
(lib.mkIf (cfg.backend == "podman") {
|
||||
virtualisation.podman.enable = true;
|
||||
@@ -556,5 +639,4 @@ in
|
||||
})
|
||||
]
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -63,8 +63,65 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
podmanRootlessTests = lib.genAttrs [ "conmon" "healthy" ] (
|
||||
type:
|
||||
makeTest {
|
||||
name = "oci-containers-podman-rootless-${type}";
|
||||
meta.maintainers = lib.teams.flyingcircus.members;
|
||||
nodes = {
|
||||
podman =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [ pkgs.redis ];
|
||||
users.groups.redis = { };
|
||||
users.users.redis = {
|
||||
isSystemUser = true;
|
||||
group = "redis";
|
||||
home = "/var/lib/redis";
|
||||
linger = type == "healthy";
|
||||
createHome = true;
|
||||
subUidRanges = [
|
||||
{
|
||||
count = 65536;
|
||||
startUid = 2147483646;
|
||||
}
|
||||
];
|
||||
subGidRanges = [
|
||||
{
|
||||
count = 65536;
|
||||
startGid = 2147483647;
|
||||
}
|
||||
];
|
||||
};
|
||||
virtualisation.oci-containers = {
|
||||
backend = "podman";
|
||||
containers.redis = {
|
||||
image = "redis:latest";
|
||||
imageFile = pkgs.dockerTools.examples.redis;
|
||||
ports = [ "6379:6379" ];
|
||||
podman = {
|
||||
user = "redis";
|
||||
sdnotify = type;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
podman.wait_for_unit("podman-redis.service")
|
||||
${lib.optionalString (type != "healthy") ''
|
||||
podman.wait_for_open_port(6379)
|
||||
''}
|
||||
podman.wait_until_succeeds("set -eo pipefail; echo 'keys *' | redis-cli")
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
lib.foldl' (attrs: backend: attrs // { ${backend} = mkOCITest backend; }) { } [
|
||||
"docker"
|
||||
"podman"
|
||||
]
|
||||
{
|
||||
docker = mkOCITest "docker";
|
||||
podman = mkOCITest "podman";
|
||||
podman-rootless-conmon = podmanRootlessTests.conmon;
|
||||
podman-rootless-healthy = podmanRootlessTests.healthy;
|
||||
}
|
||||
|
||||
@@ -110,6 +110,16 @@ rec {
|
||||
|
||||
runAsRoot = ''
|
||||
mkdir -p /data
|
||||
cat >/bin/healthcheck <<-'EOF'
|
||||
set -x
|
||||
probe="$(/bin/redis-cli ping)"
|
||||
echo "$probe"
|
||||
if [ "$probe" = 'PONG' ]; then
|
||||
exit 0
|
||||
fi
|
||||
exit 1
|
||||
EOF
|
||||
chmod +x /bin/healthcheck
|
||||
'';
|
||||
|
||||
config = {
|
||||
@@ -118,6 +128,15 @@ rec {
|
||||
Volumes = {
|
||||
"/data" = { };
|
||||
};
|
||||
Healthcheck = {
|
||||
Test = [
|
||||
"CMD-SHELL"
|
||||
"/bin/healthcheck"
|
||||
];
|
||||
Interval = 30000000000;
|
||||
Timeout = 10000000000;
|
||||
Retries = 3;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user