184 lines
7.5 KiB
Markdown
184 lines
7.5 KiB
Markdown
# P42-S.M. – Server-Monitoring-Agent
|
||
|
||
Kleiner Docker-Dienst, der die Auslastung des Ubuntu-Servers misst und als JSON-API bereitstellt.
|
||
**P42-F.W.** (Dashboard) liest die Werte aus und stellt sie dar.
|
||
|
||
Gemessen wird alle 5 Sekunden: CPU (gesamt, je Thread, iowait, Takt, Load), RAM, Swap, Platten
|
||
(Belegung je Laufwerk, Lese-/Schreibrate), Netzwerk (Rate je Schnittstelle und Summe),
|
||
Temperaturen, Prozesse, Uptime. Die letzte Stunde wird als Verlauf vorgehalten.
|
||
|
||
- Nur Python-Standardbibliothek, keine Pakete. Das Image ist `python:3.13-slim` plus etwa 15 KB Code.
|
||
- Liest `/proc` und `/` des Hosts **nur lesend** ein und läuft ohne Root, ohne Capabilities und mit
|
||
schreibgeschütztem Dateisystem.
|
||
- Kein Docker-Socket: der Container sieht keine anderen Container und kann nichts steuern.
|
||
|
||
## Betrieb
|
||
|
||
### Einrichten und Starten
|
||
|
||
```bash
|
||
git clone https://github.com/guyg1474/p42.s-m.git ~/festplatte/dockercontainer/p42-sm
|
||
cd ~/festplatte/dockercontainer/p42-sm
|
||
./deploy/update-docker.sh
|
||
```
|
||
|
||
Beim ersten Aufruf legt das Skript `.env` mit einem zufälligen Token an und gibt ihn aus. **Diesen Token
|
||
an P42-F.W. weitergeben.** Nach etwa 10 Sekunden zeigt `docker compose ps` den Zustand `healthy`.
|
||
|
||
Test auf dem Server:
|
||
|
||
```bash
|
||
curl -H "Authorization: Bearer $(grep ^SM_TOKEN= .env | cut -d= -f2)" http://127.0.0.1:9142/api/metrics
|
||
```
|
||
|
||
### Hoch- und runterfahren, beobachten
|
||
|
||
```bash
|
||
docker compose stop # anhalten
|
||
docker compose start # wieder starten
|
||
docker compose restart # nach Änderungen an .env: besser "docker compose up -d"
|
||
docker compose down # Container entfernen (Netz "p42" wird mit entfernt, falls unbenutzt)
|
||
docker compose ps # Status inkl. healthy/unhealthy
|
||
docker compose logs -f # Log: nur Start, Fehler und abgelehnte Anfragen
|
||
docker stats p42-sm # Verbrauch des Agenten selbst (wenige MB RAM)
|
||
```
|
||
|
||
Updates: `./deploy/update-docker.sh` holt per `git pull` den neuen Stand und baut den Container neu.
|
||
|
||
### Einstellungen (`.env`)
|
||
|
||
| Variable | Standard | Bedeutung |
|
||
|---|---|---|
|
||
| `SM_TOKEN` | – (Pflicht) | Zugangs-Token der API |
|
||
| `SM_PORT` | `9142` | Port auf dem Server |
|
||
| `SM_BIND` | `127.0.0.1` | An welche Adresse der Port gebunden wird. Für Abfragen aus dem LAN die Server-IP, nie `0.0.0.0` |
|
||
| `SM_INTERVAL` | `5` | Messintervall in Sekunden |
|
||
| `SM_HISTORY` | `720` | Anzahl Verlaufspunkte (720 × 5 s = 1 h) |
|
||
| `SM_UID` | `1000` | Benutzer-ID des Containers, siehe unten |
|
||
|
||
**Fehlt eine Platte in `disks`?** Der Container muss die Ordner bis zum Einhängepunkt durchsuchen dürfen.
|
||
Liegt die Platte z. B. unter `/home/<user>/festplatte` (Rechte 750), muss `SM_UID` die UID dieses Benutzers
|
||
sein (`id -u <user>`). Netzlaufwerke (NFS/SMB) werden bewusst nicht gemessen.
|
||
|
||
## Anbindung an P42-F.W.
|
||
|
||
### Erreichbarkeit
|
||
|
||
**A) Dashboard läuft als Container auf demselben Server (empfohlen):** P42-S.M. legt das Docker-Netz `p42` an.
|
||
In der `docker-compose.yml` von P42-F.W. ergänzen:
|
||
|
||
```yaml
|
||
services:
|
||
p42:
|
||
# ... wie bisher ...
|
||
networks: [default, p42]
|
||
|
||
networks:
|
||
p42:
|
||
external: true
|
||
```
|
||
|
||
Danach erreicht das Dashboard-Backend den Dienst unter **`http://p42-sm:9142`**. Ein offener Port nach außen
|
||
ist dafür nicht nötig. P42-S.M. muss zuerst gestartet sein, da es das Netz anlegt.
|
||
|
||
**B) Dashboard läuft auf einem anderen Rechner:** In `.env` `SM_BIND` auf die LAN-IP des Servers setzen.
|
||
Die Adresse lautet dann `http://<server-ip>:9142`. Per Firewall sollte nur der Dashboard-Rechner zugreifen dürfen.
|
||
|
||
Abgefragt wird **serverseitig** aus dem Dashboard-Backend, nicht aus dem Browser. Token und Port bleiben so
|
||
verborgen, deshalb gibt es kein CORS.
|
||
|
||
### API
|
||
|
||
Alle Antworten sind JSON. Byte-Werte sind ganze Zahlen in Byte, Raten in Byte pro Sekunde, Prozente auf
|
||
eine Nachkommastelle gerundet, und Zeitstempel stehen in ISO 8601 mit Zeitzone. Sinnvolles Abfrageintervall:
|
||
alle 5–30 Sekunden; häufiger bringt nichts, weil nur alle `intervalSeconds` neu gemessen wird.
|
||
|
||
| Anfrage | Token | Antwort |
|
||
|---|---|---|
|
||
| `GET /api/health` | nein | `200 {"status":"ok"}`, bzw. `503`, solange noch keine Werte da sind oder die Messung hängt |
|
||
| `GET /api/metrics` | ja | aktueller Stand, siehe unten |
|
||
| `GET /api/history` | ja | Verlauf; `?minutes=N` begrenzt auf die letzten N Minuten |
|
||
|
||
Token als Header: `Authorization: Bearer <SM_TOKEN>`. Fehlt er oder ist er falsch, kommt `401`.
|
||
Bis die erste Messung vorliegt (etwa 1 s nach dem Start), kommt `503`.
|
||
|
||
#### `GET /api/metrics`
|
||
|
||
```json
|
||
{
|
||
"timestamp": "2026-10-14T14:59:01+02:00",
|
||
"intervalSeconds": 5,
|
||
"host": { "hostname": "server", "os": "Ubuntu 24.04.1 LTS", "kernel": "6.8.0-45-generic", "uptimeSeconds": 15943 },
|
||
"cpu": {
|
||
"model": "AMD Ryzen 7 5700X 8-Core Processor",
|
||
"threads": 16,
|
||
"percent": 6.2,
|
||
"iowaitPercent": 0.0,
|
||
"perCorePercent": [2.6, 2.0, 5.5, 21.0],
|
||
"mhz": 2894,
|
||
"load": { "1m": 0.81, "5m": 0.91, "15m": 0.8 }
|
||
},
|
||
"memory": { "totalBytes": 16682397696, "usedBytes": 6809317376, "availableBytes": 9873080320, "cachedBytes": 5789290496, "percent": 40.8 },
|
||
"swap": { "totalBytes": 536866816, "usedBytes": 0, "percent": 0.0 },
|
||
"disks": [
|
||
{ "mount": "/", "device": "/dev/sda2", "fs": "ext4", "totalBytes": 983040749568, "usedBytes": 883751505920, "freeBytes": 49278058496, "percent": 94.7 }
|
||
],
|
||
"diskIo": { "readBytesPerSec": 44951, "writeBytesPerSec": 73557 },
|
||
"network": {
|
||
"rxBytesPerSec": 10279,
|
||
"txBytesPerSec": 51353,
|
||
"interfaces": [
|
||
{ "interface": "eth0", "virtual": false, "rxBytesPerSec": 10279, "txBytesPerSec": 51353, "rxBytesTotal": 704405487, "txBytesTotal": 89327228 }
|
||
]
|
||
},
|
||
"temperatures": [
|
||
{ "sensor": "k10temp", "label": "Tctl", "celsius": 51.0, "criticalCelsius": null },
|
||
{ "sensor": "nvme", "label": "Composite", "celsius": 39.85, "criticalCelsius": 79.85 }
|
||
],
|
||
"processes": { "total": 429, "running": 1 }
|
||
}
|
||
```
|
||
|
||
Hinweise zu den Feldern:
|
||
|
||
- `memory.usedBytes` ist der tatsächlich belegte Speicher, also ohne Cache, der jederzeit freigegeben werden
|
||
kann. Das entspricht der Spalte „used“ von `free`.
|
||
- `disks[].percent` wird wie bei `df` berechnet. `freeBytes` ist der für Benutzer verfügbare Platz.
|
||
- `network.rxBytesPerSec`/`txBytesPerSec` summiert nur echte Schnittstellen. Docker-Bridges und `veth*`
|
||
stehen mit `"virtual": true` in der Liste, werden aber nicht mitgezählt, sonst zählt Container-Verkehr doppelt.
|
||
- `temperatures`: welche Sensoren es gibt, hängt von der Hardware ab. Die Liste kann leer sein (z. B. bei VMs)
|
||
und `criticalCelsius` kann `null` sein.
|
||
- `cpu.mhz` ist der Durchschnitt aller Threads und kann bei VMs `null` sein.
|
||
|
||
#### `GET /api/history?minutes=30`
|
||
|
||
```json
|
||
{
|
||
"intervalSeconds": 5,
|
||
"points": [
|
||
{ "timestamp": "2026-10-14T14:58:59+02:00", "cpuPercent": 5.0, "memoryPercent": 40.9, "load1m": 0.81,
|
||
"rxBytesPerSec": 2769, "txBytesPerSec": 6111, "readBytesPerSec": 0, "writeBytesPerSec": 2757518 }
|
||
]
|
||
}
|
||
```
|
||
|
||
Älteste Punkte zuerst. Der Verlauf liegt nur im Speicher und beginnt nach einem Neustart des Containers von vorn.
|
||
Für längere Zeiträume speichert das Dashboard die Werte selbst.
|
||
|
||
## Lokal testen (ohne Docker)
|
||
|
||
```bash
|
||
HOST_PROC=/proc HOST_ROOT=/ SM_TOKEN=test PORT=9142 HOST=127.0.0.1 python3 -m app
|
||
```
|
||
|
||
## Aufbau
|
||
|
||
```
|
||
app/collector.py liest /proc und /sys, berechnet Raten aus zwei Messungen
|
||
app/server.py HTTP-API, Token-Prüfung, Mess-Thread, Verlauf
|
||
app/__main__.py Start, sauberes Beenden bei "docker stop"
|
||
Dockerfile, docker-compose.yml
|
||
deploy/update-docker.sh Bauen/Aktualisieren, legt beim ersten Mal .env mit Token an
|
||
.env Token und Einstellungen (nicht ins Git)
|
||
```
|