fix(vm109-dr): trap cleanup to stop VM 109 on script exit
The DR test script used set -euo pipefail, so a failing SSH shutdown command caused the script to exit before qm stop. On 2026-04-20 this left VM 109 running for 2.5 days and triggered an OOM cascade when pvemini HA-failed over to pveelite. Adds EXIT trap that force-stops VM 109 regardless of exit path, and makes the Step 7 SSH shutdown tolerant of failure. Incident details: proxmox/cluster/incidents/2026-04-20-cluster-outage.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
306
proxmox/cluster/incidents/2026-04-20-cluster-outage.md
Normal file
306
proxmox/cluster/incidents/2026-04-20-cluster-outage.md
Normal file
@@ -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"
|
||||
```
|
||||
Reference in New Issue
Block a user