#!/usr/bin/env sh # docker-rootless-cgroup/install.sh — give rootless Docker cgroup v2 controller # delegation on a systemd-less host (e.g. Kasm), so per-container cgroups work. # (This was originally done to support `kind`, but kind never worked reliably # against rootless Docker even with delegation — bin/ now only installs kind # where full Docker is available. The delegation itself is still useful for # per-container resource limits.) # # Why: rootless Docker only receives delegated cgroup v2 controllers when # systemd sets `Delegate=yes` on the user session. Kasm images run no systemd # (PID 1 is a VNC startup script), so the daemon comes up with CgroupDriver=none # and `kind create cluster` fails with: # "running kind with rootless provider requires setting systemd property # Delegate=yes" # The suggested fix can't be applied — there is no systemd unit to set it on. # # What this does, reproducing that delegation by hand (needs passwordless sudo # for the host cgroup writes): # 1. Evacuate the cgroup-namespace root into an /init leaf, then enable the # controllers in the root's subtree_control. cgroup v2's "no internal # processes" rule forbids enabling controllers while a cgroup still holds # processes, so the evacuation has to come first. (In a container, # /sys/fs/cgroup is the namespace root but a real nested cgroup on the # host, so it is subject to that rule; only the true system root is exempt.) # 2. Create a user-owned /rootless slice with the controllers delegated down. # 3. Install a PATH shim at ~/.local/bin/dockerd-rootless.sh that drops the # daemon into a PRIVATE leaf (/rootless/daemon) before exec'ing the real # launcher. Rootless dockerd enables cgroups by self-evacuating (moving its # own process into a child cgroup, then enabling subtree_control); it can # only do that in a cgroup it does not share with foreign processes. The # Kasm supervisor forks the daemon and so shares its launch cgroup, which # blocks self-evacuation — hence the private leaf. # 4. Move the daemon's supervisor into the /rootless subtree and restart the # daemon so it comes back up through the shim, in the private leaf, and # flips its CgroupDriver from none to cgroupfs. # # The cgroup layout is host state a Kasm redeploy wipes, so this is meant to be # re-run at each bootstrap; the shim lives in $HOME and survives. It no-ops # (Docker untouched) unless it finds rootless Docker on a cgroup v2 host with # delegation actually missing — safe on systemd hosts and non-Docker hosts. set -eu say() { printf '%s\n' "$*" >&2; } CG=/sys/fs/cgroup SLICE="$CG/rootless" BIN_DIR="$HOME/.local/bin" SHIM="$BIN_DIR/dockerd-rootless.sh" CONTROLLERS='+cpu +cpuset +io +memory +pids' NEED='cpu cpuset io memory pids' # --- guards: act only where this is both needed and possible ---------------- # Unified cgroup v2? if [ ! -e "$CG/cgroup.controllers" ]; then say "docker-rootless-cgroup: not a unified cgroup v2 host; skipping." exit 0 fi if ! command -v docker >/dev/null 2>&1; then say "docker-rootless-cgroup: docker not installed; skipping." exit 0 fi # Rootless daemon? (systemd/rootful hosts manage their own cgroups.) case "$(docker info -f '{{.SecurityOptions}}' 2>/dev/null || true)" in *rootless*) : ;; *) say "docker-rootless-cgroup: Docker is not rootless; the host handles cgroups. Skipping." exit 0 ;; esac # Already delegated? Then there is nothing to do. driver="$(docker info -f '{{.CgroupDriver}}' 2>/dev/null || true)" if [ "$driver" != "none" ] && [ -n "$driver" ]; then say "docker-rootless-cgroup: CgroupDriver=$driver already; delegation present. Nothing to do." exit 0 fi # The host must actually have delegated the controllers to this container; if # it withheld them there is nothing we can do from inside. avail=" $(cat "$CG/cgroup.controllers") " for c in $NEED; do case "$avail" in *" $c "*) : ;; *) say "docker-rootless-cgroup: host did not delegate '$c' to this container (have:$avail); cannot proceed. Skipping." exit 0 ;; esac done # Locate the running rootless daemon and its supervisor (its parent process, # e.g. Kasm's custom_startup.sh loop that relaunches dockerd when it dies). rk="$(pgrep -x rootlesskit | head -1 || true)" if [ -z "$rk" ]; then say "docker-rootless-cgroup: rootlesskit not running; is dockerd-rootless up? Skipping." exit 0 fi sup="$(ps -o ppid= -p "$rk" 2>/dev/null | tr -d ' ' || true)" say "docker-rootless-cgroup: rootless Docker has CgroupDriver=none; delegating cgroup v2 controllers..." # --- 1. evacuate the ns-root and delegate its controllers ------------------- sudo mkdir -p "$CG/init" # Snapshot the pid list first (moving processes mutates cgroup.procs as we go). for p in $(cat "$CG/cgroup.procs"); do echo "$p" | sudo tee "$CG/init/cgroup.procs" >/dev/null 2>&1 || true done printf '%s' "$CONTROLLERS" | sudo tee "$CG/cgroup.subtree_control" >/dev/null say " delegated controllers at ns-root: $(cat "$CG/cgroup.subtree_control")" # --- 2. user-owned /rootless slice with controllers, private leaves --------- sudo mkdir -p "$SLICE/init" "$SLICE/daemon" printf '%s' "$CONTROLLERS" | sudo tee "$SLICE/cgroup.subtree_control" >/dev/null sudo chown -R "$(id -u):$(id -g)" "$SLICE" say " created delegated slice $SLICE (controllers: $(cat "$SLICE/cgroup.controllers"))" # --- 3. PATH shim that gives the daemon a private cgroup leaf ---------------- mkdir -p "$BIN_DIR" tmp="$(mktemp)" cat > "$tmp" <<'SH' #!/bin/sh # Managed by dotfiles (docker-rootless-cgroup). Shadows /usr/bin/dockerd-rootless.sh # on PATH. Places the rootless Docker daemon in a private, delegated cgroup v2 # leaf so it can enable controllers (per-container cgroups), then execs the real # launcher. No-op if the delegated slice is absent, so it is safe anywhere. _cg=/sys/fs/cgroup/rootless/daemon if [ -w /sys/fs/cgroup/rootless ] && [ -d "$_cg" ]; then echo $$ > "$_cg/cgroup.procs" 2>/dev/null || true fi exec /usr/bin/dockerd-rootless.sh "$@" SH chmod +x "$tmp" if [ -f "$SHIM" ] && cmp -s "$tmp" "$SHIM"; then rm -f "$tmp" say " daemon cgroup shim already current at $SHIM" else mv "$tmp" "$SHIM" chmod +x "$SHIM" say " installed daemon cgroup shim at $SHIM" fi # --- 4. relocate the supervisor and restart the daemon ---------------------- # Put the supervisor inside the /rootless subtree so the daemon it relaunches # (via the shim) starts there too — then the shim's move into /rootless/daemon # needs no privilege (the common ancestor /rootless is user-owned). if [ -n "$sup" ]; then echo "$sup" | sudo tee "$SLICE/init/cgroup.procs" >/dev/null 2>&1 || true fi say " restarting rootless Docker so it comes back up in the private leaf..." kill "$rk" 2>/dev/null || true i=0 while [ "$i" -lt 30 ]; do if docker info >/dev/null 2>&1; then break; fi i=$((i + 1)) sleep 1 done # --- verify: report honestly, do not claim success on none ------------------ driver="$(docker info -f '{{.CgroupDriver}}' 2>/dev/null || true)" if [ "$driver" = "none" ] || [ -z "$driver" ]; then say "docker-rootless-cgroup: WARNING — CgroupDriver is still '${driver:-unreachable}'." say " Per-container cgroups will not work yet. Inspect the new dockerd's cgroup:" say " cat /proc/\$(pgrep -x dockerd | head -1)/cgroup # expect .../rootless/daemon" exit 0 fi say "docker-rootless-cgroup: done. CgroupDriver=$driver — rootless Docker now has" say " delegated cgroups; per-container resource limits work."