From 8b1338f824cfe50136ad3c08a102a4e121919034 Mon Sep 17 00:00:00 2001 From: Folzo Date: Sun, 12 Apr 2026 09:29:59 +0200 Subject: [PATCH] feat(beszel): Installation Beszel monitoring infrastructure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Ajout Hub Beszel pour Synology NAS (192.168.1.25) - docker-compose-hub.yml - Ajout Agent FolzoCorp avec GPU AMD RX 7800 XT - docker-compose-agent-folzocorp.yml (amd_sysfs) - Ajout Agent EffiServer (CPU only, SKIP_GPU) - docker-compose-agent-effiserver.yml - Documentation: docs/BESZEL-INSTALLATION.md - Script client Python: scripts/beszel_client.py - Permet à Effi d'interroger l'API Beszel --- beszel/docker-compose-agent-effiserver.yml | 16 ++ beszel/docker-compose-agent-folzocorp.yml | 25 ++ beszel/docker-compose-hub.yml | 23 ++ docs/BESZEL-INSTALLATION.md | 320 +++++++++++++++++++++ scripts/beszel_client.py | 218 ++++++++++++++ 5 files changed, 602 insertions(+) create mode 100644 beszel/docker-compose-agent-effiserver.yml create mode 100644 beszel/docker-compose-agent-folzocorp.yml create mode 100644 beszel/docker-compose-hub.yml create mode 100644 docs/BESZEL-INSTALLATION.md create mode 100755 scripts/beszel_client.py diff --git a/beszel/docker-compose-agent-effiserver.yml b/beszel/docker-compose-agent-effiserver.yml new file mode 100644 index 0000000..d4283d3 --- /dev/null +++ b/beszel/docker-compose-agent-effiserver.yml @@ -0,0 +1,16 @@ +# Beszel Agent - EffiServer (192.168.1.27) +# Agent pour monitoring EffiServer (Intel J5040) + +services: + beszel-agent: + image: henrygd/beszel-agent:latest + container_name: beszel-agent + restart: unless-stopped + network_mode: host + volumes: + - ./beszel_agent_data:/var/lib/beszel-agent + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + HUB_URL: http://192.168.1.25:8090 + # Pas de GPU sur effi-server (Intel J5040) + SKIP_GPU: "true" diff --git a/beszel/docker-compose-agent-folzocorp.yml b/beszel/docker-compose-agent-folzocorp.yml new file mode 100644 index 0000000..392fd75 --- /dev/null +++ b/beszel/docker-compose-agent-folzocorp.yml @@ -0,0 +1,25 @@ +# Beszel Agent - FolzoCorp (192.168.1.11) +# Agent pour monitoring FolzoCorp avec GPU AMD RX 7800 XT + +services: + beszel-agent: + image: henrygd/beszel-agent:latest + container_name: beszel-agent + restart: unless-stopped + network_mode: host + volumes: + - ./beszel_agent_data:/var/lib/beszel-agent + - /var/run/docker.sock:/var/run/docker.sock:ro + - /dev/kfd:/dev/kfd + - /dev/dri:/dev/dri + environment: + HUB_URL: http://192.168.1.25:8090 + # GPU AMD - utilise amd_sysfs ou rocm-smi automatiquement + GPU_COLLECTOR: amd_sysfs + #amd_sysfs pour AMD RX 7800 XT (Navi 32) + devices: + - /dev/kfd:/dev/kfd + - /dev/dri:/dev/dri + group_add: + - video + - render diff --git a/beszel/docker-compose-hub.yml b/beszel/docker-compose-hub.yml new file mode 100644 index 0000000..ceb6371 --- /dev/null +++ b/beszel/docker-compose-hub.yml @@ -0,0 +1,23 @@ +# Beszel Hub - Synology NAS +# Monitoring central pour FolzoCorp et EffiServer + +services: + beszel: + image: henrygd/beszel:latest + container_name: beszel + restart: unless-stopped + environment: + APP_URL: http://192.168.1.25:8090 + TZ: Europe/Paris + ports: + - "8090:8090" + volumes: + - ./beszel_data:/beszel_data + - /var/run/docker.sock:/var/run/docker.sock:ro + networks: + - beszel_network + +networks: + beszel_network: + name: beszel_network + driver: bridge diff --git a/docs/BESZEL-INSTALLATION.md b/docs/BESZEL-INSTALLATION.md new file mode 100644 index 0000000..f2b0689 --- /dev/null +++ b/docs/BESZEL-INSTALLATION.md @@ -0,0 +1,320 @@ +# Installation Beszel - Monitoring Infrastructure + +## Vue d'ensemble + +Beszel est un système de monitoring léger pour serveurs avec historique, stats Docker et alertes. + +### Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ NAS Synology (192.168.1.25) │ +│ Beszel Hub │ +│ Port: 8090 │ +└─────────────────────────────────────────────────────────────┘ + │ │ + │ │ + ▼ ▼ +┌─────────────────────────────┐ ┌─────────────────────────────┐ +│ FolzoCorp (192.168.1.11) │ │ EffiServer (192.168.1.27) │ +│ Agent Beszel │ │ Agent Beszel │ +│ GPU: AMD RX 7800 XT │ │ CPU: Intel J5040 │ +│ RAM: 32GB │ │ RAM: 8GB │ +└─────────────────────────────┘ └─────────────────────────────┘ +``` + +--- + +## 1. Installation Hub - NAS Synology (192.168.1.25) + +### Étape 1: Copier les fichiers + +```bash +# Sur le NAS Synology (via SSH) +mkdir -p ~/beszel && cd ~/beszel + +# Copier le fichier docker-compose depuis FolzoCorp +scp folzo@192.168.1.11:~/Documents/OpenCode/Projets/Effi/beszel/docker-compose-hub.yml ./ +``` + +### Étape 2: Démarrer le Hub + +```bash +cd ~/beszel +docker compose up -d +``` + +### Étape 3: Créer le compte admin + +1. Ouvrir http://192.168.1.25:8090 +2. Créer un compte admin +3. Noter les identifiants + +--- + +## 2. Installation Agent - FolzoCorp (192.168.1.11) + +### Étape 1: Préparer le répertoire + +```bash +mkdir -p ~/beszel-agent && cd ~/beszel-agent +``` + +### Étape 2: Copier la configuration + +Le fichier `docker-compose-agent-folzocorp.yml` est dans le projet Effi: +```bash +cp ~/Documents/OpenCode/Projets/Effi/beszel/docker-compose-agent-folzocorp.yml ./ +``` + +### Étape 3: Démarrer l'agent + +```bash +docker compose up -d +``` + +### Étape 4: Ajouter le système dans Beszel UI + +1. Ouvrir http://192.168.1.25:8090 +2. Cliquer **Add System** +3. Choisir **Socket** comme type +4. Utiliser le chemin: `/var/run/beszel-agent/beszel.sock` +5. Récupérer le TOKEN et KEY affichés + +### Étape 5: Configurer TOKEN et KEY + +```bash +# Arrêter l'agent +docker compose down + +# Éditer docker-compose.yml et ajouter TOKEN et KEY +nano docker-compose.yml + +# Redémarrer +docker compose up -d +``` + +### Vérification GPU + +```bash +# Vérifier que le monitoring GPU fonctionne +docker exec beszel-agent beszel-agent status + +# Ou vérifier les logs +docker logs beszel-agent | grep -i gpu +``` + +--- + +## 3. Installation Agent - EffiServer (192.168.1.27) + +### Étape 1: Se connecter en SSH + +```bash +ssh effi@192.168.1.27 +``` + +### Étape 2: Préparer le répertoire + +```bash +mkdir -p ~/beszel-agent && cd ~/beszel-agent +``` + +### Étape 3: Copier la configuration + +```bash +scp folzo@192.168.1.11:~/Documents/OpenCode/Projets/Effi/beszel/docker-compose-agent-effiserver.yml ./ +``` + +### Étape 4: Démarrer l'agent + +```bash +docker compose up -d +``` + +### Étape 5: Ajouter dans Beszel UI + +Même procédure que FolzoCorp, mais utiliser `/var/run/beszel-agent/beszel.sock` + +--- + +## 4. Endpoints API Beszel + +Beszel expose une API REST pour qu'Effi puisse récupérer les métriques. + +### Authentification + +```bash +# Login pour obtenir un token +curl -X POST http://192.168.1.25:8090/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"identity": "admin", "password": "votre_password"}' +``` + +### Récupérer les stats d'un système + +```bash +# Lister les systèmes +curl http://192.168.1.25:8090/api/systems \ + -H "Authorization: Bearer " + +# Stats détaillées d'un système +curl http://192.168.1.25:8090/api/systems//stats \ + -H "Authorization: Bearer " +``` + +### Exemple de réponse stats + +```json +{ + "cpu": 12.5, + "memory": 45.2, + "swap": 8.1, + "disk": 67.3, + "load": [1.2, 0.8, 0.5], + "temperature": {}, + "gpu": { + "AMD RX 7800 XT": { + "memory_used": 1788194816, + "memory_total": 17163091968, + "utilization_gpu": 3, + "power_draw": 20 + } + }, + "containers": [ + { + "id": "abc123", + "name": "comfyui", + "cpu_percent": 0.5, + "memory_percent": 48.69 + } + ] +} +``` + +--- + +## 5. Scripts Effi pour interroger Beszel + +### Script Python pour récupérer les stats + +```python +import requests +import os + +BESZEL_URL = "http://192.168.1.25:8090" +USERNAME = os.getenv("BESZEL_USER", "admin") +PASSWORD = os.getenv("BESZEL_PASSWORD", "") + +def get_beszel_token(): + response = requests.post(f"{BESZEL_URL}/api/auth/login", json={ + "identity": USERNAME, + "password": PASSWORD + }) + return response.json().get("token") + +def get_system_stats(system_id, token): + headers = {"Authorization": f"Bearer {token}"} + response = requests.get(f"{BESZEL_URL}/api/systems/{system_id}/stats", headers=headers) + return response.json() + +def check_resource_usage(): + token = get_beszel_token() + + # FolzoCorp + folzo_stats = get_system_stats("folzocorp", token) + if folzo_stats["memory"]["used_percent"] > 90: + print("⚠️ FolzoCorp RAM > 90%") + + # EffiServer + effi_stats = get_system_stats("effiserver", token) + if effi_stats["cpu"] > 80: + print("⚠️ EffiServer CPU > 80%") +``` + +--- + +## 6. Alertes + +### Configurer des alertes dans Beszel UI + +1. Ouvrir http://192.168.1.25:8090 +2. Cliquer sur un système → **Edit** +3. Aller dans **Alerts** tab +4. Configurer les alertes souhaitées: + +| Métrique | Seuil Warning | Seuil Critical | +|----------|---------------|----------------| +| CPU | 70% | 90% | +| RAM | 80% | 95% | +| VRAM | 80% | 95% | +| Disk | 80% | 90% | + +--- + +## 7. Dépannage + +### Agent ne démarre pas + +```bash +# Vérifier les logs +docker logs beszel-agent + +# Vérifier la connectivité au Hub +curl http://192.168.1.25:8090/health +``` + +### GPU non détecté sur FolzoCorp + +```bash +# Vérifier les permissions GPU +ls -la /dev/kfd +ls -la /dev/dri/ + +# Tester rocm-smi manuellement +rocm-smi --showmeminfo vram +``` + +### Socket non accessible + +```bash +# Vérifier que le socket existe +ls -la /var/run/beszel-agent/ + +# Redémarrer l'agent +docker restart beszel-agent +``` + +--- + +## Fichiers de configuration + +| Fichier | Emplacement | Description | +|---------|-------------|-------------| +| `docker-compose-hub.yml` | Synology NAS | Configuration Hub | +| `docker-compose-agent-folzocorp.yml` | FolzoCorp | Agent FolzoCorp avec GPU AMD | +| `docker-compose-agent-effiserver.yml` | EffiServer | Agent EffiServer (CPU only) | + +--- + +## Monitoring GPU + +### FolzoCorp (AMD RX 7800 XT) + +- Utilise `amd_sysfs` collector (recommandé) +- Métriques: VRAM, utilisation GPU, power draw +- Pas besoin de `rocm-smi` (amd_sysfs plus efficace) + +### EffiServer (Intel J5040) + +- GPU monitoring désactivé (`SKIP_GPU=true`) +- J5040 n'a pas de GPU dédié + +--- + +## Liens utiles + +- [Beszel GitHub](https://github.com/henrygd/beszel) +- [Documentation Beszel](https://beszel.dev) +- [GPU Monitoring](https://beszel.dev/guide/gpu) +- [REST API](https://beszel.dev/guide/rest-api) diff --git a/scripts/beszel_client.py b/scripts/beszel_client.py new file mode 100755 index 0000000..fd1b466 --- /dev/null +++ b/scripts/beszel_client.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +# Beszel API Client - Pour qu'Effi interroge les métriques Beszel +# Usage: python3 beszel_client.py [--system SYSTEM_ID] [--check CHECK_TYPE] + +import requests +import argparse +import sys +import os +import json +from datetime import datetime + +BESZEL_URL = os.getenv("BESZEL_URL", "http://192.168.1.25:8090") + +class BeszelClient: + def __init__(self, username=None, password=None): + self.url = BESZEL_URL + self.token = None + self.username = username or os.getenv("BESZEL_USER", "admin") + self.password = password or os.getenv("BESZEL_PASSWORD", "") + + def login(self): + try: + response = requests.post(f"{self.url}/api/auth/login", json={ + "identity": self.username, + "password": self.password + }, timeout=10) + if response.status_code == 200: + self.token = response.json().get("token") + return True + return False + except Exception as e: + print(f"Erreur login: {e}") + return False + + def get_systems(self): + if not self.token: + return None + try: + headers = {"Authorization": f"Bearer {self.token}"} + response = requests.get(f"{self.url}/api/systems", headers=headers, timeout=10) + if response.status_code == 200: + return response.json() + return None + except Exception as e: + print(f"Erreur get_systems: {e}") + return None + + def get_system_stats(self, system_id): + if not self.token: + return None + try: + headers = {"Authorization": f"Bearer {self.token}"} + response = requests.get(f"{self.url}/api/systems/{system_id}/stats", + headers=headers, timeout=10) + if response.status_code == 200: + return response.json() + return None + except Exception as e: + print(f"Erreur get_stats: {e}") + return None + + def get_system_info(self, system_id): + if not self.token: + return None + try: + headers = {"Authorization": f"Bearer {self.token}"} + response = requests.get(f"{self.url}/api/systems/{system_id}", + headers=headers, timeout=10) + if response.status_code == 200: + return response.json() + return None + except Exception as e: + print(f"Erreur get_info: {e}") + return None + + def format_stats_text(self, stats, system_name="System"): + lines = [] + lines.append(f"=== {system_name} ===") + lines.append(f"CPU: {stats.get('cpu', 0):.1f}%") + lines.append(f"Mémoire: {stats.get('memory', 0):.1f}%") + + if 'swap' in stats: + lines.append(f"Swap: {stats.get('swap', 0):.1f}%") + + if 'disk' in stats: + for disk, info in stats['disk'].items(): + lines.append(f"Disque {disk}: {info.get('used_percent', 0):.1f}%") + + if 'load' in stats: + load = stats['load'] + lines.append(f"Load: {load[0]:.2f} {load[1]:.2f} {load[2]:.2f}") + + if 'gpu' in stats and stats['gpu']: + lines.append("") + lines.append("GPU:") + for gpu_name, gpu_data in stats['gpu'].items(): + vram_used = gpu_data.get('memory_used', 0) / (1024**3) + vram_total = gpu_data.get('memory_total', 0) / (1024**3) + vram_pct = (gpu_data.get('memory_used', 0) / gpu_data.get('memory_total', 1)) * 100 + lines.append(f" {gpu_name}:") + lines.append(f" VRAM: {vram_used:.2f}GB / {vram_total:.2f}GB ({vram_pct:.1f}%)") + lines.append(f" Utilisation GPU: {gpu_data.get('utilization_gpu', 0)}%") + if 'temperature' in gpu_data: + lines.append(f" Température: {gpu_data.get('temperature', 0)}°C") + if 'power_draw' in gpu_data: + lines.append(f" Consommation: {gpu_data.get('power_draw', 0)}W") + + if 'containers' in stats and stats['containers']: + lines.append("") + lines.append("Containers Docker:") + for container in stats['containers']: + name = container.get('name', 'unknown') + cpu = container.get('cpu_percent', 0) + mem = container.get('memory_percent', 0) + lines.append(f" {name}: CPU {cpu:.1f}% MEM {mem:.1f}%") + + return "\n".join(lines) + + def check_thresholds(self, stats, thresholds=None): + if thresholds is None: + thresholds = { + 'cpu': 80, + 'memory': 90, + 'vram': 90 + } + + alerts = [] + + cpu = stats.get('cpu', 0) + if cpu > thresholds['cpu']: + alerts.append(f"⚠️ CPU elevated: {cpu:.1f}%") + + memory = stats.get('memory', 0) + if memory > thresholds['memory']: + alerts.append(f"⚠️ Memory elevated: {memory:.1f}%") + + if 'gpu' in stats and stats['gpu']: + for gpu_name, gpu_data in stats['gpu'].items(): + vram_pct = (gpu_data.get('memory_used', 0) / gpu_data.get('memory_total', 1)) * 100 + if vram_pct > thresholds['vram']: + alerts.append(f"⚠️ VRAM {gpu_name} elevated: {vram_pct:.1f}%") + + util = gpu_data.get('utilization_gpu', 0) + if util > thresholds['vram']: + alerts.append(f"⚠️ GPU {gpu_name} utilization elevated: {util}%") + + return alerts + + +def main(): + parser = argparse.ArgumentParser(description="Beszel API Client") + parser.add_argument("--system", "-s", help="System ID to query") + parser.add_argument("--list-systems", "-l", action="store_true", help="List all systems") + parser.add_argument("--check", "-c", choices=["stats", "alerts", "full"], + default="stats", help="Type of check") + parser.add_argument("--json", "-j", action="store_true", help="Output JSON") + parser.add_argument("--user", "-u", help="Beszel username") + parser.add_argument("--password", "-p", help="Beszel password") + + args = parser.parse_args() + + client = BeszelClient(username=args.user, password=args.password) + + if not client.login(): + print("❌ Échec de connexion à Beszel") + sys.exit(1) + + if args.list_systems: + systems = client.get_systems() + if systems: + print("=== Systèmes surveillés ===") + for sys in systems.get('items', []): + print(f" - {sys.get('name')} (ID: {sys.get('id')})") + return + + if args.system: + if args.check == "stats": + stats = client.get_system_stats(args.system) + if stats: + if args.json: + print(json.dumps(stats, indent=2)) + else: + info = client.get_system_info(args.system) + name = info.get('name', args.system) if info else args.system + print(client.format_stats_text(stats, name)) + + elif args.check == "alerts": + stats = client.get_system_stats(args.system) + if stats: + alerts = client.check_thresholds(stats) + if alerts: + print("\n".join(alerts)) + else: + print("✅ Tout est normal") + + elif args.check == "full": + stats = client.get_system_stats(args.system) + info = client.get_system_info(args.system) + if stats and info: + if args.json: + print(json.dumps({'info': info, 'stats': stats}, indent=2, default=str)) + else: + name = info.get('name', args.system) + print(f"=== {name} ===") + print(client.format_stats_text(stats, name)) + alerts = client.check_thresholds(stats) + if alerts: + print("\n" + "\n".join(alerts)) + else: + systems = client.get_systems() + if systems: + print("Systèmes disponibles (utiliser --system ID):") + for sys in systems.get('items', []): + print(f" - {sys.get('name')} (ID: {sys.get('id')})") + + +if __name__ == "__main__": + main()