Files

58 lines
5.1 KiB
Bash

#!/usr/bin/env bash
# Testvorgehen für den Cluster knaster (docs/BETRIEB.md, Abschnitt 13). Läuft auf dem Admin-Laptop
# (Git-Bash) oder auf ops, nur lesend. Jede Zeile ist eine Prüfung mit OK/FEHLER, Exit 1 bei Fehlern.
#
# platform/proxmox/scripts/healthcheck.sh # alle Knoten
# platform/proxmox/scripts/healthcheck.sh pve44 pve52
set -uo pipefail
nodes=("$@"); [ ${#nodes[@]} -gt 0 ] || nodes=(pve44 pve46 pve48 pve52 pve61)
fail=0
ok() { printf ' OK %s\n' "$1"; }
bad() { printf ' FEHLER %s\n' "$1"; fail=1; }
check() { local msg=$1; shift; if "$@" >/dev/null 2>&1; then ok "$msg"; else bad "$msg"; fi; }
r() { ssh -T -o BatchMode=yes -o ConnectTimeout=8 "$@"; }
echo "== Cluster (über ${nodes[0]})"
status=$(r "${nodes[0]}" pvecm status 2>/dev/null)
grep -q 'Quorate: *Yes' <<<"$status" && ok "Quorum vorhanden" || bad "kein Quorum"
votes=$(awk '/^Total votes/ {print $3}' <<<"$status"); expected=$(awk '/^Expected votes/ {print $3}' <<<"$status")
[ -n "$votes" ] && [ "$votes" = "$expected" ] && ok "alle Knoten im Cluster ($votes/$expected)" || bad "Knoten fehlen ($votes/$expected)"
ha=$(r "${nodes[0]}" ha-manager status 2>/dev/null)
grep -q 'quorum OK' <<<"$ha" && ok "HA-Manager: Quorum OK" || bad "HA-Manager meldet kein Quorum"
grep -E '^service' <<<"$ha" | grep -vq 'started' && bad "HA-Dienst nicht gestartet: $(grep -E '^service' <<<"$ha" | grep -v started | tr '\n' ';')" || ok "HA-Dienste gestartet"
repl=$(r "${nodes[0]}" pvesr status 2>/dev/null | tail -n +2)
[ -n "$repl" ] && { grep -vqE ' OK$' <<<"$repl" && bad "Replikation mit Fehler: $(grep -vE ' OK$' <<<"$repl" | tr '\n' ';')" || ok "Replikation: alle Jobs OK"; }
fw=$(r "${nodes[0]}" cat /etc/pve/firewall/cluster.fw 2>/dev/null)
grep -q '^enable: 1' <<<"$fw" && ok "Datacenter-Firewall aktiv" || bad "Datacenter-Firewall nicht aktiv"
grep -q 'IN ACCEPT -source +admin' <<<"$fw" && ok "Firewall: Regel für +admin vorhanden" || bad "Firewall: Regel für +admin fehlt"
matchers=$(r "${nodes[0]}" pvesh get /cluster/notifications/matchers --output-format json 2>/dev/null)
grep -q '"name":"knaster-alles"' <<<"$matchers" && ok "Benachrichtigung: Matcher knaster-alles" || bad "Benachrichtigung: Matcher knaster-alles fehlt"
users=$(r "${nodes[0]}" pveum user list --output-format json 2>/dev/null)
for u in r.rapp@pve healer@pve tofu@pve monitoring@pve; do grep -q "\"userid\":\"$u\"" <<<"$users" && ok "Benutzer $u" || bad "Benutzer $u fehlt"; done
tfa=$(r "${nodes[0]}" pvesh get /access/tfa/r.rapp@pve --output-format json 2>/dev/null)
grep -q '"type":"totp"' <<<"$tfa" && ok "2FA (TOTP) für r.rapp@pve" || bad "2FA für r.rapp@pve fehlt"
for n in "${nodes[@]}"; do
echo "== $n"
out=$(r "$n" 'echo "kernel=$(uname -r)"; echo "failed=$(systemctl --failed --no-legend | wc -l)"; echo "fw=$(pve-firewall status)"; echo "ntp=$(timedatectl show -p NTPSynchronized --value)"; echo "vmbr0=$(ip -br a show vmbr0 | awk "{print \$2}")"; echo "root=$(df --output=pcent / | tail -1 | tr -d " %")"; echo "zpool=$(zpool list -H -o health tank 2>/dev/null || echo none)"; echo "cert=$(openssl x509 -in /etc/pve/local/pve-ssl.pem -noout -checkend 2592000 >/dev/null && echo ok || echo laeuft-ab)"; echo "gpu=$(lspci -nnk -d 10de: 2>/dev/null | grep -c "in use: vfio-pci")"; echo "gpus=$(lspci -n -d 10de: 2>/dev/null | wc -l)"; echo "hang=$(dmesg 2>/dev/null | grep -ci "unit hang")"' 2>/dev/null) || { bad "nicht erreichbar per SSH"; continue; }
v() { awk -F= -v k="$1" '$1==k {print $2}' <<<"$out"; }
ok "erreichbar, Kernel $(v kernel)"
[ "$(v failed)" = 0 ] && ok "keine fehlgeschlagenen Dienste" || bad "$(v failed) fehlgeschlagene Dienste (systemctl --failed)"
[[ "$(v fw)" == *enabled/running* ]] && ok "Knoten-Firewall läuft" || bad "Knoten-Firewall: $(v fw)"
[ "$(v ntp)" = yes ] && ok "Zeit per NTP synchron" || bad "Zeit nicht synchron"
[ "$(v vmbr0)" = UP ] && ok "vmbr0 UP" || bad "vmbr0 nicht UP"
[ "$(v root)" -lt 85 ] && ok "Systemplatte $(v root) % belegt" || bad "Systemplatte $(v root) % belegt"
case "$(v zpool)" in none) ok "kein ZFS-Pool (erwartet auf pve46/pve61)";; ONLINE) ok "zpool tank ONLINE";; *) bad "zpool tank: $(v zpool)";; esac
[ "$(v cert)" = ok ] && ok "Knotenzertifikat länger als 30 Tage gültig" || bad "Knotenzertifikat läuft in 30 Tagen ab"
if [ "$(v gpus)" -gt 0 ]; then [ "$(v gpu)" = "$(v gpus)" ] && ok "alle $(v gpus) NVIDIA-Funktionen an vfio-pci" || bad "nur $(v gpu) von $(v gpus) NVIDIA-Funktionen an vfio-pci"; fi
[ "$(v hang)" = 0 ] && ok "keine NIC-Hänger (e1000e/r8152)" || bad "$(v hang) NIC-Hänger im Kernel-Log"
ip=$(ssh -G "$n" | awk '$1=="hostname"{print $2}')
check "Weboberfläche https://$ip:8006 antwortet" bash -c "curl -sk -m 6 -o /dev/null -w '%{http_code}' https://$ip:8006/ | grep -q 200"
done
echo "== Firewall-Negativtest (aus GPU-VM 246 auf pve46, nicht in +admin)"
neg=$(r pve46 'qm guest exec 246 --timeout 15 -- bash -c "curl -sk -m 4 -o /dev/null -w %{http_code} https://192.168.100.210:8006/ || echo blockiert"' 2>/dev/null)
grep -q blockiert <<<"$neg" && ok "VM erreicht 8006 nicht" || bad "VM erreicht 8006 (Firewall wirkt nicht)"
[ $fail = 0 ] && echo "ERGEBNIS: alles OK" || { echo "ERGEBNIS: Fehler, siehe oben"; exit 1; }