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

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

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

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

Patru bug-uri gasite prin rulare pe cluster live:

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

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

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

View File

@@ -61,6 +61,7 @@ $GuestStartOrder = @(
101, # minecraft
110, # moltbot
303, # Win11-Adina
304, # Win11-Marius
302, # oracle-test-302
109, # oracle-dr-windows
301, # docker-portainer-template
@@ -74,9 +75,26 @@ $GuestStartOrder = @(
# 301, 310 - template-uri, nu servicii.
$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
$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
$QuorumWait = 300
@@ -119,6 +137,21 @@ function Invoke-NodeChange {
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 {
param([string]$Message)
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
# ------------------------------------------------- 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"
@@ -262,7 +380,7 @@ foreach ($g in $toStart) {
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"
@@ -306,11 +424,24 @@ function Wait-Oracle {
Write-Log "astept ca instanta Oracle sa accepte interogari..."
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
while ($waited -lt $OracleWait) {
$out = Invoke-Node -Node $Node -Command $sql
if ($LASTEXITCODE -eq 0 -and (($out -join '') -match '[A-Z]')) {
$out = Invoke-NodeScript -Node $Node -Script $sql
# '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-Ok "Oracle deschis si interogabil (${waited}s)"
return
@@ -342,7 +473,7 @@ foreach ($g in $toStart) {
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"
@@ -363,7 +494,7 @@ foreach ($node in $NodeIp.Keys) {
}
}
# ------------------------------------------------------- pas 6: verificare
# ------------------------------------------------------- pas 7: verificare
Write-Step "Verificare finala"
@@ -396,6 +527,9 @@ if ($failed.Count -gt 0) {
Write-Host ""
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 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 ""