Commit Graph

6 Commits

Author SHA1 Message Date
vh 0559e12a2d docs(pfi): add an ops-lessons playbook for the transferable failures
Sibling to model-quantization-playbook.md, and it exists for the same
reason that one does: hard-won lessons were dying inside per-host
runbooks where nobody finds them until after repeating the mistake.

Six entries seeded from the esh-pve-nas migration, all of which would
bite identically on any other host:

1. mount --rbind into a chroot needs --make-rslave, and losing cgroup2
   impersonates failing root-disk I/O closely enough that it was
   misdiagnosed as exactly that.
2. A reboot is not confirmed until the host is observed DOWN; "never
   rebooted" and "rebooted fast" are indistinguishable otherwise.
3. Assert the effective value, not the presence of a substring. Grep
   proves presence; only evaluation proves effect.
4. Ask the server who its clients are -- documented dependent lists rot.
   Plus the corollary that an idle hard NFS mount blocks and resumes, so
   quiescing means stopping consumers, not always unmounting.
5. The scoped-looking command can be the dangerous one; setting a ZFS
   cachefile on one pool of three would have stopped the other two from
   importing at boot.
6. Long uptime hides breakage, and a forced look is worth more than it
   appears -- one migration surfaced an 82-day-dead pvestatd, a 126-day
   hung vzdump, a VM in prelaunch for four months, and an undocumented
   cluster, none of them caused by the work.

Carries a superseded-claims table so corrections are dated rather than
silently edited, same discipline as the quantization playbook. The ESH
runbook now links here so the general rules are reachable from the
specific story and vice versa.
2026-08-18 07:14:48 -07:00
vh 5f11d1b3cb feat(esh-pve-nas): cut PVE root over to ZFS on the mirrored NVMe
Root is now nvme/ROOT/pve-1. The USB DOM keeps the ESP and /boot but is
out of the runtime I/O path, so a bus reset can no longer drop root from
under a running hypervisor. All five guests healthy, three pools ONLINE,
system running, ext4 pve-root intact and unmounted as the rollback with
its own kernel and initrd. zfs-import-cache is now the active import
path -- the all-three-pools cachefile fix doing its job.

The window cost an unplanned outage, and the cause was this repo's own
tooling rather than the migration.

The staging chroot ran `mount --rbind /dev` and /sys with no
--make-rslave. On systemd `/` has shared propagation, so the cutover's
`umount -R` propagated back into the live host and removed the real
/sys/fs/cgroup, /dev/pts and /dev/shm. With cgroup2 gone systemd-logind
could not create a session: ping fine, TCP fine, SSH authentication
succeeded, resident daemons kept serving -- and every new exec hung,
including /sbin/reboot, so the reboot never ran at all.

It impersonates failing root-disk I/O almost perfectly, and I called it
as the DOM dying. That was wrong. dmesg had the answer throughout: the
DOM attached cleanly with no errors, and the last log timestamp was
12114881s -- 140 days -- meaning this was still the original boot. A
down-detector had also never reported the host down, which I read as a
fast reboot rather than as no reboot.

Fixes and guards:

- --make-rslave after every rbind, plus a guard that refuses to proceed
  while any chroot bind still reports shared propagation.
- Confirm a reboot by observing the host DOWN, not by watching for it to
  come back. Those two states are indistinguishable otherwise.
- Blast radius now measured from the server: `ss` inside CT 103 found
  five NFS clients, not the two documented. The new one that mattered is
  esh-vm-db, hard-mounted and unreachable by ssh. Left mounted on
  purpose and it came through read-write.
- grub-reboot's one-shot does NOT work here: grubenv sits on an LVM LV
  which GRUB reads but cannot write, so next_entry survived the boot
  that consumed it. Steady state is saved_entry=pve-zfs-root with no
  next_entry. There is no auto-fallback on this host and no IPMI.

Recovery needed no console: an idempotent cgroup2/devpts/shm remount
landed in the brief windows where exec succeeded. No data was lost, and
neither the DOM nor any pool was ever at risk.
2026-08-18 06:39:34 -07:00
vh c4b2278e7d feat(esh-pve-nas): stage the PVE root migration off the USB DOM
Everything but the reboot. Two rerunnable elway playbooks; the host is
still running from the ext4 root and its boot path is byte-identical to
the last 140 days, because grub-install is deliberately held back to the
cutover window.

Phase 1 (esh-pve-nas-stage-zfs-root.yaml): carve a 512 MB /boot LV out
of the 768 MB swap LV, populate it, rsync the 4.3 GB ext4 root into
nvme/ROOT/pve-1, write the copy's fstab.

Phase 2 (esh-pve-nas-stage-bootloader.yaml): ZFS initramfs, grub.cfg,
explicit pve-zfs-root and pve-ext4-rollback entries with stable ids,
grubenv pinned to the rollback so cutover's grub-reboot is a one-shot.

Three landmines the plan did not predict, all caught by verify steps
asserting effective state rather than by reading the plan:

