feat(cluster): corosync ring1 pe insula, test de repornire cu WoL, fixuri in scripturi

Ring1 aplicat si verificat pe cluster live: config_version 17, ring1_addr pe
10.10.10.20x la fiecare nod, interface{linknumber:1}. Validat cu `corosync -t`
inainte de instalare. Testat prin oprirea reala a lui ring0 pe pveelite: link0
disconnected, link1 connected, cvorum 3/3 neatins - exact scenariul care pe
27 august a lasat nodul mort 16 ore.

Test de repornire completa a clusterului, cu Wake-on-LAN (trezire in ~15s).
Insula a urcat singura pe toate trei nodurile; ipoteza enumerarii tarzii a
USB-ului nu s-a materializat. Unealta noua: wake-cluster.ps1.

Testul a scos la iveala o linie ramasa in /etc/fstab pe pve1 si pveelite, care
monta storage-ul NFS de pe IP-ul de productie inaintea lui pvestatd. Backup-ul
trecea tacut pe reteaua gresita, cu storage.cfg corect. cluster-startup verifica
acum asta automat, impreuna cu IP-urile de insula si conectivitatea reala.

Patru bug-uri gasite prin rulare pe cluster live:

- sonda Oracle nu avea timeout: un sqlplus agatat pe o instanta in pornire a
  blocat cluster-startup 14 minute, fara mesaj, cu propriul prag de 600s
  nefolosit, fiindca bucla n-a apucat o iteratie;
- `bash -c` in loc de `bash -lc`: sqlplus lipsea din PATH, deci baza nu se
  oprea si containerul s-ar fi inchis peste ea;
- PowerShell 5.1 pierde ghilimelele duble catre exe-uri native, deci comanda
  ajungea rupta pe nod - trecut pe trimitere codificata base64;
- backup-ul de crontab si fisierul de stare se rescriau la o a doua rulare,
  lasand monitorizarea oprita permanent si lista de repornit goala.

Documentatia de oprire planificata pornea de la o afirmatie devenita falsa
("nu exista datacenter.cfg") si de la o comanda care ar fi adaugat o a doua
linie `ha:`. Actualizata, impreuna cu inventarul de guest-uri (VM 304 lipsea
din toate listele de ordine si cadea in maturarea de dupa Oracle).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RJFQZcA7uerXRaL1NrsXpS
This commit is contained in:
Marius
2026-08-29 21:13:34 +03:00
parent fd2105d1f9
commit d7b4007af8
10 changed files with 801 additions and 113 deletions

View File

@@ -1,17 +1,24 @@
# Reatasarea adaptoarelor USB LAN dupa un reset pe magistrala USB. # Reatasarea adaptoarelor USB LAN dupa un reset pe magistrala USB.
# #
# De ce exista: adaptoarele Realtek RTL8156 (driver r8152) folosite ca placa de # De ce exista: adaptoarele Realtek RTL8156 (driver r8152) de pe pve1 si pveelite
# retea principala pe pve1 si pveelite se reseteaza singure pe xHCI. Kernelul # se reseteaza singure pe xHCI. Kernelul sterge interfata si o recreeaza, dar
# sterge interfata si o recreeaza, dar nimeni NU o readauga in vmbr0 si nu o # nimeni NU o reconfigureaza - se ridica doar ca efect secundar la boot.
# ridica - interfata e declarata "inet manual", fara "auto" si fara
# "allow-hotplug", deci se ridica doar ca efect secundar la boot.
# #
# Rezultatul: nodul merge perfect si e invizibil in retea, pe termen nelimitat. # Cand a fost scrisa regula, dongle-ul era portul puntii vmbr0, adica placa de
# Pe pveelite asta a insemnat 16 ore de tacere in noaptea de 27 spre 28 august # retea principala: un reset lasa nodul perfect functional si invizibil in retea,
# 2026 - vezi ../../incidents/2026-08-27-pveelite-down.md # pe termen nelimitat. Pe pveelite asta a insemnat 16 ore de tacere in noaptea de
# 27 spre 28 august 2026 - vezi ../../incidents/2026-08-27-pveelite-down.md
#
# Din 2026-08-29 vmbr0 e pe interfata onboard, iar dongle-ul tine doar IP-ul
# retelei dedicate de cluster (10.10.10.200 / 10.10.10.202, strofa "auto ...
# inet static"). Miza s-a schimbat: un reset nu mai ia nodul offline, ci opreste
# tacut replicarea ZFS si NFS-ul backup-pvemini.
# Vezi ../../docs/switch-2.5g-dedicat-noduri.md
# #
# Regula prinde momentul in care interfata reapare si reaplica configuratia. # Regula prinde momentul in care interfata reapare si reaplica configuratia.
# Se declanseaza si la boot; e inofensiv, ifreload -a e idempotent. # Se declanseaza si la boot - inofensiv (ifreload -a e idempotent) si chiar util:
# acopera cazul in care USB-ul se enumereaza dupa networking.service, singurul
# risc ramas pentru reteaua de insula.
ACTION=="add", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="r8152", RUN+="/usr/bin/systemctl --no-block start usb-lan-hotplug@$env{INTERFACE}.service" ACTION=="add", SUBSYSTEM=="net", ENV{ID_NET_DRIVER}=="r8152", RUN+="/usr/bin/systemctl --no-block start usb-lan-hotplug@$env{INTERFACE}.service"

View File

@@ -1,5 +1,5 @@
[Unit] [Unit]
Description=Reataseaza adaptorul USB LAN %I in punte dupa un reset USB Description=Reaplica configuratia de retea pentru adaptorul USB LAN %I dupa un reset USB
Documentation=file:///etc/udev/rules.d/70-usb-lan-hotplug.rules Documentation=file:///etc/udev/rules.d/70-usb-lan-hotplug.rules
After=network.target After=network.target
@@ -11,9 +11,10 @@ Type=oneshot
# inainte ca astea sa se aseze prinde interfata intr-o stare intermediara. # inainte ca astea sa se aseze prinde interfata intr-o stare intermediara.
ExecStartPre=/bin/sleep 3 ExecStartPre=/bin/sleep 3
# ifreload -a, nu ifup: interfata e "inet manual" si e port de punte, iar doar # ifreload -a, nu ifup: e comanda verificata in teren - exact ea a readus pveelite
# ifreload reface legatura cu vmbr0. E si comanda verificata in teren - exact ea # in retea pe 28.08 la 09:49, de la consola fizica. Atunci interfata era port de
# a readus pveelite in retea pe 28.08 la 09:49, de la consola fizica. # punte si doar ifreload refacea legatura cu vmbr0; din 2026-08-29 are strofa
# proprie cu IP static pe reteaua de cluster, pe care ifreload o reaplica la fel.
# Aplica numai diferentele fata de /etc/network/interfaces, deci nu atinge # Aplica numai diferentele fata de /etc/network/interfaces, deci nu atinge
# interfetele tap/veth ale guest-urilor. # interfetele tap/veth ale guest-urilor.
ExecStart=/usr/sbin/ifreload -a ExecStart=/usr/sbin/ifreload -a

View File

