Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions nixos/doc/manual/release-notes/rl-2505.section.md
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,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.
Expand Down
124 changes: 103 additions & 21 deletions nixos/modules/virtualisation/oci-containers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ let
{ ... }:
{

config = {
podman = mkIf (cfg.backend == "podman") { };
};

options = {

image = mkOption {
Expand Down Expand Up @@ -280,6 +284,43 @@ let
'';
};

podman = mkOption {
type = types.nullOr (
types.submodule {
options = {
sdnotify = mkOption {
default = "conmon";
type = types.enum [
"conmon"
"healthy"
"container"
Copy link
Member Author

@Ma27 Ma27 Dec 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: this probably requires some testing before merging. I haven't really verified if all the settings are correct for this case.

];
description = ''
The way 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;
Expand Down Expand Up @@ -372,16 +413,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";
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still kinda amazing, how this is the result of an RFC that has

We want a unified Nix code style that's consistent with itself, easily readable, accessible and results in readable diffs.

(emphasis mine) as explicit goal.

after =
lib.optionals (cfg.backend == "docker") [
"docker.service"
Expand All @@ -391,9 +436,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
Expand All @@ -417,9 +468,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"
]
Expand Down Expand Up @@ -447,13 +498,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";

Expand Down Expand Up @@ -483,6 +534,9 @@ let
Environment = "PODMAN_SYSTEMD_UNIT=podman-${name}.service";
Type = "notify";
NotifyAccess = "all";
Delegate = mkIf (container.podman.sdnotify == "healthy") true;
User = effectiveUser;
RuntimeDirectory = escapedName;
};
};

Expand Down Expand Up @@ -531,17 +585,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;
Expand All @@ -551,5 +634,4 @@ in
})
]
);

}
65 changes: 61 additions & 4 deletions nixos/tests/oci-containers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 19 additions & 0 deletions pkgs/build-support/docker/examples.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -118,6 +128,15 @@ rec {
Volumes = {
"/data" = { };
};
Healthcheck = {
Test = [
"CMD-SHELL"
"/bin/healthcheck"
];
Interval = 30000000000;
Timeout = 10000000000;
Retries = 3;
};
};
};

Expand Down
Loading