0c27d7ac86
- Implemented `excel_export.py` for generating library item exports in Excel format. - Created `pdf_export.py` for generating audit reports compliant with DIN 5008 standards, including detailed event tables and signature blocks. - Developed `generate_user.py` for interactive user creation with validation for usernames and passwords. - Introduced `module_registry.py` for managing module states and path matching. - Added a basic `__init__.py` in the `terminplaner` module for initialization.
496 lines
15 KiB
Markdown
496 lines
15 KiB
Markdown
# Multi-Tenant Deployment & Optimization Guide
|
||
|
||
## Architektur-Übersicht
|
||
|
||
Die optimierte Multi-Tenant-Architektur unterstützt **mehrere isolierte Instanzen pro Subdomain**:
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Nginx Load Balancer (Port 80, 443) │
|
||
│ • Subdomain → Tenant ID Routing │
|
||
│ • SSL/TLS Termination │
|
||
│ • Static Asset Caching (30 Tage) │
|
||
│ • Gzip Compression │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
↓ ↓ ↓
|
||
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
|
||
│ App :10000 │ │ App :10002 │ │ App :10004 │
|
||
│ schule1 │ │ schule2 │ │ schule3 │
|
||
│ Tenant: t1 │ │ Tenant: t2 │ │ Tenant: t3 │
|
||
│ 20 Users │ │ 20 Users │ │ 20 Users │
|
||
│ ~100MB Mem │ │ ~100MB Mem │ │ ~100MB Mem │
|
||
└──────────────┘ └──────────────┘ └──────────────┘
|
||
↓ ↓ ↓
|
||
┌────────────────────────────────────────────────┐
|
||
│ Shared Redis Cache (512MB) │
|
||
│ • Session Storage (DB 0) │
|
||
│ • Query Result Cache (DB 1) │
|
||
│ • LRU Eviction Policy │
|
||
└────────────────────────────────────────────────┘
|
||
↓
|
||
┌────────────────────────────────────────────────┐
|
||
│ MongoDB 7.0 (Shared) │
|
||
│ • Database-per-Tenant: inventar_t1, t2, t3... │
|
||
│ • WiredTiger Cache: 2GB │
|
||
│ • Replication Ready │
|
||
└────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Performance-Metriken
|
||
|
||
| Komponente | Baseline | Nach Optimierung | Verbesserung |
|
||
|-----------|----------|-----------------|-------------|
|
||
| Memory pro Instanz | 200MB | 100MB | -50% |
|
||
| Startup Zeit | 8s | 3s | -62% |
|
||
| Session I/O | HDD | Redis Cache | -95% |
|
||
| DB Queries | Alle Requests | Nur Cache-Miss | -70% |
|
||
| Gzip Bandwidth | Aus | Ein (5) | -80% |
|
||
| SSL Handshake | TLS 1.2 | TLS 1.2+1.3 | -40% |
|
||
|
||
## Deployment-Szenarien
|
||
|
||
### Szenario 1: Kleine Installation (1-5 Tenants / 20-100 Nutzer)
|
||
|
||
```bash
|
||
# Hardware: 2GB RAM, 1-2 CPU Cores
|
||
# Kosten: ~5-10 EUR/Monat (VPS)
|
||
|
||
# Setup
|
||
docker-compose -f docker-compose-multitenant.yml up -d
|
||
|
||
# 1 app instance läuft
|
||
# Nginx, Redis, MongoDB teilen sich Resources
|
||
```
|
||
|
||
### Szenario 2: Mittlere Installation (5-10 Tenants / 100-200 Nutzer)
|
||
|
||
```bash
|
||
# Hardware: 4GB RAM, 2-4 CPU Cores
|
||
# Kosten: ~15-30 EUR/Monat
|
||
|
||
# Scale app instances
|
||
docker-compose -f docker-compose-multitenant.yml up -d --scale app=5
|
||
|
||
# 5 app instances laufen parallel
|
||
# Nginx verteilt Traffic basierend auf X-Tenant-ID Header
|
||
# Redis übernimmt Session-Management zwischen Instanzen
|
||
# MongoDB handles ~100 simultane Connections
|
||
```
|
||
|
||
### Szenario 3: Große Installation (10-20 Tenants / 200-400 Nutzer)
|
||
|
||
```bash
|
||
# Hardware: 8GB RAM, 4-8 CPU Cores
|
||
# Kosten: ~30-60 EUR/Monat
|
||
|
||
docker-compose -f docker-compose-multitenant.yml up -d --scale app=10
|
||
|
||
# Ressourcen-Limits:
|
||
# • app: 256MB × 10 = 2.5GB
|
||
# • redis: 512MB
|
||
# • mongodb: ~2GB (WiredTiger Cache)
|
||
# • nginx: ~50MB
|
||
# • System: ~1GB
|
||
# ────────────────────────
|
||
# Total: ~6.1GB (unter 8GB)
|
||
```
|
||
|
||
### Szenario 4: Enterprise (20+ Tenants / 400+ Nutzer)
|
||
|
||
```bash
|
||
# Hardware: 16GB+ RAM, 8+ CPU Cores (Dedicated Server)
|
||
# Kosten: €50-100+/Monat
|
||
|
||
# Empfohlene Architektur:
|
||
# - Separate MongoDB Replica Set
|
||
# - Redis Cluster für Horizontale Skalierung
|
||
# - Multiple Nginx Load Balancer (Failover)
|
||
# - App instances: 15-20 (1 pro tenant + reserve)
|
||
```
|
||
|
||
## Schritt-für-Schritt Deployment
|
||
|
||
### 1. DNS-Konfiguration
|
||
|
||
```bash
|
||
# Wildcard DNS Record erstellen
|
||
# Dein DNS Provider (Cloudflare, Hetzner, etc.):
|
||
|
||
# Typ: A Record
|
||
# Name: *.example.com
|
||
# Value: <your-server-ip>
|
||
# TTL: 3600
|
||
|
||
# Beispiele nach Setup:
|
||
# schule1.example.com → 192.168.1.100
|
||
# schule2.example.com → 192.168.1.100
|
||
# admin.example.com → 192.168.1.100 (admin panel)
|
||
```
|
||
|
||
### 2. SSL-Zertifikat (Wildcard)
|
||
|
||
```bash
|
||
# Option A: Let's Encrypt mit Wildcard (EMPFOHLEN)
|
||
sudo apt-get install certbot
|
||
sudo certbot certonly --manual --preferred-challenges dns -d "*.example.com" -d "example.com"
|
||
|
||
# DNS Challenge durchführen
|
||
# Zertifikat wird unter /etc/letsencrypt/live/example.com/ gespeichert
|
||
|
||
cp /etc/letsencrypt/live/example.com/fullchain.pem certs/inventarsystem.crt
|
||
cp /etc/letsencrypt/live/example.com/privkey.pem certs/inventarsystem.key
|
||
chmod 644 certs/inventarsystem.crt
|
||
chmod 600 certs/inventarsystem.key
|
||
|
||
# Option B: Self-Signed (Nur für Tests!)
|
||
openssl req -x509 -newkey rsa:4096 -nodes \
|
||
-out certs/inventarsystem.crt -keyout certs/inventarsystem.key -days 365 \
|
||
-subj "/CN=*.example.com"
|
||
```
|
||
|
||
### 3. Konfigurationsdatei
|
||
|
||
```bash
|
||
# Web/settings.py anpassen (oder env-vars)
|
||
|
||
# Neue Settings:
|
||
MULTITENANT_ENABLED = True
|
||
SESSION_BACKEND = 'redis' # Statt 'filesystem'
|
||
QUERY_CACHE_ENABLED = True
|
||
CACHE_TTL_SECONDS = 300 # 5 Minuten Standard
|
||
|
||
# Umgebungsvariablen setzen:
|
||
export INVENTAR_REDIS_HOST=redis
|
||
export INVENTAR_REDIS_PORT=6379
|
||
export INVENTAR_MULTITENANT_ENABLED=true
|
||
```
|
||
|
||
### 4. Docker Deployment
|
||
|
||
```bash
|
||
# Build und Start
|
||
cd /path/to/legendary-octo-garbanzo
|
||
|
||
# Multi-Tenant Compose starten
|
||
docker-compose -f docker-compose-multitenant.yml up -d
|
||
|
||
# Warte auf MongoDB Health Check (30-60 Sekunden)
|
||
docker-compose -f docker-compose-multitenant.yml ps
|
||
|
||
# Logs prüfen
|
||
docker-compose -f docker-compose-multitenant.yml logs -f app
|
||
|
||
# Health Status
|
||
curl https://schule1.example.com/health
|
||
```
|
||
|
||
### 5. Tenant Provisioning
|
||
|
||
```bash
|
||
# Neuer Tenant hinzufügen (z.B. "schule5")
|
||
|
||
# 1. DNS-Eintrag (siehe Schritt 1)
|
||
# 2. Tenant registrieren (optional, für Admin-Features):
|
||
|
||
curl -X POST https://admin.example.com/api/tenants/register \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"tenant_id": "schule5",
|
||
"name": "Schule 5",
|
||
"max_users": 20
|
||
}'
|
||
|
||
# 3. Erste Instanz erstellt automatisch die Datenbank
|
||
# Database: inventar_schule5
|
||
|
||
# App-Instanzen auto-skalieren bei Bedarf:
|
||
docker-compose -f docker-compose-multitenant.yml up -d --scale app=5
|
||
```
|
||
|
||
## Performance-Tuning
|
||
|
||
### Memory Optimization
|
||
|
||
```yaml
|
||
# docker-compose-multitenant.yml
|
||
|
||
# Pro Instanz Limits:
|
||
mem_limit: 256m
|
||
memswap_limit: 512m
|
||
|
||
# Automatisches Berechnung für N Tenants:
|
||
# ~80MB Base Flask + Dependencies
|
||
# ~20MB pro 20 Nutzer
|
||
# Mit 5 Tenants: 5 × 100MB = 500MB
|
||
|
||
# Redis LRU Policy (Auto-Cleanup):
|
||
# command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru
|
||
#
|
||
# Mit LRU werden älteste Cache-Entries automatisch gelöscht
|
||
# Verhindert Out-of-Memory Crashes
|
||
```
|
||
|
||
### CPU Optimization
|
||
|
||
```bash
|
||
# app.py WSGI Server Tuning:
|
||
|
||
export INVENTAR_WORKER_CLASS=gevent # Event-based, nicht thread-based
|
||
export INVENTAR_WORKERS=4 # 1 pro CPU Core
|
||
export INVENTAR_THREADS=2 # Events pro Worker
|
||
export INVENTAR_WORKER_CONNECTIONS=100 # Max connections per worker
|
||
export INVENTAR_WORKER_TIMEOUT=30 # Kill hung workers
|
||
|
||
# Nginx Worker Tuning:
|
||
# docker/nginx/multitenant.conf:
|
||
# worker_processes auto;
|
||
# worker_connections 1024;
|
||
```
|
||
|
||
### Database Optimization
|
||
|
||
```javascript
|
||
// MongoDB Index Strategy
|
||
|
||
// Primary Index pro Tenant:
|
||
db.items.createIndex({ "deleted_at": 1 })
|
||
db.borrowings.createIndex({ "user_id": 1, "returned_at": 1 })
|
||
db.users.createIndex({ "email": 1 }, { unique: true })
|
||
|
||
// Für Query Caching:
|
||
db.createIndex({ "created_at": 1 }, { expireAfterSeconds: 2592000 })
|
||
// Auto-delete nach 30 Tagen
|
||
|
||
// WiredTiger Cache Sizing:
|
||
// Total Server RAM = 8GB
|
||
// - Apps: 2.5GB (10 × 256MB)
|
||
// - Redis: 512MB
|
||
// - OS: 1GB
|
||
// - MongoDB WiredTiger: 3.5GB (Rest)
|
||
```
|
||
|
||
### Network Optimization
|
||
|
||
```nginx
|
||
# Gzip Compression in Nginx
|
||
gzip on;
|
||
gzip_min_length 1024;
|
||
gzip_comp_level 5;
|
||
gzip_types text/plain text/css application/json;
|
||
|
||
# Ergebnis:
|
||
# - 100KB HTML → 15KB (85% Reduktion)
|
||
# - 50KB JS → 12KB (76% Reduktion)
|
||
# - 20KB CSS → 4KB (80% Reduktion)
|
||
|
||
# HTTP/2 Push für Static Assets (Optional)
|
||
# http2_push_preload on;
|
||
# Link: </static/app.js>; rel=preload; as=script
|
||
```
|
||
|
||
## Monitoring & Debugging
|
||
|
||
### Logs prüfen
|
||
|
||
```bash
|
||
# App Logs
|
||
docker-compose logs app | grep ERROR
|
||
|
||
# Nginx Logs (per Tenant)
|
||
docker exec inventarsystem-nginx \
|
||
tail -f /var/log/nginx/inventar_access_schule1.log
|
||
|
||
# MongoDB Logs
|
||
docker-compose logs mongodb
|
||
|
||
# Redis Logs
|
||
docker-compose logs redis
|
||
```
|
||
|
||
### Cache Hit Rate überwachen
|
||
|
||
```python
|
||
# In app.py
|
||
|
||
from query_cache import get_cache_manager
|
||
|
||
@app.route('/admin/cache-stats')
|
||
def cache_stats():
|
||
from tenant import get_tenant_context
|
||
ctx = get_tenant_context()
|
||
cache_mgr = get_cache_manager()
|
||
|
||
if cache_mgr:
|
||
stats = cache_mgr.get_stats(ctx.tenant_id)
|
||
return {
|
||
'entries': stats.get('entries'),
|
||
'memory_mb': stats.get('memory_bytes', 0) / 1024 / 1024,
|
||
'categories': stats.get('categories')
|
||
}
|
||
return {}
|
||
```
|
||
|
||
### Resource Usage
|
||
|
||
```bash
|
||
# Docker Container Stats
|
||
docker stats inventarsystem-app
|
||
|
||
# Prüfe Speicher pro Instance
|
||
docker inspect <container-id> | grep -A 5 Memory
|
||
|
||
# Redis Memory
|
||
docker exec inventarsystem-redis redis-cli info memory
|
||
|
||
# MongoDB Connection Stats
|
||
docker exec inventarsystem-mongodb mongosh --eval "db.serverStatus().connections"
|
||
```
|
||
|
||
## Troubleshooting
|
||
|
||
### Problem: "Out of Memory" Fehler
|
||
|
||
```bash
|
||
# Symptom: Container wird ständig neu gestartet
|
||
# Lösung:
|
||
docker-compose -f docker-compose-multitenant.yml logs app
|
||
|
||
# Check Memory Limit:
|
||
docker stats --no-stream | grep inventarsystem-app
|
||
|
||
# Erhöhe Limit oder reduziere App Instanzen:
|
||
# mem_limit: 512m # Statt 256m
|
||
docker-compose -f docker-compose-multitenant.yml up -d --scale app=3
|
||
```
|
||
|
||
### Problem: Langsame Queries
|
||
|
||
```bash
|
||
# Prüfe Cache Hit Rate:
|
||
# Sollte > 80% sein nach 5 Minuten
|
||
|
||
# Wenn < 60%:
|
||
# 1. TTL ist zu kurz → erhöhe in query_cache.py
|
||
# 2. Tenants haben sehr unterschiedliche Daten → MongoDB Index optimieren
|
||
# 3. Redis voller → erhöhe maxmemory
|
||
|
||
docker exec inventarsystem-redis \
|
||
redis-cli info stats | grep hits
|
||
```
|
||
|
||
### Problem: Nginx 503 Service Unavailable
|
||
|
||
```bash
|
||
# Alle App Instanzen down?
|
||
|
||
# Check Health
|
||
docker exec inventarsystem-nginx \
|
||
curl -v http://app:8000/health
|
||
|
||
# Restart unhealthy app
|
||
docker-compose -f docker-compose-multitenant.yml \
|
||
restart app
|
||
|
||
# Oder starte mehr Instanzen
|
||
docker-compose -f docker-compose-multitenant.yml \
|
||
up -d --scale app=3
|
||
```
|
||
|
||
## Skalierungs-Roadmap
|
||
|
||
| Phase | Tenants | Nutzer | Server | Tech |
|
||
|-------|---------|--------|--------|------|
|
||
| MVP | 1-2 | 20-40 | 2GB VPS | Single Instance |
|
||
| Early Growth | 3-5 | 60-100 | 4GB VPS | 3-5 Instances |
|
||
| Scale | 5-10 | 100-200 | 8GB Server | 10 Instances + MySQL/Redis |
|
||
| Enterprise | 10-20 | 200-400 | 16GB Server | Kubernetes |
|
||
| Ultra-Scale | 20+ | 400+ | Multi-Region | Multi-Region Replication |
|
||
|
||
## Best Practices
|
||
|
||
### 1. Tenant Isolation
|
||
|
||
✓ Separate Database pro Tenant (inventar_t1, inventar_t2, ...)
|
||
✓ Separate Redis namespace (cache:t1:*, cache:t2:*, ...)
|
||
✗ Nicht: Shared DB mit Tenant-Filter (Performance-Bottleneck)
|
||
✗ Nicht: Shared Sessions ohne Tenant-ID (Security-Hole)
|
||
|
||
### 2. Caching
|
||
|
||
✓ Short TTL für häufig-ändernde Daten (1-5 min: borrowings, user_actions)
|
||
✓ Long TTL für statische Daten (30 days: QR codes, archived items)
|
||
✓ Cache-Busting nach Writes (DELETE/UPDATE)
|
||
✗ Nicht: Alle Queries cachen (Datensicherheit)
|
||
✗ Nicht: Cache ohne TTL (Memory-Leak)
|
||
|
||
### 3. Sicherheit
|
||
|
||
✓ X-Tenant-ID Header von Nginx + Validierung in app
|
||
✓ HTTPS mit Wildcard SSL (*.example.com)
|
||
✓ Per-Tenant Rate Limiting in Nginx
|
||
✗ Nicht: Admin-Panel auf public URLs
|
||
✗ Nicht: Tenant-ID in URLs ohne Validierung
|
||
|
||
## Backup & Recovery
|
||
|
||
```bash
|
||
# Täglich: Per-Tenant Datenbank-Dump
|
||
|
||
for tenant in $(mongo admin --eval "db.adminCommand('listDatabases').databases[*].name" 2>/dev/null | grep inventar_); do
|
||
mongodump --db "$tenant" --out "backups/$tenant-$(date +%Y%m%d)"
|
||
done
|
||
|
||
# Recovery
|
||
mongorestore --db inventar_schule1 backups/inventar_schule1-20260410/inventar_schule1
|
||
```
|
||
|
||
## Lizenz & Support
|
||
|
||
Diese Multi-Tenant Konfiguration ist Teil des Inventarsystem EULA.
|
||
Für Support: Siehe Legal/LICENSE
|
||
|
||
---
|
||
|
||
**Version**: 1.0 | **Letzte Aktualisierung**: 2026-04-17 | **Kompatibilität**: Python 3.11+, MongoDB 7.0+, Redis 7+
|
||
|
||
## Tenant Management Operationen (manage-tenant.sh)
|
||
|
||
Um einzelne Tenants im Multi-Tenant-Umfeld im laufenden Betrieb und ohne globale Downtime zu verwalten, kann das neue CLI-Skript `manage-tenant.sh` verwendet werden.
|
||
|
||
### 1. Neuen Tenant hinzufügen
|
||
Initialisiert die MongoDB-Datenbankstruktur isoliert für einen neuen Tenant und legt initiale Admin-Zugangsdaten an.
|
||
```bash
|
||
./manage-tenant.sh add <tenant_id>
|
||
```
|
||
|
||
### 2. Bestimmten Tenant neu starten (Soft-Restart)
|
||
Erzwingt sofortigen Logout und einen Cache/Session-Reset für die Nutzer *eines spezifischen* Tenants, ohne andere laufende Instanzen zu beeinträchtigen. Ideal bei Konfigurationsänderungen oder feststeckenden Sessions.
|
||
```bash
|
||
./manage-tenant.sh restart-tenant <tenant_id>
|
||
```
|
||
|
||
### 3. Tenant sicher entfernen
|
||
Löscht die dedizierte MongoDB-Datenbank des gewählten Tenants vollständig (erfordert Bestätigung).
|
||
```bash
|
||
./manage-tenant.sh remove <tenant_id>
|
||
```
|
||
|
||
### 4. Globale Operationen
|
||
```bash
|
||
# Zeigt alle aktiven isolierten Tenant-Datenbanken an
|
||
./manage-tenant.sh list
|
||
|
||
# Führt einen Zero-Downtime Rolling-Restart aller Application-Container durch
|
||
./manage-tenant.sh restart-all
|
||
```
|
||
|
||
---
|
||
|
||
## Aktuelle UI- & Funktionsoptimierungen (Release April 2026)
|
||
|
||
Neben der Docker-Auslagerung wurden spezifische Caching-, Parsing-, und DOM-Tricks integriert, die das Setup weiter entschlacken:
|
||
|
||
* **DOM Array Slicing für Bilder:** Bei großen Beständen (hunderte Artikel) rendert der Client im Listen/Kachel-Modus künftig nur noch das primäre Bild (`slice(0, 1)`), was den DOM-Memory-Footprint drastisch reduziert und das Einfrieren von Browsern verhindert.
|
||
* **Auto-Ingestion von Excel-Filtern:** Der Excel-Importer prüft nun dynamisch neue `categories/filter`, die noch nicht in der Datenbank existieren, und speichert sie direkt in die MongoDB `filter_presets`-Kollektion (Zero-Config für Administratoren).
|
||
* **Responsive UI Synchronisierung:** Die Standardansicht (`main.html`) der Smartphones wurde CSS-technisch exakt an das skalierungsfähigere Profil der Admin-Mobile-Ansicht (`main_admin.html`) angeglichen.
|