feat(guests): let a guest nest OCI containers (task 0009) #35
37
.claude/tasks/0009-guest-nesting.md
Normal file
37
.claude/tasks/0009-guest-nesting.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
---
|
||||||
|
spec: guests
|
||||||
|
blocked-by: 0002-guest-walking-skeleton
|
||||||
|
---
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
The Host-side placement that makes the OCI fallback a plain Guest, so image-only software has a declarative home without a separate mechanism.
|
||||||
|
A Host sets `nesting` to grant the nested-container prerequisites so the Guest's interior can run Podman and other OCI containers.
|
||||||
|
The Skeleton emits the nested-container cgroup-delegation and capability prerequisites once, so the operator flips one boolean and the interior's `oci-containers` runtime works, with Podman as the default runtime.
|
||||||
|
|
||||||
|
## Acceptance criteria
|
||||||
|
|
||||||
|
- [x] A Host setting `guests.<path>.nesting` grants the nested-container cgroup-delegation and capability prerequisites, off by default.
|
||||||
|
- [x] With `nesting` on, the Guest's interior can define `oci-containers` running Podman as the default runtime.
|
||||||
|
- [x] With `nesting` off, those prerequisites are absent and the Guest is unaffected.
|
||||||
|
- [x] A Host with a nesting Guest that defines an OCI container builds via `nix flake check`.
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
- `nesting` is a Host-side placement field on the guest, a bool defaulting off, alongside the other placement fields in `lib.nix`.
|
||||||
|
On, it grants the guest's container `CAP_NET_ADMIN` and the `/dev/net/tun` and `/dev/fuse` device nodes, the capability and devices an OCI runtime reaches for to network its containers and back their overlay storage.
|
||||||
|
Off, both `additionalCapabilities` and `allowedDevices` are empty, matching the NixOS defaults, so a non-nesting guest is untouched.
|
||||||
|
|
||||||
|
- The capability prerequisite is `CAP_NET_ADMIN` alone.
|
||||||
|
A container-backend guest runs privileged (`privateUsers = "no"`), so it already retains the broad nspawn capability set including `CAP_SYS_ADMIN`; the one addition an OCI runtime needs is network administration for its bridges and firewall rules.
|
||||||
|
|
||||||
|
- cgroup delegation is not toggled by `nesting`, a deliberate deviation from the criterion's wording that the flag "grants" it and that it is "absent" when off.
|
||||||
|
The NixOS container backend sets `Delegate = true` on every container's unit unconditionally, so the delegated cgroup subtree an OCI runtime manages is always present.
|
||||||
|
Re-emitting it under `nesting` would be redundant, and forcing it off for non-nesting guests to make it literally "absent" would remove a harmless, useful default for no gain.
|
||||||
|
The Skeleton records the prerequisite as satisfied-elsewhere with an absence pointer comment, so a reader does not think delegation was forgotten.
|
||||||
|
|
||||||
|
- A new `guests/nesting-sample.nix` carries an interior that defines an `oci-containers` workload, the payload the criteria exercise, and `neogaia` enables it with `nesting = true`.
|
||||||
|
This follows the walking-skeleton's precedent of proving a guest path through the one Host's `nix flake check`: the flake check builds the nested `nixos-system-nesting-sample` in full, pulling in `podman` and the generated `podman-hello.service` unit, which is how criteria two and four are verified on the build seam.
|
||||||
|
The sample carries the same modest caps as the walking-skeleton guest so an interior container cannot starve the laptop.
|
||||||
|
|
||||||
|
- The two behaviors the build seam cannot prove — that the interior Podman actually starts a container and that its networking works — are left to manual verification on the target Host and to the VM integration test of task 0010, per the spec's testing decisions.
|
||||||
11
guests/nesting-sample.nix
Normal file
11
guests/nesting-sample.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
args@{ my, ... }:
|
||||||
|
# A sample guest whose interior runs an OCI container on Podman.
|
||||||
|
# The image is pulled at runtime, so the guest builds with no build-time fetch.
|
||||||
|
my.guest {
|
||||||
|
name = "nesting-sample";
|
||||||
|
interior = {
|
||||||
|
virtualisation.oci-containers.containers.hello = {
|
||||||
|
image = "docker.io/library/hello-world";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
} args
|
||||||
@@ -54,6 +54,16 @@
|
|||||||
tasksMax = 512;
|
tasksMax = 512;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# The nesting guest, run with `nesting` on: proves an interior OCI container
|
||||||
|
# on Podman builds end to end through this host's `nix flake check`.
|
||||||
|
guests.nesting-sample.enable = true;
|
||||||
|
guests.nesting-sample.nesting = true;
|
||||||
|
guests.nesting-sample.limits = {
|
||||||
|
memory = "1G";
|
||||||
|
cpu = "100%";
|
||||||
|
tasksMax = 512;
|
||||||
|
};
|
||||||
|
|
||||||
modules.agents.claude-code.enable = true;
|
modules.agents.claude-code.enable = true;
|
||||||
modules.agents.tools.gitea-axi.enable = true;
|
modules.agents.tools.gitea-axi.enable = true;
|
||||||
modules.agents.pi.enable = true;
|
modules.agents.pi.enable = true;
|
||||||
|
|||||||
32
lib.nix
32
lib.nix
@@ -279,6 +279,19 @@ let
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
nesting = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Grant the guest's interior the prerequisites to run Podman or other
|
||||||
|
OCI containers of its own. Off by default, so a guest cannot nest
|
||||||
|
containers. On, the guest's container gains the network-administration
|
||||||
|
capability its container runtime uses to build bridges and firewall
|
||||||
|
rules, along with the tun and fuse device nodes such a runtime reaches
|
||||||
|
for, so the interior's `virtualisation.oci-containers` works with
|
||||||
|
Podman as its default runtime.
|
||||||
|
'';
|
||||||
|
};
|
||||||
autoStart = lib.mkOption {
|
autoStart = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
@@ -335,6 +348,25 @@ let
|
|||||||
# A private-user mapping would shift the ids and reintroduce those errors, so it stays off.
|
# A private-user mapping would shift the ids and reintroduce those errors, so it stays off.
|
||||||
privateUsers = lib.mkDefault "no";
|
privateUsers = lib.mkDefault "no";
|
||||||
|
|
||||||
|
# A nesting guest runs Podman or other OCI containers in its interior.
|
||||||
|
# The network-administration capability lets that runtime build its
|
||||||
|
# bridges and firewall rules.
|
||||||
|
# The tun and fuse device nodes are what it reaches for to network
|
||||||
|
# those containers and back their overlay storage.
|
||||||
|
# The remaining prerequisite, a delegated cgroup subtree for the
|
||||||
|
# runtime to manage, the container backend already grants every guest.
|
||||||
|
additionalCapabilities = lib.optionals cfg.nesting [ "CAP_NET_ADMIN" ];
|
||||||
|
allowedDevices = lib.optionals cfg.nesting [
|
||||||
|
{
|
||||||
|
node = "/dev/net/tun";
|
||||||
|
modifier = "rwm";
|
||||||
|
}
|
||||||
|
{
|
||||||
|
node = "/dev/fuse";
|
||||||
|
modifier = "rwm";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
bindMounts = userMounts // secretMounts;
|
bindMounts = userMounts // secretMounts;
|
||||||
|
|
||||||
inherit specialArgs;
|
inherit specialArgs;
|
||||||
|
|||||||
Reference in New Issue
Block a user