Skip to content

Perlmutter spike: login-node findings (layer 7) #192

Description

@EiffL

Results of the login-node half of the layer-7 spike (CLAUDE.md "Pending the one-time Perlmutter spike"), run on login37, 2026-08-21. The allocation half (sbatch materialize, multi-node) is still pending — list at the bottom.

Host: SLES 15-SP6, kernel 6.4.0-cray_shasta, x86_64 · podman-hpc 1.2.2 wrapping podman 5.8.2, no docker · uv self-installed in ~/.local/bin · git-annex wheel 10.20260717 · $SCRATCH = Lustre, $HOME = GPFS.

Spike questions answered

Landlock is not in the SLES boot LSM list

/sys/kernel/security/lsmlockdown,capability,selinux,bpf. Direct landlock_create_ruleset syscall returns ENOTSUP. The handled branch works exactly as designed: lc run executes, prints the downgrade note, lc status shows sandbox: none, runs record fs: open. So on Perlmutter direct mode is unenforced and containerized mode is the only real boundary — worth saying in the user docs' NERSC material.

One message nit: the note's parenthetical says "kernel < 5.13, blocked by seccomp, or unsupported arch" — none of which is the cause here (kernel 6.4; the LSM simply isn't in the boot lsm= list). A fourth clause would make the note truthful on the machine most likely to show it. Compute nodes may boot a different image; re-check there.

podman-hpc migrate: bare id accepted, re-run is a no-op

podman-hpc migrate <bare-image-id> exits 0 in 0.55 s, and a second invocation is the same 0.55 s no-op. runtime_for_run's unconditional migrate is validated — no guard needed.

--module site mounts vs fs: declared: honest by default, one leak vector

All 13 site modules (/etc/podman_hpc/modules.d/) are opt-in, each gated on a CLI flag or an env var: MOUNT_HOME, MOUNT_SCRATCH, MOUNT_CFS, MOUNT_JUPYTER, ENABLE_GPU, ENABLE_MPICH_SS, ENABLE_NCCL*, ENABLE_OPENMPI_*, ENABLE_CUDA_MPICH_SS, ENABLE_CVMFS. A bare podman-hpc run injects no site mounts (verified: /proc/mounts inside the container shows nothing beyond standard plumbing), and nothing in /etc/profile.d sets the gate variables. So fs: declared is honest out of the box.

The leak vector is real, though: MOUNT_SCRATCH=1 podman-hpc run … silently bind-mounts $SCRATCH into the container (verified) — and lc passes the driver's ambient environment to the runtime wrapper (the container env allowlist doesn't cover the wrapper's own env). A user with export MOUNT_HOME=1 in their dotfiles gets $HOME inside every recipe container while the manifest attests fs: declared. This is the recorded "one item that could add a flag", now with a concrete shape. Tension to weigh before deciding: ENABLE_GPU/ENABLE_MPICH_SS are the legitimate site mechanism for GPU/MPI recipes under podman-hpc, so a blanket scrub (the UV_* treatment) would close the honesty hole and break GPU recipes with it. Options seem to be: scrub MOUNT_* only; attest the gate variables that were set; or a declaration surface for modules. Not decided here.

git-annex on Lustre vs GPFS: throughput fine, small-file add is the cost

200 MB file + 500×16 KB files, template .gitattributes, annex.thin=true add — the exact operations lc pays:

operation $SCRATCH (Lustre) $HOME (GPFS)
git add 200 MB (filter=annex, thin) 1.9 s 2.1 s
git add 500 × 16 KB 25.4 s (~50 ms/file) 8.6 s
commit 2.2 s 1.7 s
git annex get 200 MB (same-fs origin) 4.5 s 3.1 s
git annex get 500 small 1.0 s 0.6 s

Thin hard links behave normally on Lustre (link count 2, object store not doubled) — the "does Lustre make the hard link behave differently" question is answered: no. The real cost is per-file metadata latency on add (wall 25 s vs ~8 s CPU): an output of many small files pays ~50 ms each on $SCRATCH. One output = one file stays cheap; sharded outputs (thousands of files) are where this bites. Nothing to design around yet.

nidXXXXXX resolution — partial (login-node) answer

nid200001 / nid001000 resolve via site DNS from the login node (to .chn.perlmutter addresses). Whether a peer compute node resolves and can reach SLURMD_NODENAME's address (vs needing --interface hsn0) still needs the allocation.

