Unverified Commit 659f9ea3 authored by Maximilian Bosch's avatar Maximilian Bosch Committed by GitHub
Browse files

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

parents 94c61286 7d443d37
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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.
+103 −21
Original line number Diff line number Diff line
@@ -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:
              {
                imageFile,
                imageStream,
                podman,
                ...
              }:
              [
                {
                  assertion = imageFile == null || imageStream == null;

                message = "You can only define one of imageFile and imageStream";
              };
                  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
          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
          lib.mapAttrsToList toAssertion cfg.containers;
            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
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
@@ -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;
      };
    };
  };