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
165 lines
5.4 KiB
PowerShell
165 lines
5.4 KiB
PowerShell
<#
|
|
.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
|
|
}
|