b92097688c
esh-pve hard-froze at 03:34 on 2026-08-19 and stayed frozen ~4.5 hours until a manual power cycle. No panic, no OOM, no MCE — the journal stops mid-operation. The whole ESH site lost DNS with it, because esh-userland (VLAN 10, the PVC SSID and wired userland LAN) was handed exactly one resolver: 10.0.50.45, AdGuard on esh-docker-vm, on a different VLAN, with no secondary. Internet and routing were healthy throughout. Two fixes. 1. DNS: 10.0.10.1 (the gateway, verified resolving) added as secondary on esh-userland via the UDM Classic API. Note this is degradation cover, not clean failover — clients that query resolvers in parallel will bypass AdGuard for a share of lookups. 2. Watchdog: softdog -> iTCO_wdt under systemd (RuntimeWatchdogSec=60), watchdog-mux masked. The box looked watchdog-protected and was not: a software watchdog cannot fire when the kernel it lives in is wedged, and watchdog-mux only pets the device while an HA client is connected, which never happens on a cluster with no HA resources. Firmware does not block the TCO timer here, checked before committing to it. Also pins VM 102 off (onboot: 0). It starts with full GPU passthrough and vfio-pci enabling that device is the last thing the kernel logged, 39 minutes before the freeze. The other suspect is the kernel itself: the host ran 4.5 months on 6.8.12-16, took 6.8.12-42 in an apt batch on 08-18, and died 20 hours into the first boot on it. 6.8.12-16 is still installed as the rollback. The playbook is idempotent — a second run skips all six steps and passes all six verifies. The watchdog is confirmed armed (identity=iTCO_wdt, state=active, held by PID 1) but has NOT been observed firing; proving that needs a deliberate wedge. Memory also corrects two wrong mid-incident calls: the mgmt VLAN is routed over the site tunnel and is not firewalled off — both symptoms were the dead host generating ICMP unreachables.
121 lines
5.8 KiB
YAML
121 lines
5.8 KiB
YAML
# esh-pve — move from the software watchdog to the PCH hardware watchdog.
|
|
#
|
|
# WHY: esh-pve hard-froze at 03:34 on 2026-08-19 (no panic, no OOM, no MCE —
|
|
# the journal simply stops mid-line) and stayed frozen for ~4.5 hours until it
|
|
# was power-cycled by hand. Everything on it went with it, including the only
|
|
# DNS resolver the esh-userland VLAN is handed, so the whole house lost name
|
|
# resolution.
|
|
#
|
|
# Nothing on the box could have recovered it:
|
|
# - `softdog` was the loaded watchdog. A SOFTWARE watchdog cannot rescue a
|
|
# hard kernel freeze, because the frozen kernel is the thing that would
|
|
# have to fire its timer.
|
|
# - Proxmox's `watchdog-mux` held /dev/watchdog but never armed it: it only
|
|
# pets the device while an HA client is connected, and this cluster has no
|
|
# HA resources configured (`ha-manager status` reports quorum only).
|
|
#
|
|
# The board's PCH TCO timer is present and NOT blocked by firmware — verified
|
|
# before writing this:
|
|
# iTCO_wdt: Found a Intel PCH TCO device (Version=6, TCOBASE=0x0400)
|
|
# iTCO_wdt: initialized. heartbeat=30 sec (nowayout=0)
|
|
# (no "unable to reset NO_REBOOT flag" line, which is the BIOS-blocked case).
|
|
#
|
|
# APPROACH: systemd owns the hardware watchdog directly. Setting
|
|
# WATCHDOG_MODULE=iTCO_wdt in /etc/default/pve-ha-manager would point
|
|
# watchdog-mux at the right device but would still never arm it without HA, so
|
|
# it does not solve this. systemd's RuntimeWatchdogSec pets unconditionally,
|
|
# which is what "reboot me if I wedge" actually requires.
|
|
#
|
|
# ⚠️ CONSEQUENCE — READ BEFORE ENABLING PROXMOX HA ON THIS CLUSTER.
|
|
# This masks `watchdog-mux`. If HA is ever configured on esh-pve, watchdog-mux
|
|
# must own /dev/watchdog again and this must be reverted, or HA fencing will
|
|
# not work. That is not a near-term concern: esh-pve-cluster is TWO nodes with
|
|
# no qdevice, so a single node loss already costs quorum and the survivor would
|
|
# fence itself. HA here would make availability worse, not better.
|
|
#
|
|
# Revert: unmask + enable watchdog-mux, delete the three dropped files,
|
|
# `systemctl daemon-reexec`, reboot.
|
|
#
|
|
# Run: scripts/elway esh-pve --playbook playbooks/esh-pve-hardware-watchdog.yaml
|
|
|
|
vars:
|
|
# 60s: long enough that a busy-but-healthy host is never reset, short enough
|
|
# that a freeze costs a minute rather than half a working day. systemd pets
|
|
# at half this interval. PID 1 does not block on filesystem I/O, so the known
|
|
# NFS-wedge history on this host does not put it at risk of a false trip.
|
|
runtime_watchdog_sec: 60
|
|
|
|
steps:
|
|
- name: Load iTCO_wdt at every boot
|
|
shell: |
|
|
printf '# PCH hardware watchdog — see playbooks/esh-pve-hardware-watchdog.yaml\niTCO_wdt\n' \
|
|
> /etc/modules-load.d/itco-watchdog.conf
|
|
when: "! grep -qx 'iTCO_wdt' /etc/modules-load.d/itco-watchdog.conf 2>/dev/null"
|
|
|
|
- name: Stop softdog being auto-loaded so iTCO_wdt claims watchdog0
|
|
shell: |
|
|
printf '# softdog cannot rescue a hard freeze; iTCO_wdt can.\n# See playbooks/esh-pve-hardware-watchdog.yaml\nblacklist softdog\n' \
|
|
> /etc/modprobe.d/blacklist-softdog.conf
|
|
when: "! grep -qx 'blacklist softdog' /etc/modprobe.d/blacklist-softdog.conf 2>/dev/null"
|
|
|
|
- name: Mask watchdog-mux (idle without HA, and it holds the device)
|
|
shell: systemctl disable --now watchdog-mux.service && systemctl mask watchdog-mux.service
|
|
when: "[ \"$(systemctl is-enabled watchdog-mux.service 2>/dev/null)\" != masked ]"
|
|
|
|
- name: Hand the watchdog to systemd
|
|
shell: |
|
|
mkdir -p /etc/systemd/system.conf.d
|
|
cat > /etc/systemd/system.conf.d/watchdog.conf <<'EOF'
|
|
# Hardware watchdog (iTCO_wdt). See playbooks/esh-pve-hardware-watchdog.yaml
|
|
# for why systemd owns this rather than Proxmox's watchdog-mux.
|
|
[Manager]
|
|
RuntimeWatchdogSec={{ runtime_watchdog_sec }}
|
|
RebootWatchdogSec=10min
|
|
EOF
|
|
when: "! grep -q 'RuntimeWatchdogSec={{ runtime_watchdog_sec }}' /etc/systemd/system.conf.d/watchdog.conf 2>/dev/null"
|
|
|
|
# Renumber live so the change takes effect without waiting for a reboot:
|
|
# softdog currently holds watchdog0, so systemd would otherwise arm the
|
|
# software watchdog — the exact device that failed us.
|
|
- name: Make iTCO_wdt the active watchdog0 now
|
|
shell: |
|
|
rmmod softdog 2>/dev/null || true
|
|
rmmod iTCO_wdt 2>/dev/null || true
|
|
modprobe iTCO_wdt
|
|
# Gated, not unconditional: once systemd holds /dev/watchdog0 the rmmod
|
|
# would fail anyway, and re-running this on an already-correct host would
|
|
# otherwise churn the device for no reason. Only renumber when watchdog0
|
|
# is NOT already iTCO_wdt.
|
|
when: "! grep -qx 'iTCO_wdt' /sys/class/watchdog/watchdog0/identity 2>/dev/null"
|
|
|
|
- name: Re-exec systemd so RuntimeWatchdogSec takes effect
|
|
# daemon-reload does NOT apply [Manager] settings; a re-exec is required.
|
|
# Skipped once systemd already reports it owns the hardware watchdog.
|
|
shell: systemctl daemon-reexec
|
|
when: "! journalctl -b --no-pager | grep -q 'Using hardware watchdog .iTCO_wdt.'"
|
|
|
|
verify:
|
|
- name: iTCO_wdt is the kernel's watchdog0
|
|
shell: grep -qx 'iTCO_wdt' /sys/class/watchdog/watchdog0/identity
|
|
changed_when: "false"
|
|
|
|
- name: The watchdog is ARMED, not merely present
|
|
shell: grep -qx 'active' /sys/class/watchdog/watchdog0/state
|
|
changed_when: "false"
|
|
|
|
- name: systemd reports it owns a hardware watchdog
|
|
shell: journalctl -b --no-pager | grep -q 'Using hardware watchdog .iTCO_wdt.'
|
|
changed_when: "false"
|
|
|
|
- name: softdog is not loaded
|
|
shell: "! lsmod | grep -qE '^softdog'"
|
|
changed_when: "false"
|
|
|
|
- name: watchdog-mux is masked
|
|
shell: "[ \"$(systemctl is-enabled watchdog-mux.service 2>/dev/null)\" = masked ]"
|
|
changed_when: "false"
|
|
|
|
- name: Config survives a reboot
|
|
shell: grep -qx 'iTCO_wdt' /etc/modules-load.d/itco-watchdog.conf && grep -q RuntimeWatchdogSec /etc/systemd/system.conf.d/watchdog.conf
|
|
changed_when: "false"
|