diff --git a/proxmox/cluster/incidents/2026-04-20-cluster-outage.md b/proxmox/cluster/incidents/2026-04-20-cluster-outage.md new file mode 100644 index 0000000..f82f4a9 --- /dev/null +++ b/proxmox/cluster/incidents/2026-04-20-cluster-outage.md @@ -0,0 +1,306 @@ +# Incident 2026-04-20 — Cluster outage pvemini + pveelite + +**Severity:** Major (LXC/VM downtime pe 2 din 3 noduri, ~12h impact intermitent) +**Detected:** 2026-04-20 ~12:45 EEST (user) +**Resolved:** 2026-04-20 12:49 EEST (reboot manual toate nodurile) +**Author:** Claude Code (mmarius28@gmail.com) + +--- + +## TL;DR — lanțul cauzal (versiune finală) + +**Topologie reală:** pvemini (64 GB) = gazdă primară pentru TOATE: CT 100, 101, 104, 106, 108 + VM 109. pveelite (16 GB) = gazdă secundară de failover HA. pve1 (32 GB) = quorum-only, nu găzduiește servicii. + +1. **Luni 2026-04-20 00:23:24** — `pvemini` îngheață total, **fără niciun log kernel**. Cauză necunoscută (nu avem mcelog, kdump, IPMI SEL, UPS logs). +2. **00:39:47** — pvemini revine SINGUR (BIOS auto-power-on sau watchdog reset). **User nu a intervenit** — primul reboot manual de la user a fost abia la 12:49. +3. **00:40:17** — pveelite devine HA master, declanșează fence pe pvemini (pentru că pvemini era deja booted dar HA nu știa să-l recunoască imediat post-crash). +4. **00:41:28** — HA recovery masiv: CT 100 + CT 104 + CT 106 + CT 108 mutate de pe pvemini pe pveelite. VM 109 (HA state=`started` de la scriptul DR de sâmbătă, care n-a reușit să-l oprească) re-pornită pe pveelite (preferred în `ha-group-elite`). +5. **00:42:29** — primul OOM kill pe pveelite: VM 109 (kvm, 6.3 GB RSS) omorât. pveelite avea brusc CT 108 Oracle (8 GB) + VM 109 (6 GB) + CT 104 flowise + CT 100 portainer + CT 106 gitea + overhead. CT 101 minecraft NU era pe pveelite (era tot pe pvemini). Total cerere mult peste 16 GB. +6. **00:42 → 11:45** — **Buclă OOM infinită timp de ~3 ore**: HA LRM reporniseș VM 109 la fiecare 20 s, OOM killer îl omora. CT 104 și CT 108 erau de asemenea killed — amândouă **n-au `max_restart`/`max_relocate`** configurat → repornire infinită. CT 100 și CT 106 aveau limite, s-au oprit după 3 încercări. +7. **11:45:35** — pveelite devine nefuncțional, user intervine cu reboot la 11:46. +8. **12:49** — user reboot general manual pe ambele noduri → cluster stabil. + +**Root cause dublu:** +- **Trigger:** crash misterios pvemini fără diagnostic + DR test script lasă VM 109 în HA state=`started` (bug `set -e`). +- **Amplificator:** pveelite (16 GB) nu are capacitate să preia load-ul pvemini (care rulează ~25 GB de servicii). Limite HA incoerente (CT 104, CT 108, VM 109 fără `max_restart`). pve1 (32 GB) nefolosit. + +--- + +## Cronologie (EEST) + +| Ora | Nod | Eveniment | Sursa | +|-----|-----|-----------|-------| +| 2026-04-18 06:00 | pveelite | DR test script pornește VM 109. SSH `10.0.20.37` nu răspunde în 180s. Script crashează la STEP 7 (`ssh shutdown` fail + `set -e`). **VM 109 rămâne RUNNING** | `/var/log/oracle-dr/dr_test_20260418_060001.log` | +| 2026-04-18 → 04-20 | pveelite | VM 109 stă pornit 2.5 zile (~6 GB). CT 101 (minecraft, 8 GB) rula deja → total ~14 GB pe 16 GB host | - | +| 2026-04-20 00:23:24 | pvemini | **Ultimul log** (`pmxcfs data verification successful`), apoi tăcere. Mașina îngheață total. | `journalctl -b -2` | +| 00:40:17 | pveelite | HA CRM detectează pvemini offline, devine master. pvemini trece în `unknown`. | `pve-ha-crm` | +| 00:41:17 | pveelite | pvemini trece în `fence`. Serviciile ct:100, ct:104, ct:106, ct:108 marcate `fence`. | `pve-ha-crm` | +| 00:41:27 | pveelite | HA obține lock-ul de fence, pvemini considerat oficial DOWN. | `pve-ha-crm` | +| **00:41:28** | pveelite | **Recovery masiv:** ct:100, ct:104, ct:106, ct:108 mutate de pe pvemini pe pveelite. VM 109 era deja pe pveelite. | `pve-ha-crm` | +| 00:42:29 | pveelite | **Primul OOM-kill VM 109** (kvm, 6.3 GB RSS). CT 108 (oracle 8 GB) tocmai pornea. | kernel oom-killer | +| 00:42 → 11:45 | pveelite | Buclă OOM infinită: HA repornește VM 109 și alte servicii fără limită (CT 104, CT 108 nu au `max_restart`). Zeci de OOM-kill per oră. | `pve-ha-lrm` + kernel | +| 00:39:47 | pvemini | Boot automat kernel 6.8.12-15-pve (BIOS auto-power-on, **NU** intervenție user). | kernel boot | +| 00:43 → 12:47 | pvemini | pvemini rulează normal ~12h (serviciile sale erau fence-uite pe pveelite). Chill. | - | +| 11:30 → 11:45 | pveelite | OOM începe să omoare și alte procese (containerd, tailscaled, pvefw-logger, ora_w003_xe, python3). | kernel | +| 11:45:35 | pveelite | Host nefuncțional, ultim log. | `journalctl --list-boots` | +| 11:46:41 | pveelite | Reboot (utilizator). | reboot history | +| 11:51:37 | pveelite | HA shutdown controlat VM 109 (user a schimbat HA state la `stopped`). | `pve-ha-lrm` | +| 11:53:37 | pveelite | HA shutdown controlat CT 105. | `pve-ha-lrm` | +| 12:02:09 | pveelite | Al doilea reboot (utilizator, cleanup). | reboot history | +| **12:47:30** | pvemini | Shutdown (**utilizator, prima intervenție pe pvemini azi**). | reboot history | +| 12:47:53 | pveelite | Shutdown. | reboot history | +| 12:49:50 | pvemini + pveelite | Boot simultan, cluster Quorate 3/3. | kernel boot | + +--- + +## Analiză detaliată + +### 1. Crash pvemini 00:23 — CAUZĂ NECUNOSCUTĂ + +`pvemini` = HP ProDesk 600 G2 DM (i7-6700T, 32 GB RAM)... Actually corecție — sistemul DMI raportează "Venus Series" pentru pvemini (posibil Minisforum/Beelink mini PC). Verificare necesară. + +Revenirea automată la 00:39 (fără power button) sugerează: +- **BIOS "AC Power Loss Recovery: Power On"** activ. +- **Kernel panic + automatic reboot** (dacă `kernel.panic=N` setat) — dar 16 min e prea lung pentru automatic reboot. +- **Hardware watchdog reset** cu delay — posibil. +- **Pană de curent scurtă** — neverificat (nu există `apcupsd` activ pe pvemini sau pveelite pentru jurnalizare evenimente UPS). + +**Gol diagnostic total:** nu există `mcelog`, `kdump`, `netconsole`, IPMI SEL. Următoarea oară nu vom ști mai mult decât acum. + +### 2. DR test script bug — CAUZĂ PRINCIPALĂ PREVENTIBILĂ + +`proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh`, linia 507-511: + +```bash +ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 30" 2>/dev/null +sleep 60 +qm stop "$DR_VM_ID" 2>/dev/null +track_step "VM Shutdown" true "VM stopped" "$step_start" +``` + +Scriptul are `set -euo pipefail`. Când VM 109 nu e SSH-reachable (cazul Apr 18, 03-21→04-18 toate), comanda ssh eșuează cu non-zero. **`set -e` termină scriptul imediat**, înainte de `qm stop`. VM 109 rămâne pornit. + +Logurile tuturor testelor eșuate (21 martie → 18 aprilie, 5 săptămâni) au exact 1069 bytes — același punct de crash. VM 109 a fost pornit și nu a fost oprit niciodată după Apr 18. + +### 3. HA recovery overload — CAUZĂ ARHITECTURALĂ + +Când pvemini a căzut, HA a aplicat politica de recovery. `ha-group-main` are: +``` +nodes pve1:33, pvemini:100, pveelite:50 +``` + +pve1 = quorum-only (confirmat de user, dar HA nu știe asta — are pondere 33 în grup). HA a ales pveelite (pondere 50 > 33) pentru recovery. + +Dar **pveelite (16 GB) nu poate găzdui serviciile pvemini** în stare normală, darămite peste ce rulează deja: +- CT 101 (minecraft, 8 GB) — deja pe pveelite +- CT 108 (central-oracle, 8 GB) — mutat de pe pvemini +- VM 109 (6 GB) — deja pe pveelite (din bug-ul DR) +- CT 100 + CT 104 + CT 106 — overhead ~3-4 GB + +Total cerință ≥25 GB pe 16 GB host → OOM inevitabil. + +În plus, CT 104 (flowise) și CT 108 (central-oracle) **nu aveau** `max_restart`/`max_relocate` definite. CT 100, CT 101, CT 106 aveau `max_restart=3 max_relocate=3`. Incoerență de configurare. + +### 4. pve1 ca quorum-only — capacitate nefolosită + +pve1 are i7-6700T (8 core) + 32 GB RAM. **Dar user confirmă**: este folosit exclusiv pentru quorum, nu pentru găzduire. Deci la failover HA nu are target adecvat pentru serviciile pvemini. + +--- + +## Plan de prevenție + +Ordinea reflectă **impactul real asupra incidentului**. + +### P0 — Oprește bug-ul DR test (5 minute, OBLIGATORIU) + +Scriptul trebuie să garanteze oprirea VM 109 chiar dacă SSH eșuează: + +```bash +# Editare /opt/scripts/weekly-dr-test-proxmox.sh pe pveelite, linia ~505-511 + +# ÎNLOCUIEȘTE: +# ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 30" 2>/dev/null +# sleep 60 +# qm stop "$DR_VM_ID" 2>/dev/null + +# CU: + # Încearcă shutdown soft; ignoră eșecul pentru a NU ieși din script + ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" \ + "shutdown /s /t 30" 2>/dev/null || log_warning "SSH shutdown failed, forcing qm stop" + sleep 60 + # Forțează oprire indiferent de starea SSH + qm stop "$DR_VM_ID" --skiplock 2>/dev/null || true + # Verificare finală — dacă încă pornit, forțează stop + if qm status "$DR_VM_ID" | grep -q "running"; then + qm stop "$DR_VM_ID" --skiplock --timeout 30 2>/dev/null || true + sleep 5 + qm status "$DR_VM_ID" | grep -q "stopped" && log "VM $DR_VM_ID confirmed stopped" \ + || log_error "VM $DR_VM_ID still running after force stop" + fi +``` + +**Alternativ, cleanup global la sfârșitul scriptului** (pattern `trap`): + +```bash +cleanup_vm() { + log "Cleanup: ensuring VM $DR_VM_ID is stopped" + qm stop "$DR_VM_ID" --skiplock 2>/dev/null || true +} +trap cleanup_vm EXIT # rulează la orice ieșire, inclusiv set -e +``` + +### P0 — Limite de restart pe toate resursele HA (1 minut) + +CT 104 și CT 108 lipsesc limitele. Uniformizează: + +```bash +ssh root@10.0.20.200 bash <<'EOF' +ha-manager set ct:104 --max_restart 3 --max_relocate 2 +ha-manager set ct:108 --max_restart 3 --max_relocate 2 +ha-manager set vm:109 --max_restart 0 --max_relocate 0 +# VM 109 nu trebuie gestionat de HA deloc +ha-manager config +EOF +``` + +`max_restart=0 max_relocate=0` pentru VM 109 = HA nu încearcă să-l repornească. Combinat cu `state stopped` → VM 109 rămâne oprit până cineva îl pornește explicit (scriptul DR). + +### P0 — Scoate VM 109 din HA (recomandare) + +VM 109 e un DR test VM, nu un serviciu live. Nu ar trebui gestionat de HA. + +```bash +# Șterge complet din HA +ha-manager remove vm:109 +``` + +Scriptul DR va folosi `qm start 109` pe pveelite (unde rulează scriptul). HA nu va mai interveni. Eliminarea rezolvă și failover-ul nedorit către pveelite. + +### P1 — Re-arhitecturare HA recovery pentru pvemini crash + +**Problema:** dacă pvemini cade, serviciile sale (CT 108 Oracle 8 GB + CT 104 flowise + CT 100 portainer + CT 106 gitea) nu încap pe pveelite (16 GB). + +**3 opțiuni, alege una:** + +**Opțiunea A — Acceptă downtime pentru Oracle la crash pvemini (SIMPLU)** +```bash +# Scoate CT 108 din HA (acceptă manual restart dacă pvemini cade) +ha-manager remove ct:108 +# CT 108 va porni doar cu onboot=1, pe pvemini, manual după crash +``` +**Pro:** pveelite nu mai e sufocat. **Contra:** Oracle DB (serviciul critic) nu se recuperează automat. + +**Opțiunea B — Permite pve1 să preia serviciile critice (SCALARE)** +User a spus că pve1 e quorum-only, dar are 32 GB RAM liberi. Dacă rolul ar putea fi extins: +```bash +# Modifică ha-group-main să prefere pve1 peste pveelite +cat > /etc/pve/ha/groups.cfg <<'EOF' +group: ha-group-main + nodes pvemini:100,pve1:75,pveelite:25 + nofailback 0 + restricted 0 +EOF +``` +**Pro:** CT 108 ar putea merge pe pve1 (32 GB) în loc de pveelite. **Contra:** pve1 devine parte activă din cluster, nu mai e doar quorum. + +**Opțiunea C — Investește în redundanță pvemini (HARDWARE)** +Dacă pvemini e singurul punct de failure real, atunci: RAID, UPS dedicat, backup nod identical. Scump, dar evită problema. + +**Recomandare:** Opțiunea A pentru CT 108 (Oracle DB acceptă downtime de 5-10 min pentru reintervenție manuală) + păstrează HA pentru CT 100/104/106 (lightweight, pot sta pe pveelite temporar). + +### P1 — Swap pe pveelite (10 minute, insurance) + +```bash +ssh root@10.0.20.202 bash <<'EOF' +zfs create -V 8G -b 4K -o compression=zle -o logbias=throughput \ + -o sync=always -o primarycache=metadata -o secondarycache=none \ + rpool/swap +mkswap -f /dev/zvol/rpool/swap +echo "/dev/zvol/rpool/swap none swap sw 0 0" >> /etc/fstab +swapon -a +echo "vm.swappiness=10" > /etc/sysctl.d/99-swap.conf +sysctl -p /etc/sysctl.d/99-swap.conf +EOF +``` + +Swap = scade OOM-killer trigger (dă timp pentru migrare/fallback) dar e bandage, nu fix. + +### P2 — Diagnostic crash pvemini + +```bash +ssh root@10.0.20.201 bash <<'EOF' +apt update && apt install -y mcelog kdump-tools +systemctl enable --now mcelog +# kdump: în /etc/default/grub adaugă crashkernel=256M +# netconsole către pve1 +modprobe netconsole netconsole=@10.0.20.201/vmbr0,6666@10.0.20.200/ +echo "options netconsole netconsole=@10.0.20.201/vmbr0,6666@10.0.20.200/" >> /etc/modprobe.d/netconsole.conf +# Watchdog NMI pentru hang detection +echo "kernel.nmi_watchdog=1" > /etc/sysctl.d/99-nmi.conf +sysctl -p /etc/sysctl.d/99-nmi.conf +EOF +# Verifică BIOS update la Minisforum/Beelink — ultimul versiune disponibilă +# memtest86+ bootabil la următoarea mentenanță +``` + +### P2 — Configurare UPS monitoring + +Niciun nod nu are `apcupsd`/`nut` pornit → nu există log când pică curentul. Dacă pvemini e pe UPS, configurează monitorizare: + +```bash +apt install apcupsd +# Configurare /etc/apcupsd/apcupsd.conf per setup UPS +# Jurnalizare evenimente în /var/log/apcupsd.events +``` + +### P3 — Alertare OOM + node-down + +```bash +# /opt/scripts/oom-alert.sh — cron */1 * * * * +#!/bin/bash +COUNT=$(journalctl --since "1 minute ago" -k | grep -c "Killed process") +[ "$COUNT" -gt 0 ] && echo "OOM x$COUNT on $(hostname)" | \ + mail -s "[ALERT] OOM $(hostname)" proxmox@romfast.ro +``` + +Plus: fix DKIM/SPF pe `romfast.ro` (Gmail a respins notificarea în dimineața incidentului — vezi log 00:00:02). + +--- + +## Acțiuni — executate 2026-04-20 13:00–14:00 + +- [x] **P0-1** Fix `weekly-dr-test-proxmox.sh` — trap `cleanup_vm EXIT` + SSH shutdown `|| log_warning` (pveelite:/opt/scripts/) +- [x] **P0-2** `ha-manager set ct:104 ct:108 --max_restart 3 --max_relocate 2` +- [x] **P0-3** `ha-manager remove vm:109` — VM 109 nu mai e gestionat de HA +- [x] **P1-1** Corosync token mărit la 10 s (`config_version: 16`) — tolerează glitch-uri USB pveelite +- [x] **P1-2** Swap 8 GB pe pveelite (ZFS zvol `rpool/swap`, `vm.swappiness=10`) +- [x] **P2-1** `rasdaemon` instalat + activ pe pvemini (MCE + PCIe AER monitoring) +- [x] **P2-2** `netconsole` pvemini → pve1 (log: `/var/log/netconsole-pvemini.log`, systemd unit `netconsole-receiver`) +- [x] **P2-3** `kernel.panic=10` pe pvemini (auto-reboot la panic după 10s) + +## Rămas de făcut (opțional, necesită reboot sau decizie) + +- [ ] **kdump-tools** pe pvemini — captură kernel crash dump (necesită `crashkernel=256M` în GRUB + reboot) +- [ ] **Fix DKIM/SPF `romfast.ro`** — Gmail respinge notificările (vezi log 2026-04-20 00:00:02) +- [ ] **apcupsd/nut** monitorizare UPS pe pvemini (cablul UPS Cypress USB-to-Serial e conectat, dar nu există jurnal evenimente power event) +- [ ] **Al 2-lea link corosync** pentru pveelite prin eno1 (necesită cablu fizic în NIC integrat) +- [ ] **Alertare OOM** — cron `oom-alert.sh` cu mail la fiecare `Killed process` +- [ ] **Cauza 00:23 crash pvemini** — **necunoscută**. Dacă se repetă, `netconsole` + `rasdaemon` ar trebui să dea indicii. + +--- + +## Anexă — verificări + +```bash +# Confirmare VM 109 oprit +ssh root@10.0.20.202 "qm status 109; ha-manager status | grep 109" + +# Verificare limite HA după fix +ssh root@10.0.20.200 "ha-manager config" + +# Confirmare pveelite stabil +ssh root@10.0.20.202 "free -h; journalctl -k --since today | grep -c 'Killed process'" + +# Verificare DR script fix +ssh root@10.0.20.202 "head -520 /opt/scripts/weekly-dr-test-proxmox.sh | tail -25" +``` diff --git a/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh b/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh index 3adc80e..f52b187 100644 --- a/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh +++ b/proxmox/vm109-windows-dr/scripts/weekly-dr-test-proxmox.sh @@ -22,6 +22,19 @@ set -euo pipefail +# Cleanup trap: ensure VM 109 is always stopped on script exit +# Fixes incident 2026-04-20: script crashed at SSH step and left VM 109 running +# for 2.5 days, causing OOM cascade on pveelite after pvemini HA failover. +cleanup_vm() { + local rc=$? + if qm status "${DR_VM_ID:-109}" 2>/dev/null | grep -q running; then + echo "[trap] VM ${DR_VM_ID:-109} still running at exit (rc=$rc), forcing stop" + qm stop "${DR_VM_ID:-109}" --skiplock 2>/dev/null || true + fi + exit $rc +} +trap cleanup_vm EXIT + # Set proper PATH for cron execution export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" @@ -504,7 +517,8 @@ run_dr_test() { step_start=$(date +%s) log "STEP 7: Shutting down VM" - ssh -p "$DR_VM_PORT" "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 30" 2>/dev/null + ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 30" 2>/dev/null \ + || log_warning "SSH shutdown failed, will force qm stop" sleep 60 qm stop "$DR_VM_ID" 2>/dev/null