Partie 3 : Migration vers Flask + Gunicorn
Octobre 2025
Pourquoi Flask ?
Après quelques jours sous Docker avec http.server, les limitations étaient évidentes :
- Single-threaded : Une seule requête à la fois
- Pas de routes : Impossible d'ajouter de la logique
- Pas de templates : HTML statique uniquement
- Performance : Pas conçu pour la production
- Évolution : Impossible d'ajouter des features
Flask résout tous ces problèmes !
La migration Flask dans Docker
L'énorme avantage de Docker : la migration est triviale !
1. Création de requirements.txt
Flask==3.0.0
gunicorn==21.2.0
2. Création de app.py
#!/usr/bin/env python3
"""
Application Flask pour Home-Fonta.fr
"""
from flask import Flask, send_from_directory, abort
import os
app = Flask(__name__, static_folder='.', static_url_path='')
# Configuration
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 0 # Désactive le cache pour dev
@app.route('/')
def index():
"""Page d'accueil"""
return send_from_directory('.', 'index.html')
@app.route('/presentation')
def presentation():
"""Page de présentation"""
return send_from_directory('.', 'presentation.html')
@app.route('/services')
def services():
"""Page des services"""
return send_from_directory('.', 'services.html')
@app.route('/galerie')
def galerie():
"""Galerie photos"""
return send_from_directory('.', 'galerie.html')
@app.route('/menu')
def menu():
"""Menu pour AJAX"""
return send_from_directory('.', 'menu.html')
@app.route('/static/<path:filename>')
def static_files(filename):
"""Sert les fichiers statiques (CSS, JS, images)"""
return send_from_directory('static', filename)
@app.route('/health')
def health():
"""Health check pour Docker"""
return {'status': 'ok', 'server': 'Flask+Gunicorn'}, 200
@app.errorhandler(404)
def not_found(e):
"""Page 404 personnalisée"""
if os.path.exists('404.html'):
return send_from_directory('.', '404.html'), 404
return "404 - Page non trouvée", 404
if __name__ == '__main__':
# Mode debug pour tests locaux uniquement
app.run(host='0.0.0.0', port=8000, debug=True)
3. Ajout de Gunicorn (WSGI server)
# wsgi.py
from app import app
if __name__ == "__main__":
app.run()
Gunicorn est un serveur WSGI qui permet :
- Multi-workers : Plusieurs processus Python
- Multi-threads : Plusieurs threads par worker
- Graceful reload : Mise à jour sans coupure
- Production-ready : Utilisé par Instagram, Pinterest...
4. Nouveau Dockerfile optimisé
# Dockerfile v2 - Flask + Gunicorn
FROM python:3.11-slim
LABEL maintainer="home-fonta.fr"
LABEL description="Flask + Gunicorn pour Home-Fonta.fr"
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PORT=8000
# Créer un utilisateur non-root
RUN useradd -m -u 1000 webuser
WORKDIR /app
# Installer les dépendances Python (cache Docker si unchanged)
COPY --chown=webuser:webuser requirements.txt /app/
RUN pip install --no-cache-dir -r requirements.txt
# Copier l'application
COPY --chown=webuser:webuser . /app/
EXPOSE 8000
USER webuser
# Lancer Gunicorn avec 2 workers et 2 threads
CMD ["gunicorn", "--bind", "0.0.0.0:8000", \
"--workers", "2", \
"--threads", "2", \
"--timeout", "60", \
"--access-logfile", "-", \
"--error-logfile", "-", \
"wsgi:app"]
Migration sans interruption
#!/bin/bash
# setup-flask.sh - Migration Python simple → Flask
echo "Migration vers Flask + Gunicorn"
# 1. Test local Flask
python app.py
# Vérifier sur http://localhost:8000
# 2. Build nouvelle image
docker-compose down
docker-compose build --no-cache
# 3. Lancer le nouveau container
docker-compose up -d
# 4. Vérification
sleep 5
curl -I http://localhost:8001
# HTTP/1.1 200 OK
# Server: gunicorn
Comparaison des performances
J'ai fait des benchmarks avec Apache Bench :
# Test avec http.server
ab -n 1000 -c 10 http://localhost:8000/
Requests per second: 83.45 [#/sec]
Time per request: 119.844 [ms]
# Test avec Flask + Gunicorn
ab -n 1000 -c 10 http://localhost:8001/
Requests per second: 412.67 [#/sec]
Time per request: 24.232 [ms]
5x plus rapide !
Nouvelles possibilités avec Flask
1. Routes dynamiques
@app.route('/api/stats')
def stats():
return {
'visitors': get_visitor_count(),
'pages': count_pages(),
'uptime': get_uptime()
}
2. Templates Jinja2
from flask import render_template
@app.route('/blog/<article>')
def blog(article):
return render_template('blog.html',
article=article,
date=datetime.now())
3. Formulaires
from flask import request
@app.route('/contact', methods=['POST'])
def contact():
name = request.form.get('name')
email = request.form.get('email')
# Envoyer email, sauvegarder en DB...
return {'status': 'sent'}
4. Sessions et cookies
from flask import session
@app.route('/login', methods=['POST'])
def login():
session['user'] = request.form.get('username')
return redirect('/')
Problème découvert : Fichiers statiques
Même avec Flask, servir les fichiers statiques directement depuis Python n'est pas optimal. Les images lourdes causaient encore des lenteurs.
Solution temporaire
# Route explicite pour les statiques
@app.route('/static/<path:filename>')
def static_files(filename):
return send_from_directory('static', filename)
Mais ce n'était qu'un patch...
Monitoring avec Gunicorn
Gunicorn offre des métriques utiles :
# Logs détaillés
docker logs home-fonta-flask
[2025-10-29 10:23:45 +0000] [7] [INFO] Starting gunicorn 21.2.0
[2025-10-29 10:23:45 +0000] [7] [INFO] Listening at: http://0.0.0.0:8000 (7)
[2025-10-29 10:23:45 +0000] [7] [INFO] Using worker: threads
[2025-10-29 10:23:45 +0000] [10] [INFO] Booting worker with pid: 10
[2025-10-29 10:23:45 +0000] [11] [INFO] Booting worker with pid: 11
# Stats en temps réel
docker exec home-fonta-flask ps aux
USER PID %CPU %MEM VSZ RSS COMMAND
1000 1 0.0 0.8 56432 32768 gunicorn: master
1000 10 0.1 1.2 78652 49152 gunicorn: worker
1000 11 0.1 1.2 78652 49152 gunicorn: worker
Configuration avancée de Gunicorn
# gunicorn_config.py
bind = "0.0.0.0:8000"
workers = 2
threads = 2
worker_class = "gthread"
timeout = 60
keepalive = 5
max_requests = 1000
max_requests_jitter = 50
preload_app = True
accesslog = "-"
errorlog = "-"
log_level = "info"
Optimisations Docker + Flask
Layer caching intelligent
# Les dépendances changent rarement
COPY requirements.txt .
RUN pip install -r requirements.txt
# Le code change souvent
COPY . .
Health check amélioré
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s # Gunicorn met du temps à démarrer
Résultats après Flask
| Métrique | http.server | Flask+Gunicorn | Amélioration |
|---|---|---|---|
| Req/sec | 83 | 412 | 5x |
| Latence | 120ms | 24ms | 5x |
| Workers | 1 | 2 | 2x |
| Threads | 1 | 4 | 4x |
| CPU usage | 95% | 45% | 2x |
| RAM | 50MB | 100MB | Acceptable |
Leçons apprises
- Flask transforme un site statique en app web
- Gunicorn est indispensable en production
- Multi-workers = performance
- Docker rend les migrations triviales
- Les fichiers statiques restent un problème
Prochaine étape évidente
Le site tournait bien mieux avec Flask, mais je savais que ce n'était qu'une étape. La vraie transformation viendrait avec Kubernetes.
Mon NAS hébergeait plein d'autres services Docker. Pourquoi ne pas tout migrer vers un vrai cluster ?
L'idée : Créer un cluster K3s avec 4 Raspberry Pi !
Suite : Partie 4 - L'aventure Kubernetes commence