diff --git a/proxmox/README.md b/proxmox/README.md index be02d26..622ec7f 100644 --- a/proxmox/README.md +++ b/proxmox/README.md @@ -12,7 +12,8 @@ proxmox/ │ ├── incidents/ # Post-mortems incidente cluster │ │ ├── 2026-04-20-cluster-outage.md # Cascadă OOM pveelite + USB LAN watchdog │ │ ├── 2026-04-30-pvemini-backup-ssd-hang.md # Kingston SSD hang → emergency mode -│ │ └── 2026-07-31-lxc110-dns-tailscale-oom.md # DNS mort (Tailscale logout) + zram pe pve1 +│ │ ├── 2026-07-31-lxc110-dns-tailscale-oom.md # DNS mort (Tailscale logout) + zram pe pve1 +│ │ └── 2026-08-27-pveelite-down.md # DESCHIS — pveelite mort, suspiciune alimentare/hardware │ └── ups/ # Sistem UPS pentru cluster │ ├── README.md │ ├── docs/ diff --git a/proxmox/cluster/README.md b/proxmox/cluster/README.md index 2005b5f..0556717 100644 --- a/proxmox/cluster/README.md +++ b/proxmox/cluster/README.md @@ -870,6 +870,7 @@ swapon -a - **2026-04-20 Cluster Outage:** `incidents/2026-04-20-cluster-outage.md` — post-mortem complet + plan prevenție - **2026-04-30 pvemini backup SSD hang:** `incidents/2026-04-30-pvemini-backup-ssd-hang.md` — Kingston thermal hang → emergency mode - **2026-07-31 LXC 110 DNS + OOM:** `incidents/2026-07-31-lxc110-dns-tailscale-oom.md` — logout Tailscale → DNS mort; zram instalat pe pve1 +- **2026-08-27 pveelite down:** `incidents/2026-08-27-pveelite-down.md` — **DESCHIS** — reset spontan 16:01, cădere definitivă 17:26, jurnal tăcut ⇒ suspiciune alimentare/hardware. Verificare la repornire: `scripts/verifica-pveelite.ps1` ### LXC Containers - **LXC 108 - Oracle Database:** `../lxc108-oracle/README.md` diff --git a/proxmox/cluster/incidents/2026-08-27-pveelite-down.md b/proxmox/cluster/incidents/2026-08-27-pveelite-down.md new file mode 100644 index 0000000..3076ad8 --- /dev/null +++ b/proxmox/cluster/incidents/2026-08-27-pveelite-down.md @@ -0,0 +1,220 @@ +# Incident 2026-08-27 — pveelite cade definitiv la 4 ore după repornirea planificată a clusterului + +**Severity:** Medium (zero impact pe producție — nodul nu găzduia niciun serviciu viu — +dar clusterul rămâne pe **2 voturi din 3**, fără marjă de quorum, și replicarea către +pveelite e oprită) +**Detected:** 2026-08-27 17:31 EEST (alertă automată `pveelite-down-alert.sh` → email) +**Status:** **DESCHIS** — nodul e inaccesibil, necesită intervenție fizică +**Author:** Claude Code (mmarius28@gmail.com) + +--- + +## TL;DR — lanțul cauzal + +1. **12:25–12:36** — oprire **planificată** a întregului cluster, comandată din stația de + admin (10.0.20.144). Guest-urile oprite ordonat prin API (`qmshutdown:303` la 12:25 de la + `root@pam`), apoi nodurile. Oprirea a fost **curată** pe toate trei — `systemd-poweroff.service: + Finished`, `Reached target poweroff.target`. Tiparul e exact al lui + [`cluster-shutdown.ps1`](../scripts/cluster-shutdown.ps1). +2. **13:20:26–13:20:41** — toate trei nodurile pornesc la loc și reformează clusterul complet + (membership `1.1c7`, Members: 1 2 3). **pveelite a revenit normal** și a funcționat 2h40m. +3. **16:01:48 → 16:03:17** — pveelite dispare din corosync și revine după ~89 de secunde. + Nu a fost un flap de rețea: la 16:03:10 se reatașează ca **client NFS** (`rpc.mountd: v4.2 + client attached from 10.0.20.202:986`), iar mount-ul NFS se reface doar la boot. **Nodul + s-a resetat singur.** Nimeni nu i-a comandat asta. +4. **17:15:01** — ultimul semn de viață: cronul de pe pveelite se conectează prin SSH la + pvemini, ca la fiecare 15 minute. +5. **17:26:44** — pveelite pică definitiv. **Nu a mai revenit.** +6. **17:27:03** — corosync formează membership `1.1d8` fără nodul 3. Quorum păstrat (2/3). +7. **17:28:04–17:28:23** — HA fence executat **corect**: lock obținut, `vm:109` recuperat de + pe nodul fenced pe pvemini și lăsat în starea în care era (`stopped`). Fără repornire + nedorită, fără buclă OOM — spre deosebire de [incidentul din 2026-04-20](2026-04-20-cluster-outage.md). +8. **17:31** — alerta a plecat pe email, după cele 5 minute de prag din script. Lanțul de + alertare a funcționat end-to-end. + +**Root cause:** nedeterminabilă de la distanță — nodul e mort la nivel L2. Dovezile +disponibile exclud însă cauzele software și indică **defect hardware / de alimentare pe +cutia pveelite**, cel mai probabil declanșat de ciclul de oprire-pornire de la prânz: + +- **Nu e resurse:** RRD-ul de pe pvemini arată nodul complet inactiv în ora dinaintea + căderii — CPU 0,4 %, load 0,10, RAM 2,15 din 15,3 GB, rootfs 18,5 GB constant. Nu e + repetarea scenariului OOM din aprilie. +- **Nu e rețea:** în ultimele 7 zile nu există niciun flap knet în afara zilei de azi. + pve1 ↔ pvemini sunt stabile continuu de la 13:20. +- **Nu e UPS:** `ups.status: OL`, baterie 100 %, input 239,6 V, load 8 %. Ultimul test de + autonomie de azi a fost pe UPS-ul serverului 36, nu pe cel al clusterului. +- **Nu e comandă de la noi:** singura rulare `ups-shutdown` de azi (14:12) a fost în + **dry-run**, iar în jurnal nu există niciun poweroff/reboot trimis către 10.0.20.202. +- **Tiparul e de hardware:** reset spontan la 16:01, apoi moarte definitivă 85 de minute + mai târziu, pe o mașină idle. Semnătură clasică de PSU / RAM / termic. + +--- + +## Cronologie (EEST) + +| Ora | Nod | Eveniment | Sursa | +|-----|-----|-----------|-------| +| 12:25:02 | pvemini | ` qmshutdown:303` — începe oprirea ordonată a guest-urilor | `pvedaemon` | +| 12:28:59 | pvemini | Stația de admin (10.0.20.144) se conectează prin SSH | `sshd` | +| 12:33:56 | pvemini | `Members left: 3` — pveelite se oprește primul | `corosync` | +| 12:35:58 | pvemini | `Members left: 1` — pve1 se oprește | `corosync` | +| 12:36:58 | pvemini | `Finished systemd-poweroff.service` — oprire curată, ultimul nod | `journalctl -b -1` | +| 13:20:24 | pve1 | boot | `uptime -s` | +| 13:20:26 | pvemini | boot | `uptime -s` | +| 13:20:41 | — | Cluster complet: membership `1.1c7`, Members[3]: 1 2 3 | `corosync` | +| 14:12:26 | pvemini | `ups-shutdown` orchestrat — **DRY-RUN**, fără efect. Status OL, baterie 100 % | `/var/log/ups-shutdown.log` | +| 16:00:02 | pvemini | cron de pe pveelite, SSH normal | `sshd` | +| **16:01:48** | pvemini | `link: host: 3 link: 0 is down` — pveelite dispare | `corosync` | +| 16:02:07 | pvemini | `Members left: 3`, `Failed to receive the leave message` | `corosync` | +| 16:03:10 | pvemini | `rpc.mountd: v4.2 client attached from 10.0.20.202` — **remount NFS ⇒ pveelite a bootat** | `rpc.mountd` | +| 16:03:17 | pvemini | `Members joined: 3` — pveelite revine în cluster | `corosync` | +| 16:15 / 16:30 / 16:45 / 17:00 | pvemini | cronurile de pe pveelite rulează normal | `sshd` | +| **17:15:01** | pvemini | **ultimul semn de viață** al lui pveelite | `sshd` | +| 17:26:35 | — | ultimul timestamp LRM pveelite | `ha-manager status` | +| **17:26:44** | pvemini | `link: host: 3 link: 0 is down` — cădere definitivă | `corosync` | +| 17:26:48 | pvemini | `Token has not been received in 7987 ms` | `corosync` | +| 17:27:03 | pvemini | Membership `1.1d8`, Members[2]: 1 2. **Quorum păstrat** | `corosync` | +| 17:27:13 | pvemini | `node 'pveelite': online => unknown` | `pve-ha-crm` | +| 17:28:04 | pvemini | `node 'pveelite': unknown => fence`; `vm:109` → `fence` | `pve-ha-crm` | +| 17:28:13 | pvemini | Lock de fence obținut; `vm:109` recuperat pe pvemini, `recovery` → `request_stop` | `pve-ha-crm` | +| 17:28:23 | pvemini | `vm:109` → `stopped`. Recovery încheiat curat | `pve-ha-crm` | +| **17:31** | pvemini | Alertă email trimisă (prag de 5 minute atins) | `/var/run/pveelite-down-alerted` | +| 21:22 | — | Verificare: ping 100 % loss, `ip neigh` → `FAILED`, nod inaccesibil de 4 ore | investigație | + +--- + +## Starea la momentul investigației (21:22) + +### Cluster + +``` +Expected votes: 3 | Total votes: 2 | Quorum: 2 | Quorate: Yes +Members: 0x1 10.0.20.200 (pve1), 0x2 10.0.20.201 (pvemini, local) +/etc/pve/.members: pveelite online: 0 +``` + +**Clusterul funcționează, dar fără nicio marjă.** Dacă mai cade un nod, se pierde quorumul +și `/etc/pve` devine read-only pe tot clusterul — toate guest-urile îngheață. + +### Ce rula pe pveelite + +**Nimic viu.** Singurul guest asignat nodului era `CT 301 docker-portainer-template` +(`template: 1`, oprit). `vm:109` era înregistrat în HA pe pveelite, dar în starea `stopped`, +și a fost recuperat pe pvemini la 17:28. Niciun serviciu HA activ nu era pe pveelite. + +Motivul pentru care asta a mers bine: după incidentul din aprilie, sarcina a fost consolidată +pe pvemini/pve1, iar pveelite a rămas doar țintă de replicare + rezervă de failover. + +### Replicare + +| Job | Țintă | Ultima sincronizare | Stare | +|-----|-------|---------------------|-------| +| 100-1, 102-1, 103-1, 104-1 | pveelite | **2026-08-26** ~21:05–21:21 | ✗ eșec, FailCount 1 | +| 106-1 | pveelite | 2026-08-27 16:00:03 | ✗ eșec, FailCount 1 | +| 108-1, 171-1, 201-1 | pveelite | 2026-08-27 17:15 | ✗ eșec, FailCount 1 | +| 109-1 | pveelite | niciodată | ✗ eșec, FailCount 1 | +| **toate `*-0`** | **pve1** | **2026-08-27 20:00–21:20** | ✓ **OK** | + +Eroarea e uniformă: `ssh ... root@10.0.20.202 -- pvesr prepare-local-job ... failed: exit code 255` +(SSH nu poate deschide conexiunea). + +**Copia DR pe pve1 e intactă și la zi** — CT 108 Oracle și VM 201 replicate la 21:15, RPO 15 min +respectat. Redundanța a scăzut de la 2 copii la 1, nu la 0. + +### Fără acces out-of-band + +`tailscale status` → `pveelite ... offline, last seen 27d ago`. Nu doar că nodul e mort, dar +**tailscaled nu mai rulează pe el de 27 de zile** — deci nici dacă ar fi doar o problemă de +rețea LAN, tot n-am putea ajunge la el. Singura cale rămasă e fizică. + +### Colateral observat + +- `local-zfs` pe pvemini la **92,06 %** (69 GB liberi din 875 GB). Nu e cauza incidentului, + dar e o problemă separată care merită tratată. +- Scriptul `/opt/scripts/pveelite-down-alert.sh` descrie în corpul alertei un export NFS + `10.0.20.202:/mnt/pve/oracle-backups` care **nu mai există** în `storage.cfg`. Textul + alertei e depășit și induce în eroare la citire. + +--- + +## Ce a funcționat bine + +Merită consemnat, pentru că sunt exact lucrurile reparate după aprilie: + +1. **Fence-ul a mers curat.** Lock obținut, un singur serviciu recuperat, starea `stopped` + respectată. Fără repornire nedorită, fără cascadă. +2. **Consolidarea sarcinii pe pvemini/pve1 a făcut incidentul un non-eveniment** pentru + producție. În aprilie, căderea unui nod muta 4 containere pe o mașină de 16 GB și + declanșa 3 ore de buclă OOM. +3. **Alertarea a funcționat** — prag de 5 minute, email livrat la 17:31 prin + `mail.romfast.ro`, coadă goală, `status=sent`. +4. **Replicarea către pve1 nu a fost afectată** — a doua țintă și-a făcut treaba. + +--- + +## Ce urmează — verificare la repornirea nodului + +Nodul se pornește fizic. Imediat după ce revine, se rulează +[`verifica-pveelite.ps1`](../scripts/verifica-pveelite.ps1) din stația de admin: + +```powershell +.\proxmox\cluster\scripts\verifica-pveelite.ps1 +``` + +Scriptul acoperă, în ordine: + +1. **De ce s-a resetat la 16:01 și de ce a murit la 17:26** — `journalctl -b -1` și `-b -2` + pentru erori, `mcelog`/MCE în `dmesg`, evenimente termice, `Hardware Error`. + **Dacă log-urile sunt tăcute (ca la pvemini în aprilie), concluzia e alimentare/hardware, + nu software** — și atunci nodul nu mai e de încredere ca țintă de failover. +2. **Integritatea ZFS** — `zpool status -v` după două opriri necurate; se așteaptă + eventual un resilver automat, ca în aprilie. +3. **Revenirea în cluster** — quorum 3/3, `pvecm status`, `/etc/pve/.members`. +4. **Repornirea replicării** — `pvesr status`, FailCount înapoi la 0, toate cele 9 job-uri + recuperate. Nu necesită intervenție manuală; se reiau singure. +5. **HA** — LRM pveelite `active`, nu `dead`. +6. **SMART pe discuri** — temperaturi și erori, dat fiind precedentul Kingston. +7. **Tailscale** — repornit și `Online`, ca să existe acces out-of-band data viitoare. + +--- + +## Plan de prevenție + +### 🔴 Tier 1 — la repornirea nodului + +**1. Tailscale pe pveelite** +Mort de 27 de zile. Fără el, orice incident viitor pe nodul ăsta cere din nou deplasare +fizică. +```bash +ssh root@10.0.20.202 "systemctl enable --now tailscaled && tailscale up && tailscale status" +``` + +**2. Verdictul hardware, scris explicit.** +Dacă `journalctl -b -1` nu conține nimic înainte de 17:26 (tăcere completă, ca la pvemini pe +2026-04-20), atunci **cauza e hardware și nodul trebuie tratat ca nesigur** până la +înlocuirea sursei / testare RAM. Nu se lasă concluzia „a mers, deci e bine". + +**3. Textul alertei — scos exportul NFS inexistent.** +`/opt/scripts/pveelite-down-alert.sh` menționează `10.0.20.202:/mnt/pve/oracle-backups`, care +nu mai e în `storage.cfg`. Cine citește alerta la 3 noaptea o ia pe o pistă falsă. + +### 🟡 Tier 2 — de decis după verdictul hardware + +**4. Ce se întâmplă cu marja de quorum.** +Cât timp pveelite e nesigur, clusterul funcționează pe 2 din 3 voturi. Nu se umblă la +`expected votes` cât timp e quorate — dar dacă nodul urmează să stea oprit zile întregi, +merită decis conștient dacă rămâne în cluster sau se scoate temporar. + +**5. `local-zfs` pe pvemini la 92 %.** +Separat de incident, dar pe același nod pe care stă acum toată producția. + +--- + +## Referințe + +- [Incident 2026-04-20 — cluster outage pvemini + pveelite](2026-04-20-cluster-outage.md) — + precedentul de „nod care moare fără niciun log" +- [Incident 2026-04-30 — Kingston backup SSD hang](2026-04-30-pvemini-backup-ssd-hang.md) +- [Oprire planificată a clusterului](../docs/oprire-planificata-cluster.md) +- [Failover și replicare](../failover/README.md) +- Script de verificare: [`../scripts/verifica-pveelite.ps1`](../scripts/verifica-pveelite.ps1) diff --git a/proxmox/cluster/scripts/verifica-pveelite.ps1 b/proxmox/cluster/scripts/verifica-pveelite.ps1 new file mode 100644 index 0000000..dce0ff0 --- /dev/null +++ b/proxmox/cluster/scripts/verifica-pveelite.ps1 @@ -0,0 +1,354 @@ +<# +.SYNOPSIS + Verificare completa a nodului pveelite dupa revenirea din incidentul 2026-08-27. + +.DESCRIPTION + Nodul pveelite s-a resetat singur la 16:01 pe 27.08.2026 si a murit definitiv la + 17:26, pe o masina complet idle. Cauza nu s-a putut stabili de la distanta - nodul + era mort la nivel L2. Scriptul asta se ruleaza IMEDIAT dupa ce nodul e pornit fizic + la loc si raspunde la doua intrebari, in ordinea asta: + + 1. DE CE a cazut - din log-urile boot-urilor anterioare, care acum sunt citibile. + 2. E nodul in stare buna acum - ZFS, cluster, replicare, HA, SMART, Tailscale. + + Verdictul de la pasul 1 conteaza mai mult decat restul. Daca log-urile boot-ului + anterior sunt TACUTE inainte de ora caderii - fara MCE, fara eroare termica, fara + panic - atunci cauza e alimentare/hardware, exact ca la pvemini pe 2026-04-20, iar + nodul NU e de incredere ca tinta de failover pana la testarea sursei si a memoriei. + "A pornit, deci e bine" nu e o concluzie valida aici. + + Context complet: ../incidents/2026-08-27-pveelite-down.md + + RULEAZA DE PE STATIA DE ADMIN. Motivul: SSH intre nodurile Proxmox trece prin + Tailscale si cere autentificare interactiva, deci comenzile inlantuite de tip + "ssh pvemini ssh pveelite" se blocheaza. + + Necesita clientul OpenSSH din Windows si o cheie SSH acceptata de root pe noduri. + +.PARAMETER Wait + Asteapta pana la N minute ca pveelite sa raspunda, in loc sa iasa imediat. + Util daca pornesti scriptul inainte sa apesi butonul de power. + +.EXAMPLE + .\verifica-pveelite.ps1 + +.EXAMPLE + .\verifica-pveelite.ps1 -Wait 10 +#> +[CmdletBinding()] +param( + [int]$Wait = 0 +) + +$ErrorActionPreference = 'Stop' + +# ---------------------------------------------------------------- configurare + +$NodeIp = [ordered]@{ + pve1 = '10.0.20.200' + pvemini = '10.0.20.201' + pveelite = '10.0.20.202' +} + +# Reperele incidentului 2026-08-27, folosite pentru a taia log-urile la fereastra +# relevanta si a nu ineca verdictul in zgomot. +$ResetSpontan = '2026-08-27 16:01:48' # reset care s-a auto-reparat in 89 s +$CadereFinala = '2026-08-27 17:26:44' # caderea definitiva +$IncidentStart = '2026-08-27 13:20' # boot-ul de dupa oprirea planificata + +# Tipare de eroare hardware in jurnalul kernel. Daca NICIUNUL nu apare, verdictul +# inclina spre alimentare (masina nu apuca sa scrie nimic inainte sa dispara). +$TipareHw = 'mce|machine check|hardware error|thermal|temperature above|critical temp|' + + 'nmi|kernel panic|oops|BUG:|edac|pcieport.*error|ATA bus error|I/O error' + +$ReplJobs = 9 # 9 job-uri de replicare au pveelite ca tinta + +# ------------------------------------------------------------------- utilitare + +function Write-Step { param([string]$Text) Write-Host ""; Write-Host "== $Text" -ForegroundColor Green } +function Write-Ok { param([string]$Text) Write-Host " [ok] $Text" -ForegroundColor Green } +function Write-Warn { param([string]$Text) Write-Host " [!] $Text" -ForegroundColor Yellow } +function Write-Bad { param([string]$Text) Write-Host " [X] $Text" -ForegroundColor Red } +function Write-Info { param([string]$Text) Write-Host " $Text" -ForegroundColor DarkGray } + +function Test-Alive { + param([string]$Address, [int]$TimeoutMs = 2000) + try { + $p = New-Object System.Net.NetworkInformation.Ping + return ($p.Send($Address, $TimeoutMs).Status -eq 'Success') + } catch { + return $false + } +} + +function Invoke-Node { + param([string]$Node, [string]$Command, [int]$Timeout = 15) + # stderr e capturat separat: pe Windows PowerShell 5.1, 2>&1 pe un executabil + # nativ transforma fiecare linie de stderr in ErrorRecord si strica $?. + $out = ssh -o BatchMode=yes -o ConnectTimeout=$Timeout ` + -o StrictHostKeyChecking=accept-new -o LogLevel=ERROR ` + "root@$($NodeIp[$Node])" $Command + return $out +} + +# Colectorul de concluzii: fiecare pas adauga aici, la final se tipareste bilantul. +$Rezultate = [System.Collections.ArrayList]::new() +function Add-Rezultat { + param([string]$Nume, [ValidateSet('ok','warn','bad')][string]$Stare, [string]$Detaliu) + [void]$Rezultate.Add([pscustomobject]@{ Nume = $Nume; Stare = $Stare; Detaliu = $Detaliu }) +} + +# =========================================================== 0. nodul raspunde + +Write-Host "" +Write-Host "VERIFICARE pveelite dupa incidentul din 2026-08-27" -ForegroundColor Cyan +Write-Host "Referinta: proxmox/cluster/incidents/2026-08-27-pveelite-down.md" -ForegroundColor DarkGray + +Write-Step "0. Nodul raspunde?" + +$deadline = (Get-Date).AddMinutes($Wait) +$viu = Test-Alive $NodeIp.pveelite +while (-not $viu -and (Get-Date) -lt $deadline) { + Write-Info "inca nu raspunde, reincerc... (pana la $($deadline.ToString('HH:mm')))" + Start-Sleep -Seconds 15 + $viu = Test-Alive $NodeIp.pveelite +} + +if (-not $viu) { + Write-Bad "pveelite ($($NodeIp.pveelite)) nu raspunde la ping." + Write-Info "Porneste nodul fizic, apoi reia. Sau ruleaza cu -Wait 10 ca sa astepte." + exit 1 +} +Write-Ok "ping OK" + +try { + $hostn = Invoke-Node -Node pveelite -Command 'hostname' + Write-Ok "SSH OK - $hostn" +} catch { + Write-Bad "raspunde la ping dar nu la SSH. Verifica pe consola fizica." + exit 1 +} + +$uptime = Invoke-Node -Node pveelite -Command 'uptime -p; uptime -s' +Write-Info ($uptime -join ' | ') + +# ================================================= 1. DE CE a cazut (esentialul) + +Write-Step "1. De ce a cazut - log-urile boot-urilor anterioare" + +Write-Info "boot-uri inregistrate:" +$boots = Invoke-Node -Node pveelite -Command 'journalctl --list-boots --no-pager | tail -5' +$boots | ForEach-Object { Write-Info " $_" } + +# Boot -1 = fereastra 16:03 -> 17:26 (caderea definitiva). +# Boot -2 = fereastra 13:20 -> 16:01 (resetul spontan). +foreach ($b in @( + @{ Idx = -1; Cand = $CadereFinala; Ce = 'caderea definitiva de la 17:26' }, + @{ Idx = -2; Cand = $ResetSpontan; Ce = 'resetul spontan de la 16:01' } +)) { + Write-Host "" + Write-Info "--- boot $($b.Idx): ultimele linii inainte de $($b.Ce) ---" + $tail = Invoke-Node -Node pveelite -Command "journalctl -b $($b.Idx) --no-pager | tail -15" + if ($tail) { $tail | ForEach-Object { Write-Host " $_" -ForegroundColor DarkGray } } + else { Write-Warn "boot $($b.Idx) nu mai exista in jurnal (rotit sau volatile)" } + + $err = Invoke-Node -Node pveelite -Command "journalctl -b $($b.Idx) -p err --no-pager | tail -20" + if ($err) { + Write-Warn "erori in boot $($b.Idx):" + $err | ForEach-Object { Write-Host " $_" -ForegroundColor Yellow } + } else { + Write-Info "fara erori de prioritate >= err in boot $($b.Idx)" + } +} + +# Cautarea tintita de semnaturi hardware, peste toate boot-urile de ieri. +Write-Host "" +Write-Info "--- semnaturi hardware in jurnal (MCE / termic / panic / EDAC) ---" +$hw = Invoke-Node -Node pveelite -Command ` + "journalctl --since '$IncidentStart' --no-pager | grep -iE '$TipareHw' | tail -25" + +$verdictHw = $false +if ($hw) { + Write-Bad "GASIT - nodul a lasat urme de eroare hardware:" + $hw | ForEach-Object { Write-Host " $_" -ForegroundColor Red } + Add-Rezultat 'Cauza caderii' 'bad' 'semnaturi hardware in jurnal - vezi liniile de mai sus' + $verdictHw = $true +} else { + Write-Info "niciun MCE, eroare termica, panic sau EDAC in jurnal" +} + +# mcelog / edac, daca sunt instalate +$mce = Invoke-Node -Node pveelite -Command ` + "which mcelog >/dev/null 2>&1 && mcelog --client 2>/dev/null | tail -10; ls /sys/devices/system/edac/mc/mc0/*_count 2>/dev/null | head -3" +if ($mce) { $mce | ForEach-Object { Write-Info " $_" } } + +# Cate reseturi necurate a inregistrat masina +$necurate = Invoke-Node -Node pveelite -Command ` + "journalctl --list-boots --no-pager | wc -l; last -x reboot shutdown 2>/dev/null | head -8" +Write-Info "istoric reboot/shutdown:" +$necurate | Select-Object -Skip 1 | ForEach-Object { Write-Info " $_" } + +if (-not $verdictHw) { + Write-Host "" + Write-Bad "VERDICT: jurnalul e TACUT inainte de ambele caderi." + Write-Info "Masina a disparut fara sa apuce sa scrie nimic - exact tiparul pvemini" + Write-Info "din 2026-04-20. Cauza e alimentare sau hardware (sursa / RAM / termic)," + Write-Info "NU software. Nodul nu e de incredere ca tinta de failover pana cand:" + Write-Info " - sursa e testata sau inlocuita" + Write-Info " - memoria e testata (memtest86+, minim o trecere completa)" + Write-Info " - se verifica praful si ventilatia (vezi temperaturile de la pasul 6)" + Add-Rezultat 'Cauza caderii' 'bad' 'jurnal tacut => alimentare/hardware, nod nesigur' +} + +# ============================================================ 2. integritate ZFS + +Write-Step "2. Integritate ZFS (dupa doua opriri necurate)" + +$zpool = Invoke-Node -Node pveelite -Command 'zpool status -v' +$zpool | ForEach-Object { Write-Info $_ } + +if ($zpool -match 'state: ONLINE' -and $zpool -match 'No known data errors') { + if ($zpool -match 'resilver|scan: resilvered') { + Write-Warn "pool ONLINE, dar a rulat un resilver - normal dupa oprire necurata" + Add-Rezultat 'ZFS' 'warn' 'ONLINE, resilver efectuat, fara erori de date' + } else { + Write-Ok "pool ONLINE, fara erori de date" + Add-Rezultat 'ZFS' 'ok' 'ONLINE, fara erori' + } +} else { + Write-Bad "pool NU e curat - citeste iesirea de mai sus inainte de orice altceva" + Add-Rezultat 'ZFS' 'bad' 'pool degradat sau cu erori de date' +} + +# ============================================================ 3. inapoi in cluster + +Write-Step "3. Revenirea in cluster" + +$pvecm = Invoke-Node -Node pvemini -Command 'pvecm status; echo "---"; cat /etc/pve/.members' +$pvecm | ForEach-Object { Write-Info $_ } + +$m = $pvecm | Select-String 'Total votes:\s*(\d+)' | Select-Object -First 1 +$totalVotes = if ($m) { $m.Matches[0].Groups[1].Value } else { '?' } +$eliteOnline = [bool]($pvecm -match '"pveelite".*"online": 1') + +if ($totalVotes -eq '3' -and $eliteOnline) { + Write-Ok "cluster complet: 3 din 3 voturi, pveelite online" + Add-Rezultat 'Cluster' 'ok' '3/3 voturi, marja de quorum refacuta' +} else { + Write-Bad "cluster inca incomplet: Total votes = $totalVotes" + Add-Rezultat 'Cluster' 'bad' "Total votes = $totalVotes, pveelite inca in afara" +} + +# ================================================================ 4. replicarea + +Write-Step "4. Replicarea catre pveelite" + +Write-Info "job-urile se reiau singure la urmatorul interval - nu e nevoie de interventie." +Write-Info "Daca vrei sa nu astepti: pvesr run --id pe pvemini." + +$repl = Invoke-Node -Node pvemini -Command "pvesr status" -Timeout 30 +$repl | ForEach-Object { Write-Info $_ } + +$esecuri = @($repl | Select-String 'pveelite' | Where-Object { $_ -notmatch '\s0\s+OK' -and $_ -match 'failed' }) +if ($esecuri.Count -eq 0) { + Write-Ok "toate job-urile catre pveelite sunt OK, FailCount 0" + Add-Rezultat 'Replicare' 'ok' "toate cele $ReplJobs job-uri recuperate" +} else { + Write-Warn "$($esecuri.Count) job-uri inca in eroare - normal daca nodul tocmai a pornit" + Write-Info "reverifica peste 15-20 min sau forteaza cu: pvesr run --id " + Add-Rezultat 'Replicare' 'warn' "$($esecuri.Count) job-uri inca in eroare, de reverificat" +} + +# ======================================================================= 5. HA + +Write-Step "5. HA - LRM-ul lui pveelite" + +$ha = Invoke-Node -Node pvemini -Command 'ha-manager status' +$ha | ForEach-Object { Write-Info $_ } + +if ($ha -match 'lrm pveelite \(active') { + Write-Ok "LRM pveelite activ" + Add-Rezultat 'HA' 'ok' 'LRM activ' +} elseif ($ha -match 'lrm pveelite.*old timestamp') { + Write-Warn "LRM pveelite inca 'dead' - poate dura un minut dupa boot; reverifica" + Add-Rezultat 'HA' 'warn' "LRM inca 'old timestamp'" +} else { + Write-Warn "starea LRM neclara, citeste iesirea de mai sus" + Add-Rezultat 'HA' 'warn' 'stare LRM neclara' +} + +# Atentie deliberata: vm:109 a fost recuperat pe pvemini in timpul incidentului. +if ($ha -match 'service vm:109.*pveelite') { + Write-Warn "vm:109 a revenit pe pveelite - verifica intentionat, a fost mutat pe pvemini" +} + +# ===================================================================== 6. SMART + +Write-Step "6. SMART si temperaturi (precedent: incidentul Kingston din aprilie)" + +$smart = Invoke-Node -Node pveelite -Command @' +for d in $(lsblk -dno NAME,TYPE | awk '$2=="disk"{print $1}'); do + echo "--- /dev/$d ---" + smartctl -H -A /dev/$d 2>/dev/null | grep -iE 'overall-health|Temperature|Reallocated|Media_Wearout|Percentage Used|Power_On|Unsafe_Shutdown|CRC' | head -10 +done +'@ -Timeout 30 +$smart | ForEach-Object { Write-Info $_ } + +if ($smart -match 'FAILED') { + Write-Bad "SMART raporteaza FAILED pe cel putin un disc" + Add-Rezultat 'SMART' 'bad' 'un disc raporteaza FAILED' +} else { + Write-Ok "SMART: fara FAILED (citeste totusi temperaturile de mai sus)" + Add-Rezultat 'SMART' 'ok' 'fara FAILED' +} + +# Senzorii placii, daca exista - relevant pentru ipoteza termica +$sens = Invoke-Node -Node pveelite -Command "which sensors >/dev/null 2>&1 && sensors 2>/dev/null | grep -iE 'Core|Package|temp' | head -12" +if ($sens) { + Write-Info "senzori:" + $sens | ForEach-Object { Write-Info " $_" } +} else { + Write-Info "lm-sensors nu e instalat (apt install lm-sensors) - fara citiri termice live" +} + +# ================================================================= 7. Tailscale + +Write-Step "7. Tailscale - accesul out-of-band" + +Write-Info "In incident, tailscaled era mort de 27 de zile: fara el, orice cadere" +Write-Info "viitoare a nodului cere din nou deplasare fizica." + +$ts = Invoke-Node -Node pveelite -Command 'systemctl is-active tailscaled 2>/dev/null; tailscale status 2>&1 | head -3' +$ts | ForEach-Object { Write-Info $_ } + +if ($ts -match '^active') { + Write-Ok "tailscaled activ" + Add-Rezultat 'Tailscale' 'ok' 'activ' +} else { + Write-Warn "tailscaled NU e activ - reactiveaza-l acum:" + Write-Info " ssh root@$($NodeIp.pveelite) 'systemctl enable --now tailscaled && tailscale up'" + Add-Rezultat 'Tailscale' 'warn' 'inactiv - fara acces out-of-band' +} + +# ==================================================================== bilantul + +Write-Host "" +Write-Host "================ BILANT ================" -ForegroundColor Cyan +foreach ($r in $Rezultate) { + switch ($r.Stare) { + 'ok' { Write-Host (" [ok] {0,-16} {1}" -f $r.Nume, $r.Detaliu) -ForegroundColor Green } + 'warn' { Write-Host (" [!] {0,-16} {1}" -f $r.Nume, $r.Detaliu) -ForegroundColor Yellow } + 'bad' { Write-Host (" [X] {0,-16} {1}" -f $r.Nume, $r.Detaliu) -ForegroundColor Red } + } +} +Write-Host "" + +$cauza = $Rezultate | Where-Object { $_.Nume -eq 'Cauza caderii' } +if ($cauza -and $cauza.Stare -eq 'bad') { + Write-Host "Concluzia care conteaza: cauza caderii NU e lamurita ca fiind software." -ForegroundColor Red + Write-Host "Trateaza pveelite ca nod nesigur si consemneaza rezultatul in" -ForegroundColor Red + Write-Host "proxmox/cluster/incidents/2026-08-27-pveelite-down.md inainte sa inchizi incidentul." -ForegroundColor Red + Write-Host "" +} + +if ($Rezultate | Where-Object { $_.Stare -eq 'bad' }) { exit 1 } else { exit 0 }