Bugs found

  1. tests/test_container_smoke.py::_inspect_id KeyError: 'podman-hpc' — the helper's argv dict has only podman/docker keys, but the suite parametrizes over podman-hpc. Fails test_build_commits…[podman-hpc] and test_materialize_end_to_end…[podman-hpc] at their final assert. Everything before the helper passed — see below. podman-hpc wraps podman, so ["podman-hpc", "image", "inspect", …] would work; the fix is treating the podman family positively, as container._PODMAN_FAMILY does. This bug is only reachable on a host with podman-hpc installed — which is exactly why the spike says running the suite here is the spike.

  2. Bare podman cannot run containers on a login nodepodman run --userns=keep-id fails at container creation: creating an ID-mapped copy of layer … chown …/.local/share/containers/storage/…: operation not permitted, with podman's own warning Network file system detected as backing store ($HOME is GPFS). Build works; run does not. This is precisely why NERSC ships podman-hpc, and detection order (podman-hpc → podman) means real lc usage never hits it. Consequence for the suite: test_the_probe_and_its_boundary[podman] and test_materialize_end_to_end…[podman] fail on this host, so LC_CONTAINER_TESTS_REQUIRED=1 cannot go fully green on a NERSC login node. Worth considering a skip (with reason) for the bare-podman parametrization when its graph root sits on a network filesystem — podman's warning string shows it can be probed.

  3. Minor, template/docs: the scaffolded pyproject.toml pins [tool.uv] required-version = ">=0.12"; NERSC users self-install uv (per docs/user/install.md), and a stale one (0.11.8 here) fails lc init with uv's own clear uv self update message. Works as designed, just recording that the failure mode is real on this site (fixed here by updating; uv is now 0.12.5).

What works end-to-end on a login node

  • lc init on $SCRATCH: 1.2 s, converges clean on Lustre.
  • Login guard: lc materialize refuses (exit 1) with the exact salloc/sbatch remedies; the rerun entry point (python -m lightcone.engine.worker …) refuses too (exit 2). lc status / lc materialize --check / lc run all work here, as documented.
  • lc run (direct mode): 0.5 s round trip on $SCRATCH, correct downgrade note.
  • lc build via podman-hpc: real project on $SCRATCH, [tool.lightcone.image] with apt-install — build, docker-archive save, annex commit (225 MB, thin: link count 2) in 11 s warm. Detection picked podman-hpc over podman correctly with NERSC_HOST set, no fixtures.
  • Containerized lc run: executes in-image, ~9 s including the in-container env converge. lc status header: sandbox: podman-hpc (fs: declared, network: allowed).
  • Smoke suite under podman-hpc: test_the_probe_and_its_boundary[podman-hpc] passed; test_a_rerun_on_a_clone_fetches_the_archive_and_reproduces[podman-hpc] passed — a real datalad rerun on a bytes-free clone fetched the archive through the annex and reproduced, on the site runtime. The e2e materialize ran a real Dask cluster in-image and wrote a manifest asserting mechanism: podman-hpc, fs: declared before dying in the helper bug above.
  • Full suite baseline: 564 passed, 27 skipped, 4 failed in 7:42 — the 4 failures are exactly the two bugs above (2× the _inspect_id helper under podman-hpc, 2× bare podman's network-fs store). Everything else is green on this host, including the venue suite's real worker-process path and the crate smoke against the real validator; the sandbox enforcement suite skips as designed (no mechanism, LC_SANDBOX_TESTS_REQUIRED unset).

Still needs the allocation

  • srun --overlap / --cpus-per-task behavior inside salloc/sbatch steps
  • SLURMD_NODENAME reachability from peer nodes (else --interface hsn0)
  • SLURM_CPUS_ON_NODE on a CPU node (128 vs 256 hyperthreads)
  • cold-Lustre distributed import vs the 120 s worker wait (warm import: 0.9 s)
  • one real lc materialize through sbatch, then multi-node containerized materialize (the podman-hpc shared-store claim in anger)
  • compute-node checks of the two host facts above: the boot LSM list, and whether any prolog sets MOUNT_* there

Residue left on the machine: uv upgraded 0.11.8→0.12.5 (~/.local/bin), and the smoke images in the per-user podman-hpc stores (/images/$UID_hpc, $SCRATCH/storage — ~235 MB squash). All probe repos/projects were deleted.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions