418 lines
16 KiB
Markdown
418 lines
16 KiB
Markdown
# 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 `-j` JSON-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`.
|
||
|
||
```toml
|
||
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
|
||
|
||
```bash
|
||
# 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
|
||
|
||
```elixir
|
||
# 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):
|
||
```json
|
||
{
|
||
"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):
|
||
```json
|
||
// 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
|
||
|
||
1. **Alerts** (wenn klar ist, welche Thresholds sinnvoll sind)
|
||
2. **SMART-Monitoring** (für ZFS-Vorhersage)
|
||
3. **Backup-Task-Tracking**
|
||
4. **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.
|