8.0 KiB
reglement-definitif-api — Fix WORKER TIMEOUT/OOM + serveur MCP + intégration hermes-tt
Instance auteur : hermes-nyora Date : 2026-08-23 Tags : [infra, mcp, docker, flask, gunicorn, weasyprint, tunisie-telecom, gemini] Statut : valide
Probleme
L'application Flask « Règlement Définitif » (Tunisie Telecom, DCZS) tournait en boucle de crash depuis fin juillet 2026 :
[CRITICAL] WORKER TIMEOUT (pid:7)
[ERROR] Worker (pid:7) was sent SIGKILL! Perhaps out of memory?
Le master gunicorn démarrait, les 2 workers bootaient, puis timeout à ~30s (défaut gunicorn) → SIGKILL → reboot workers → re-timeout, en boucle. Dernier incident avant fix : 22/08/2026.
Cause racine confirmée : imports top-level lourds (from weasyprint import HTML, CSS dans routes/reports.py et services/pdf_generator.py) exécutés à chaque boot de worker via create_app(). Sur le Celeron J4125 du NAS DS920+, 2 workers concurrents chargeant WeasyPrint/CFFI + google-api-client dépassaient les 30s sans heartbeat → kill du master. Facteur aggravant RAM : WeasyPrint ~150-200 Mo/worker sur un NAS contraint.
Deuxième objectif du projet : exposer les données de l'app comme outils MCP pour hermes-agent-tt (l'app n'a aucune API REST, que du web UI derrière Flask-Login).
Contexte et contraintes
- Conteneur d'origine créé SANS Dockerfile visible, SANS volume (code cuit dans l'image), SANS labels compose → rebuild impossible tel quel, données non persistées.
credentials.json(Google Service Accountexcel-sync-483316) etusers.dbn'existaient QUE dans le conteneur → extraction obligatoire AVANT suppression (perte définitive sinon).- Réseau Docker partagé :
n8n(external), où vivent aussihermes-agent-ttet le reste de la stack. - Ports host : api
5099:5055, MCP5098:8000(5098 libre vérifié au préalable). - Le projet est piloté par un workflow AntiGravity-Gemini (exécution complète, accès NAS via
ssh nas) avec Hermes-Nyora en vérification/cadrage uniquement.
Ce qui NE fonctionne PAS
| Tentative | Erreur obtenue | Raison de l'échec |
|---|---|---|
| Gunicorn défaut (2 workers sync, timeout 30s) | [CRITICAL] WORKER TIMEOUT + SIGKILL OOM en boucle |
Imports WeasyPrint top-level > 30s au boot ×2 workers concurrents |
Repo Gitea reglement-definitif (état antérieur au projet) |
Runbook 04/07 affirmait « source + Dockerfile sur Gitea » | FAUX — vérifié par API Gitea : aucun repo existant. Le conteneur était orphelin de toute source |
Appel tools/list MCP direct sans session |
{"error":{"code":-32600,"message":"Bad Request: Missing session ID"}} |
StreamableHTTP exige initialize d'abord puis header mcp-session-id sur chaque appel |
Endpoint /mcp sans Bearer |
HTTP 401 | Normal — auth active (testé et validé) |
Solution validée
Architecture retenue (décisions Gemini, validées Hermes)
- Fix app (aucun changement métier) :
- Lazy import WeasyPrint (déplacé dans les fonctions de rendu) → boot < 0,5s
cachetools.TTLCache(maxsize=256, ttl=300)remplace le dict global non borné- Résilience credentials : classe
GoogleCredentialsError+check_credentials_status()— l'app démarre même sans credentials.json (erreur claire sur les écrans Sheets, pas de crash) - Gunicorn :
--workers 1 --timeout 120 --graceful-timeout 30, PAS de--preload(WeasyPrint/CFFI + fork = risque deadlock) - Dockerfile
python:3.11-slim+ libs système Pango/Cairo/GdkPixbuf + fonts-dejavu + healthcheckpython -c urllib(wget absent des images slim)
- Serveur MCP séparé (
reglement-mcp) : FastMCP StreamableHTTP sur/mcp, port 5098:8000, auth BearerMCP_SECRET_TOKEN. Importe directementapp/config.py+app/services/(gsheets, cache) — ces fichiers n'importent PAS Flask (vérifié). Image slim sans WeasyPrint. - 8 outils MCP agrégés : list_marches, get_marche, po_stats, detail_po, appels_commande, consommation (+alertes >90%), instance_validation, dashboard_summary. Format tunisien DT, pas de dump brut.
Ordre d'exécution (respecté)
# 1. SAUVEGARDE avant toute suppression (critique)
docker cp reglement-definitif-api:/app/credentials.json /volume1/docker/reglement-definitif/credentials.json
docker cp reglement-definitif-api:/app/data/users.db /volume1/docker/reglement-definitif/data/users.db.bak
# 2. Déployer l'API (sur le NAS, piège PATH Synology)
ssh nas "export PATH=/usr/local/bin:\$PATH; cd /volume1/docker/reglement-definitif && docker compose build --no-cache && docker compose up -d"
ssh nas "export PATH=/usr/local/bin:\$PATH; docker update --restart unless-stopped reglement-definitif-api"
# 3. Déployer le MCP
ssh nas "export PATH=/usr/local/bin:\$PATH; cd /volume1/docker/reglement-definitif/reglement-mcp && docker compose build --no-cache && docker compose up -d"
# 4. Config hermes-tt (/opt/data/config.yaml du conteneur hermes-agent-tt)
# mcp_servers.reglement-definitif: url http://reglement-mcp:8000/mcp + Bearer token
Verification
Vérifié DEUX FOIS : preuves Gemini + tests indépendants Hermes-Nyora depuis le réseau n8n (23/08/2026).
# Conteneurs healthy
docker ps --filter name=reglement
# reglement-mpc / reglement-definitif-api : Up (healthy) tous les deux
# API web
curl -I http://127.0.0.1:5099/login # → HTTP 200
# MCP health
curl -s http://reglement-mcp:8000/health # → {"status":"healthy",...,"endpoint":"/mcp"}
# Auth active
curl -o /dev/null -w "%{http_code}" http://reglement-mcp:8000/mcp # sans token → 401
# Flux MCP complet (StreamableHTTP = initialize d'abord pour obtenir la session)
curl -D - -X POST http://reglement-mcp:8000/mcp \
-H "Authorization: Bearer $MCP_SECRET_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
# → récupérer le header mcp-session-id, puis tools/list AVEC ce header → 8 tools
# → tools/call reglement_list_marches → {"status":"success","total_marches":8,"nb_fournisseurs":4}
# Découverte côté hermes-tt (preuve Gemini)
docker exec hermes-agent-tt hermes mcp test reglement-definitif
# → Connected (281ms), Tools discovered: 8
Dépôt code : bolbol/reglement-definitif branche v0 (commits efbe03b, 80ed7bc). Repo privé — invisible en API anonyme (vérifié : 404 anonyme = normal pour un repo privé).
Pieges specifiques DSM / NAS
- Code cuit sans volume : l'ancien déploiement n'avait NI volume NI Dockerfile. Toute app historique dans cet état est une bombe : credentials et SQLite perdus au premier recreate. Toujours extraire AVANT (
docker cp). - StreamableHTTP ≠ SSE simple : chaque appel après initialize exige le header
mcp-session-idretourné par initialize. Un curl nu renvoie-32600 Missing session ID. - Piège PATH SSH Synology :
export PATH=/usr/local/bin:$PATHobligatoire dans chaque commande ssh nas non-interactive, sinondocker: command not found. - Repo privé Gitea : une recherche API anonyme ne voit PAS les repos privés — « not found » ne prouve pas l'inexistence. Vérifier authentifié avant de conclure (erreur commise pendant la vérification, corrigée).
- Healthcheck slim : wget/curl absents de python:3.11-slim → healthcheck en
python -c urllib.request. - Credentials manquants ≠ crash : l'app et le MCP démarrent verts sans credentials.json ; seuls les outils Sheets renvoient l'erreur explicite. Ne pas croire un statut healthy = données OK.
References
- Repo code :
bolbol/reglement-definitif@v0(Gitea) - Prompt mission + recadrages :
/opt/data/workspace/prompts/antigravity-reglement-definitif.md,GO-reglement-definitif.md,RECADRAGE-GO-execution-gemini.md - Suivi projet :
/opt/data/workspace/prompts/SUIVI-reglement-definitif.md - Code extrait du conteneur d'origine :
/opt/data/workspace/apis/reglement-definitif/ - Ports registry : à mettre à jour (ligne reglement-mcp 5098)