Merge: nixos/oci-containers: support rootless containers & healthchecks (#368565)

This commit is contained in:
Maximilian Bosch
2025-02-27 19:59:34 +01:00
committed by GitHub
4 changed files with 185 additions and 24 deletions
@@ -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.
+102 -20
View File
@@ -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
})
]
);
}
+61 -4
View File
@@ -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;
}
+19
View File
@@ -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;
};
};
};