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:
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,11 +242,23 @@ 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?"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,10 +203,20 @@ 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
|
||||||
|
|
||||||
|
|||||||
@@ -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 ""
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
164
proxmox/cluster/scripts/wake-cluster.ps1
Normal file
164
proxmox/cluster/scripts/wake-cluster.ps1
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user