16 KiB
Proxmox Monitor — Konzept
Eine Agent-Server-Anwendung zum Monitoring von Proxmox-Hosts mit Fokus auf ZFS-Gesundheit und VM-Übersicht. Implementiert in Elixir/OTP.
Designprinzipien
- KISS: Jede Entscheidung zugunsten der einfacheren Lösung, solange sie funktioniert.
- YAGNI: Features werden erst gebaut, wenn sie konkret gebraucht werden — nicht prophylaktisch.
- Read-only: Der Agent führt keine verändernden Commands auf dem Proxmox-Host aus.
- Push-Architektur: Agents initiieren die Verbindung zum Server (NAT-freundlich).
Architektur-Übersicht
┌──────────────────┐ ┌────────────────────┐
│ Proxmox-Host 1 │ │ Server (LXC) │
│ ┌────────────┐ │ │ im Rechenzentrum │
│ │ Agent │──┼──WSS──┐ │ │
│ └────────────┘ │ │ │ ┌──────────────┐ │
└──────────────────┘ └──▶│ │ Caddy │ │
│ │ Reverse Proxy│ │
┌──────────────────┐ │ └──────┬───────┘ │
│ Proxmox-Host 2 │ │ │ │
│ ┌────────────┐ │ │ ┌──────▼───────┐ │
│ │ Agent │──┼──WSS─────▶│ │ Phoenix │ │
│ └────────────┘ │ │ │ LiveView │ │
└──────────────────┘ │ └──────┬───────┘ │
... │ │ │
│ ┌──────▼───────┐ │
┌──────────────────┐ │ │ SQLite │ │
│ Proxmox-Host N │ │ └──────────────┘ │
│ ┌────────────┐ │ │ │
│ │ Agent │──┼──WSS─────▶│ │
│ └────────────┘ │ │ │
└──────────────────┘ └────────────────────┘
Technologie-Stack
| Komponente | Technologie | Begründung |
|---|---|---|
| Agent | Elixir + Burrito | Eigenständige Binary, keine Erlang-Installation auf Proxmox nötig |
| Server | Phoenix + LiveView | Realtime-UI ohne separates Frontend |
| Transport | Phoenix Channels (WSS) | Persistente Verbindung, Auto-Reconnect, Offline-Detection gratis |
| Datenbank | SQLite + Ecto | Für ~20 Hosts vollkommen ausreichend, keine separate DB-Instanz |
| TLS / Reverse Proxy | Caddy (bereits vorhanden) | Let's Encrypt automatisch |
| Deployment (Server) | LXC-Container auf Proxmox im RZ | Geringer Overhead, saubere Isolation |
| Deployment (Agent) | systemd-Service | Standard auf Debian/Proxmox |
Systemanforderungen
- Proxmox-Hosts: Proxmox VE 8.3+ mit OpenZFS 2.3+ (für
-jJSON-Output) - Server: LXC oder VM mit ausreichend RAM (1 GB reicht), Debian/Ubuntu
- Netzwerk: Server muss öffentlich über HTTPS erreichbar sein (via Caddy)
Agent
Verantwortlichkeiten
Der Agent läuft auf jedem Proxmox-Host, sammelt in festen Intervallen Metriken und schickt sie an den Server. Er hält eine persistente WebSocket-Verbindung zum Server über Phoenix Channels.
Sammlungs-Intervalle
Nicht alles muss gleich häufig gesammelt werden. Daten mit hoher Änderungsrate werden öfter abgefragt, statische Informationen seltener.
| Intervall | Daten |
|---|---|
| 30 Sekunden | Host-Metriken, VM-Runtime-Status, ZFS-Pool-Status, Storage-Auslastung |
| 5 Minuten | Snapshots, Dataset-Liste, VM-Config, Guest-Agent-IPs |
| 30 Minuten | Proxmox-Version, pending APT-Updates, ZFS-Version |
Zu sammelnde Daten
Host-Metriken (aus /proc und uptime)
- CPU-Auslastung (%), Load-Average (1/5/15)
- RAM: used, total, available
- Uptime in Sekunden
- Root-Filesystem: used, total
- Hostname, Kernel-Version
Proxmox-Storage (pvesh get /nodes/<node>/storage --output-format json)
- Alle konfigurierten Storages (ZFS, NFS, Local, etc.)
- Typ, Status (active/inactive), used, total
- Content-Typen (images, backup, iso, ...)
ZFS-Pools (zpool status -j --json-flat-vdevs --json-int, zpool list -j --json-int)
- Rohes JSON (vollständig gespeichert für spätere Analyse)
- Zusätzlich extrahiertes Summary:
- Pool-Name, Health-State
- Size, Allocated, Free (Bytes)
- Fragmentation (%), Capacity (%)
- Error-Counter (read/write/checksum)
- Scrub-Status, letzter erfolgreicher Scrub
- Anzahl vdevs, Anzahl degraded vdevs
ZFS-Datasets & Snapshots (zfs list -j --json-int)
- Rohes JSON (Datasets und Snapshots)
- Snapshot-Summary pro Dataset:
- Anzahl Snapshots
- Alter des ältesten / neuesten Snapshots
- Gesamt-Speicher (usedbysnapshots)
Virtuelle Maschinen & LXC-Container (pvesh)
- Statisch (aus Config): VMID, Name, Type (qemu/lxc), Cores, RAM (max), Disks (mit Storage-Backend), Tags, Autostart
- Dynamisch (aus Runtime-Status): Status, Uptime, CPU-Auslastung, RAM-Verbrauch, Disk-I/O, Netzwerk-I/O
- Via Guest-Agent (QEMU, optional): IP-Adressen aller Interfaces, OS-Info, Disk-Usage im Guest
- LXC: IPs direkt aus Container-Config
System-Info
- Proxmox-Version (
pveversion) - Anzahl verfügbarer Updates (
apt list --upgradable 2>/dev/null | wc -l) - ZFS-Version
- Agent-Version (fest einkompiliert)
Konfiguration
Datei: /etc/proxmox-monitor/agent.toml, Zugriffsrechte 0600, Eigentümer root.
server_url = "wss://monitor.example.com/socket/websocket"
token = "agent_abc123xyz..."
host_id = "pve-host-01" # optional, sonst Hostname
[intervals]
fast_seconds = 30
medium_seconds = 300
slow_seconds = 1800
Deployment
# Einmaliges Setup auf einem Proxmox-Host
scp proxmox-monitor-agent root@pve-host-01:/usr/local/bin/
scp agent.toml root@pve-host-01:/etc/proxmox-monitor/
scp proxmox-monitor-agent.service root@pve-host-01:/etc/systemd/system/
ssh root@pve-host-01 "systemctl enable --now proxmox-monitor-agent"
Der Agent muss als root laufen, da zpool status bei Degraded-Pools und einigen ZFS-Details Root-Rechte benötigt. Das ist akzeptabel, weil der Agent ausschließlich lesende Commands ausführt und keine eingehenden Verbindungen akzeptiert.
Server
Verantwortlichkeiten
- Nimmt Verbindungen von Agents entgegen, authentifiziert sie
- Speichert Metriken in SQLite
- Stellt LiveView-Dashboard bereit
- Cleanup alter Metriken (Retention)
Datenmodell
# hosts
- id (PK)
- name (unique)
- token_hash (bcrypt)
- created_at
- last_seen_at
- agent_version (nullable)
- proxmox_version (nullable)
- zfs_version (nullable)
- status ("online" | "offline" | "never_connected")
# metrics (Zeitreihe, ein Eintrag pro Sample)
- id (PK)
- host_id (FK)
- collected_at (indexed)
- interval_type ("fast" | "medium" | "slow")
- payload (JSON) # Gesamter Sample-Inhalt
# Indexiert wegen Dashboard-Queries:
# - (host_id, collected_at DESC) für "letzte N Samples eines Hosts"
# - (collected_at) für Retention-Cleanup
Begründung für JSON-Payload statt normalisierter Spalten:
Die Daten werden fast immer vollständig pro Host/Zeitpunkt gelesen (für die Detail-View). Separate Spalten pro Metrik würden die Schema-Evolution aufwendig machen. SQLite unterstützt JSON-Operatoren (->>) für Queries, falls wir mal einzelne Felder filtern müssen.
Phoenix Channels — Protokoll
Join (Agent → Server):
{
"topic": "host:pve-host-01",
"payload": {
"token": "agent_abc123xyz...",
"agent_version": "0.1.0"
}
}
Server validiert Token gegen hosts.token_hash. Bei Erfolg: Assign der host_id an den Socket, last_seen_at aktualisieren, Status auf online setzen.
Events (Agent → Server):
// Event: "metric:fast" | "metric:medium" | "metric:slow"
{
"collected_at": "2026-04-21T12:34:56Z",
"data": { /* je nach Intervall-Typ */ }
}
Disconnect-Handling (Server):
Wenn der Channel-Prozess stirbt (Terminate-Callback), wird der Host sofort als offline markiert. Kein Polling, keine Timeouts nötig.
LiveView-Seiten
1. Übersicht (/)
- Eine Karte pro Host, Grid-Layout
- Status-Ampel: Grün (alles ok) / Gelb (Warnung) / Rot (kritisch) / Grau (offline)
- Kritisch = Pool DEGRADED/FAULTED, Capacity > 90%, Agent offline
- Warnung = Capacity 80-90%, alte Snapshots, pending Updates, Scrub überfällig
- Pro Karte sichtbar: Host-Name, CPU, RAM, Uptime, Pool-Status (Zusammenfassung), VM-Anzahl
- Sortier-/Filter-Optionen: nach Status, nach Host-Name
2. Host-Detail (/hosts/:name)
- Header mit Proxmox-Version, Uptime, letzter Kontakt
- Tabs oder Sektionen:
- Metriken: CPU/RAM/Load-Graphen über 24h (simple Line-Charts mit Chart.js oder Contex)
- ZFS-Pools: Pro Pool eine Box mit Health, Capacity-Bar, Fragmentation, Error-Counters, Scrub-Info, vdev-Liste
- Snapshots: Tabelle pro Dataset mit Anzahl, Alter des ältesten/neuesten Snapshots
- Storage: Tabelle aller Proxmox-Storages mit Auslastung
- VMs/LXCs: Tabelle mit Name, Type, Status, CPU/RAM-Auslastung, IPs, Tags
3. VM-Suche (/vms)
- Globale Suche über alle Hosts
- Input: Name oder IP
- Ergebnis: Tabelle mit VM-Name, Host, Status, IP, Ressourcen
- Killer-Feature bei 20 Hosts: "Wo läuft
nginx-proxy?"
4. Host-Verwaltung (/admin/hosts)
- Liste aller Hosts mit Status, letzter Kontakt, Agent-Version
- Button "Neuen Host hinzufügen": Formular mit Name → generiert Token, zeigt Install-Anleitung
- Pro Host: "Token revoken" (setzt neuen Token), "Host löschen"
Authentifizierung
Web-UI:
- Single-User, Login via Passwort (Argon2-Hash in Environment-Variable oder Secret-Config)
- Minimal-Setup mit einer einzigen Session/Cookie
- Kein User-Management-UI nötig
Agents:
- Shared Token pro Agent
- Token-Generierung: 32 Bytes Random, Base64-URL-encoded
- In DB als Bcrypt-Hash gespeichert
- Revoke durch Token-Neugenerierung im Admin-UI
Retention / Cleanup
Ein GenServer läuft stündlich und löscht:
metricsälter als 48 Stunden
Das reicht für "was ist in den letzten 2 Tagen passiert"-Analysen. Bei 20 Hosts × 30s-Samples × 48h = ca. 230.000 Rows. Völlig unproblematisch für SQLite.
Projekt-Struktur
Monorepo mit zwei getrennten Mix-Projekten:
proxmox_monitor/
├── README.md
├── konzept.md # Dieses Dokument
│
├── agent/ # Mix-Projekt → Burrito-Binary
│ ├── mix.exs
│ ├── config/
│ │ ├── config.exs
│ │ └── runtime.exs
│ ├── lib/
│ │ ├── agent.ex
│ │ ├── agent/
│ │ │ ├── application.ex
│ │ │ ├── config.ex
│ │ │ ├── reporter.ex # Phoenix Channel Client
│ │ │ ├── supervisor.ex
│ │ │ └── collectors/
│ │ │ ├── host.ex
│ │ │ ├── storage.ex
│ │ │ ├── zfs.ex
│ │ │ ├── vms.ex
│ │ │ └── system_info.ex
│ │ └── agent/schema/ # Structs für Samples
│ │ ├── sample.ex
│ │ ├── pool_summary.ex
│ │ └── ...
│ ├── rel/
│ │ └── burrito.exs
│ └── test/
│
└── server/ # Phoenix-Projekt
├── mix.exs
├── config/
├── lib/
│ ├── server/
│ │ ├── application.ex
│ │ ├── hosts.ex # Kontext: Host-Verwaltung
│ │ ├── metrics.ex # Kontext: Metrik-Speicherung
│ │ ├── retention.ex # GenServer: Cleanup
│ │ └── schema/
│ │ ├── host.ex
│ │ └── metric.ex
│ └── server_web/
│ ├── router.ex
│ ├── endpoint.ex
│ ├── channels/
│ │ ├── agent_socket.ex
│ │ └── host_channel.ex
│ ├── live/
│ │ ├── overview_live.ex
│ │ ├── host_detail_live.ex
│ │ ├── vm_search_live.ex
│ │ └── admin_hosts_live.ex
│ └── controllers/
│ └── auth_controller.ex
├── priv/
│ └── repo/migrations/
└── test/
Sicherheit
- TLS erzwungen über Caddy, Agent verifiziert Server-Zertifikat
- Token niemals in Plaintext in DB (Bcrypt) oder Logs
- Agent-Config mit Rechten
0600, Root - Rate-Limiting auf Channel-Join via Hammer-Library (z.B. 5 Versuche/Minute pro IP)
- Keine eingehenden Verbindungen zum Agent — Agent initiiert immer
- Server-URL im Agent fest gepinnt (keine Redirects)
- Read-only auf dem Proxmox-Host — Agent führt keine verändernden Commands aus
MVP-Scope — Was ist drin, was nicht
✅ Im MVP
- Agent sammelt Host-, ZFS-, VM-, Storage-, System-Metriken
- Persistente WebSocket-Verbindung via Phoenix Channels
- SQLite-Speicherung mit 48h Retention
- Dashboard: Übersicht, Host-Detail, VM-Suche, Admin
- Single-User-Auth für Web-UI
- Token-basierte Agent-Auth
- Burrito-Binary für Agent
- LXC-Deployment für Server
❌ Bewusst nicht im MVP (YAGNI)
- Alerts per E-Mail/Telegram/Gotify — Dashboard zeigt farbig, reicht erstmal
- SMART-Werte der Disks — eigenes Unterprojekt, später
- Backup-Task-Historie — aufwendiges Parsing, später
- Cluster-Support — nicht benötigt
- Agent-Self-Update — manuell via scp reicht bei 20 Hosts
- Remote-Actions (VM starten, Scrub triggern) — Read-only bleibt Read-only
- Multi-User, RBAC — Single-User reicht
- Langzeit-Historie (>48h) — separates Konzept (Downsampling) wenn gebraucht
- Mobile App — Web-UI responsive reicht
Roadmap
Phase 1 — Grundgerüst (Woche 1-2)
- Agent-Skeleton: Ein Collector (Host-Metriken), Output auf Console
- Server-Skeleton: Phoenix-Projekt, Host-Schema, einfacher Channel, der Daten empfängt und loggt
- Ende: Agent verbindet sich lokal zum Server und pusht CPU-Metriken
Phase 2 — ZFS & VMs (Woche 3-4)
- ZFS-Collector (pool status, list, snapshots)
- VM-Collector (pvesh)
- Storage-Collector
- Server speichert in SQLite, simple Anzeige-Route
Phase 3 — LiveView-Dashboard (Woche 5-6)
- Übersichtsseite mit Status-Ampeln
- Host-Detail mit allen Sektionen
- VM-Suche
- Auth für Web-UI
Phase 4 — Admin & Deployment (Woche 7)
- Admin-UI für Host-Verwaltung
- Retention-GenServer
- Burrito-Build für Agent
- LXC-Deployment-Dokumentation
Phase 5 — Produktiv-Rollout (Woche 8)
- Deployment auf Server im RZ
- Caddy-Konfiguration
- Agent auf 2-3 Test-Hosts ausrollen
- Nach erfolgreichem Test: Rollout auf alle 20 Hosts
Nach dem MVP — ggf. als nächstes
- Alerts (wenn klar ist, welche Thresholds sinnvoll sind)
- SMART-Monitoring (für ZFS-Vorhersage)
- Backup-Task-Tracking
- Langzeit-Historie mit Downsampling
Offene Entscheidungen / Vormerkungen
Diese Punkte können beim Implementieren entschieden werden, sind aber fürs Konzept nicht kritisch:
- Chart-Library: Chart.js (JS, via LiveView-Hook) vs. Contex (SVG, pure Elixir). Contex ist "eleganter", Chart.js bietet mehr Optionen out-of-the-box.
- Shared Structs zwischen Agent/Server: Anfangs duplizieren. Falls das wehtut, später in eine geteilte Lib extrahieren.
- Exception-Handling bei pvesh/zpool-Fehlern: Ausfall einer Datenquelle darf nicht das ganze Sample verwerfen. Teil-Samples mit Fehler-Flag akzeptieren.
- Logs: Strukturiert (JSON) vs. Text. Vorschlag: Server strukturiert für spätere Auswertung, Agent textbasiert für einfaches journalctl-Debugging.