@@ -6,7 +6,15 @@ relocheze resursele** și **fără ca watchdog-ul să reseteze hard vreun nod**.
Se aplică oricărei opriri planificate a întregului cluster: lucrări electrice, Se aplică oricărei opriri planificate a întregului cluster: lucrări electrice,
mutare rack, mentenanță UPS, intervenții la switch. mutare rack, mentenanță UPS, intervenții la switch.
**Autor:** Claude Code · **Ultima verificare pe cluster live:** 2026-08-27 **Autor:** Claude Code · **Ultima verificare pe cluster live:** 2026-08-29
> **Atenție, s-a schimbat ceva important pe 2026-08-29.** Clusterul are acum o rețea
> dedicată, `10.10.10.0/24` („insula"), pe un switch separat — vezi
> [switch 2.5G dedicat nodurilor](switch-2.5g-dedicat-noduri.md). De ea atârnă
> **toată replicarea ZFS** (`migration: network=10.10.10.0/24` în `datacenter.cfg`)
> și storage-ul NFS `backup-pvemini-nfs` (server `10.10.10.201`). Pe pve1 și
> pveelite IP-ul de insulă stă pe **dongle-ul USB**, deci la revenire trebuie
> verificat că a urcat — vezi [Repornirea, pasul 3](#repornirea).
--- ---
@@ -24,11 +32,18 @@ cd E:\proiecte\ROMFASTSQL\proxmox\cluster\scripts
.\cluster-shutdown.ps1 -DryRun # arată exact ce ar face, nu atinge nimic .\cluster-shutdown.ps1 -DryRun # arată exact ce ar face, nu atinge nimic
.\cluster-shutdown.ps1 # oprire completă, cu confirmări .\cluster-shutdown.ps1 # oprire completă, cu confirmări
# ... după revenirea curentului, cu nodurile pornite fizic ... # ... după revenirea curentului ...
.\wake-cluster.ps1 # trezește nodurile prin Wake-on-LAN
.\cluster-startup.ps1 # repornire completă .\cluster-startup.ps1 # repornire completă
``` ```
**Wake-on-LAN merge pe toate trei nodurile** — verificat pe 2026-08-29, trezire în ~15 secunde.
E posibil abia de la mutarea lui `vmbr0` pe interfețele onboard: dongle-urile USB nu suportă WoL
și pierd alimentarea la oprire. Dacă lucrarea e la rețeaua electrică și nodurile sunt oprite
de tine, `wake-cluster.ps1` te scutește de drumul până la ele. Stația de admin trebuie să fie
pe același segment L2 (`10.0.20.0/24`) — magic packet-ul e broadcast, nu trece prin router.
Cere doar clientul OpenSSH din Windows (`C:\Windows\System32\OpenSSH\ssh.exe`), Cere doar clientul OpenSSH din Windows (`C:\Windows\System32\OpenSSH\ssh.exe`),
deja prezent, și cheia SSH pentru root pe noduri. Testate pe PowerShell 5.1. deja prezent, și cheia SSH pentru root pe noduri. Testate pe PowerShell 5.1.
@@ -57,13 +72,15 @@ necesar și cum se face manual din GUI, dacă preferi.
## TL;DR (procedura manuală) ## TL;DR (procedura manuală)
1. **Datacenter → Options → HA Settings → Shutdown Policy = `Freeze`** ← fără asta, HA mută resursele 1. **Shutdown Policy = `Freeze`** ← fără asta, HA mută resursele. Din 2026-08-29 e deja setat permanent — doar verifică (Pas 0), nu adăuga o linie nouă în `datacenter.cfg`
2. Dezactivează cron-urile de alertă (`pvemini-down-alert`, `vm109-watchdog`, `pveelite-down-alert`, `oom-alert`) 2. Dezactivează cron-urile de alertă (`pvemini-down-alert`, `vm109-watchdog`, `pveelite-down-alert`, `oom-alert`)
3. Oprește guest-urile manual — **Oracle CT 108 primul**, cu `shutdown immediate` în DB 3. Oprește guest-urile manual — **Oracle CT 108 primul**, cu `shutdown immediate` în DB
4. `ha-manager status` → toate serviciile trebuie să fie `stopped` 4. `ha-manager status` → toate serviciile trebuie să fie `stopped`
5. Oprește nodurile: **pveelite → pve1 → pvemini** 5. Oprește nodurile: **pveelite → pve1 → pvemini**
La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai quorum). La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai quorum),
apoi verifici că insula `10.10.10.0/24` a urcat pe pve1 și pveelite **înainte** de a
porni guest-urile.
--- ---
@@ -71,8 +88,8 @@ La revenire: pornești **toate trei nodurile aproape simultan** (altfel nu ai qu
### Capcana 1 — politica implicită de shutdown este `conditional` ### Capcana 1 — politica implicită de shutdown este `conditional`
Pe clusterul `romfast` **nu există** `/etc/pve/datacenter.cfg`, deci PVE folosește Fără o linie `ha:` în `/etc/pve/datacenter.cfg`, PVE folosește default-ul
default-ul `shutdown_policy=conditional`: `shutdown_policy=conditional`:
| Acțiune pe nod | Comportament HA cu `conditional` | | Acțiune pe nod | Comportament HA cu `conditional` |
|----------------|----------------------------------| |----------------|----------------------------------|
@@ -84,6 +101,11 @@ VM 201 și restul resurselor HA pe pve1/pveelite — pe noduri care oricum urmea
să fie și ele oprite, cu 16 GB RAM pe pveelite și riscul din să fie și ele oprite, cu 16 GB RAM pe pveelite și riscul din
[incidentul 2026-04-20](../incidents/2026-04-20-cluster-outage.md). [incidentul 2026-04-20](../incidents/2026-04-20-cluster-outage.md).
> **Din 2026-08-29 capcana asta e deja dezamorsată permanent.** Fișierul
> `/etc/pve/datacenter.cfg` **există** și conține `ha: shutdown_policy=freeze`.
> Pasul 0 de mai jos e, în mod normal, doar o verificare. Nu presupune că fișierul
> lipsește — mai conține și linia `migration:`, care nu trebuie pierdută.
### Capcana 2 — watchdog-ul se auto-fence la pierderea quorumului ### Capcana 2 — watchdog-ul se auto-fence la pierderea quorumului
Cu 3 noduri, quorum = 2. Dacă oprești două noduri și al treilea **încă are Cu 3 noduri, quorum = 2. Dacă oprești două noduri și al treilea **încă are
@@ -106,14 +128,37 @@ inofensivă.
## Pas 0 — Shutdown Policy = Freeze ## Pas 0 — Shutdown Policy = Freeze
**GUI:** `Datacenter → Options → HA Settings → Edit → Shutdown Policy: Freeze` De obicei **nu ai nimic de făcut aici** — politica e `freeze` din 2026-08-29.
Verifică întâi:
**CLI (echivalent):**
```bash ```bash
ssh root@10.0.20.201 "echo 'ha: shutdown_policy=freeze' >> /etc/pve/datacenter.cfg" ssh root@10.0.20.201 "cat /etc/pve/datacenter.cfg"
``` ```
Așteptat (ambele linii):
```
ha: shutdown_policy=freeze
migration: network=10.10.10.0/24,type=insecure
```
Dacă `shutdown_policy` **nu** e `freeze`, **nu adăuga o linie nouă cu `>>`** —
fișierul are deja o linie `ha:`, iar a doua e o duplicare. Comanda corectă
înlocuiește linia existentă și lasă `migration:` neatinsă:
```bash
ssh root@10.0.20.201 "cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta && \
sed -i '/^ha:/d' /etc/pve/datacenter.cfg && \
printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg && \
cat /etc/pve/datacenter.cfg"
```
**GUI (echivalent):** `Datacenter → Options → HA Settings → Edit → Shutdown Policy: Freeze`
> **Nu șterge linia `migration:`.** Fără ea replicarea ZFS se întoarce tăcut pe
> rețeaua de producție — merge, dar pe altă cale decât cea proiectată, iar
> traficul de replicare ajunge peste producție.
`Freeze` = la oprirea nodului serviciile HA sunt înghețate, **nu relocate**; `Freeze` = la oprirea nodului serviciile HA sunt înghețate, **nu relocate**;
repornesc pe același nod când nodul revine. repornesc pe același nod când nodul revine.
@@ -200,17 +245,18 @@ VM 201 (IIS) și CT 104 (flowise) folosesc baza din CT 108.
| # | Nod | Guest | În HA | Cum | | # | Nod | Guest | În HA | Cum |
|---|-----|-------|-------|-----| |---|-----|-------|-------|-----|
| 1 | pvemini | VM 303 Win11-Adina | Nu | Shutdown | | 1 | pvemini | VM 303 Win11-Adina | Nu | Shutdown |
| 2 | pvemini | VM 302 oracle-test | Nu | Shutdown | | 2 | pvemini | VM 304 Win11-Marius | Nu | Shutdown |
| 3 | pvemini | VM 201 roacentral | Da | Shutdown (guest agent) | | 3 | pvemini | VM 302 oracle-test | Nu | Shutdown |
| 4 | pve1 | CT 101 minecraft | Da | Shutdown | | 4 | pvemini | VM 201 roacentral | Da | Shutdown (guest agent) |
| 5 | pve1 | CT 110 moltbot | Da | Shutdown | | 5 | pve1 | CT 101 minecraft | Da | Shutdown |
| 6 | pvemini | CT 104 flowise | Da | Shutdown | | 6 | pve1 | CT 110 moltbot | Da | Shutdown |
| 7 | pvemini | CT 106 gitea | Da | Shutdown | | 7 | pvemini | CT 104 flowise | Da | Shutdown |
| 8 | pvemini | CT 103 dokploy | Da | Shutdown | | 8 | pvemini | CT 106 gitea | Da | Shutdown |
| 9 | pvemini | CT 102 docker.romfast.ro | Nu | Shutdown | | 9 | pvemini | CT 103 dokploy | Da | Shutdown |
| 10 | pvemini | CT 100 portainer | Da | Shutdown | | 10 | pvemini | CT 102 docker.romfast.ro | Nu | Shutdown |
| 11 | pvemini | CT 171 claude-agent | Da | Shutdown | | 11 | pvemini | CT 100 portainer | Da | Shutdown |
| 12 | pvemini | **CT 108 central-oracle** | Da | `shutdown immediate` în DB, apoi Shutdown CT | | 12 | pvemini | CT 171 claude-agent | Da | Shutdown |
| 13 | pvemini | **CT 108 central-oracle** | Da | `shutdown immediate` în DB, apoi Shutdown CT |
pveelite nu are nimic pornit în mod normal (CT 301 și VM 109 sunt `stopped`). pveelite nu are nimic pornit în mod normal (CT 301 și VM 109 sunt `stopped`).
@@ -275,9 +321,10 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ
## Repornirea ## Repornirea
1. **Pornește toate trei nodurile aproximativ simultan.** Un singur nod pornit 1. **Pornește toate trei nodurile aproximativ simultan** — fizic, sau cu
nu are quorum → `/etc/pve` rămâne read-only → nu poți porni niciun guest și `.\wake-cluster.ps1` (Wake-on-LAN, verificat pe 2026-08-29). Un singur nod
nu poți modifica nimic în GUI. pornit nu are quorum → `/etc/pve` rămâne read-only → nu poți porni niciun guest
și nu poți modifica nimic în GUI.
2. Așteaptă quorum complet: 2. Așteaptă quorum complet:
@@ -286,19 +333,56 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ
ssh root@10.0.20.201 "ha-manager status" ssh root@10.0.20.201 "ha-manager status"
``` ```
3. **Pornește guest-urile în ordine inversă:** CT 108 Oracle întâi, apoi VM 201, 3. **Verifică rețeaua de cluster `10.10.10.0/24` — pasul cel mai ușor de uitat.**
Pe pve1 și pveelite IP-ul de insulă stă pe dongle-ul USB, adus la boot de un
`auto enx...` din `/etc/network/interfaces`. Dacă nucleul enumerează adaptorul
USB **după** ce `networking.service` a terminat, interfața rămâne fără IP.
Nodul pornește perfect normal pe producție — dar **replicarea ZFS și storage-ul
NFS `backup-pvemini` tac**, fără nicio alertă.
```bash
ssh root@10.0.20.200 "ip -br a | grep -E 'vmbr0|enx'" # 10.10.10.200 pe dongle
ssh root@10.0.20.202 "ip -br a | grep -E 'vmbr0|enx'" # 10.10.10.202 pe dongle
ssh root@10.0.20.201 "ip -br a | grep -E 'vmbr0|enp87s0'" # 10.10.10.201 pe enp87s0
ssh root@10.0.20.201 "ping -c1 -W2 10.10.10.200; ping -c1 -W2 10.10.10.202"
ssh root@10.0.20.200 "findmnt -no SOURCE /mnt/pve/backup-pvemini" # 10.10.10.201:/mnt/backup
```
Dacă un IP lipsește, reparația e `ifreload -a` pe nodul respectiv:
```bash
ssh root@10.0.20.202 "PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a"
```
pvemini are interfața de insulă onboard (Intel `enp87s0`) — el nu are problema asta.
**Testat pe 2026-08-29, la o oprire completă reală: insula a urcat singură pe
toate trei nodurile.** Ipoteza enumerării târzii nu s-a materializat. Pe pve1
s-a declanșat în plus și regula udev `usb-lan-hotplug@` la boot, care oricum ar
fi reparat-o. Verificarea rămâne totuși în procedură — un singur test reușit nu
exclude o cursă de sincronizare care depinde de temporizări.
**Ce a găsit totuși testul:** o linie rămasă în `/etc/fstab` pe pve1 și pveelite
monta storage-ul NFS de pe `10.0.20.201` (producție) înainte ca `pvestatd` să
apuce, deci backup-ul mergea tăcut pe rețeaua greșită, cu `storage.cfg` arătând
corect. Liniile au fost comentate (backup: `/root/fstab.bak-2026-08-29`).
Dacă vezi vreodată NFS-ul montat de pe alt server decât `10.10.10.201`, acolo
să te uiți întâi. `cluster-startup` verifică asta automat.
4. **Pornește guest-urile în ordine inversă:** CT 108 Oracle întâi, apoi VM 201,
apoi restul. Pornirea unui guest HA îi repune state-ul pe `started`. apoi restul. Pornirea unui guest HA îi repune state-ul pe `started`.
4. Verifică Oracle: 5. Verifică Oracle:
```bash ```bash
ssh root@10.0.20.201 "pct exec 108 -- docker exec oracle-xe bash -lc \ ssh root@10.0.20.201 "pct exec 108 -- docker exec oracle-xe bash -lc \
\"printf 'select status from v\\\$instance;\nexit\n' | sqlplus -s / as sysdba\"" \"printf 'select status from v\\\$instance;\nexit\n' | sqlplus -s / as sysdba\""
``` ```
5. **Re-activează cron-urile** dezactivate la Pasul 1 și job-urile de backup. 6. **Re-activează cron-urile** dezactivate la Pasul 1 și job-urile de backup.
6. Verifică replicarea: 7. Verifică replicarea:
```bash ```bash
ssh root@10.0.20.201 "pvesr status" ssh root@10.0.20.201 "pvesr status"
@@ -306,7 +390,8 @@ Așteaptă ca nodul să nu mai răspundă la ping înainte de a-l opri pe următ
Job-urile `*/15` (108, 171, 201) au ratat rulări; recuperează la primul tick. Job-urile `*/15` (108, 171, 201) au ratat rulări; recuperează la primul tick.
7. Decide ce faci cu Shutdown Policy — vezi recomandarea de la Pasul 0. 8. Decide ce faci cu Shutdown Policy — vezi recomandarea de la Pasul 0.
În mod normal rămâne `freeze`, deci nu ai ce face.
--- ---
@@ -329,7 +414,7 @@ orchestrat care poate intra peste procedura de aici.
--- ---
## Stare de referință a clusterului (2026-08-27) ## Stare de referință a clusterului (2026-08-29)
Resurse HA configurate — 10 în total: Resurse HA configurate — 10 în total:
@@ -346,16 +431,28 @@ Resurse HA configurate — 10 în total:
| vm:109 | pveelite | ha-prefer-pveelite | oracle-dr-windows (`stopped`) | | vm:109 | pveelite | ha-prefer-pveelite | oracle-dr-windows (`stopped`) |
| vm:201 | pvemini | ha-prefer-pvemini | roacentral | | vm:201 | pvemini | ha-prefer-pvemini | roacentral |
Guest-uri **în afara** HA: CT 102, CT 301, VM 302, VM 303, VM 310. Guest-uri **în afara** HA: CT 102, CT 301, VM 302, VM 303, **VM 304**, VM 310.
`nofailback` este `1` doar pe `ha-prefer-pveelite`; celelalte două grupuri au `nofailback` este `1` doar pe `ha-prefer-pveelite`; celelalte două grupuri au
`nofailback 0`, deci resursele se întorc automat pe nodul preferat când acesta revine. `nofailback 0`, deci resursele se întorc automat pe nodul preferat când acesta revine.
Rețea și storage de care depinde procedura, verificate pe 2026-08-29:
| Ce | Valoare |
|---|---|
| `datacenter.cfg` | `ha: shutdown_policy=freeze` + `migration: network=10.10.10.0/24,type=insecure` |
| insulă pve1 / pvemini / pveelite | `10.10.10.200` (USB) / `10.10.10.201` (`enp87s0`) / `10.10.10.202` (USB) |
| NFS `backup-pvemini-nfs` | server `10.10.10.201`, montat pe pve1 și pveelite |
| corosync | **două linkuri**: `LINK ID 0` pe `10.0.20.20x`, `LINK ID 1` pe `10.10.10.20x`, `config_version: 17` |
| Wake-on-LAN | merge pe toate 3 (`ethtool <if>` → `Wake-on: g`); unealta: `../scripts/wake-cluster.ps1` |
--- ---
## Referințe ## Referințe
- [Switch 2.5G dedicat nodurilor — rețeaua `10.10.10.0/24`](switch-2.5g-dedicat-noduri.md)
- [Strategia de failover și HA](../failover/README.md) - [Strategia de failover și HA](../failover/README.md)
- [Incident 2026-04-20 — cluster outage](../incidents/2026-04-20-cluster-outage.md) - [Incident 2026-04-20 — cluster outage](../incidents/2026-04-20-cluster-outage.md)
- [Incident 2026-08-27 — pveelite fără rețea 16h (USB LAN reset)](../incidents/2026-08-27-pveelite-down.md)
- [Cluster README — SSH, storage, noduri](../README.md) - [Cluster README — SSH, storage, noduri](../README.md)
- [UPS](../ups/README.md) - [UPS](../ups/README.md)

View File

@@ -4,8 +4,12 @@
**Autor:** Claude Code (mmarius28@gmail.com) **Autor:** Claude Code (mmarius28@gmail.com)
**Status: TERMINAT pe 2026-08-29.** Rețeaua dedicată de cluster `10.10.10.0/24` e activă pe toate **Status: TERMINAT pe 2026-08-29.** Rețeaua dedicată de cluster `10.10.10.0/24` e activă pe toate
3 nodurile, `vmbr0` a fost mutat pe interfețele onboard, uplink-ul dintre switch-uri a fost scos, 3 nodurile, `vmbr0` a fost mutat pe interfețele onboard, uplink-ul dintre switch-uri a fost scos,
iar replicarea și backup-ul NFS trec verificat pe insulă. Singurele lucruri rămase sunt iar replicarea și backup-ul NFS trec verificat pe insulă.
opționale — vezi „Ce a rămas de făcut".
**Completat în seara aceleiași zile:** al doilea inel corosync (ring1) pe insulă, verificat prin
oprirea reală a lui ring0; testul de repornire completă a clusterului, cu Wake-on-LAN. Testul a
scos la iveală o linie rămasă în `/etc/fstab` care fixa tăcut backup-ul NFS pe rețeaua de
producție. Toate trei sunt descrise mai jos. Singurul lucru rămas e opțional (MTU 9000).
--- ---
@@ -278,13 +282,42 @@ fiecare pas: link 2500 Mbps, ping pe toată insula, **262 / 272 MB/s**, job real
Opționale, în ordinea valorii: Opționale, în ordinea valorii:
1. **corosync ring1** pe `10.10.10.0/24` — vezi mai jos, cere fereastră dedicată. 1. ~~**corosync ring1** pe `10.10.10.0/24`~~ — **APLICAT pe 2026-08-29 seara**, vezi mai jos.
2. **MTU 9000** pe segmentul dedicat — de testat, nu se știe dacă switch-ul suportă. 2. **MTU 9000** pe segmentul dedicat — de testat, nu se știe dacă switch-ul suportă.
3. **Test la reboot.** Configurația nu a fost validată printr-o repornire. Dongle-urile USB au 3. ~~**Test la reboot.**~~ — **FĂCUT pe 2026-08-29 seara**, vezi mai jos.
acum `auto` în loc să fie porturi de punte; dacă la boot USB-ul se enumeră târziu, interfața
de insulă poate rămâne neconfigurată. Chiar și atunci nodul pornește normal pe producție și ### Test la repornire — făcut pe 2026-08-29, ipoteza infirmată
doar replicarea are de suferit — invers față de cum era înainte, când o problemă pe dongle
lua nodul offline complet. De verificat la următoarea repornire planificată. Clusterul a fost oprit complet (toate 3 nodurile) și pornit înapoi. **Insula a urcat singură pe
toate trei nodurile**, fără intervenție: enumerarea târzie a USB-ului nu s-a produs. Pe pve1
s-a declanșat în plus și regula udev `usb-lan-hotplug@` la boot (20:27:33, terminată cu succes),
care oricum ar fi reparat situația; pe pveelite a fost suficientă strofa `auto`.
**Wake-on-LAN merge pe toate trei nodurile** — nodurile oprite au fost trezite cu magic packet
de pe stația de admin, timp de răspuns ~15 secunde. E câștigul mutării lui `vmbr0` pe interfețele
onboard: dongle-urile USB nu suportă WoL. Unealta: `../scripts/wake-cluster.ps1` (MAC-urile de
producție sunt în ea). `Wake-on: g` era deja armat pe toate trei, iar BIOS-urile nu taie
alimentarea plăcii în S5.
### Ce a găsit testul: o linie rămasă în `/etc/fstab`
Repornirea a scos la iveală o deriva tăcută pe **pve1 și pveelite**:
```
10.0.20.201:/mnt/backup /mnt/pve/backup-pvemini nfs4 defaults,_netdev 0 0
```
Montarea NFS a storage-ului PVE e treaba lui `pvestatd`, din `/etc/pve/storage.cfg` (unde
serverul e corect, `10.10.10.201`). Linia din fstab o lua însă înainte la boot, ocupa punctul
de montare, iar PVE o lăsa așa. Rezultatul: **tot traficul de backup trecea pe rețeaua de
producție**, cu `storage.cfg` arătând perfect corect.
Remontarea manuală făcută pe 29 august a corectat simptomul, dar nu și cauza — starea bună a
supraviețuit exact până la prima repornire. Liniile au fost comentate pe ambele noduri
(backup: `/root/fstab.bak-2026-08-29`), montările refăcute și verificate:
`clientaddr=10.10.10.200` / `10.10.10.202`, scriibile.
`cluster-startup` verifică acum asta automat la fiecare pornire.
**De reținut:** `vmbr0` și-a schimbat MAC-ul pe toate 3 nodurile (a preluat MAC-ul noii **De reținut:** `vmbr0` și-a schimbat MAC-ul pe toate 3 nodurile (a preluat MAC-ul noii
interfețe). Dacă apar reguli pe router legate de MAC, acolo e cauza. interfețe). Dacă apar reguli pe router legate de MAC, acolo e cauza.
@@ -293,7 +326,7 @@ interfețe). Dacă apar reguli pe router legate de MAC, acolo e cauza.
## Îmbunătățiri posibile, în ordinea impactului ## Îmbunătățiri posibile, în ordinea impactului
### 1. Al doilea inel corosync (link1) pe switch-ul nou — cel mai valoros pas următor ### 1. Al doilea inel corosync (link1) pe switch-ul nou — APLICAT pe 2026-08-29
Cluster-ul are azi un singur inel corosync (`linknumber: 0`, peste `vmbr0`/IP-ul principal — Cluster-ul are azi un singur inel corosync (`linknumber: 0`, peste `vmbr0`/IP-ul principal —
vezi `/etc/pve/corosync.conf`). Când adaptorul USB al lui pveelite s-a resetat (aprilie și vezi `/etc/pve/corosync.conf`). Când adaptorul USB al lui pveelite s-a resetat (aprilie și
@@ -305,14 +338,49 @@ inclusiv pe pve1 dacă i s-ar întâmpla la fel.
verificate. Ring0 stă acum pe interfețele onboard (mai robuste), iar ring1 pe insulă ar acoperi verificate. Ring0 stă acum pe interfețele onboard (mai robuste), iar ring1 pe insulă ar acoperi
și cazul în care dongle-ul USB sau portul din switch cedează. și cazul în care dongle-ul USB sau portul din switch cedează.
#### Cum s-a aplicat
Fereastra: guest-urile oprite cu `cluster-shutdown.ps1 -NoNodes`, care lasă exact starea
necesară — toate resursele HA în `stopped`, watchdog-ul dezarmat, dar clusterul **pornit și
cvorat**, fiindcă `corosync.conf` stă pe pmxcfs și fără cvorum devine read-only.
```bash ```bash
pvecm status # verifică starea curentă înainte de orice modificare # backup pe FIECARE nod, si copia din /etc/pve, si cea locala pe care o citeste corosync
# adăugare link1 se face editând /etc/pve/corosync.conf, incrementând config_version, cp /etc/pve/corosync.conf /root/corosync.conf.bak-2026-08-29
# adăugând ring1_addr = 10.10.10.20x la fiecare nod + interface { linknumber: 1 } în totem cp /etc/corosync/corosync.conf /root/corosync.local.bak-2026-08-29
# validare INAINTE de instalare - corosync stie sa-si testeze propriul fisier
COROSYNC_MAIN_CONFIG_FILE=/root/corosync.new.conf corosync -t # exit 0 = sintaxa buna
cp /root/corosync.new.conf /etc/pve/corosync.conf.new
mv /etc/pve/corosync.conf.new /etc/pve/corosync.conf # pmxcfs propaga singur
``` ```
**Neaplicat** — schimbarea corosync.conf e sensibilă (poate rupe cvorumul dacă sintaxa greșește) Modificările: `ring1_addr: 10.10.10.20x` la fiecare nod, un al doilea bloc
și merită o fereastră dedicată. `interface { linknumber: 1 }` în `totem`, `config_version` 16 → 17. `link_mode` era deja
`passive`, potrivit pentru două linkuri. Propagarea pe toate 3 nodurile a durat sub 12 secunde.
#### Verificat prin oprirea reală a lui ring0
Nu doar configurat — **testat**. Cu `eno1` scos administrativ pe pveelite (plasă de siguranță:
`ifreload -a` programat la 120s), corosync a arătat de pe pvemini:
```
LINK ID 0 nodeid 3: disconnected
LINK ID 1 nodeid 3: connected
Quorate: Yes, Total votes: 3
```
Exact situația din 27 august, când pveelite a fost declarat mort 16 ore. Acum clusterul nici
nu clipește. Restaurarea s-a făcut prin **calea out-of-band de pe insulă**
(`ssh` de pe pvemini către `10.10.10.202`), care a funcționat cu nodul invizibil pe producție.
#### Ce NU rezolvă ring1 — de reținut
În timpul testului, **pveelite a apărut gri/indisponibil în GUI-ul Proxmox**, deși cvorumul era
intact. E corect: GUI-ul nu se uită la corosync, ci vorbește prin API pe `8006`, peste IP-ul de
management din `10.0.20.x`. Ring1 protejează **cvorumul, pmxcfs și HA** — nu accesul de
management. Pentru management out-of-band există SSH pe insulă, de pe un nod pe altul.
### 2, 3 și 4 — aplicate pe 2026-08-29 ### 2, 3 și 4 — aplicate pe 2026-08-29
@@ -327,9 +395,9 @@ pe 1G nu costă nimic pe pve1/pveelite.
## Ce NU s-a schimbat ## Ce NU s-a schimbat
- `/etc/pve/corosync.conf` — **neatins.** Ring0 folosește în continuare IP-urile `10.0.20.20x`, - `/etc/pve/corosync.conf` — neatins la mutarea rețelei; **modificat seara aceleiași zile**,
care acum stau pe interfețele onboard — deci corosync a câștigat robustețe fără nicio pentru ring1 (`config_version: 17`) — vezi mai sus. Ring0 folosește în continuare IP-urile
modificare de configurație. `10.0.20.20x`, care acum stau pe interfețele onboard.
- Cablurile nodurilor — **niciunul mutat de scripturi.** Toate mutările fizice au fost făcute - Cablurile nodurilor — **niciunul mutat de scripturi.** Toate mutările fizice au fost făcute
manual de utilizator (routerul pe switch 2, dongle-ul lui pveelite pe p4, al lui pve1 pe p3). manual de utilizator (routerul pe switch 2, dongle-ul lui pveelite pe p4, al lui pve1 pe p3).
- Cage-urile SFP+ ale plăcii X710 de pe pvemini — rămân goale, placa nefolosită. La verificări, - Cage-urile SFP+ ale plăcii X710 de pe pvemini — rămân goale, placa nefolosită. La verificări,

View File

@@ -53,6 +53,7 @@ $NodeShutdownOrder = @('pveelite', 'pve1', 'pvemini')
# Orice guest pornit care nu apare aici e oprit la final, intr-o maturare. # Orice guest pornit care nu apare aici e oprit la final, intr-o maturare.
$GuestShutdownOrder = @( $GuestShutdownOrder = @(
303, # Win11-Adina - desktop, fara dependente 303, # Win11-Adina - desktop, fara dependente
304, # Win11-Marius - desktop, fara dependente
302, # oracle-test-302 - VM de test 302, # oracle-test-302 - VM de test
310, # Win11-Template - template 310, # Win11-Template - template
201, # roacentral - IIS reverse proxy, consumator Oracle 201, # roacentral - IIS reverse proxy, consumator Oracle
@@ -110,6 +111,21 @@ function Invoke-Node {
return $out return $out
} }
# Comenzi cu ghilimele imbricate NU se trimit prin Invoke-Node.
#
# PowerShell 5.1 pierde ghilimelele duble cand paseaza un argument catre un
# executabil nativ: `bash -lc "printf '...' | sqlplus"` ajunge pe nod ca
# `bash -lc printf '...' | sqlplus`, adica printf fara format, iar sqlplus rulat
# pe NOD, unde nu exista. Esecul e usor de citit gresit ca "lipseste sqlplus".
# In loc sa ne luptam cu nivelurile de citare, trimitem scriptul codificat.
# Verificat pe 2026-08-29, dupa ce varianta directa a esuat pe cluster live.
function Invoke-NodeScript {
param([string]$Node, [string]$Script)
$lf = $Script -replace "`r`n", "`n"
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($lf))
return Invoke-Node -Node $Node -Command "echo $b64 | base64 -d | bash"
}
# Ca Invoke-Node, dar respecta -DryRun (pentru comenzi care modifica starea). # Ca Invoke-Node, dar respecta -DryRun (pentru comenzi care modifica starea).
function Invoke-NodeChange { function Invoke-NodeChange {
param([string]$Node, [string]$Command) param([string]$Node, [string]$Command)
@@ -226,10 +242,22 @@ if ($running.Count -eq 0) {
# reporneasca VM-uri oprite intentionat (302, 310, 301...). # reporneasca VM-uri oprite intentionat (302, 310, 301...).
# Format identic cu varianta bash, ca cele doua sa fie interschimbabile. # Format identic cu varianta bash, ca cele doua sa fie interschimbabile.
if (-not $DryRun) { if (-not $DryRun) {
# Nu rescrie peste o stare existenta cu un inventar gol. Cazul real: scriptul
# e rulat a doua oara (dupa -NoNodes, sau dupa o eroare), cand guest-urile sunt
# deja oprite - inventarul e gol si am pierde exact lista de repornit.
$existing = @()
if (Test-Path $StateFile) {
$existing = @(Get-Content $StateFile | Where-Object { $_ -notmatch '^\s*#' -and $_.Trim() })
}
if ($running.Count -eq 0 -and $existing.Count -gt 0) {
Write-Warn "Niciun guest pornit, dar $StateFile contine deja $($existing.Count) intrari - il PASTREZ."
Write-Warn "Altfel cluster-startup n-ar mai sti ce sa porneasca."
} else {
$lines = @("# stare cluster salvata la $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')") $lines = @("# stare cluster salvata la $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')")
foreach ($g in $running) { $lines += "$($g.Type) $($g.Node) $($g.Id) $($g.Status) $($g.Ha)" } foreach ($g in $running) { $lines += "$($g.Type) $($g.Node) $($g.Id) $($g.Status) $($g.Ha)" }
Set-Content -Path $StateFile -Value $lines -Encoding utf8 Set-Content -Path $StateFile -Value $lines -Encoding utf8
Write-Ok "stare salvata in $StateFile" Write-Ok "stare salvata in $StateFile"
}
} }
Confirm-Step "Se opresc $($running.Count) guest-uri, apoi nodurile: $($NodeShutdownOrder -join ', ')." Confirm-Step "Se opresc $($running.Count) guest-uri, apoi nodurile: $($NodeShutdownOrder -join ', ')."
@@ -246,6 +274,9 @@ if ($currentPolicy -eq 'freeze') {
Write-Ok "deja pe freeze" Write-Ok "deja pe freeze"
} else { } else {
if ($null -ne $currentPolicy -or ($dcCfg | Measure-Object).Count -gt 0) { if ($null -ne $currentPolicy -or ($dcCfg | Measure-Object).Count -gt 0) {
# sed sterge DOAR linia 'ha:', nu rescrie fisierul: din 2026-08-29 acolo
# sta si 'migration: network=10.10.10.0/24,type=insecure', fara de care
# replicarea se intoarce tacut pe reteaua de productie.
Invoke-NodeChange -Node 'pvemini' -Command 'cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta' | Out-Null Invoke-NodeChange -Node 'pvemini' -Command 'cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta' | Out-Null
Invoke-NodeChange -Node 'pvemini' -Command "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg" | Out-Null Invoke-NodeChange -Node 'pvemini' -Command "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg" | Out-Null
} else { } else {
@@ -262,7 +293,10 @@ Write-Step "Dezactivare cron-uri de alerta"
$sedCron = "crontab -l | sed -E '/^[^#]/ s%^(.*($CronPattern)\.sh.*)`$%#MENTENANTA \1%' | crontab -" $sedCron = "crontab -l | sed -E '/^[^#]/ s%^(.*($CronPattern)\.sh.*)`$%#MENTENANTA \1%' | crontab -"
foreach ($node in $NodeIp.Keys) { foreach ($node in $NodeIp.Keys) {
Invoke-NodeChange -Node $node -Command "crontab -l > $CronBackup 2>/dev/null || true" | Out-Null # Backup DOAR daca crontab-ul curent nu e deja comentat. Altfel o a doua rulare
# a scriptului (dupa o eroare, de exemplu) ar salva peste backup versiunea deja
# comentata, iar cluster-startup ar "restaura" monitorizarea oprita - tacut.
Invoke-NodeChange -Node $node -Command "if crontab -l 2>/dev/null | grep -q '^#MENTENANTA'; then echo 'backup pastrat'; else crontab -l > $CronBackup 2>/dev/null || true; echo 'backup nou'; fi" | Out-Null
Invoke-NodeChange -Node $node -Command $sedCron | Out-Null Invoke-NodeChange -Node $node -Command $sedCron | Out-Null
if (-not $DryRun) { if (-not $DryRun) {
$n = Invoke-Node -Node $node -Command "crontab -l | grep -c '^#MENTENANTA' || true" $n = Invoke-Node -Node $node -Command "crontab -l | grep -c '^#MENTENANTA' || true"
@@ -285,13 +319,27 @@ if (-not $oracleGuest) {
if ($DryRun) { if ($DryRun) {
Write-Host " [dry-run] shutdown immediate in $OracleDocker" -ForegroundColor DarkGray Write-Host " [dry-run] shutdown immediate in $OracleDocker" -ForegroundColor DarkGray
} else { } else {
$sqlCmd = 'pct exec ' + $OracleCt + ' -- docker exec ' + $OracleDocker + ' bash -c "printf ''shutdown immediate\nexit\n'' | sqlplus -s / as sysdba"' # 'bash -lc', NU 'bash -c': fara shell de login, PATH-ul Oracle nu e incarcat
$out = Invoke-Node -Node $oracleGuest.Node -Command $sqlCmd # si comanda esueaza cu "sqlplus: command not found" - adica baza ramane
if ($LASTEXITCODE -ne 0) { # DESCHISA, iar containerul ar fi oprit peste ea.
Write-Warn "Shutdown-ul bazei a raportat eroare. Verifica manual inainte de a continua!" # '2>&1' se face pe nod, nu in PowerShell: altfel stderr-ul lui ssh devine
$out | Select-Object -Last 5 | Write-Host # ErrorRecord in PS 5.1 si arunca exceptie in loc sa fie tratat aici.
# 'timeout 180': un 'shutdown immediate' poate dura, dar nu la infinit. Fara
# el, un sqlplus blocat ar tine scriptul agatat fara niciun mesaj (patit pe
# 2026-08-29, la sonda echivalenta din cluster-startup).
$sqlScript = @"
timeout 180 pct exec $OracleCt -- docker exec $OracleDocker bash -lc "printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba" 2>&1
"@
$out = Invoke-NodeScript -Node $oracleGuest.Node -Script $sqlScript
# Codul de iesire nu e o dovada: sqlplus intoarce 0 si cand n-a inchis nimic.
# Singura confirmare reala e mesajul instantei.
if (($out -join "`n") -match 'ORACLE instance shut down') {
Write-Ok "instanta Oracle oprita curat"
} else { } else {
Write-Ok "instanta Oracle oprita" Write-Warn "Baza NU a confirmat 'ORACLE instance shut down'. Ultimele linii:"
$out | Select-Object -Last 8 | Write-Host
Confirm-Step "Oprirea CT $OracleCt peste o baza posibil deschisa. Continui?"
} }
} }
} }

View File

@@ -34,6 +34,7 @@ NODE_SHUTDOWN_ORDER=(pveelite pve1 pvemini)
# Orice guest pornit care nu apare aici e oprit la final, într-o măturare. # Orice guest pornit care nu apare aici e oprit la final, într-o măturare.
GUEST_SHUTDOWN_ORDER=( GUEST_SHUTDOWN_ORDER=(
303 # Win11-Adina — desktop, fără dependențe 303 # Win11-Adina — desktop, fără dependențe
304 # Win11-Marius — desktop, fără dependențe
302 # oracle-test-302 — VM de test 302 # oracle-test-302 — VM de test
310 # Win11-Template — template 310 # Win11-Template — template
201 # roacentral — IIS reverse proxy, consumator Oracle 201 # roacentral — IIS reverse proxy, consumator Oracle
@@ -202,9 +203,19 @@ fi
# Salvează starea LOCAL — cluster-startup.sh pornește exact ce era pornit, # Salvează starea LOCAL — cluster-startup.sh pornește exact ce era pornit,
# ca să nu repornească VM-uri oprite intenționat (302, 310, 301...). # ca să nu repornească VM-uri oprite intenționat (302, 310, 301...).
if (( ! DRY_RUN )); then if (( ! DRY_RUN )); then
# Nu rescrie peste o stare existentă cu un inventar gol. Cazul real: scriptul
# e rulat a doua oară (după --no-nodes, sau după o eroare), când guest-urile
# sunt deja oprite — inventarul e gol și am pierde exact lista de repornit.
EXISTING=0
[[ -f "$STATE_FILE" ]] && EXISTING=$(grep -cv -e '^[[:space:]]*#' -e '^[[:space:]]*$' "$STATE_FILE" || true)
if [[ -z "$RUNNING" ]] && (( EXISTING > 0 )); then
warn "Niciun guest pornit, dar $STATE_FILE conține deja $EXISTING intrări — îl PĂSTREZ."
warn "Altfel cluster-startup.sh n-ar mai ști ce să pornească."
else
{ echo "# stare cluster salvată la $(date '+%Y-%m-%d %H:%M:%S')" { echo "# stare cluster salvată la $(date '+%Y-%m-%d %H:%M:%S')"
printf '%s\n' "${RUNNING:-}"; } > "$STATE_FILE" printf '%s\n' "${RUNNING:-}"; } > "$STATE_FILE"
ok "stare salvată în $STATE_FILE" ok "stare salvată în $STATE_FILE"
fi
fi fi
confirm "Se opresc $RUNNING_COUNT guest-uri, apoi nodurile: ${NODE_SHUTDOWN_ORDER[*]}." confirm "Se opresc $RUNNING_COUNT guest-uri, apoi nodurile: ${NODE_SHUTDOWN_ORDER[*]}."
@@ -218,6 +229,9 @@ if [[ "$CURRENT_POLICY" == "freeze" ]]; then
ok "deja pe freeze" ok "deja pe freeze"
else else
if nssh pvemini "test -f /etc/pve/datacenter.cfg"; then if nssh pvemini "test -f /etc/pve/datacenter.cfg"; then
# sed șterge DOAR linia 'ha:', nu rescrie fișierul: din 2026-08-29 acolo
# stă și 'migration: network=10.10.10.0/24,type=insecure', fără de care
# replicarea se întoarce tăcut pe rețeaua de producție.
run pvemini "cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta" run pvemini "cp /etc/pve/datacenter.cfg /root/datacenter.cfg.backup-mentenanta"
run pvemini "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg" run pvemini "sed -i '/^ha:/d' /etc/pve/datacenter.cfg; printf 'ha: shutdown_policy=freeze\n' >> /etc/pve/datacenter.cfg"
else else
@@ -231,7 +245,10 @@ fi
step "Dezactivare cron-uri de alertă" step "Dezactivare cron-uri de alertă"
for node in "${!NODE_IP[@]}"; do for node in "${!NODE_IP[@]}"; do
run "$node" "crontab -l > $CRON_BACKUP 2>/dev/null || true" # Backup DOAR dacă crontab-ul curent nu e deja comentat. Altfel o a doua rulare
# a scriptului (după o eroare, de exemplu) ar salva peste backup versiunea deja
# comentată, iar cluster-startup.sh ar „restaura" monitorizarea oprită — tăcut.
run "$node" "if crontab -l 2>/dev/null | grep -q '^#MENTENANTA'; then echo 'backup păstrat'; else crontab -l > $CRON_BACKUP 2>/dev/null || true; fi"
run "$node" "crontab -l | sed -E '/^[^#]/ s%^(.*($CRON_PATTERN)\.sh.*)\$%#MENTENANTA \1%' | crontab -" run "$node" "crontab -l | sed -E '/^[^#]/ s%^(.*($CRON_PATTERN)\.sh.*)\$%#MENTENANTA \1%' | crontab -"
if (( ! DRY_RUN )); then if (( ! DRY_RUN )); then
n=$(nssh "$node" "crontab -l | grep -c '^#MENTENANTA' || true") n=$(nssh "$node" "crontab -l | grep -c '^#MENTENANTA' || true")
@@ -253,9 +270,23 @@ else
if (( DRY_RUN )); then if (( DRY_RUN )); then
printf '%s [dry-run] shutdown immediate în %s%s\n' "$c_dim" "$ORACLE_DOCKER" "$c_off" printf '%s [dry-run] shutdown immediate în %s%s\n' "$c_dim" "$ORACLE_DOCKER" "$c_off"
else else
nssh "$ORACLE_NODE" "pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -c \"printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba\"" \ # `bash -lc`, NU `bash -c`: fără shell de login, PATH-ul Oracle nu e încărcat
|| warn "Shutdown-ul bazei a raportat eroare. Verifică manual înainte de a continua!" # și comanda eșuează cu „sqlplus: command not found" — adică baza rămâne
ok "instanța Oracle oprită" # DESCHISĂ, iar containerul ar fi oprit peste ea.
# `timeout 180`: un „shutdown immediate" poate dura, dar nu la infinit. Fără
# el, un sqlplus blocat ar ține scriptul agățat fără niciun mesaj (pățit pe
# 2026-08-29, la sonda echivalentă din cluster-startup.sh).
SQL_OUT=$(nssh "$ORACLE_NODE" "timeout 180 pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -lc \"printf 'shutdown immediate\nexit\n' | sqlplus -s / as sysdba\" 2>&1" || true)
# Codul de ieșire nu e o dovadă: sqlplus întoarce 0 și când n-a închis nimic.
# Singura confirmare reală e mesajul instanței.
if grep -q 'ORACLE instance shut down' <<<"$SQL_OUT"; then
ok "instanța Oracle oprită curat"
else
warn "Baza NU a confirmat „ORACLE instance shut down\". Ultimele linii:"
tail -8 <<<"$SQL_OUT"
confirm "Oprirea CT $ORACLE_CT peste o bază posibil deschisă. Continui?"
fi
fi fi
fi fi

View File

@@ -61,6 +61,7 @@ $GuestStartOrder = @(
101, # minecraft 101, # minecraft
110, # moltbot 110, # moltbot
303, # Win11-Adina 303, # Win11-Adina
304, # Win11-Marius
302, # oracle-test-302 302, # oracle-test-302
109, # oracle-dr-windows 109, # oracle-dr-windows
301, # docker-portainer-template 301, # docker-portainer-template
@@ -74,9 +75,26 @@ $GuestStartOrder = @(
# 301, 310 - template-uri, nu servicii. # 301, 310 - template-uri, nu servicii.
$NeverAutostart = @(109, 301, 310) $NeverAutostart = @(109, 301, 310)
# Reteaua dedicata de cluster ("insula"), existenta din 2026-08-29. De ea atarna
# replicarea ZFS (migration: network=10.10.10.0/24 in datacenter.cfg) si storage-ul
# NFS backup-pvemini. Pe pve1 si pveelite IP-ul sta pe dongle-ul USB, adus la boot
# de un "auto enx..." din /etc/network/interfaces: daca nucleul enumereaza USB-ul
# DUPA ce networking.service a terminat, interfata ramane fara IP, iar nodul pare
# perfect sanatos. Detalii: ../docs/switch-2.5g-dedicat-noduri.md
$IslandIp = [ordered]@{
pve1 = '10.10.10.200'
pvemini = '10.10.10.201'
pveelite = '10.10.10.202'
}
$IslandNfsMount = '/mnt/pve/backup-pvemini'
$IslandNfsServer = '10.10.10.201'
$IslandNfsNodes = @('pve1', 'pveelite')
$OracleCt = 108 $OracleCt = 108
$OracleDocker = 'oracle-xe' $OracleDocker = 'oracle-xe'
$OracleWait = 600 # secunde asteptate pana baza raspunde la interogari # Masurat pe 2026-08-29, la pornire de la rece dupa oprirea completa a clusterului:
# instanta a avut nevoie de ~10 minute ca sa ajunga OPEN. 600s erau prea strimti.
$OracleWait = 1200 # secunde asteptate pana baza raspunde la interogari
$NodeWait = 1800 # secunde asteptate pana apar toate nodurile $NodeWait = 1800 # secunde asteptate pana apar toate nodurile
$QuorumWait = 300 $QuorumWait = 300
@@ -119,6 +137,21 @@ function Invoke-NodeChange {
return Invoke-Node -Node $Node -Command $Command return Invoke-Node -Node $Node -Command $Command
} }
# Comenzi cu ghilimele imbricate NU se trimit prin Invoke-Node.
#
# PowerShell 5.1 pierde ghilimelele duble cand paseaza un argument catre un
# executabil nativ: `bash -lc "printf '...' | sqlplus"` ajunge pe nod ca
# `bash -lc printf '...' | sqlplus`, adica printf fara format, iar sqlplus rulat
# pe NOD, unde nu exista. Esecul e usor de citit gresit ca "lipseste sqlplus".
# In loc sa ne luptam cu nivelurile de citare, trimitem scriptul codificat.
# Verificat pe 2026-08-29, dupa ce varianta directa a esuat pe cluster live.
function Invoke-NodeScript {
param([string]$Node, [string]$Script)
$lf = $Script -replace "`r`n", "`n"
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($lf))
return Invoke-Node -Node $Node -Command "echo $b64 | base64 -d | bash"
}
function Confirm-Step { function Confirm-Step {
param([string]$Message) param([string]$Message)
if ($Yes -or $DryRun) { return } if ($Yes -or $DryRun) { return }
@@ -209,7 +242,92 @@ Write-Ok "quorum OK, $votes/3 voturi"
Invoke-Node -Node 'pvemini' -Command 'ha-manager status' | Select-Object -First 4 | Write-Host Invoke-Node -Node 'pvemini' -Command 'ha-manager status' | Select-Object -First 4 | Write-Host
# ------------------------------------------------- pas 3: ce trebuie pornit # ------------------------------------ pas 3: reteaua dedicata de cluster (insula)
# Se verifica INAINTE de pornirea guest-urilor: daca insula e jos, replicarea si
# backup-ul NFS tac fara nicio alerta, iar simptomul apare abia peste ore.
# Nu blocheaza pornirea - la revenirea curentului, Oracle sus conteaza mai mult
# decat replicarea - dar nu te lasa sa treci pe langa problema fara sa o vezi.
Write-Step "Verific reteaua de cluster (10.10.10.0/24)"
if ($DryRun) {
Write-Warn "dry-run - sar peste verificarea insulei"
} else {
$islandBad = @()
foreach ($node in $NodeIp.Keys) {
$want = $IslandIp[$node]
$have = (Invoke-Node -Node $node -Command "ip -4 -o addr show | grep -c ' $want/' || true") -join ''
if ($have.Trim() -eq '0') {
# Cazul asteptat de esec: dongle-ul USB enumerat tarziu la boot.
# ifreload -a aplica doar diferentele fata de /etc/network/interfaces,
# deci nu atinge interfetele tap/veth ale guest-urilor.
Write-Warn "$node - lipseste $want, incerc 'ifreload -a'"
Invoke-Node -Node $node -Command 'PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a' | Out-Null
Start-Sleep -Seconds 8
$have = (Invoke-Node -Node $node -Command "ip -4 -o addr show | grep -c ' $want/' || true") -join ''
}
if ($have.Trim() -eq '0') {
$islandBad += $node
Write-Warn "$node - TOT fara $want dupa ifreload"
} else {
Write-Ok "$node - $want prezent"
}
}
# Un IP configurat local nu garanteaza si cablul/switch-ul. pvemini e sursa
# replicarii si serverul NFS, deci se verifica din el spre celelalte doua.
foreach ($node in @('pve1', 'pveelite')) {
$ip = $IslandIp[$node]
$p = (Invoke-Node -Node 'pvemini' -Command "ping -c1 -W2 $ip >/dev/null 2>&1 && echo OK || echo FAIL") -join ''
if ($p.Trim() -eq 'OK') {
Write-Ok "pvemini -> $node ($ip) - ping OK"
} else {
if ($islandBad -notcontains $node) { $islandBad += $node }
Write-Warn "pvemini -> $node ($ip) - NU raspunde (cablu? switch?)"
}
}
$nfsBad = @()
foreach ($node in $IslandNfsNodes) {
$src = (Invoke-Node -Node $node -Command "findmnt -no SOURCE $IslandNfsMount 2>/dev/null || true") -join ''
if ($src -match [regex]::Escape($IslandNfsServer)) {
Write-Ok "$node - NFS $IslandNfsMount montat de pe $IslandNfsServer"
} elseif ($src.Trim()) {
# Prins pe 2026-08-29 la primul boot la rece: o linie ramasa in /etc/fstab
# monta storage-ul de pe IP-ul de productie INAINTE ca pvestatd sa apuce,
# deci backup-ul mergea tacut pe reteaua gresita, cu storage.cfg corect.
$nfsBad += $node
Write-Warn "$node - NFS montat de pe ALT server: $src (asteptat $IslandNfsServer)"
Write-Warn " verifica: ssh root@$($NodeIp[$node]) 'grep nfs /etc/fstab'"
} else {
# pvestatd remonteaza singur in cateva zeci de secunde daca reteaua e sus.
Write-Warn "$node - NFS $IslandNfsMount NEMONTAT (pvestatd il reia daca insula urca)"
}
}
if ($nfsBad.Count -gt 0) {
Write-Warn "Backup-ul NFS trece pe alta retea decat cea proiectata pe: $($nfsBad -join ', ')"
}
if ($islandBad.Count -gt 0) {
Write-Warn ""
Write-Warn "INSULA E JOS pe: $($islandBad -join ', ')"
Write-Warn "Consecinta: replicarea ZFS si storage-ul NFS backup-pvemini NU vor functiona."
Write-Warn "Guest-urile pornesc si merg normal - problema e tacuta, se vede abia in pvesr status."
Write-Warn "Verifica: ssh root@<nod> `"ip -br a; dmesg | tail -20`" (dongle USB enumerat tarziu?)"
Confirm-Step "Continui cu pornirea guest-urilor, cu replicarea jos?"
} elseif ($nfsBad.Count -eq 0) {
Write-Ok "insula completa - replicarea si NFS-ul au calea libera"
} else {
Write-Warn "insula e sus, dar NFS-ul nu o foloseste - vezi mai sus"
}
}
# ------------------------------------------------- pas 4: ce trebuie pornit
Write-Step "Ce se porneste" Write-Step "Ce se porneste"
@@ -262,7 +380,7 @@ foreach ($g in $toStart) {
Confirm-Step "Se pornesc guest-urile de mai sus, Oracle primul." Confirm-Step "Se pornesc guest-urile de mai sus, Oracle primul."
# ------------------------------------------------- pas 4: pornire guest-uri # ------------------------------------------------- pas 5: pornire guest-uri
Write-Step "Pornire guest-uri" Write-Step "Pornire guest-uri"
@@ -306,11 +424,24 @@ function Wait-Oracle {
Write-Log "astept ca instanta Oracle sa accepte interogari..." Write-Log "astept ca instanta Oracle sa accepte interogari..."
if ($DryRun) { return } if ($DryRun) { return }
$sql = 'pct exec ' + $OracleCt + ' -- docker exec ' + $OracleDocker + ' bash -c "printf ''set pagesize 0 feedback off\nselect global_name from global_name;\nexit\n'' | sqlplus -s / as sysdba"' # 'bash -lc', NU 'bash -c': fara shell de login nu exista sqlplus in PATH, iar
# asteptarea ar expira dupa $OracleWait secunde chiar cu baza perfect deschisa.
#
# 'timeout 30' e OBLIGATORIU, nu o precautie. Pe 2026-08-29, la pornirea de la
# rece, sqlplus lansat catre o instanta inca in startup s-a blocat definitiv:
# docker exec a mostenit blocajul, ssh a asteptat, iar bucla de mai jos n-a
# apucat sa faca nici macar o iteratie. Scriptul a stat agatat 14 minute fara
# niciun mesaj, cu propriul prag de asteptare nefolosit. Sonda trebuie sa
# raspunda mereu, fie si cu esec.
$sql = @"
timeout 30 pct exec $OracleCt -- docker exec $OracleDocker bash -lc "printf 'set pagesize 0 feedback off\nselect status from v\`$instance;\nexit\n' | sqlplus -s / as sysdba" 2>&1
"@
$waited = 0 $waited = 0
while ($waited -lt $OracleWait) { while ($waited -lt $OracleWait) {
$out = Invoke-Node -Node $Node -Command $sql $out = Invoke-NodeScript -Node $Node -Script $sql
if ($LASTEXITCODE -eq 0 -and (($out -join '') -match '[A-Z]')) { # 'OPEN' e raspunsul instantei deschise; orice altceva (ORA-, container
# inca in pornire) inseamna ca mai asteptam.
if (($out -join '') -match 'OPEN') {
Write-Host "`r `r" -NoNewline Write-Host "`r `r" -NoNewline
Write-Ok "Oracle deschis si interogabil (${waited}s)" Write-Ok "Oracle deschis si interogabil (${waited}s)"
return return
@@ -342,7 +473,7 @@ foreach ($g in $toStart) {
if (-not (Start-Guest -Guest $g)) { $failed += "$($g.Type):$($g.Id)" } if (-not (Start-Guest -Guest $g)) { $failed += "$($g.Type):$($g.Id)" }
} }
# ------------------------------------------------ pas 5: restaurare cron-uri # ------------------------------------------------ pas 6: restaurare cron-uri
Write-Step "Restaurare cron-uri de monitorizare" Write-Step "Restaurare cron-uri de monitorizare"
@@ -363,7 +494,7 @@ foreach ($node in $NodeIp.Keys) {
} }
} }
# ------------------------------------------------------- pas 6: verificare # ------------------------------------------------------- pas 7: verificare
Write-Step "Verificare finala" Write-Step "Verificare finala"
@@ -396,6 +527,9 @@ if ($failed.Count -gt 0) {
Write-Host "" Write-Host ""
Write-Host " De verificat manual:" Write-Host " De verificat manual:"
Write-Host " - shutdown_policy e inca 'freeze' (recomandat sa ramana asa)" Write-Host " - datacenter.cfg are inca AMBELE linii: 'ha: shutdown_policy=freeze'"
Write-Host " si 'migration: network=10.10.10.0/24,type=insecure'"
Write-Host " - daca lipsesc date recente, compara cu ultima replicare din pvesr status" Write-Host " - daca lipsesc date recente, compara cu ultima replicare din pvesr status"
Write-Host " - daca a fost prima repornire dupa 2026-08-29: noteaza in"
Write-Host " docs/switch-2.5g-dedicat-noduri.md daca insula a urcat singura la boot"
Write-Host "" Write-Host ""

View File

@@ -46,6 +46,7 @@ GUEST_START_ORDER=(
101 # minecraft 101 # minecraft
110 # moltbot 110 # moltbot
303 # Win11-Adina 303 # Win11-Adina
304 # Win11-Marius
302 # oracle-test-302 302 # oracle-test-302
109 # oracle-dr-windows — DR, în mod normal rămâne oprit 109 # oracle-dr-windows — DR, în mod normal rămâne oprit
301 # docker-portainer-template 301 # docker-portainer-template
@@ -59,9 +60,26 @@ GUEST_START_ORDER=(
# 301, 310 — template-uri, nu servicii. # 301, 310 — template-uri, nu servicii.
NEVER_AUTOSTART=(109 301 310) NEVER_AUTOSTART=(109 301 310)
# Rețeaua dedicată de cluster („insula"), existentă din 2026-08-29. De ea atârnă
# replicarea ZFS (migration: network=10.10.10.0/24 în datacenter.cfg) și storage-ul
# NFS backup-pvemini. Pe pve1 și pveelite IP-ul stă pe dongle-ul USB, adus la boot
# de un „auto enx..." din /etc/network/interfaces: dacă nucleul enumerează USB-ul
# DUPĂ ce networking.service a terminat, interfața rămâne fără IP, iar nodul pare
# perfect sănătos. Detalii: ../docs/switch-2.5g-dedicat-noduri.md
declare -A ISLAND_IP=(
[pve1]=10.10.10.200
[pvemini]=10.10.10.201
[pveelite]=10.10.10.202
)
ISLAND_NFS_MOUNT=/mnt/pve/backup-pvemini
ISLAND_NFS_SERVER=10.10.10.201
ISLAND_NFS_NODES=(pve1 pveelite)
ORACLE_CT=108 ORACLE_CT=108
ORACLE_DOCKER=oracle-xe ORACLE_DOCKER=oracle-xe
ORACLE_WAIT=600 # secunde așteptate până baza răspunde la interogări # Măsurat pe 2026-08-29, la pornire de la rece după oprirea completă a clusterului:
# instanța a avut nevoie de ~10 minute ca să ajungă OPEN. 600s erau prea strâmți.
ORACLE_WAIT=1200 # secunde așteptate până baza răspunde la interogări
NODE_WAIT=1800 # secunde așteptate până apar toate nodurile NODE_WAIT=1800 # secunde așteptate până apar toate nodurile
QUORUM_WAIT=300 QUORUM_WAIT=300
@@ -177,7 +195,91 @@ ok "quorum OK, $VOTES/3 voturi"
nssh pvemini "ha-manager status | head -4" nssh pvemini "ha-manager status | head -4"
# ------------------------------------------------- pas 3: ce trebuie pornit # ----------------------------------- pas 3: rețeaua dedicată de cluster (insula)
# Se verifică ÎNAINTE de pornirea guest-urilor: dacă insula e jos, replicarea și
# backup-ul NFS tac fără nicio alertă, iar simptomul apare abia peste ore.
# Nu blochează pornirea — la revenirea curentului, Oracle sus contează mai mult
# decât replicarea — dar nu te lasă să treci pe lângă problemă fără să o vezi.
step "Verific rețeaua de cluster (10.10.10.0/24)"
if (( DRY_RUN )); then
warn "dry-run — sar peste verificarea insulei"
else
ISLAND_BAD=()
for node in "${!NODE_IP[@]}"; do
want="${ISLAND_IP[$node]}"
have=$(nssh "$node" "ip -4 -o addr show | grep -c ' $want/' || true")
if [[ "$have" == "0" ]]; then
# Cazul așteptat de eșec: dongle-ul USB enumerat târziu la boot.
# ifreload -a aplică doar diferențele față de /etc/network/interfaces,
# deci nu atinge interfețele tap/veth ale guest-urilor.
warn "$node — lipsește $want, încerc 'ifreload -a'"
nssh "$node" "PATH=/usr/sbin:/sbin:/usr/bin:/bin ifreload -a" || true
sleep 8
have=$(nssh "$node" "ip -4 -o addr show | grep -c ' $want/' || true")
fi
if [[ "$have" == "0" ]]; then
ISLAND_BAD+=("$node")
warn "$node — TOT fără $want după ifreload"
else
ok "$node — $want prezent"
fi
done
# Un IP configurat local nu garantează și cablul/switch-ul. pvemini e sursa
# replicării și serverul NFS, deci se verifică din el spre celelalte două.
for node in pve1 pveelite; do
ip="${ISLAND_IP[$node]}"
if nssh pvemini "ping -c1 -W2 $ip >/dev/null 2>&1"; then
ok "pvemini → $node ($ip) — ping OK"
else
printf '%s\n' "${ISLAND_BAD[@]:-}" | grep -qx "$node" || ISLAND_BAD+=("$node")
warn "pvemini → $node ($ip) — NU răspunde (cablu? switch?)"
fi
done
NFS_BAD=()
for node in "${ISLAND_NFS_NODES[@]}"; do
src=$(nssh "$node" "findmnt -no SOURCE $ISLAND_NFS_MOUNT 2>/dev/null || true")
if [[ "$src" == *"$ISLAND_NFS_SERVER"* ]]; then
ok "$node — NFS $ISLAND_NFS_MOUNT montat de pe $ISLAND_NFS_SERVER"
elif [[ -n "${src// /}" ]]; then
# Prins pe 2026-08-29 la primul boot la rece: o linie rămasă în /etc/fstab
# monta storage-ul de pe IP-ul de producție ÎNAINTE ca pvestatd să apuce,
# deci backup-ul mergea tăcut pe rețeaua greșită, cu storage.cfg corect.
NFS_BAD+=("$node")
warn "$node — NFS montat de pe ALT server: $src (așteptat $ISLAND_NFS_SERVER)"
warn " verifică: ssh root@${NODE_IP[$node]} 'grep nfs /etc/fstab'"
else
# pvestatd remontează singur în câteva zeci de secunde dacă rețeaua e sus.
warn "$node — NFS $ISLAND_NFS_MOUNT NEMONTAT (pvestatd îl reia dacă insula urcă)"
fi
done
if (( ${#NFS_BAD[@]} )); then
warn "Backup-ul NFS trece pe altă rețea decât cea proiectată pe: ${NFS_BAD[*]}"
fi
if (( ${#ISLAND_BAD[@]} )); then
warn ""
warn "INSULA E JOS pe: ${ISLAND_BAD[*]}"
warn "Consecință: replicarea ZFS și storage-ul NFS backup-pvemini NU vor funcționa."
warn "Guest-urile pornesc și merg normal — problema e tăcută, se vede abia în pvesr status."
warn "Verifică: ssh root@<nod> \"ip -br a; dmesg | tail -20\" (dongle USB enumerat târziu?)"
confirm "Continui cu pornirea guest-urilor, cu replicarea jos?"
elif (( ${#NFS_BAD[@]} == 0 )); then
ok "insulă completă — replicarea și NFS-ul au calea liberă"
else
warn "insula e sus, dar NFS-ul nu o folosește — vezi mai sus"
fi
fi
# ------------------------------------------------- pas 4: ce trebuie pornit
step "Ce se pornește" step "Ce se pornește"
@@ -223,7 +325,7 @@ done <<<"$TO_START"
confirm "Se pornesc guest-urile de mai sus, în ordinea: ${GUEST_START_ORDER[*]}" confirm "Se pornesc guest-urile de mai sus, în ordinea: ${GUEST_START_ORDER[*]}"
# ------------------------------------------------- pas 4: pornire guest-uri # ------------------------------------------------- pas 5: pornire guest-uri
step "Pornire guest-uri" step "Pornire guest-uri"
@@ -269,7 +371,16 @@ wait_oracle() {
(( DRY_RUN )) && return 0 (( DRY_RUN )) && return 0
local waited=0 local waited=0
while (( waited < ORACLE_WAIT )); do while (( waited < ORACLE_WAIT )); do
if nssh "$node" "pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -c \"printf 'set pagesize 0 feedback off\nselect global_name from global_name;\nexit\n' | sqlplus -s / as sysdba\" 2>/dev/null | grep -q '[A-Z]'"; then # `bash -lc`, NU `bash -c`: fără shell de login nu există sqlplus în PATH, iar
# așteptarea ar expira după $ORACLE_WAIT secunde chiar cu baza perfect deschisă.
#
# `timeout 30` e OBLIGATORIU, nu o precauție. Pe 2026-08-29, la pornirea de la
# rece, sqlplus lansat către o instanță încă în startup s-a blocat definitiv:
# docker exec a moștenit blocajul, ssh a așteptat, iar bucla n-a apucat să
# facă nici măcar o iterație. Scriptul a stat agățat 14 minute fără niciun
# mesaj, cu propriul prag de așteptare nefolosit. Sonda trebuie să răspundă
# mereu, fie și cu eșec.
if nssh "$node" "timeout 30 pct exec $ORACLE_CT -- docker exec $ORACLE_DOCKER bash -lc \"printf 'set pagesize 0 feedback off\nselect status from v\\\$instance;\nexit\n' | sqlplus -s / as sysdba\" 2>/dev/null | grep -q OPEN"; then
ok "Oracle deschis și interogabil (${waited}s)" ok "Oracle deschis și interogabil (${waited}s)"
return 0 return 0
fi fi
@@ -305,7 +416,7 @@ while read -r type node id status ha; do
start_guest "$type" "$node" "$id" "$ha" || FAILED+=("$type:$id") start_guest "$type" "$node" "$id" "$ha" || FAILED+=("$type:$id")
done <<<"$TO_START" done <<<"$TO_START"
# ------------------------------------------------ pas 5: restaurare cron-uri # ------------------------------------------------ pas 6: restaurare cron-uri
step "Restaurare cron-uri de monitorizare" step "Restaurare cron-uri de monitorizare"
@@ -325,7 +436,7 @@ for node in "${!NODE_IP[@]}"; do
fi fi
done done
# ------------------------------------------------------- pas 6: verificare # ------------------------------------------------------- pas 7: verificare
step "Verificare finală" step "Verificare finală"
@@ -354,7 +465,10 @@ fi
cat <<EOF cat <<EOF
De verificat manual: De verificat manual:
- shutdown_policy e încă 'freeze' (recomandat să rămână așa) - datacenter.cfg are încă AMBELE linii: 'ha: shutdown_policy=freeze'
și 'migration: network=10.10.10.0/24,type=insecure'
- dacă lipsesc date recente, compară cu ultima replicare din pvesr status - dacă lipsesc date recente, compară cu ultima replicare din pvesr status
- dacă a fost prima repornire după 2026-08-29: notează în
docs/switch-2.5g-dedicat-noduri.md dacă insula a urcat singură la boot
EOF EOF

View File

@@ -3,22 +3,31 @@
Instaleaza reatasarea automata a adaptoarelor USB LAN dupa un reset USB. Instaleaza reatasarea automata a adaptoarelor USB LAN dupa un reset USB.
.DESCRIPTION .DESCRIPTION
Adaptoarele Realtek RTL8156 (driver r8152) folosite ca placa de retea Adaptoarele Realtek RTL8156 (driver r8152) de pe pve1 si pveelite se
principala pe pve1 si pveelite se reseteaza singure pe magistrala USB. reseteaza singure pe magistrala USB. Kernelul sterge interfata si o
Kernelul sterge interfata si o recreeaza, dar nimeni nu o readauga in vmbr0: recreeaza, dar nimeni nu o reconfigureaza: se ridica doar ca efect secundar,
in /etc/network/interfaces ea e declarata "inet manual", fara "auto" si fara la boot.
"allow-hotplug", deci se ridica doar ca efect secundar, la boot.
Consecinta e un nod care merge perfect si e invizibil in retea, pe termen
nelimitat. Pe pveelite asta a insemnat 16 ore de tacere in noaptea de 27 spre
28 august 2026, plus inca un reset a doua zi la 10:31.
Scriptul instaleaza o regula udev care prinde reaparitia interfetei si o Scriptul instaleaza o regula udev care prinde reaparitia interfetei si o
unitate systemd care reaplica configuratia de retea. Detecteaza singur ce unitate systemd care reaplica configuratia de retea (ifreload -a).
noduri au adaptor r8152 si le sare pe celelalte - pvemini are placa Intel Detecteaza singur ce noduri au adaptor r8152 si le sare pe celelalte -
igc pe PCIe si nu are nevoie. pvemini are placa Intel pe PCIe si nu are nevoie.
CE S-A SCHIMBAT PE 2026-08-29. Cand a fost scris scriptul, dongle-ul USB era
portul puntii vmbr0, adica placa de retea PRINCIPALA - de-aia un reset USB
lasa nodul complet invizibil (16 ore de tacere pe pveelite in noaptea de 27
spre 28 august 2026). Intre timp vmbr0 a fost mutat pe interfata onboard, iar
dongle-ul a ramas doar cu IP-ul retelei dedicate de cluster
(10.10.10.200 / 10.10.10.202), declarat acum "auto ... inet static".
Regula ramane la fel de utila, dar miza s-a schimbat: un reset USB nu mai ia
nodul offline, ci opreste tacut replicarea ZFS si storage-ul NFS
backup-pvemini. In plus, fiind pe ACTION=="add", regula acopera si cazul in
care USB-ul se enumereaza tarziu la boot, dupa networking.service - singurul
risc ramas pentru insula. Neconfirmat inca la o repornire reala.
Context: ../incidents/2026-08-27-pveelite-down.md Context: ../incidents/2026-08-27-pveelite-down.md
../docs/switch-2.5g-dedicat-noduri.md
RULEAZA DE PE STATIA DE ADMIN. SSH intre nodurile Proxmox trece prin RULEAZA DE PE STATIA DE ADMIN. SSH intre nodurile Proxmox trece prin
Tailscale si cere autentificare interactiva. Tailscale si cere autentificare interactiva.
@@ -138,13 +147,28 @@ done
} }
Write-Info "adaptoare r8152: $($ifs -join ', ')" Write-Info "adaptoare r8152: $($ifs -join ', ')"
# Interfata conteaza doar daca e chiar portul puntii; altfel regula e inutila. # Regula e utila doar daca interfata chiar are ce sa i se reaplice: fie e
$port = Invoke-NodeScript -Ip $ip -Script @' # portul puntii (configuratia dinainte de 2026-08-29), fie are propria strofa
sed -n 's/^[[:space:]]*bridge-ports[[:space:]]*//p' /etc/network/interfaces # in /etc/network/interfaces (azi: IP-ul retelei dedicate de cluster).
# Fara niciuna, ifreload -a n-ar avea ce configura si regula n-ar folosi la nimic.
$rol = Invoke-NodeScript -Ip $ip -Script @'
port=$(sed -n 's/^[[:space:]]*bridge-ports[[:space:]]*//p' /etc/network/interfaces)
for n in /sys/class/net/*; do
i=${n##*/}
d=$(readlink -f "$n/device/driver" 2>/dev/null)
case "$d" in *r8152) ;; *) continue ;; esac
case " $port " in *" $i "*) echo "$i: port al puntii vmbr0"; continue ;; esac
if grep -qE "^[[:space:]]*(auto|allow-hotplug)[[:space:]]+$i([[:space:]]|$)" /etc/network/interfaces; then
echo "$i: strofa proprie ($(sed -n "s/^[[:space:]]*iface[[:space:]]\+$i[[:space:]]\+//p" /etc/network/interfaces | head -1)), IP: $(ip -4 -o addr show "$i" | awk '{print $4}' | tr '\n' ' ')"
else
echo "$i: NECONFIGURAT in /etc/network/interfaces"
fi
done
'@ '@
Write-Info "bridge-ports vmbr0: $port" $rol | ForEach-Object { Write-Info $_ }
if ($ifs -notcontains $port) { if ($rol -match 'NECONFIGURAT') {
Write-Warn "adaptorul USB nu e portul puntii - instalez oricum, dar verifica manual" Write-Warn "un adaptor r8152 nu e nici port de punte, nici cu strofa proprie -"
Write-Warn "regula se instaleaza, dar pentru el ifreload -a n-are ce aplica"
} }
if ($DryRun) { if ($DryRun) {

View File

@@ -0,0 +1,164 @@
<#
.SYNOPSIS
Porneste nodurile Proxmox prin Wake-on-LAN.
.DESCRIPTION
Trimite magic packet catre placile de retea de PRODUCTIE ale nodurilor si
asteapta sa apara pe retea.
De ce merge abia din 2026-08-29: pana atunci pve1 si pveelite aveau IP-ul
principal pe dongle-uri USB Realtek, care nu suporta WoL si oricum pierd
alimentarea cand nodul e oprit. Mutarea lui vmbr0 pe interfetele onboard a
facut WoL posibil - vezi ../docs/switch-2.5g-dedicat-noduri.md.
NU trezi un singur nod si atat, daca ai oprit tot clusterul: un nod pornit
singur nu are quorum, /etc/pve ramane read-only si nu poti porni niciun guest.
Implicit scriptul le trezeste pe toate trei.
RULEAZA DE PE STATIA DE ADMIN, care trebuie sa fie pe acelasi segment L2
(10.0.20.0/24) - magic packet-ul e broadcast si nu trece prin router.
.PARAMETER Node
Trezeste doar nodul dat. Implicit: toate trei.
.PARAMETER Wait
Secunde de asteptat sa apara nodurile (implicit 300).
.PARAMETER NoWait
Trimite pachetele si iese, fara sa astepte.
.EXAMPLE
.\wake-cluster.ps1
.EXAMPLE
.\wake-cluster.ps1 -Node pveelite
#>
[CmdletBinding()]
param(
[ValidateSet('pve1','pvemini','pveelite')]
[string]$Node,
[int]$Wait = 300,
[switch]$NoWait
)
$ErrorActionPreference = 'Stop'
# MAC-ul interfetei de PRODUCTIE (portul puntii vmbr0), nu al dongle-ului de insula.
# Citite de pe noduri pe 2026-08-29 cu: ethtool <if>; cat /sys/class/net/<if>/address
$Nodes = [ordered]@{
pve1 = @{ Ip = '10.0.20.200'; Mac = 'fc:3f:db:0a:0d:d8'; If = 'eno1' }
pvemini = @{ Ip = '10.0.20.201'; Mac = '58:47:ca:7d:51:3c'; If = 'enp90s0' }
pveelite = @{ Ip = '10.0.20.202'; Mac = '84:69:93:57:b2:ea'; If = 'eno1' }
}
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-Info { param([string]$Text) Write-Host " $Text" -ForegroundColor DarkGray }
function Test-Alive {
param([string]$Address, [int]$TimeoutMs = 1500)
try {
$p = New-Object System.Net.NetworkInformation.Ping
return ($p.Send($Address, $TimeoutMs).Status -eq 'Success')
} catch { return $false }
}
function Send-MagicPacket {
param([string]$Mac)
$bytes = ($Mac -split '[:-]') | ForEach-Object { [byte]([Convert]::ToInt32($_, 16)) }
if ($bytes.Count -ne 6) { throw "MAC invalid: $Mac" }
# Magic packet: 6 octeti 0xFF, apoi MAC-ul repetat de 16 ori.
$packet = New-Object byte[] 102
for ($i = 0; $i -lt 6; $i++) { $packet[$i] = 0xFF }
for ($r = 0; $r -lt 16; $r++) { [Array]::Copy($bytes, 0, $packet, 6 + $r * 6, 6) }
# Trimis si pe broadcast global, si pe cel de subretea: unele stive le trateaza
# diferit, iar pachetul e mic - nu costa nimic sa mearga pe ambele.
# Porturile 9 si 7 sunt cele uzuale; placile asculta oricum dupa continut.
$udp = New-Object System.Net.Sockets.UdpClient
try {
$udp.EnableBroadcast = $true
foreach ($dest in @('255.255.255.255', '10.0.20.255')) {
foreach ($port in @(9, 7)) {
[void]$udp.Send($packet, $packet.Length, $dest, $port)
}
}
} finally {
$udp.Close()
}
}
$targets = if ($Node) { @($Node) } else { $Nodes.Keys }
Write-Step "Trimit magic packet"
$deja = @()
foreach ($n in $targets) {
if (Test-Alive $Nodes[$n].Ip) {
Write-Ok "$n ($($Nodes[$n].Ip)) - deja pornit, nu trimit"
$deja += $n
continue
}
Send-MagicPacket -Mac $Nodes[$n].Mac
Write-Ok "$n -> $($Nodes[$n].Mac) ($($Nodes[$n].If))"
}
$asteptate = @($targets | Where-Object { $deja -notcontains $_ })
if ($asteptate.Count -eq 0) {
Write-Warn "Toate nodurile cerute erau deja pornite."
exit 0
}
if ($NoWait) {
Write-Info "-NoWait: nu astept. Verifica singur cu ping."
exit 0
}
Write-Step "Astept nodurile (max ${Wait}s)"
# Retrimitem periodic: daca placa nu era inca alimentata la primul pachet
# (alimentare tocmai revenita, de exemplu), al doilea o prinde.
$waited = 0
$up = @()
while ($waited -lt $Wait) {
foreach ($n in $asteptate) {
if ($up -contains $n) { continue }
if (Test-Alive $Nodes[$n].Ip) {
$up += $n
Write-Ok "$n a pornit (${waited}s)"
}
}
if ($up.Count -eq $asteptate.Count) { break }
Start-Sleep -Seconds 5
$waited += 5
if ($waited % 60 -eq 0) {
foreach ($n in $asteptate) {
if ($up -notcontains $n) { Send-MagicPacket -Mac $Nodes[$n].Mac }
}
Write-Info "am retrimis pachetele (${waited}s)"
}
}
$lipsa = @($asteptate | Where-Object { $up -notcontains $_ })
Write-Step "Rezultat"
if ($lipsa.Count -eq 0) {
Write-Ok "toate nodurile cerute au pornit"
Write-Host ""
Write-Host " Urmatorul pas: .\cluster-startup.ps1"
Write-Host ""
} else {
Write-Warn "NU au pornit: $($lipsa -join ', ')"
Write-Info "Cauza cea mai probabila e in BIOS, nu in retea:"
Write-Info " - 'Wake on LAN' / 'Power On by PCI-E' dezactivat"
Write-Info " - ErP Ready / Deep Sleep ACTIVAT (taie alimentarea placii in S5)"
Write-Info "Verifica si ca statia de admin e pe acelasi segment L2 (10.0.20.0/24)."
Write-Info "Pe nodurile pornite, starea WoL se vede cu: ethtool <if> | grep Wake-on"
exit 1
}