- The /boot LV had nowhere to live. VG pve had 4 MB free and mounted
  ext4 cannot shrink; freeing space from root needs a rescue boot, which
  costs the one-reboot property. Space came from swap (768M -> 256M).

- The runbook's `zpool set cachefile=... nvme` would have broken the
  NAS. Populating a cachefile flips the host from import-by-scan to
  import-by-cache, so a one-pool cache leaves ssd and tank unimported --
  and CT 103 esh-nas has twelve bind mounts spanning all three pools.
  Set on all three instead, verified in the resulting cache.

- update-grub silently emitted a pool-less root=ZFS=/ROOT/pve-1, which
  boots to an initramfs prompt. Debian's 10_linux builds ${rpool}${bootfs}
  and rpool comes from grub-probe --target=fs_label, which returns empty
  because GRUB's ZFS reader cannot open a pool with encryption,
  large_dnode and zstd_compress -- the same feature set that forced /boot
  to stay ext4. The probe failure is swallowed by `2>/dev/null || true`.
  Fixed with a /etc/default/grub.d drop-in plus explicit menu entries.

The transferable lesson: the original verify grepped for the correct
root= string appearing somewhere in grub.cfg, which passes while every
menu entry is still broken. Assert the effective value, not the presence
of a substring.
2026-08-17 22:11:14 -07:00
vh 8ddc87c852 docs(esh-pve-nas): record the blocked-patching driver and the upgrade ordering
The operator-visible symptom is that PVE cannot be updated on this box for lack
of room. Measured: 225 packages pending, 161 carrying deb12uN/Debian-Security
bumps including ssh, against esh-pve's 8.4.14 versus this host's 8.4.11 and 20
weeks of uptime.

Records the ordering explicitly -- migrate first, upgrade after. The pending set
includes proxmox-kernel-6.8.12-42-pve-signed, roughly 250 MB of kernel plus
initramfs landing in /boot which is on root with 1.3 GB free. Unpacking 225
packages including dpkg and perl into that headroom risks filling the disk
mid-transaction and wedging dpkg on a hypervisor running five guests.

Notes the apt archive-dir redirect as a partial escape hatch if patching cannot
wait, and that zfs-initramfs 2.2.8 is fully capable of root-on-ZFS so there is
no need to upgrade ZFS before migrating.
2026-08-17 21:40:24 -07:00
vh 3e311756d7 docs(esh-pve-nas): split boot from root instead of reinstalling
Operator's proposal, and it is strictly better than the reinstall plan.

Boot and root do not have to share a device. Keep the ESP and /boot on the DOM
as ext4 -- so GRUB never has to read ZFS, which matters because the nvme pool
has encryption, large_dnode and zstd_compress enabled and GRUB cannot read
those -- and move root to nvme/ROOT/pve-1. The initramfs imports the pool and
pivots.

What this buys over the reinstall: the nvme pool survives, so no guest
migration, no export/import of ssd and tank, no reinstall. Downtime is one
reboot rather than half a day. Rollback is a GRUB menu entry, because the ext4
root stays on the DOM untouched. And it retires the actual top risk -- with
root on NVMe, a USB bus reset mid-run no longer takes the running system down;
the DOM becomes read-mostly, written only on kernel updates.

Preconditions verified and already met: UEFI with grub-efi, zfs-initramfs
2.2.8-pve1 installed with 76 ZFS files already in the running initrd, root only
4.3 GB to copy, swap negligible against 125 GB RAM.

Two traps recorded: canmount=noauto on the root dataset or ZFS tries to mount
over the running root; and cachefile is currently none with a 0-byte
zpool.cache, so the pool imports by scan today and must be given a cachefile
before the initramfs is rebuilt.

The reinstall plan is retained as the fallback.
2026-08-17 21:36:30 -07:00
vh 2275e11be0 docs(esh-pve-nas): plan the migration off the USB DOM; flag the NFS blast radius
PVE root on esh-pve-nas is a USB Disk-on-Module: 6 GB ext4 with the host's only
ESP. A DOM is SLC/pSLC so wear is not the driver -- the problems are that it is
on the USB bus (a reset drops root under a running hypervisor), has no headroom,
and is unmirrored while 928 GB of mirrored NVMe sits 96% empty.

Runbook targets a fresh PVE install to ZFS RAID1 across both NVMes. In-place
conversion is unsupported, and adding an ESP to the existing NVMes is impossible
-- both are whole-disk ZFS members with 1.7 MiB free and proxmox-boot-tool
manages nothing today.

The headline risk is not on the host being rebuilt: CT 103 esh-nas IS the NAS
at 10.0.50.50, and both esh-docker-vm and esh-pve mount it hard. Taking this box
down stalls esh-pve's storage layer and wedges esh-docker-vm into the D-state
whose only remedy is a host reboot -- the incident shape already on record.
Quiescing those clients is step one of the window, and the README now warns
against casual reboots.

Config snapshot captured off-box to nh3-dev (0600) with /etc/pve, network and
fstab config plus zpool/zfs/disk-by-id/guest state; the newest on-disk copy
before this was June 2024.
2026-08-17 21:32:33 -07:00