fix(dr): Windows Update rebota VM 109 in mijlocul testului DR
Testul DR din 2026-08-08 a raportat "Restore failed" dupa 11 secunde, fara niciun log RMAN. Cauza nu a fost restore-ul: KB5101001 fusese descarcat in timpul testului din 2026-08-01 (singurul moment in care VM 109 e pornit), a ramas staged dupa qm stop si s-a finalizat la boot-ul testului urmator. Cronologie din Event Log-ul guest-ului: 06:00:58 RestartManager 10010 - nu poate reporni powershell.exe (restore-ul) 06:01:04 SCM 7034 - OpenSSH SSH Server terminat neasteptat 06:01:06 pveelite: client_loop: send disconnect: Broken pipe -> FAILED 06:01:38 VM-ul se reboteaza singur Fereastra testului (Sambata 06:00) era in afara Active Hours (08:00-17:00), deci pentru Windows era fereastra de mentenanta valida - iar VM 109 fiind pornit doar in timpul testului, aceea era singura fereastra posibila. Agravant: sshd nu avea acsiuni de recovery (RESET_PERIOD 0), deci dupa ce a murit a ramas mort si au esuat si colectarea logului si shutdown-ul gratios. Masuri: - NoAutoUpdate=1 + AUOptions=2 pe VM 109 (aplicat direct in registry) - actiuni de recovery pentru sshd: restart la 5s/10s/30s, reset=86400 - guard "STEP 3b: Windows servicing" inainte de restore (check_servicing.ps1): asteapta idle 300s, consuma controlat un reboot in asteptare, altfel abandoneaza cu "ABORTED - Windows servicing" in loc de un "Restore failed" inselator. Fail-open daca checkul lipseste - nu are voie sa pice testul. - fereastra lunara de patching (vm109-patch-window.sh + install_updates.ps1), prima duminica 03:00, cu re-armare NoAutoUpdate=1 indiferent de rezultat Adaugat si .gitattributes: cu core.autocrlf=true scripturile .sh ajungeau in working tree cu CRLF, iar ele se deployeaza prin scp direct pe Proxmox. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BhQBTegE4PiMPPaapLHjkc
This commit is contained in:
202
proxmox/vm109-windows-dr/scripts/vm109-patch-window.sh
Normal file
202
proxmox/vm109-windows-dr/scripts/vm109-patch-window.sh
Normal file
@@ -0,0 +1,202 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# vm109-patch-window.sh — fereastră lunară de patching pentru VM 109 (DR Oracle).
|
||||
#
|
||||
# De ce există: incident 2026-08-08.
|
||||
# VM 109 este pornit doar în timpul testului DR săptămânal (Sâmbătă 06:00).
|
||||
# Prin urmare acela era singurul moment în care Windows Update putea rula —
|
||||
# iar 06:00 este în afara Active Hours (08:00-17:00), deci Windows îl trata ca
|
||||
# fereastră de mentenanță validă. KB5101001 a fost descărcat în timpul testului
|
||||
# din 2026-08-01, a rămas staged după `qm stop`, și s-a finalizat la boot-ul
|
||||
# testului din 2026-08-08: RestartManager a omorât powershell.exe (restore-ul)
|
||||
# și sshd, VM-ul a rebootat la 72s după boot, iar testul a raportat
|
||||
# "Restore failed" fără niciun log RMAN.
|
||||
#
|
||||
# Soluția: VM 109 are acum NoAutoUpdate=1 (Windows Update nu mai pornește
|
||||
# singur), iar patching-ul se face aici — într-o fereastră dedicată, unde
|
||||
# reboot-urile sunt așteptate și inofensive.
|
||||
#
|
||||
# Instalare (pe nodul care găzduiește VM 109 — și pe pvemini, pentru failover):
|
||||
# cp vm109-patch-window.sh /opt/scripts/
|
||||
# chmod +x /opt/scripts/vm109-patch-window.sh
|
||||
# crontab -e
|
||||
# # Prima duminică din lună, 03:00 — cron face OR între DOM și DOW,
|
||||
# # deci restricția pe duminică se face în script (GUARD_FIRST_SUNDAY).
|
||||
# 0 3 1-7 * * /opt/scripts/vm109-patch-window.sh >/dev/null 2>&1
|
||||
#
|
||||
# Rulare manuală (ignoră garda de calendar):
|
||||
# /opt/scripts/vm109-patch-window.sh --now
|
||||
|
||||
set -euo pipefail
|
||||
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
|
||||
|
||||
DR_VM_ID="109"
|
||||
DR_VM_IP="10.0.20.37"
|
||||
DR_VM_PORT="22122"
|
||||
DR_VM_USER="romfast"
|
||||
DEBUG_FLAG="/var/run/vm109-debug.flag"
|
||||
LOG="/var/log/oracle-dr/patch-window.log"
|
||||
MAX_BOOT_WAIT=300 # secunde de așteptare pentru SSH după boot/reboot
|
||||
MAX_REBOOTS=3 # cicluri de reboot permise într-o fereastră
|
||||
MAIL_TO="root"
|
||||
|
||||
mkdir -p "$(dirname "$LOG")"
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG"; }
|
||||
|
||||
# Rulăm doar pe nodul care găzduiește efectiv VM 109 (cluster-aware, ca watchdog-ul).
|
||||
[ -f "/etc/pve/qemu-server/${DR_VM_ID}.conf" ] || exit 0
|
||||
|
||||
# Garda de calendar: prima duminică din lună. Cron nu poate exprima asta singur.
|
||||
if [ "${1:-}" != "--now" ]; then
|
||||
if [ "$(date +%u)" != "7" ]; then exit 0; fi
|
||||
if [ "$(date +%-d)" -gt 7 ]; then exit 0; fi
|
||||
fi
|
||||
|
||||
# Nu ne suprapunem peste testul DR (Sâmbătă 05:55-07:30) și nici peste altă rulare.
|
||||
exec 9>/var/run/vm109-patch-window.lock
|
||||
if ! flock -n 9; then
|
||||
log "Another patch window is already running, exiting"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
VM_STARTED_BY_US=false
|
||||
FLAG_SET_BY_US=false
|
||||
RESULT="UNKNOWN"
|
||||
|
||||
cleanup() {
|
||||
local rc=$?
|
||||
if [ "$VM_STARTED_BY_US" = "true" ] && qm status "$DR_VM_ID" 2>/dev/null | grep -q running; then
|
||||
log "Cleanup: stopping VM $DR_VM_ID"
|
||||
ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /s /t 15 /f" 2>/dev/null || true
|
||||
sleep 45
|
||||
qm stop "$DR_VM_ID" 2>/dev/null || true
|
||||
fi
|
||||
if [ "$FLAG_SET_BY_US" = "true" ]; then
|
||||
rm -f "$DEBUG_FLAG"
|
||||
log "Cleanup: watchdog debug flag cleared"
|
||||
fi
|
||||
log "Patch window finished with result=$RESULT (rc=$rc)"
|
||||
exit $rc
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
wait_for_ssh() {
|
||||
local waited=0
|
||||
while [ $waited -lt $MAX_BOOT_WAIT ]; do
|
||||
if ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 -o StrictHostKeyChecking=no -o BatchMode=yes \
|
||||
"$DR_VM_USER@$DR_VM_IP" "powershell -Command 'Write-Output ready'" >/dev/null 2>&1; then
|
||||
log "VM responsive after ${waited}s"
|
||||
return 0
|
||||
fi
|
||||
sleep 10
|
||||
waited=$((waited + 10))
|
||||
done
|
||||
log "ERROR: VM did not become responsive within ${MAX_BOOT_WAIT}s"
|
||||
return 1
|
||||
}
|
||||
|
||||
# NoAutoUpdate: 1 = Windows Update dezactivat (starea normală a VM 109),
|
||||
# 0 = permis, doar pe durata acestei ferestre.
|
||||
set_auto_update() {
|
||||
local value="$1"
|
||||
ssh -p "$DR_VM_PORT" -o ConnectTimeout=15 "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -Command \"Set-ItemProperty -Path 'HKLM:\\SOFTWARE\\Policies\\Microsoft\\Windows\\WindowsUpdate\\AU' -Name NoAutoUpdate -Value $value -Type DWord\"" 2>/dev/null
|
||||
}
|
||||
|
||||
log "=========================================="
|
||||
log "VM $DR_VM_ID monthly patch window - Starting"
|
||||
log "=========================================="
|
||||
|
||||
# Watchdog-ul oprește forțat VM 109 dacă rulează > 60 min în afara ferestrei de
|
||||
# test. Patching-ul depășește ușor 60 min, deci cerem exceptarea documentată.
|
||||
if [ ! -f "$DEBUG_FLAG" ]; then
|
||||
touch "$DEBUG_FLAG"
|
||||
FLAG_SET_BY_US=true
|
||||
log "Watchdog debug flag set (patching exceeds the 60 min watchdog limit)"
|
||||
fi
|
||||
|
||||
if qm status "$DR_VM_ID" 2>/dev/null | grep -q running; then
|
||||
log "ERROR: VM $DR_VM_ID is already running - refusing to interfere"
|
||||
RESULT="SKIPPED_VM_BUSY"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log "Starting VM $DR_VM_ID"
|
||||
qm start "$DR_VM_ID"
|
||||
VM_STARTED_BY_US=true
|
||||
wait_for_ssh || { RESULT="BOOT_FAILED"; exit 1; }
|
||||
|
||||
log "Enabling Windows Update for the duration of this window"
|
||||
set_auto_update 0
|
||||
|
||||
reboots=0
|
||||
while :; do
|
||||
log "Running update scan/download/install..."
|
||||
set +e
|
||||
out=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=20 "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\install_updates.ps1" 2>&1 | tr -d '\r')
|
||||
set -e
|
||||
echo "$out" >> "$LOG"
|
||||
|
||||
found=$(echo "$out" | grep -oP '^FOUND=\K\d+' | tail -1 || echo 0)
|
||||
installed=$(echo "$out"| grep -oP '^INSTALLED=\K\d+' | tail -1 || echo 0)
|
||||
failed=$(echo "$out" | grep -oP '^FAILED=\K\d+' | tail -1 || echo 0)
|
||||
reboot_req=$(echo "$out" | grep -oP '^REBOOT_REQUIRED=\K\S+' | tail -1 || echo false)
|
||||
ps_result=$(echo "$out"| grep -oP '^RESULT=\K\S+' | tail -1 || echo ERROR)
|
||||
|
||||
log "Scan result: found=$found installed=$installed failed=$failed reboot=$reboot_req status=$ps_result"
|
||||
|
||||
if [ "$ps_result" = "NO_UPDATES" ]; then
|
||||
log "System fully patched"
|
||||
RESULT="OK_NO_UPDATES"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$ps_result" = "ERROR" ]; then
|
||||
log "ERROR: update installation reported failures"
|
||||
RESULT="UPDATE_ERRORS"
|
||||
break
|
||||
fi
|
||||
|
||||
if [ "$reboot_req" != "true" ]; then
|
||||
log "Updates installed, no reboot required"
|
||||
RESULT="OK_PATCHED"
|
||||
break
|
||||
fi
|
||||
|
||||
reboots=$((reboots + 1))
|
||||
if [ $reboots -gt $MAX_REBOOTS ]; then
|
||||
log "ERROR: exceeded $MAX_REBOOTS reboot cycles, giving up"
|
||||
RESULT="TOO_MANY_REBOOTS"
|
||||
break
|
||||
fi
|
||||
|
||||
log "Reboot required - rebooting VM (cycle $reboots/$MAX_REBOOTS)"
|
||||
ssh -p "$DR_VM_PORT" -o ConnectTimeout=10 "$DR_VM_USER@$DR_VM_IP" "shutdown /r /t 5 /f" 2>/dev/null || true
|
||||
sleep 60
|
||||
wait_for_ssh || { RESULT="REBOOT_FAILED"; break; }
|
||||
done
|
||||
|
||||
# Re-armăm blocarea, indiferent de rezultat: VM 109 nu are voie să intre în
|
||||
# testul DR de sâmbătă cu Windows Update activ.
|
||||
log "Re-arming NoAutoUpdate=1"
|
||||
set_auto_update 1
|
||||
|
||||
# Verificăm că sistemul rămâne într-o stare curată pentru testul următor.
|
||||
set +e
|
||||
servicing=$(ssh -p "$DR_VM_PORT" -o ConnectTimeout=15 "$DR_VM_USER@$DR_VM_IP" \
|
||||
"powershell -ExecutionPolicy Bypass -File D:\\oracle\\scripts\\check_servicing.ps1" 2>/dev/null | tr -d '\r')
|
||||
set -e
|
||||
log "Post-patch servicing state: ${servicing:-unavailable}"
|
||||
|
||||
if ! echo "${servicing:-}" | grep -q "STATE=IDLE"; then
|
||||
log "WARNING: servicing not idle after patch window - next DR test may be affected"
|
||||
RESULT="${RESULT}_DIRTY"
|
||||
fi
|
||||
|
||||
# Notificare doar când e ceva de semnalat — succesul tăcut e suficient.
|
||||
if [ "$RESULT" != "OK_NO_UPDATES" ] && [ "$RESULT" != "OK_PATCHED" ]; then
|
||||
printf 'VM %s monthly patch window ended with result: %s\n\nLog: %s\n' \
|
||||
"$DR_VM_ID" "$RESULT" "$LOG" \
|
||||
| mail -s "[DR] VM $DR_VM_ID patch window: $RESULT" "$MAIL_TO" 2>/dev/null || true
|
||||
fi
|
||||
Reference in New Issue
Block a user