Produktiver WSGI-Server (Gunicorn) statt Flasks Entwicklungsserver

tesm.service startete die App bisher direkt per 'python3 app.py', was
Flasks eingebauten Werkzeug-Entwicklungsserver nutzt -- der meldet bei
jedem Start 'WARNING: This is a development server. Do not use it in a
production deployment.' und ist bewusst nicht für Dauerbetrieb gebaut.

Umgestellt auf Gunicorn, mit zwei bewusst gesetzten Einschraenkungen:
- '--workers 1': die App haelt laufende Wartungs-Jobs/Import-Vorschauen
  nur im Arbeitsspeicher DES EINEN Prozesses (_maintenance_jobs,
  _pending_imports, ...) -- mehrere Worker-Prozesse wuerden diesen
  Zustand auf mehrere, sich nicht kennende Prozesse verteilen.
- '--worker-class gthread --threads 16' statt eines async-Workers: das
  Browser-SSH-Terminal (flask-sock) belegt pro offener WebSocket-
  Verbindung einen blockierenden Thread.
- '--timeout 0': deaktiviert Gunicorns eigenen 30s-Worker-Timeout, der
  sonst auch eine offene, aber gerade inaktive SSH-Terminal-Verbindung
  killen wuerde.

Live auf POETEST verifiziert: keine WARNING-Zeile mehr, genau 1 Master-
+ 1 Worker-Prozess, alle Kernseiten weiterhin 200, und als kritischer
Regressionstest das WebSocket-SSH-Terminal Ende-zu-Ende gegen einen
echten Host erfolgreich getestet (Host-Key/Passwort-Prompt/Login/Shell).

Dabei einen unabhaengigen Altlast-Fehler in POETESTs venv gefunden und
allgemein in install.sh gefixt: './venv/bin/pip' hat eine zur
Venv-Erstellung fest einprogrammierte Shebang-Zeile -- nach der
historischen Umbenennung 'poe_manager' -> 'tesm' zeigte sie auf einen
nicht mehr existierenden Pfad und schlug mit 'cannot execute: required
file not found' fehl. 'python3 -m pip' umgeht das, da nur der (korrekt
referenzierte) venv-Python-Interpreter selbst gebraucht wird -- betrifft
sonst auch jedes kuenftige In-Place-Update auf einem aehnlich
gewachsenen Host.
This commit is contained in:
2026-08-14 09:09:57 +02:00
parent 9b3051c984
commit 16e1603c57
4 changed files with 35 additions and 5 deletions
+10 -2
View File
@@ -6,8 +6,16 @@ After=network.target
Type=simple
User=root
WorkingDirectory=/srv/tesm
# Nutze die virtuelle Umgebung
ExecStart=/srv/tesm/venv/bin/python3 /srv/tesm/app.py
# Produktiver WSGI-Server (gunicorn) statt Flasks eigenem app.run()-
# Entwicklungsserver -- siehe requirements.txt für die ausführliche
# Begründung von "--workers 1" (In-Memory-Zustand) und "--worker-class
# gthread" (blockierende WebSocket-Threads für das Browser-SSH-Terminal).
# "--timeout 0" deaktiviert Gunicorns Worker-Timeout: der greift sonst
# nach 30s auch bei einer offenen, aber gerade inaktiven SSH-Terminal-
# WebSocket-Verbindung und würde den Worker (und damit JEDE Verbindung,
# da nur ein Prozess läuft) killen.
ExecStart=/srv/tesm/venv/bin/gunicorn --workers 1 --worker-class gthread --threads 16 \
--timeout 0 --bind 0.0.0.0:5000 app:app
Restart=always
RestartSec=5
+11 -2
View File
@@ -45,11 +45,20 @@ sudo rsync -a --delete --exclude 'venv' --exclude 'sqlite.db' --exclude 'fernet.
print_status "Application files copied"
# ---- Python venv ----
# "python3 -m pip" statt des "pip"-Skripts im venv selbst: dessen Shebang
# enthält einen zur Erstellungszeit FEST einprogrammierten, absoluten Pfad
# zum venv-Python. Wird das venv-Verzeichnis später umbenannt/verschoben
# (wie hier historisch bei der Umbenennung "poe_manager" -> "tesm"
# passiert), bleibt die alte, nicht mehr existierende Pfadangabe stehen --
# "./venv/bin/pip install ..." schlägt dann mit "cannot execute: required
# file not found" fehl (live auf POETEST reproduziert), obwohl das venv
# selbst voll funktionsfähig ist. "python3 -m pip" braucht dagegen nur den
# (korrekt referenzierten) venv-Python-Interpreter selbst.
step "Setting up Python virtual environment"
cd /srv/tesm
sudo python3 -m venv venv
sudo ./venv/bin/pip install --upgrade pip >>/var/log/tesm-install.log 2>&1
sudo ./venv/bin/pip install -r requirements.txt >>/var/log/tesm-install.log 2>&1
sudo ./venv/bin/python3 -m pip install --upgrade pip >>/var/log/tesm-install.log 2>&1
sudo ./venv/bin/python3 -m pip install -r requirements.txt >>/var/log/tesm-install.log 2>&1
print_status "Virtual environment ready"
# ---- Datenbank ----
+1 -1
View File
@@ -1 +1 @@
1.0.1
1.0.2
+13
View File
@@ -7,6 +7,7 @@ Flask==3.1.2
Flask-Bcrypt==1.0.1
Flask-Login==0.6.3
Flask-Sock==0.7.0
gunicorn==23.0.0
itsdangerous==2.2.0
Jinja2==3.1.6
ldap3==2.9.1
@@ -31,3 +32,15 @@ Werkzeug==3.1.3
# ldap3 (reines Python, keine System-Bibliothek wie libldap nötig) für die
# optionale Active-Directory/LDAP-Anmeldung (Systemeinstellungen → LDAP) —
# Search+Bind gegen einen AD-Domain-Controller oder generischen LDAP-Server.
#
# gunicorn: produktiver WSGI-Server für tesm.service (siehe dort) statt
# Flasks eigenem app.run()-Entwicklungsserver ("WARNING: This is a
# development server..."). Läuft bewusst mit genau EINEM Worker-Prozess,
# aber mehreren Threads (--worker-class gthread) -- mehrere Prozesse
# würden den In-Memory-Zustand für laufende Wartungs-Jobs/Import-
# Vorschauen (_maintenance_jobs/_pending_imports/...) auf mehrere,
# voneinander isolierte Prozesse verteilen, wodurch eine Anfrage, die vom
# jeweils ANDEREN Worker beantwortet wird, diesen Zustand nie sehen würde.
# Mehrere Threads (statt eines async-Workers) sind nötig, weil das
# Browser-SSH-Terminal (flask-sock) pro offener WebSocket-Verbindung einen
# blockierenden Thread belegt.