
Voici la liste complète des sections [OK] dans l'ordre :

## [LISTE] **Liste des sections de la cheatsheet Gunicorn**

1. [OK] **COMPRENDRE GUNICORN - EXPLICATIONS POUR DÉBUTANTS**
   - Qu'est-ce que Gunicorn?
   - Pourquoi Gunicorn?
   - Concepts de base (WSGI, Master, Workers, Worker Classes)

2. [OK] **INSTALLATION - PREMIERS PAS**
   - Prérequis
   - Installation basique
   - Votre première application avec Gunicorn
   - Tester l'application
   - Arrêter Gunicorn
   - Comprendre la syntaxe module:variable

3. [OK] **UTILISATION DE BASE - OPTIONS ESSENTIELLES**
   - Spécifier l'adresse et le port
   - Nombre de workers
   - Comprendre les types de workers
   - Quand utiliser quel type de worker?
   - Exemple complet avec explications

4. [OK] **LOGGING - COMPRENDRE LES LOGS**
   - Pourquoi les logs sont importants
   - Logs d'accès (access logs)
   - Logs d'erreur (error logs)
   - Niveaux de log
   - Configuration logs complète
   - Lire et comprendre les logs

5. [OK] **DAEMON MODE - LANCER EN ARRIÈRE-PLAN**
   - Mode développement vs production
   - Lancer en daemon
   - Contrôler le daemon
   - Exemple complet daemon

6. [OK] **TIMEOUTS - ÉVITER LES BLOCAGES**
   - Comprendre les timeouts
   - Timeout par défaut
   - Configurer le timeout
   - Quand ajuster le timeout?
   - Graceful timeout
   - Keep-alive

7. [OK] **EXEMPLE PRATIQUE COMPLET POUR DÉBUTANT**
   - Projet: Blog Simple (7 étapes complètes)
   - Script de gestion (manage.sh)

8. [OK] **FICHIER DE CONFIGURATION - MÉTHODE PROPRE**
   - Pourquoi un fichier de config?
   - Créer gunicorn.conf.py
   - Configuration serveur, workers, mémoire, timeouts, logs, processus, sécurité, hooks
   - Configurations multiples (dev/prod)

9. [OK] **NGINX + GUNICORN - LE DUO CLASSIQUE**
   - Pourquoi Nginx avec Gunicorn?
   - Architecture
   - Configuration Nginx simple
   - Activer le site Nginx
   - Utiliser socket Unix
   - Exemple complet avec SSL

10. [OK] **SYSTEMD - GESTION AUTOMATIQUE DU SERVICE**
    - Pourquoi systemd?
    - Créer le service
    - Gérer le service
    - Logs systemd
    - Example complet: Déploiement

11. [OK] **DÉPLOIEMENT - WORKFLOW COMPLET**
    - Préparation du serveur
    - Déploiement de l'application
    - Configuration systemd
    - Configuration Nginx
    - SSL avec Let's Encrypt
    - Vérification
    - Mise à jour de l'application

12. [OK] **TROUBLESHOOTING - RÉSOUDRE LES PROBLÈMES COURANTS**
    - Problème 1: Gunicorn ne démarre pas
    - Problème 2: Worker timeout
    - Problème 3: Connection refused
    - Problème 4: 502 Bad Gateway (Nginx)
    - Problème 5: Application lente
    - Problème 6: Trop de mémoire utilisée
    - Problème 7: Logs ne s'affichent pas
    - Problème 8: Cannot bind to address
    - Checklist de debugging

13. [OK] **DOCKER - CONTAINERISER VOTRE APPLICATION**
    - Pourquoi Docker avec Gunicorn?
    - Dockerfile simple
    - Construire et lancer
    - Dockerfile optimisé (multi-stage)
    - Docker-compose complet
    - Nginx config pour Docker
    - Commandes docker-compose
    - .dockerignore

14. [OK] **PERFORMANCE - OPTIMISATION AVANCÉE**
    - Comprendre les métriques
    - Benchmarking avec AB (Apache Bench)
    - Benchmarking avec wrk
    - Tester différentes configurations
    - Formule optimale workers
    - Profiling applicatif
    - Optimisations spécifiques
    - Configuration haute performance
    - Monitoring en production

15. [OK] **SÉCURITÉ - PROTÉGER VOTRE APPLICATION**
    - Principes de base
    - Utilisateur non-root
    - Limites de sécurité
    - Headers de sécurité (via Nginx)
    - Proxy headers
    - Rate limiting (Nginx)
    - SSL/TLS avec Let's Encrypt
    - Variables d'environnement sécurisées
    - Audit de sécurité

16. [OK] **RÉCAPITULATIF - COMMANDES ESSENTIELLES**
    - Développement
    - Production
    - Contrôle (signaux)
    - Systemd
    - Docker
    - Debugging

17. [OK] **RESSOURCES & AIDE**
    - Documentation officielle
    - Tutoriels recommandés
    - Outils complémentaires
    - Comparaison serveurs WSGI
    - Aide communautaire
    - Checklist finale

**Total : 17 sections principales** couvrant progressivement tout de débutant à expert ! [RAPIDE]

# === VOTRE PREMIÈRE APPLICATION AVEC GUNICORN ===

# EXEMPLE 1: Application Flask ultra-simple
# Créer un fichier: app.py

from flask import Flask

# Créer l'application Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello from Gunicorn!'

@app.route('/about')
def about():
    return 'This is the about page'

# IMPORTANT: Cette section n'est PAS nécessaire avec Gunicorn
# Elle sert uniquement pour le dev server Flask
if __name__ == '__main__':
    app.run()


# === LANCER AVEC GUNICORN (PREMIÈRE FOIS) ===

# Syntaxe de base:
# gunicorn [module]:[variable_app]

# Pour notre exemple:
gunicorn app:app

# EXPLICATION DÉTAILLÉE:
# - Premier 'app': nom du fichier Python (app.py)
# - Deuxième 'app': nom de la variable Flask dans le fichier
# - Pas besoin d'écrire '.py', Gunicorn le sait

# Ce qui se passe:
# [INFO] Starting gunicorn 21.2.0
# [INFO] Listening at: http://127.0.0.1:8000 (12345)
# [INFO] Using worker: sync
# [INFO] Booting worker with pid: 12346

# DÉCRYPTAGE DES MESSAGES:
# - "Starting gunicorn 21.2.0": Version de Gunicorn
# - "Listening at: http://127.0.0.1:8000": Adresse d'écoute
#   -> Votre app est accessible sur http://localhost:8000
# - "(12345)": PID du processus master
# - "Using worker: sync": Type de worker utilisé (par défaut)
# - "Booting worker with pid: 12346": Worker créé avec ce PID


# === TESTER L'APPLICATION ===

# Ouvrir un nouveau terminal (laisser Gunicorn tourner)

# Méthode 1: Navigateur web
# Ouvrir: http://localhost:8000
# Vous devriez voir: "Hello from Gunicorn!"

# Méthode 2: Ligne de commande
curl http://localhost:8000
# Output: Hello from Gunicorn!

curl http://localhost:8000/about
# Output: This is the about page


# === ARRÊTER GUNICORN ===

# Dans le terminal où Gunicorn tourne:
# Appuyer sur: Ctrl + C

# Vous verrez:
# [INFO] Handling signal: int
# [INFO] Worker exiting (pid: 12346)
# [INFO] Shutting down: Master


# === COMPRENDRE LA SYNTAXE module:variable ===

# EXEMPLE 2: Si votre fichier s'appelle 'myapp.py'
# et que votre variable Flask s'appelle 'application'

# myapp.py
from flask import Flask
application = Flask(__name__)  # <- Nom différent

@application.route('/')
def home():
    return 'Home page'

# Lancer:
gunicorn myapp:application
#        ^      ^
#        |      └─ Nom de la variable
#        └──────── Nom du fichier (sans .py)


# EXEMPLE 3: Application dans un sous-dossier

# Structure:
# myproject/
#   └── backend/
#       └── app.py

# Lancer depuis myproject/:
cd myproject
gunicorn backend.app:app
#        ^         ^
#        |         └─ Variable app
#        └─────────── Chemin Python (avec points)


# EXEMPLE 4: Fonction factory (pattern avancé Flask)

# app.py avec fonction factory
from flask import Flask

def create_app():
    app = Flask(__name__)
    
    @app.route('/')
    def home():
        return 'Factory app'
    
    return app

# Lancer avec parenthèses pour appeler la fonction:
gunicorn "app:create_app()"
#        ^              ^
#        |              └─ Appelle la fonction
#        └──────────────── Entre guillemets!


[OK] UTILISATION DE BASE - OPTIONS ESSENTIELLES


# === SPÉCIFIER L'ADRESSE ET LE PORT ===

# Par défaut, Gunicorn écoute sur 127.0.0.1:8000
# Cela signifie: accessible UNIQUEMENT depuis votre machine locale

# PROBLÈME: Si vous êtes sur un serveur distant, vous ne pouvez pas y accéder!

# SOLUTION: Écouter sur toutes les interfaces réseau

# Option --bind (ou -b)
gunicorn app:app --bind 0.0.0.0:8000
#                ^
#                └─ 0.0.0.0 = Toutes les interfaces réseau

# EXPLICATION DES ADRESSES:
# - 127.0.0.1 (localhost): Uniquement local
# - 0.0.0.0: Toutes interfaces (accessible de l'extérieur)
# - 192.168.1.10: Interface réseau spécifique

# Version courte:
gunicorn app:app -b 0.0.0.0:8000

# Changer le port:
gunicorn app:app -b 0.0.0.0:5000     # Port 5000
gunicorn app:app -b 0.0.0.0:80       # Port 80 (nécessite sudo)

# Écouter uniquement sur localhost avec port personnalisé:
gunicorn app:app -b 127.0.0.1:5000

# Format court pour le port (écoute sur toutes interfaces):
gunicorn app:app -b :8000            # Équivalent à 0.0.0.0:8000


# === NOMBRE DE WORKERS (CRUCIAL POUR PERFORMANCE) ===

# CONCEPT: Un worker = un processus qui traite les requêtes

# Par défaut: 1 worker (pas optimal!)
gunicorn app:app
# -> Traite une seule requête à la fois

# Spécifier nombre de workers:
gunicorn app:app --workers 4
#                ^
#                └─ 4 processus qui travaillent en parallèle

# Version courte:
gunicorn app:app -w 4

# COMMENT CHOISIR LE NOMBRE DE WORKERS?

# FORMULE RECOMMANDÉE:
# workers = (2 × nombre_de_CPU) + 1

# Exemple: Serveur avec 2 CPUs
# workers = (2 × 2) + 1 = 5 workers

# Vérifier nombre de CPUs:
# Linux/Mac:
nproc                               # Nombre de CPUs
lscpu                               # Info détaillées

# Python:
python -c "import os; print(os.cpu_count())"

# EXEMPLES PRATIQUES:

# Petit serveur (1 CPU):
gunicorn app:app -w 3               # (2×1)+1 = 3

# Serveur moyen (4 CPUs):
gunicorn app:app -w 9               # (2×4)+1 = 9

# Gros serveur (8 CPUs):
gunicorn app:app -w 17              # (2×8)+1 = 17


# VISUALISATION AVEC DIFFÉRENTS NOMBRES DE WORKERS:

# 1 worker: Une requête à la fois
# [Master] -> [Worker 1] <- Requête A
#            (attente)  <- Requête B bloquée
#            (attente)  <- Requête C bloquée

# 4 workers: 4 requêtes simultanées
# [Master] -> [Worker 1] <- Requête A
#         -> [Worker 2] <- Requête B
#         -> [Worker 3] <- Requête C
#         -> [Worker 4] <- Requête D
#            (attente)  <- Requête E (attend worker libre)


# === COMPRENDRE LES TYPES DE WORKERS ===

# WORKER SYNC (par défaut)
# - Traite une requête à la fois par worker
# - Simple et fiable
# - Bon pour: Applications CPU-intensives
gunicorn app:app -w 4 --worker-class sync
gunicorn app:app -w 4 -k sync       # Version courte

# Exemple avec 4 workers sync:
# Total: 4 requêtes simultanées max

# WORKER GTHREAD (threads)
# - Un worker avec plusieurs threads
# - Gère plusieurs requêtes par worker
# - Bon pour: Applications mixtes (CPU + I/O)
gunicorn app:app -w 4 -k gthread --threads 2

# Exemple avec 4 workers × 2 threads:
# Total: 8 requêtes simultanées
# [Master] -> [Worker 1] -> [Thread 1] <- Requête A
#                      -> [Thread 2] <- Requête B
#         -> [Worker 2] -> [Thread 1] <- Requête C
#                      -> [Thread 2] <- Requête D
#         ... etc

# WORKER GEVENT (asynchrone)
# - Utilise des coroutines (async)
# - Peut gérer BEAUCOUP de connexions
# - Bon pour: APIs, requêtes I/O intensives
# - Nécessite: pip install gunicorn[gevent]
gunicorn app:app -w 4 -k gevent --worker-connections 1000

# Exemple avec 4 workers × 1000 connexions:
# Total: 4000 connexions simultanées possibles!


# === QUAND UTILISER QUEL TYPE DE WORKER? ===

# SYNC - Utilisez si:
# [OK] Application simple sans beaucoup de trafic
# [OK] Calculs intensifs (traitement d'images, ML)
# [OK] Vous débutez (plus simple à debugger)
gunicorn app:app -w 4 -k sync

# GTHREAD - Utilisez si:
# [OK] Application web classique
# [OK] Mélange de calculs et d'accès DB/API
# [OK] Trafic modéré à élevé
gunicorn app:app -w 4 -k gthread --threads 2

# GEVENT - Utilisez si:
# [OK] Beaucoup de connexions simultanées
# [OK] APIs avec calls externes (DB, Redis, HTTP)
# [OK] WebSockets, long-polling
# [OK] Application I/O-bound
gunicorn app:app -w 4 -k gevent --worker-connections 1000


# === EXEMPLE COMPLET AVEC EXPLICATIONS ===

# Application Flask pour un blog
# blog.py

from flask import Flask, jsonify
import time

app = Flask(__name__)

@app.route('/')
def home():
    # Requête rapide, pas de DB
    return 'Welcome to my blog!'

@app.route('/posts')
def posts():
    # Simule lecture DB (I/O)
    time.sleep(0.1)  # 100ms
    return jsonify([
        {'id': 1, 'title': 'First post'},
        {'id': 2, 'title': 'Second post'}
    ])

@app.route('/process')
def process():
    # Simule calcul intensif (CPU)
    time.sleep(2)  # 2 secondes
    return 'Processing done!'


# LANCER AVEC DIFFÉRENTES CONFIGS:

# Config 1: Basique (1 worker sync)
gunicorn blog:app
# -> Lent si plusieurs utilisateurs!
# -> Chaque requête /process bloque pendant 2 secondes

# Config 2: Multiple workers sync (meilleur)
gunicorn blog:app -w 4 -b 0.0.0.0:8000
# -> 4 requêtes /process simultanées
# -> Mais chaque worker bloqué pendant 2 secondes

# Config 3: Workers avec threads (recommandé pour ce cas)
gunicorn blog:app -w 4 -k gthread --threads 4 -b 0.0.0.0:8000
# -> 4 workers × 4 threads = 16 requêtes simultanées
# -> Meilleure gestion du I/O (/posts avec DB)

# Config 4: Async avec gevent (optimal pour I/O)
gunicorn blog:app -w 4 -k gevent --worker-connections 1000 -b 0.0.0.0:8000
# -> 4000 connexions possibles
# -> Parfait si beaucoup d'utilisateurs accèdent à /posts


[OK] LOGGING - COMPRENDRE LES LOGS


# === POURQUOI LES LOGS SONT IMPORTANTS ===

# Les logs vous disent:
# - Quelles requêtes arrivent (access logs)
# - Quels problèmes surviennent (error logs)
# - Performance de votre application
# - Comportement des workers

# Par défaut, Gunicorn affiche les logs dans le terminal


# === LOGS D'ACCÈS (ACCESS LOGS) ===

# Afficher tous les accès:
gunicorn app:app --access-logfile -
#                ^                ^
#                |                └─ "-" = stdout (terminal)
#                └──────────────── Option pour access logs

# Exemple de sortie:
# 192.168.1.10 - - [20/Nov/2024:10:30:45 +0000] "GET / HTTP/1.1" 200 18 "-" "Mozilla/5.0"
#       ^        ^        ^                              ^        ^  ^   ^         ^
#       |        |        |                              |        |  |   |         └─ User Agent
#       |        |        |                              |        |  |   └─────────── Referer
#       |        |        |                              |        |  └─────────────── Taille réponse
#       |        |        |                              |        └────────────────── Status code
#       |        |        |                              └─────────────────────────── Requête
#       |        |        └──────────────────────────────────────────────────────── Date/Heure
#       |        └───────────────────────────────────────────────────────────────── Utilisateur
#       └────────────────────────────────────────────────────────────────────────── IP client

# Sauver dans un fichier:
gunicorn app:app --access-logfile /var/log/gunicorn/access.log

# IMPORTANT: Créer le dossier d'abord!
mkdir -p /var/log/gunicorn


# === LOGS D'ERREUR (ERROR LOGS) ===

# Afficher les erreurs dans le terminal:
gunicorn app:app --error-logfile -

# Sauver dans un fichier:
gunicorn app:app --error-logfile /var/log/gunicorn/error.log

# Exemple d'erreur:
# [ERROR] Exception in worker process
# Traceback (most recent call last):
#   File "app.py", line 10, in home
#     return undefined_variable
# NameError: name 'undefined_variable' is not defined


# === NIVEAUX DE LOG ===

# Contrôle la verbosité des logs

# debug: TOUT (très verbeux, développement uniquement)
gunicorn app:app --log-level debug

# info: Informations générales (défaut, bon pour production)
gunicorn app:app --log-level info

# warning: Seulement avertissements et erreurs
gunicorn app:app --log-level warning

# error: Seulement erreurs critiques
gunicorn app:app --log-level error

# critical: Seulement erreurs fatales
gunicorn app:app --log-level critical


# === CONFIGURATION LOGS COMPLÈTE ===

# Développement: Tout dans le terminal, très verbeux
gunicorn app:app \
    --access-logfile - \
    --error-logfile - \
    --log-level debug

# Production: Fichiers séparés, moins verbeux
gunicorn app:app \
    --access-logfile /var/log/gunicorn/access.log \
    --error-logfile /var/log/gunicorn/error.log \
    --log-level warning

# Désactiver access log (si pas besoin):
gunicorn app:app --access-logfile /dev/null


# === LIRE ET COMPRENDRE LES LOGS ===

# Suivre les logs en temps réel (tail):
tail -f /var/log/gunicorn/access.log
tail -f /var/log/gunicorn/error.log

# Chercher des erreurs:
grep "ERROR" /var/log/gunicorn/error.log
grep "500" /var/log/gunicorn/access.log    # Status 500

# Compter les requêtes:
wc -l /var/log/gunicorn/access.log         # Nombre total
grep "GET" /var/log/gunicorn/access.log | wc -l  # Nombre de GET


[OK] DAEMON MODE - LANCER EN ARRIÈRE-PLAN


# === MODE DÉVELOPPEMENT VS PRODUCTION ===

# MODE DÉVELOPPEMENT (terminal actif):
gunicorn app:app
# [OK] Vous voyez les logs en direct
# [X] Bloque le terminal
# [X] S'arrête si vous fermez le terminal

# MODE PRODUCTION (daemon/arrière-plan):
gunicorn app:app --daemon
# [OK] Libère le terminal
# [OK] Continue même si vous vous déconnectez
# [X] Ne voyez pas les logs (ils vont dans les fichiers)


# === LANCER EN DAEMON ===

# Version minimale:
gunicorn app:app --daemon

# IMPORTANT: Toujours spécifier un PID file en mode daemon!
# PID file = fichier contenant le Process ID du master

gunicorn app:app \
    --daemon \
    --pid /tmp/gunicorn.pid \
    --access-logfile /var/log/gunicorn/access.log \
    --error-logfile /var/log/gunicorn/error.log

# EXPLICATION:
# - --daemon: Lance en arrière-plan
# - --pid /tmp/gunicorn.pid: Sauvegarde le PID pour contrôle ultérieur
# - --access-logfile: Où sauver les logs (obligatoire en daemon!)
# - --error-logfile: Où sauver les erreurs


# === CONTRÔLER LE DAEMON ===

# Vérifier si Gunicorn tourne:
ps aux | grep gunicorn

# Output:
# user  12345  gunicorn: master [app:app]    <- Master process
# user  12346  gunicorn: worker [app:app]    <- Worker 1
# user  12347  gunicorn: worker [app:app]    <- Worker 2

# Lire le PID depuis le fichier:
cat /tmp/gunicorn.pid
# Output: 12345

# Arrêter Gunicorn (graceful shutdown):
kill -QUIT $(cat /tmp/gunicorn.pid)

# Ou directement:
kill -QUIT 12345

# Arrêt immédiat (à éviter):
kill -TERM $(cat /tmp/gunicorn.pid)

# Recharger la configuration (reload):
kill -HUP $(cat /tmp/gunicorn.pid)


# === EXEMPLE COMPLET DAEMON ===

# 1. Créer les dossiers de logs
mkdir -p /var/log/gunicorn

# 2. Lancer en daemon
gunicorn app:app \
    --daemon \
    --workers 4 \
    --bind 0.0.0.0:8000 \
    --pid /tmp/gunicorn.pid \
    --access-logfile /var/log/gunicorn/access.log \
    --error-logfile /var/log/gunicorn/error.log \
    --log-level info

# 3. Vérifier que ça tourne
curl http://localhost:8000
ps aux | grep gunicorn

# 4. Voir les logs
tail -f /var/log/gunicorn/access.log

# 5. Arrêter proprement
kill -QUIT $(cat /tmp/gunicorn.pid)


[OK] TIMEOUTS - ÉVITER LES BLOCAGES


# === COMPRENDRE LES TIMEOUTS ===

# Un timeout = délai maximum pour traiter une requête

# PROBLÈME SANS TIMEOUT:
# Si une requête prend trop de temps (bug, boucle infinie),
# le worker reste bloqué indéfiniment!

# SOLUTION:
# Gunicorn tue automatiquement les workers qui dépassent le timeout


# === TIMEOUT PAR DÉFAUT ===

# Par défaut: 30 secondes
gunicorn app:app
# Si une requête prend > 30s -> Worker tué et redémarré

# Ce qui se passe dans les logs:
# [CRITICAL] WORKER TIMEOUT (pid:12346)
# [WARNING] Worker with pid 12346 was terminated due to signal 9
# [INFO] Booting worker with pid 12348


# === CONFIGURER LE TIMEOUT ===

# Timeout court (API rapide):
gunicorn app:app --timeout 10
#                ^          ^
#                |          └─ 10 secondes max par requête
#                └──────────── Option timeout

# Timeout long (traitement de fichiers):
gunicorn app:app --timeout 120      # 2 minutes

# Timeout très long (traitement lourd):
gunicorn app:app --timeout 300      # 5 minutes


# === QUAND AJUSTER LE TIMEOUT? ===

# AUGMENTER le timeout si votre application:
# [OK] Traite des fichiers volumineux (upload/download)
# [OK] Fait des calculs complexes
# [OK] Appelle des APIs externes lentes
# [OK] Génère des rapports PDF lourds

# GARDER timeout court si:
# [OK] API REST simple
# [OK] Requêtes DB rapides
# [OK] Pas de traitement lourd

# EXEMPLE D'APPLICATION:

# app.py
from flask import Flask, request
import time

app = Flask(__name__)

@app.route('/quick')
def quick():
    # Requête rapide: 0.1 seconde
    time.sleep(0.1)
    return 'Quick response'

@app.route('/slow')
def slow():
    # Requête lente: 5 secondes
    time.sleep(5)
    return 'Slow response'

@app.route('/very-slow')
def very_slow():
    # Requête très lente: 60 secondes
    time.sleep(60)
    return 'Very slow response'


# Config pour cette app:
gunicorn app:app --timeout 70
#                           ^
#                           └─ 70 secondes (plus que le plus long: 60s)

# AVEC timeout de 30s (défaut):
# /quick -> [OK] OK (0.1s < 30s)
# /slow -> [OK] OK (5s < 30s)
# /very-slow -> [X] TIMEOUT! (60s > 30s) -> Worker tué

# AVEC timeout de 70s:
# /quick -> [OK] OK
# /slow -> [OK] OK
# /very-slow -> [OK] OK (60s < 70s)


# === GRACEFUL TIMEOUT ===

# Délai pour l'arrêt propre du worker

# Scénario:
# 1. Vous faites: kill -QUIT (shutdown graceful)
# 2. Gunicorn dit aux workers: "Terminez vos requêtes et arrêtez"
# 3. Si worker ne termine pas dans graceful_timeout -> KILL force

gunicorn app:app --graceful-timeout 30
#                ^                   ^
#                |                   └─ 30 secondes pour finir les requêtes
#                └───────────────────── Option graceful timeout

# Exemple:
# Requête en cours: 20 secondes restantes
# Vous faites: kill -QUIT
# Worker a 30 secondes pour finir
# -> Requête termine (20s < 30s) -> Shutdown propre [OK]

# Si requête prendrait 40 secondes:
# -> Après 30s -> Worker tué de force -> Shutdown forcé [X]


# === KEEP-ALIVE ===

# Garde les connexions HTTP ouvertes pour réutilisation

# Temps de garde (secondes):
gunicorn app:app --keep-alive 5
#                ^            ^
#                |            └─ 5 secondes
#                └──────────────── Option keep-alive

# AVANTAGE:
# Client fait plusieurs requêtes -> Réutilise même connexion TCP
# -> Économise temps d'établissement de connexion

# EXEMPLE:
# Sans keep-alive:
# Requête 1: [Ouvrir TCP] -> [Requête] -> [Fermer TCP]
# Requête 2: [Ouvrir TCP] -> [Requête] -> [Fermer TCP]  <- Perte de temps!

# Avec keep-alive:
# Requête 1: [Ouvrir TCP] -> [Requête]
# Requête 2: -> [Requête] (même connexion)  <- Plus rapide!
# (après 5s sans requête) -> [Fermer TCP]


[OK] EXEMPLE PRATIQUE COMPLET POUR DÉBUTANT


# === PROJET: Blog Simple ===

# Structure du projet:
# myblog/
#   ├── venv/              <- Environnement virtuel
#   ├── app.py             <- Application Flask
#   ├── requirements.txt   <- Dépendances
#   └── logs/              <- Dossier logs (à créer)


# === ÉTAPE 1: Créer l'application ===

# app.py
from flask import Flask, jsonify, request
import time

app = Flask(__name__)

# Page d'accueil
@app.route('/')
def home():
    return '''
    <h1>Mon Blog</h1>
    <p>Bienvenue sur mon blog!</p>
    <a href="/posts">Voir les articles</a>
    '''

# Liste des articles (simule DB)
POSTS = [
    {'id': 1, 'title': 'Premier article', 'content': 'Contenu du premier article'},
    {'id': 2, 'title': 'Deuxième article', 'content': 'Contenu du deuxième article'},
    {'id': 3, 'title': 'Troisième article', 'content': 'Contenu du troisième article'},
]

@app.route('/posts')
def posts():
    # Simule latence DB
    time.sleep(0.2)
    return jsonify(POSTS)

@app.route('/posts/<int:post_id>')
def post_detail(post_id):
    # Simule latence DB
    time.sleep(0.2)
    post = next((p for p in POSTS if p['id'] == post_id), None)
    if post:
        return jsonify(post)
    return jsonify({'error': 'Post not found'}), 404

# Health check (important pour production!)
@app.route('/health')
def health():
    return jsonify({'status': 'healthy'}), 200


# === ÉTAPE 2: Installer les dépendances ===

# requirements.txt
flask==3.0.0
gunicorn==21.2.0

# Installation:
pip install -r requirements.txt


# === ÉTAPE 3: Tester en développement ===

# Méthode Flask (dev uniquement):
python app.py
# [X] Ne PAS utiliser en production!

# Méthode Gunicorn (mieux):
gunicorn app:app
# [OK] Accessible sur http://localhost:8000


# === ÉTAPE 4: Configuration production ===

# Créer dossier logs:
mkdir -p logs

# Lancer avec config optimale:
gunicorn app:app \
    --workers 4 \
    --worker-class gthread \
    --threads 2 \
    --bind 0.0.0.0:8000 \
    --timeout 30 \
    --keep-alive 5 \
    --access-logfile logs/access.log \
    --error-logfile logs/error.log \
    --log-level info \
    --daemon \
    --pid /tmp/myblog.pid

# EXPLICATION DE CHAQUE OPTION:
# --workers 4: 4 processus workers
# --worker-class gthread: Workers avec threads
# --threads 2: 2 threads par worker -> 4×2 = 8 threads total
# --bind 0.0.0.0:8000: Accessible de partout sur port 8000
# --timeout 30: Max 30 secondes par requête
# --keep-alive 5: Garde connexions 5 secondes
# --access-logfile logs/access.log: Logs des accès
# --error-logfile logs/error.log: Logs des erreurs
# --log-level info: Niveau d'information
# --daemon: Lance en arrière-plan
# --pid /tmp/myblog.pid: Fichier PID pour contrôle


# === ÉTAPE 5: Tester ===

# Test 1: Page d'accueil
curl http://localhost:8000/

# Test 2: Liste articles
curl http://localhost:8000/posts

# Test 3: Article spécifique
curl http://localhost:8000/posts/1

# Test 4: Health check
curl http://localhost:8000/health

# Test 5: Voir les logs
tail -f logs/access.log


# === ÉTAPE 6: Contrôler l'application ===

# Vérifier que ça tourne:
ps aux | grep gunicorn

# Voir les logs en temps réel:
tail -f logs/access.log
tail -f logs/error.log

# Recharger (après modification code):
kill -HUP $(cat /tmp/myblog.pid)

# Arrêter:
kill -QUIT $(cat /tmp/myblog.pid)


# === ÉTAPE 7: Script de gestion ===

# Créer manage.sh pour faciliter la gestion

#!/bin/bash
# manage.sh

APP="app:app"
WORKERS=4
PORT=8000
PID_FILE="/tmp/myblog.pid"

case "$1" in
    start)
        echo "Démarrage du blog..."
        gunicorn $APP \
            --workers $WORKERS \
            --worker-class gthread \
            --threads 2 \
            --bind 0.0.0.0:$PORT \
            --timeout 30 \
            --keep-alive 5 \
            --access-logfile logs/access.log \
            --error-logfile logs/error.log \
            --log-level info \
            --daemon \
            --pid $PID_FILE
        echo "Blog démarré sur http://localhost:$PORT"
        ;;
    
    stop)
        echo "Arrêt du blog..."
        if [ -f $PID_FILE ]; then
            kill -QUIT $(cat $PID_FILE)
            echo "Blog arrêté"
        else
            echo "Blog n'est pas en cours d'exécution"
        fi
        ;;
    
    reload)
        echo "Rechargement du blog..."
        if [ -f $PID_FILE ]; then
            kill -HUP $(cat $PID_FILE)
            echo "Blog rechargé"
        else
            echo "Blog n'est pas en cours d'exécution"
        fi
        ;;
    
    status)
        if [ -f $PID_FILE ]; then
            PID=$(cat $PID_FILE)
            if ps -p $PID > /dev/null; then
                echo "Blog tourne (PID: $PID)"
                echo "Workers:"
                ps aux | grep gunicorn | grep -v grep
            else
                echo "PID file existe mais processus mort"
            fi
        else
            echo "Blog arrêté"
        fi
        ;;
    
    logs)
        tail -f logs/access.log
        ;;
    
    errors)
        tail -f logs/error.log
        ;;
    
    *)
        echo "Usage: $0 {start|stop|reload|status|logs|errors}"
        exit 1
        ;;
esac

# Rendre exécutable:
chmod +x manage.sh

# Utiliser:
./manage.sh start     # Démarrer
./manage.sh stop      # Arrêter
./manage.sh reload    # Recharger
./manage.sh status    # Voir status
./manage.sh logs      # Voir logs


[OK] FICHIER DE CONFIGURATION - MÉTHODE PROPRE


# === POURQUOI UN FICHIER DE CONFIG? ===

# PROBLÈME: Ligne de commande devient très longue!
gunicorn app:app --workers 4 --worker-class gthread --threads 2 --bind 0.0.0.0:8000 --timeout 30 --keep-alive 5 --access-logfile logs/access.log --error-logfile logs/error.log --log-level info

# SOLUTION: Fichier de configuration Python


# === CRÉER gunicorn.conf.py ===

# gunicorn.conf.py
import multiprocessing

# === CONFIGURATION SERVEUR ===

# Où écouter?
bind = "0.0.0.0:8000"
# Alternatives:
# bind = "127.0.0.1:8000"           # Local uniquement
# bind = "unix:/tmp/gunicorn.sock"  # Socket Unix (avec Nginx)

# File d'attente des connexions
backlog = 2048
# EXPLICATION: Nombre de connexions en attente
# Si tous les workers sont occupés, nouvelles connexions attendent ici


# === CONFIGURATION WORKERS ===

# Nombre de workers
# Formule: (2 × CPU) + 1
workers = multiprocessing.cpu_count() * 2 + 1

# Type de worker
worker_class = "gthread"
# Options: "sync", "gthread", "gevent", "eventlet"

# Threads par worker (si worker_class = "gthread")
threads = 2
# Total threads = workers × threads
# Exemple: 4 workers × 2 threads = 8 threads

# Connexions simultanées (gevent/eventlet)
worker_connections = 1000
# Utilisé seulement avec gevent/eventlet


# === GESTION MÉMOIRE ===

# Redémarrer worker après N requêtes
max_requests = 1000
# POURQUOI? Évite les memory leaks
# Worker traite 1000 requêtes puis redémarre

# Variabilité aléatoire
max_requests_jitter = 100
# Worker redémarre entre 900-1100 requêtes
# POURQUOI? Évite que tous les workers redémarrent en même temps


# === TIMEOUTS ===

# Timeout par requête (secondes)
timeout = 30
# Worker tué si requête dépasse ce temps

# Graceful timeout (arrêt propre)
graceful_timeout = 30
# Temps donné au worker pour finir lors du shutdown

# Keep-alive
keepalive = 5
# Garde connexion ouverte 5 secondes


# === OPTIMISATION ===

# Précharger l'app avant de fork
preload_app = True
# AVANTAGE: Économise RAM (Copy-On-Write)
# INCONVÉNIENT: Désactive hot reload


# === LOGGING ===

# Fichiers de logs
accesslog = "logs/access.log"
errorlog = "logs/error.log"

# Ou vers stdout (pour Docker):
# accesslog = "-"
# errorlog = "-"

# Niveau de log
loglevel = "info"
# Options: "debug", "info", "warning", "error", "critical"

# Capturer stdout/stderr de l'app
capture_output = True


# === PROCESSUS ===

# Nom du processus
proc_name = "myblog"
# Visible dans ps, top, etc.

# Fichier PID
pidfile = "/tmp/myblog.pid"

# User/Group (sécurité)
user = None  # Par défaut: utilisateur actuel
group = None

# Umask (permissions fichiers créés)
umask = 0o007


# === SÉCURITÉ ===

# Limite taille ligne requête
limit_request_line = 4096

# Limite nombre de headers
limit_request_fields = 100

# Limite taille d'un header
limit_request_field_size = 8190


# === HOOKS (FONCTIONS CALLBACK) ===

# Ces fonctions sont appelées à différents moments
# Utile pour initialisation, nettoyage, debugging

def on_starting(server):
    """Appelé au démarrage du master"""
    print("[RAPIDE] Gunicorn démarre...")

def when_ready(server):
    """Appelé quand prêt à accepter requêtes"""
    print("[OK] Gunicorn prêt!")
    
def on_exit(server):
    """Appelé avant arrêt complet"""
    print("[WAVING_HAND_SIGN] Gunicorn s'arrête...")

def post_fork(server, worker):
    """Appelé après création d'un worker"""
    print(f"Worker {worker.pid} créé")

def pre_request(worker, req):
    """Appelé avant chaque requête"""
    # Utile pour logging custom
    pass

def post_request(worker, req, environ, resp):
    """Appelé après chaque requête"""
    # Utile pour métriques
    pass


# === UTILISER CE FICHIER ===

# Au lieu de:
gunicorn app:app --workers 4 --bind 0.0.0.0:8000 ...

# Simplement:
gunicorn app:app --config gunicorn.conf.py
# Ou version courte:
gunicorn app:app -c gunicorn.conf.py


# === EXEMPLE: CONFIGURATIONS MULTIPLES ===

# Développement: gunicorn.dev.conf.py
bind = "127.0.0.1:8000"
workers = 2
worker_class = "sync"
reload = True  # Hot reload!
accesslog = "-"
errorlog = "-"
loglevel = "debug"

# Production: gunicorn.prod.conf.py
bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "gthread"
threads = 2
preload_app = True
accesslog = "logs/access.log"
errorlog = "logs/error.log"
loglevel = "warning"
daemon = True
pidfile = "/var/run/gunicorn.pid"

# Utiliser:
gunicorn app:app -c gunicorn.dev.conf.py    # Dev
gunicorn app:app -c gunicorn.prod.conf.py   # Prod


[OK] NGINX + GUNICORN - LE DUO CLASSIQUE


# === POURQUOI NGINX AVEC GUNICORN? ===

# Gunicorn seul:
# [OK] Gère votre code Python
# [X] Pas optimisé pour servir fichiers statiques (CSS, JS, images)
# [X] Pas de cache
# [X] Pas de compression
# [X] Moins sécurisé pour exposition directe

# Nginx + Gunicorn:
# [OK] Nginx = Reverse proxy (porte d'entrée)
#   - Sert les fichiers statiques ultra rapidement
#   - Cache les réponses
#   - Compression Gzip
#   - SSL/TLS
#   - Protection DDoS
# [OK] Gunicorn = Serveur d'application
#   - Exécute votre code Python
#   - Derrière Nginx (pas exposé directement)


# === ARCHITECTURE ===

# Internet
#    v
# [Nginx] :80/443 <- Point d'entrée
#    v
#    ├─-> /static/ -> Fichiers CSS/JS (Nginx sert directement)
#    ├─-> /media/ -> Images/uploads (Nginx sert directement)
#    └─-> / -> [Gunicorn] :8000 -> Application Python
#               v
#          [Votre app Flask/Django]


# === CONFIGURATION NGINX SIMPLE ===

# /etc/nginx/sites-available/myblog

server {
    # Écouter sur port 80 (HTTP)
    listen 80;
    
    # Nom de domaine
    server_name example.com www.example.com;
    
    # Taille max upload
    client_max_body_size 10M;
    
    # Logs
    access_log /var/log/nginx/myblog-access.log;
    error_log /var/log/nginx/myblog-error.log;
    
    # Fichiers statiques (CSS, JS)
    location /static/ {
        alias /path/to/myblog/static/;
        expires 30d;  # Cache 30 jours
    }
    
    # Fichiers media (uploads utilisateur)
    location /media/ {
        alias /path/to/myblog/media/;
        expires 7d;
    }
    
    # Toutes les autres requêtes -> Gunicorn
    location / {
        # Transmet à Gunicorn
        proxy_pass http://127.0.0.1:8000;
        
        # Headers nécessaires
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# EXPLICATION DES HEADERS:
# - Host: Nom de domaine original
# - X-Real-IP: IP réelle du client
# - X-Forwarded-For: Chaîne des proxies
# - X-Forwarded-Proto: http ou https


# === ACTIVER LE SITE NGINX ===

# 1. Créer lien symbolique
sudo ln -s /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled/

# 2. Tester configuration
sudo nginx -t
# Output:
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# 3. Recharger Nginx
sudo systemctl reload nginx

# 4. Vérifier status
sudo systemctl status nginx


# === UTILISER SOCKET UNIX (RECOMMANDÉ) ===

# Plus rapide que TCP pour communication locale

# gunicorn.conf.py
bind = "unix:/tmp/gunicorn.sock"
# Crée un socket au lieu d'écouter sur un port

# Nginx config
upstream app_server {
    server unix:/tmp/gunicorn.sock fail_timeout=0;
}

server {
    listen 80;
    server_name example.com;
    
    location / {
        proxy_pass http://app_server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}


# === EXEMPLE COMPLET AVEC SSL ===

# Obtenir certificat SSL (Let's Encrypt):
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

# Nginx génère automatiquement config SSL:

server {
    listen 80;
    server_name example.com www.example.com;
    
    # Redirection HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com www.example.com;
    
    # Certificats SSL
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # Sécurité SSL
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    
    # Static files
    location /static/ {
        alias /path/to/myblog/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    
    # Application
    location / {
        proxy_pass http://unix:/tmp/gunicorn.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https;
    }
}


[OK] SYSTEMD - GESTION AUTOMATIQUE DU SERVICE


# === POURQUOI SYSTEMD? ===

# Problèmes sans systemd:
# [X] Démarrage manuel à chaque reboot serveur
# [X] Pas de redémarrage automatique si crash
# [X] Gestion compliquée (kill, pid files, etc.)

# Avec systemd:
# [OK] Démarre automatiquement au boot
# [OK] Redémarre automatiquement si crash
# [OK] Commandes simples (start, stop, restart, status)
# [OK] Logs centralisés (journalctl)


# === CRÉER LE SERVICE ===

# /etc/systemd/system/myblog.service

[Unit]
# Description du service
Description=Gunicorn pour Mon Blog
# Démarre après le réseau
After=network.target

[Service]
# Type de service
Type=notify
# notify = Gunicorn signale quand il est prêt

# Utilisateur/Groupe
User=www-data
Group=www-data

# Dossier de travail
WorkingDirectory=/path/to/myblog

# Environnement virtuel
Environment="PATH=/path/to/myblog/venv/bin"

# Variables d'environnement (optionnel)
Environment="FLASK_ENV=production"
Environment="DATABASE_URL=postgresql://..."

# Commande de démarrage
ExecStart=/path/to/myblog/venv/bin/gunicorn \
          --config /path/to/myblog/gunicorn.conf.py \
          app:app

# Reload graceful
ExecReload=/bin/kill -s HUP $MAINPID

# Comment arrêter
KillMode=mixed
# mixed = SIGTERM au master, SIGKILL aux workers si besoin

# Timeout arrêt
TimeoutStopSec=5

# Redémarrage automatique
Restart=always
RestartSec=3

# Sécurité
PrivateTmp=true

[Install]
# Démarrer au boot
WantedBy=multi-user.target


# === GÉRER LE SERVICE ===

# 1. Recharger systemd (après création/modification)
sudo systemctl daemon-reload

# 2. Activer au démarrage
sudo systemctl enable myblog.service
# Crée un lien symbolique pour démarrage auto

# 3. Démarrer
sudo systemctl start myblog.service

# 4. Voir le status
sudo systemctl status myblog.service
# Output:
# [BLACK_CIRCLE] myblog.service - Gunicorn pour Mon Blog
#    Loaded: loaded (/etc/systemd/system/myblog.service; enabled)
#    Active: active (running) since ...
#    Main PID: 12345
#    Status: "Gunicorn ready. Processing requests."
#    ...

# 5. Arrêter
sudo systemctl stop myblog.service

# 6. Redémarrer
sudo systemctl restart myblog.service

# 7. Reload graceful (sans couper connexions)
sudo systemctl reload myblog.service

# 8. Voir les logs
sudo journalctl -u myblog.service

# 9. Suivre les logs en temps réel
sudo journalctl -u myblog.service -f

# 10. Logs aujourd'hui uniquement
sudo journalctl -u myblog.service --since today

# 11. Dernières 50 lignes
sudo journalctl -u myblog.service -n 50


# === LOGS SYSTEMD ===

# Avantage: Logs centralisés et structurés

# Chercher erreurs:
sudo journalctl -u myblog.service -p err

# Entre deux dates:
sudo journalctl -u myblog.service --since "2024-01-01" --until "2024-01-31"

# Export JSON:
sudo journalctl -u myblog.service -o json-pretty


# === EXAMPLE COMPLET: DÉPLOIEMENT ===

# 1. Créer service systemd
sudo nano /etc/systemd/system/myblog.service
# (Copier config ci-dessus)

# 2. Recharger systemd
sudo systemctl daemon-reload

# 3. Activer et démarrer
sudo systemctl enable myblog.service
sudo systemctl start myblog.service

# 4. Vérifier
sudo systemctl status myblog.service
curl http://localhost:8000

# 5. Configurer Nginx
sudo nano /etc/nginx/sites-available/myblog
# (Copier config Nginx ci-dessus)

# 6. Activer site Nginx
sudo ln -s /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

# 7. Tester depuis internet
curl https://example.com

# 8. Voir les logs
sudo journalctl -u myblog.service -f


[OK] DÉPLOIEMENT - WORKFLOW COMPLET


# === PRÉPARATION DU SERVEUR ===

# 1. Se connecter au serveur
ssh user@your-server.com

# 2. Mettre à jour système
sudo apt update
sudo apt upgrade -y

# 3. Installer dépendances
sudo apt install -y python3 python3-venv python3-pip nginx

# 4. Créer utilisateur pour l'app (sécurité)
sudo useradd -m -s /bin/bash webapp
sudo usermod -aG www-data webapp


# === DÉPLOIEMENT DE L'APPLICATION ===

# 1. Créer dossier projet
sudo mkdir -p /var/www/myblog
sudo chown webapp:www-data /var/www/myblog

# 2. Se connecter comme webapp
sudo su - webapp

# 3. Cloner le code (ou upload)
cd /var/www/myblog
git clone https://github.com/username/myblog.git .
# Ou:
# scp -r local/myblog/* user@server:/var/www/myblog/

# 4. Créer environnement virtuel
python3 -m venv venv
source venv/bin/activate

# 5. Installer dépendances
pip install -r requirements.txt

# 6. Créer dossiers nécessaires
mkdir -p logs static media

# 7. Configuration
cp gunicorn.conf.py.example gunicorn.conf.py
nano gunicorn.conf.py  # Ajuster config

# 8. Variables d'environnement
nano .env
# Ajouter:
# DATABASE_URL=postgresql://...
# SECRET_KEY=...

# 9. Tester Gunicorn
gunicorn app:app -c gunicorn.conf.py
# Ctrl+C pour arrêter

# 10. Retour root
exit


# === CONFIGURATION SYSTEMD ===

# 1. Créer service
sudo nano /etc/systemd/system/myblog.service

[Unit]
Description=My Blog Gunicorn Service
After=network.target

[Service]
Type=notify
User=webapp
Group=www-data
WorkingDirectory=/var/www/myblog
Environment="PATH=/var/www/myblog/venv/bin"
EnvironmentFile=/var/www/myblog/.env
ExecStart=/var/www/myblog/venv/bin/gunicorn \
          --config /var/www/myblog/gunicorn.conf.py \
          app:app
ExecReload=/bin/kill -s HUP $MAINPID
Restart=always
RestartSec=3

[Install]
WantedBy=multi-user.target

# 2. Activer et démarrer
sudo systemctl daemon-reload
sudo systemctl enable myblog.service
sudo systemctl start myblog.service
sudo systemctl status myblog.service


# === CONFIGURATION NGINX ===

# 1. Créer config
sudo nano /etc/nginx/sites-available/myblog

server {
    listen 80;
    server_name your-domain.com www.your-domain.com;
    
    client_max_body_size 10M;
    
    location /static/ {
        alias /var/www/myblog/static/;
        expires 30d;
    }
    
    location /media/ {
        alias /var/www/myblog/media/;
        expires 7d;
    }
    
    location / {
        proxy_pass http://unix:/tmp/gunicorn.sock;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

# 2. Activer site
sudo ln -s /etc/nginx/sites-available/myblog /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx


# === SSL AVEC LET'S ENCRYPT ===

# 1. Installer Certbot
sudo apt install -y certbot python3-certbot-nginx

# 2. Obtenir certificat
sudo certbot --nginx -d your-domain.com -d www.your-domain.com

# 3. Renouvellement automatique (déjà configuré)
sudo certbot renew --dry-run


# === VÉRIFICATION ===

# 1. Service Gunicorn
sudo systemctl status myblog.service

# 2. Nginx
sudo systemctl status nginx

# 3. Connexion
curl http://your-domain.com
curl https://your-domain.com

# 4. Logs
sudo journalctl -u myblog.service -f
tail -f /var/log/nginx/access.log


# === MISE À JOUR DE L'APPLICATION ===

# Script de déploiement: deploy.sh

#!/bin/bash
set -e

echo "[RAPIDE] Déploiement en cours..."

# 1. Aller dans dossier app
cd /var/www/myblog

# 2. Pull nouveau code
echo "[ENTREE] Récupération du code..."
git pull origin main

# 3. Activer venv
source venv/bin/activate

# 4. Mettre à jour dépendances
echo "[PACKAGE] Installation dépendances..."
pip install -r requirements.txt

# 5. Migrations DB (Django)
# python manage.py migrate --noinput

# 6. Collecter static files (Django)
# python manage.py collectstatic --noinput

# 7. Reload Gunicorn (graceful)
echo "[SYNC] Reload Gunicorn..."
sudo systemctl reload myblog.service

# 8. Vérifier
echo "[OK] Vérification..."
curl -f http://localhost:8000/health || {
    echo "[X] Health check échoué!"
    exit 1
}

echo "[OK] Déploiement réussi!"

# Utilisation:
chmod +x deploy.sh
./deploy.sh


[OK] TROUBLESHOOTING - RÉSOUDRE LES PROBLÈMES COURANTS


# === PROBLÈME 1: Gunicorn ne démarre pas ===

# Symptôme:
sudo systemctl start myblog.service
# Job for myblog.service failed

# Diagnostic:
sudo systemctl status myblog.service
# Regarde la ligne "Active:"

sudo journalctl -u myblog.service -n 50
# Voir les derniers logs

# Causes fréquentes:

# A) Erreur Python (import, syntax)
# Solution: Tester manuellement
source venv/bin/activate
python -c "from app import app; print('OK')"

# B) Mauvais chemin dans service
# Vérifier dans /etc/systemd/system/myblog.service:
# - WorkingDirectory existe?
# - Path vers venv correct?
# - ExecStart pointe vers bon gunicorn?

# C) Permissions
# Solution:
sudo chown -R webapp:www-data /var/www/myblog
sudo chmod 755 /var/www/myblog


# === PROBLÈME 2: Worker timeout ===

# Symptôme dans les logs:
# [CRITICAL] WORKER TIMEOUT (pid:12345)
# [WARNING] Worker with pid 12345 was terminated due to signal 9

# Cause: Requête prend trop de temps

# Solution 1: Augmenter timeout
# gunicorn.conf.py
timeout = 60  # Au lieu de 30

# Solution 2: Identifier requêtes lentes
# Ajouter logging dans votre app:
import time
import logging

@app.route('/slow')
def slow():
    start = time.time()
    # ... traitement ...
    duration = time.time() - start
    if duration > 5:
        logging.warning(f"Requête lente: {duration}s")
    return "Done"


# === PROBLÈME 3: Connection refused ===

# Symptôme:
curl http://localhost:8000
# curl: (7) Failed to connect to localhost port 8000: Connection refused

# Diagnostic:

# A) Gunicorn tourne?
sudo systemctl status myblog.service
ps aux | grep gunicorn

# B) Écoute sur bon port?
sudo netstat -tlnp | grep 8000
# Ou:
sudo ss -tlnp | grep 8000

# C) Firewall bloque?
sudo ufw status
sudo iptables -L

# Solution:
# Ouvrir port:
sudo ufw allow 8000
# Ou pour HTTP/HTTPS:
sudo ufw allow 'Nginx Full'


# === PROBLÈME 4: 502 Bad Gateway (Nginx) ===

# Symptôme: Page Nginx affiche "502 Bad Gateway"

# Cause: Nginx ne peut pas joindre Gunicorn

# Diagnostic:

# A) Gunicorn tourne?
sudo systemctl status myblog.service

# B) Socket existe?
ls -l /tmp/gunicorn.sock
# Devrait afficher le socket

# C) Permissions socket?
# Solution:
# gunicorn.conf.py
umask = 0o007  # Permissions 770

# D) Nginx config correcte?
sudo nginx -t

# E) Logs Nginx
sudo tail -f /var/log/nginx/error.log


# === PROBLÈME 5: Application lente ===

# Diagnostic:

# A) Voir temps réponse dans logs
tail -f logs/access.log
# Regarder les temps en fin de ligne

# B) Profiler avec py-spy
pip install py-spy

# Trouver PID d'un worker
ps aux | grep gunicorn
# Choisir un PID de worker (pas master)

# Profiler
sudo py-spy top --pid 12346
# Montre en temps réel où le temps est dépensé

# C) Augmenter workers/threads
# gunicorn.conf.py
workers = 8  # Plus de workers
threads = 4  # Plus de threads par worker


# === PROBLÈME 6: Trop de mémoire utilisée ===

# Diagnostic:
ps aux | grep gunicorn
# Colonne RSS = mémoire en KB

# Ou:
htop  # Puis filtrer par "gunicorn"

# Solutions:

# A) Réduire workers
workers = 2  # Moins de workers

# B) Memory leak -> Redémarrer workers régulièrement
max_requests = 500
max_requests_jitter = 50

# C) Activer preload (économise RAM)
preload_app = True


# === PROBLÈME 7: Logs ne s'affichent pas ===

# Cause: Logs vont ailleurs que prévu

# Solutions:

# A) Vérifier config
# gunicorn.conf.py
accesslog = "logs/access.log"  # Chemin relatif
# Ou absolu:
accesslog = "/var/www/myblog/logs/access.log"

# B) Permissions dossier logs
sudo chown -R webapp:www-data /var/www/myblog/logs
sudo chmod 755 /var/www/myblog/logs

# C) Avec systemd, logs vont dans journald:
sudo journalctl -u myblog.service -f


# === PROBLÈME 8: Cannot bind to address ===

# Symptôme:
# [ERROR] Retrying in 1 second.
# [ERROR] Can't connect to ('127.0.0.1', 8000)

# Cause: Port déjà utilisé

# Diagnostic:
sudo netstat -tlnp | grep 8000
# Ou:
sudo lsof -i :8000

# Solution:

# A) Tuer processus qui utilise le port
kill <PID>

# B) Utiliser autre port
bind = "0.0.0.0:8001"

# C) Si Gunicorn déjà lancé:
sudo systemctl stop myblog.service


# === CHECKLIST DE DEBUGGING ===

# Quand quelque chose ne marche pas, vérifier dans l'ordre:

# [OK] 1. Service systemd tourne?
sudo systemctl status myblog.service

# [OK] 2. Logs systemd?
sudo journalctl -u myblog.service -n 50

# [OK] 3. Logs Gunicorn?
tail -f /var/www/myblog/logs/error.log

# [OK] 4. Logs Nginx?
sudo tail -f /var/log/nginx/error.log

# [OK] 5. Processus actifs?
ps aux | grep gunicorn

# [OK] 6. Ports ouverts?
sudo netstat -tlnp

# [OK] 7. Firewall?
sudo ufw status

# [OK] 8. Permissions fichiers?
ls -la /var/www/myblog/

# [OK] 9. Python peut importer app?
source venv/bin/activate
python -c "from app import app; print('OK')"

# [OK] 10. Test direct Gunicorn (bypass systemd)?
cd /var/www/myblog
source venv/bin/activate
gunicorn app:app --bind 127.0.0.1:8001


[OK] DOCKER - CONTAINERISER VOTRE APPLICATION


# === POURQUOI DOCKER AVEC GUNICORN? ===

# Avantages:
# [OK] Environnement isolé et reproductible
# [OK] Déploiement simplifié
# [OK] Scaling facile (Kubernetes, Docker Swarm)
# [OK] Même environnement dev/prod


# === DOCKERFILE SIMPLE ===

# Dockerfile
FROM python:3.11-slim

# Empêche Python de bufferiser stdout/stderr
ENV PYTHONUNBUFFERED=1

# Empêche Python d'écrire des .pyc
ENV PYTHONDONTWRITEBYTECODE=1

# Dossier de travail
WORKDIR /app

# Copier requirements et installer
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copier le code
COPY . .

# Port exposé
EXPOSE 8000

# Commande de démarrage
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "4"]


# === CONSTRUIRE ET LANCER ===

# Construire l'image
docker build -t myblog:latest .

# Lancer le container
docker run -d -p 8000:8000 --name myblog myblog:latest

# Tester
curl http://localhost:8000

# Voir les logs
docker logs -f myblog

# Arrêter
docker stop myblog

# Supprimer
docker rm myblog


# === DOCKERFILE OPTIMISÉ (MULTI-STAGE) ===

# Dockerfile
# Stage 1: Builder (installe dépendances)
FROM python:3.11-slim as builder

WORKDIR /app

# Installer dépendances de build si nécessaire
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Copier et installer requirements
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt


# Stage 2: Runtime (image finale légère)
FROM python:3.11-slim

WORKDIR /app

# Copier les dépendances depuis builder
COPY --from=builder /root/.local /root/.local

# Ajouter au PATH
ENV PATH=/root/.local/bin:$PATH

# Variables d'environnement
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1

# Créer utilisateur non-root (sécurité)
RUN useradd -m -u 1000 appuser && \
    chown -R appuser:appuser /app

# Copier l'application
COPY --chown=appuser:appuser . .

# Changer vers utilisateur non-root
USER appuser

# Port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1

# Démarrage
CMD ["gunicorn", "app:app", \
     "--bind", "0.0.0.0:8000", \
     "--workers", "4", \
     "--worker-class", "gthread", \
     "--threads", "2", \
     "--timeout", "30", \
     "--access-logfile", "-", \
     "--error-logfile", "-", \
     "--log-level", "info"]


# === DOCKER-COMPOSE COMPLET ===

# docker-compose.yml
version: '3.8'

services:
  # Application web
  web:
    build: .
    container_name: myblog_web
    command: >
      gunicorn app:app
      --bind 0.0.0.0:8000
      --workers 4
      --worker-class gthread
      --threads 2
      --timeout 30
      --access-logfile -
      --error-logfile -
      --log-level info
    volumes:
      # Code (dev uniquement, supprimer en prod)
      - .:/app
      # Fichiers persistants
      - static_volume:/app/static
      - media_volume:/app/media
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/myblog
      - REDIS_URL=redis://redis:6379/0
      - SECRET_KEY=your-secret-key-here
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    restart: unless-stopped
    networks:
      - app_network

  # Nginx reverse proxy
  nginx:
    image: nginx:alpine
    container_name: myblog_nginx
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - static_volume:/static:ro
      - media_volume:/media:ro
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - web
    restart: unless-stopped
    networks:
      - app_network

  # Base de données PostgreSQL
  db:
    image: postgres:15-alpine
    container_name: myblog_db
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=myblog
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - app_network

  # Redis (cache, sessions, celery)
  redis:
    image: redis:7-alpine
    container_name: myblog_redis
    restart: unless-stopped
    networks:
      - app_network

volumes:
  postgres_data:
  static_volume:
  media_volume:

networks:
  app_network:
    driver: bridge


# === NGINX CONFIG POUR DOCKER ===

# nginx.conf
events {
    worker_connections 1024;
}

http {
    upstream app {
        server web:8000;
    }

    server {
        listen 80;
        server_name localhost;

        client_max_body_size 10M;

        location /static/ {
            alias /static/;
            expires 30d;
            add_header Cache-Control "public, immutable";
        }

        location /media/ {
            alias /media/;
            expires 7d;
        }

        location / {
            proxy_pass http://app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            
            # Timeouts
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }
    }
}


# === COMMANDES DOCKER-COMPOSE ===

# Construire les images
docker-compose build

# Démarrer tous les services
docker-compose up -d

# Voir les logs
docker-compose logs -f
docker-compose logs -f web      # Seulement web

# Arrêter
docker-compose down

# Arrêter et supprimer volumes
docker-compose down -v

# Redémarrer un service
docker-compose restart web

# Exécuter commande dans container
docker-compose exec web bash
docker-compose exec web python manage.py migrate

# Voir status
docker-compose ps


# === .DOCKERIGNORE ===

# .dockerignore (évite de copier fichiers inutiles)
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.so
*.egg
*.egg-info/
dist/
build/

# Environnement virtuel
venv/
env/
.venv/

# Git
.git/
.gitignore

# IDE
.vscode/
.idea/
*.swp

# Tests
.pytest_cache/
.coverage
htmlcov/

# Logs
*.log
logs/

# Base de données
*.db
*.sqlite3

# Environnement
.env
.env.local


[OK] PERFORMANCE - OPTIMISATION AVANCÉE


# === COMPRENDRE LES MÉTRIQUES ===

# Métriques clés à surveiller:
# 1. Temps de réponse (latence)
# 2. Throughput (requêtes/seconde)
# 3. Utilisation CPU
# 4. Utilisation mémoire
# 5. Taux d'erreur


# === BENCHMARKING AVEC AB (APACHE BENCH) ===

# Installer
sudo apt install apache2-utils

# Test simple
ab -n 1000 -c 10 http://localhost:8000/
#  ^       ^  ^
#  |       |  └─ 10 connexions simultanées
#  |       └──── 1000 requêtes total
#  └──────────── Apache Bench

# Résultats importants:
# - Time taken for tests: Temps total
# - Requests per second: Throughput
# - Time per request (mean): Latence moyenne
# - Failed requests: Erreurs

# Test avec keep-alive
ab -n 1000 -c 10 -k http://localhost:8000/

# Test POST avec JSON
ab -n 100 -c 10 -p data.json -T "application/json" http://localhost:8000/api/

# data.json
{"key": "value"}


# === BENCHMARKING AVEC WRK ===

# Installer
sudo apt install wrk

# Test basique
wrk -t4 -c100 -d30s http://localhost:8000/
#   ^   ^     ^
#   |   |     └─ Durée: 30 secondes
#   |   └─────── 100 connexions simultanées
#   └─────────── 4 threads

# Résultats:
# Latency: Distribution des temps de réponse
# Req/Sec: Requêtes par seconde
# Transfer/sec: Données transférées


# === TESTER DIFFÉRENTES CONFIGURATIONS ===

# Test 1: Workers sync
gunicorn app:app -w 4 -k sync -b :8001 &
ab -n 10000 -c 100 http://localhost:8001/
# Note: Requests per second

# Test 2: Workers gthread
gunicorn app:app -w 4 -k gthread --threads 4 -b :8002 &
ab -n 10000 -c 100 http://localhost:8002/
# Comparer: Requests per second

# Test 3: Workers gevent
gunicorn app:app -w 4 -k gevent --worker-connections 1000 -b :8003 &
ab -n 10000 -c 100 http://localhost:8003/
# Comparer: Requests per second

# Choisir config avec meilleur throughput


# === FORMULE OPTIMALE WORKERS ===

# Règle générale: (2 × CPU) + 1

# Mais à tester selon votre app!

# Script de test:
#!/bin/bash
# test_workers.sh

for workers in 2 4 8 16; do
    echo "Test avec $workers workers..."
    
    # Lancer Gunicorn
    gunicorn app:app -w $workers -b :8000 --daemon --pid /tmp/gunicorn.pid
    sleep 2
    
    # Benchmark
    ab -n 10000 -c 100 -k http://localhost:8000/ > results_${workers}w.txt
    
    # Arrêter
    kill -QUIT $(cat /tmp/gunicorn.pid)
    sleep 2
done

echo "Voir results_*.txt pour comparer"


# === PROFILING APPLICATIF ===

# Identifier goulots d'étranglement dans votre code

# Avec cProfile:
import cProfile
import pstats
import io

@app.route('/profile')
def profile():
    pr = cProfile.Profile()
    pr.enable()
    
    # Votre code à profiler
    result = heavy_computation()
    
    pr.disable()
    
    s = io.StringIO()
    ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
    ps.print_stats(20)  # Top 20 fonctions
    
    return f"<pre>{s.getvalue()}</pre>"


# Avec Flask-Profiler:
pip install flask_profiler

from flask_profiler import Profiler

app.config["flask_profiler"] = {
    "enabled": True,
    "storage": {
        "engine": "sqlite"
    },
    "basicAuth": {
        "enabled": True,
        "username": "admin",
        "password": "admin"
    }
}

profiler = Profiler()
profiler.init_app(app)

# Accéder: http://localhost:8000/flask-profiler/


# === OPTIMISATIONS SPÉCIFIQUES ===

# 1. PRELOAD APP (économise mémoire)
# gunicorn.conf.py
preload_app = True

# ATTENTION: Désactive hot reload!
# À utiliser uniquement en production


# 2. MAX REQUESTS (évite memory leaks)
max_requests = 1000
max_requests_jitter = 100


# 3. WORKER CONNECTIONS (gevent)
worker_connections = 1000
# Plus = plus de connexions simultanées


# 4. BACKLOG (queue connexions)
backlog = 2048
# File d'attente avant refus


# 5. KEEP-ALIVE (réutilise connexions)
keepalive = 5
# Économise handshakes TCP


# === CONFIGURATION HAUTE PERFORMANCE ===

# gunicorn_perf.conf.py
import multiprocessing

# Maximum workers
workers = multiprocessing.cpu_count() * 2 + 1

# Threads pour I/O
worker_class = "gthread"
threads = 4

# Connexions
backlog = 4096
keepalive = 5

# Mémoire
preload_app = True
max_requests = 2000
max_requests_jitter = 200

# Timeouts adaptés
timeout = 30
graceful_timeout = 30

# Logs minimaux (moins d'I/O)
accesslog = None  # Désactive access log
errorlog = "-"
loglevel = "warning"


# === MONITORING EN PRODUCTION ===

# 1. Prometheus + Grafana

# Installer prometheus-flask-exporter
pip install prometheus-flask-exporter

# Dans votre app:
from prometheus_flask_exporter import PrometheusMetrics

metrics = PrometheusMetrics(app)

# Endpoint /metrics automatiquement créé
# Prometheus scrape ce endpoint

# Métriques automatiques:
# - flask_http_request_duration_seconds
# - flask_http_request_total
# - flask_http_request_exceptions_total


# 2. StatsD

# Gunicorn avec StatsD:
gunicorn app:app --statsd-host localhost:8125 --statsd-prefix myapp

# Métriques envoyées:
# - myapp.requests
# - myapp.request.duration
# - myapp.workers
# - myapp.failures


[OK] SÉCURITÉ - PROTÉGER VOTRE APPLICATION


# === PRINCIPES DE BASE ===

# 1. Ne JAMAIS exposer Gunicorn directement sur Internet
#    -> Toujours utiliser Nginx devant

# 2. Ne JAMAIS lancer Gunicorn en root
#    -> Utiliser utilisateur dédié

# 3. Toujours utiliser HTTPS en production
#    -> Certificat SSL/TLS

# 4. Limiter les ressources
#    -> Timeouts, taille uploads, etc.


# === UTILISATEUR NON-ROOT ===

# Créer utilisateur dédié
sudo useradd -m -s /bin/bash webapp
sudo usermod -aG www-data webapp

# Dans systemd service:
[Service]
User=webapp
Group=www-data


# === LIMITES DE SÉCURITÉ ===

# gunicorn.conf.py

# Limite taille ligne requête (contre buffer overflow)
limit_request_line = 4096

# Limite nombre de headers (contre DoS)
limit_request_fields = 100

# Limite taille d'un header
limit_request_field_size = 8190


# === HEADERS DE SÉCURITÉ (VIA NGINX) ===

# nginx.conf
server {
    # ...
    
    # Empêche clickjacking
    add_header X-Frame-Options "SAMEORIGIN" always;
    
    # Empêche MIME sniffing
    add_header X-Content-Type-Options "nosniff" always;
    
    # Protection XSS
    add_header X-XSS-Protection "1; mode=block" always;
    
    # Content Security Policy
    add_header Content-Security-Policy "default-src 'self'" always;
    
    # HSTS (force HTTPS)
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    
    # Referrer policy
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}


# === PROXY HEADERS (SI DERRIÈRE REVERSE PROXY) ===

# gunicorn.conf.py

# IPs autorisées pour X-Forwarded-* headers
forwarded_allow_ips = "127.0.0.1"

# Support PROXY protocol
proxy_protocol = False  # True si Nginx utilise proxy_protocol
proxy_allow_ips = "127.0.0.1"


# === RATE LIMITING (NGINX) ===

# nginx.conf
http {
    # Zone de rate limiting
    limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
    
    server {
        location / {
            # Max 10 requêtes/sec par IP
            # Burst: permet 20 requêtes d'un coup
            limit_req zone=mylimit burst=20 nodelay;
            
            proxy_pass http://app;
        }
    }
}


# === SSL/TLS AVEC LET'S ENCRYPT ===

# 1. Installer Certbot
sudo apt install certbot python3-certbot-nginx

# 2. Obtenir certificat
sudo certbot --nginx -d example.com -d www.example.com

# 3. Renouvellement automatique (cron déjà configuré)
sudo certbot renew --dry-run

# 4. Config générée automatiquement:
server {
    listen 443 ssl http2;
    
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # Protocoles modernes uniquement
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # Ciphers sécurisés
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;
}


# === VARIABLES D'ENVIRONNEMENT SÉCURISÉES ===

# NE JAMAIS mettre de secrets dans le code!

# Méthode 1: Fichier .env (ne pas committer!)
# .env
SECRET_KEY=super-secret-key-here
DATABASE_URL=postgresql://user:pass@localhost/db

# Charger dans app:
from dotenv import load_dotenv
load_dotenv()

import os
secret = os.getenv('SECRET_KEY')


# Méthode 2: Variables systemd
# /etc/systemd/system/myblog.service
[Service]
Environment="SECRET_KEY=super-secret-key"
Environment="DATABASE_URL=postgresql://..."

# Ou depuis fichier:
EnvironmentFile=/etc/myblog/secrets.env


# Méthode 3: Docker secrets
# docker-compose.yml
services:
  web:
    secrets:
      - db_password
    environment:
      DATABASE_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt


# === AUDIT DE SÉCURITÉ ===

# Scanner vulnérabilités Python
pip install safety
safety check

# Scanner dépendances
pip install pip-audit
pip-audit

# Tester configuration SSL
# https://www.ssllabs.com/ssltest/


[OK] RÉCAPITULATIF - COMMANDES ESSENTIELLES


# === DÉVELOPPEMENT ===

# Lancer simple
gunicorn app:app

# Avec reload auto
gunicorn app:app --reload

# Port personnalisé
gunicorn app:app -b :5000

# Debug verbeux
gunicorn app:app --log-level debug


# === PRODUCTION ===

# Config minimale
gunicorn app:app -w 4 -b 0.0.0.0:8000

# Config recommandée
gunicorn app:app \
    -w 4 \
    -k gthread \
    --threads 2 \
    -b 0.0.0.0:8000 \
    --timeout 30 \
    --access-logfile logs/access.log \
    --error-logfile logs/error.log

# Avec fichier config
gunicorn app:app -c gunicorn.conf.py

# En daemon
gunicorn app:app -c gunicorn.conf.py --daemon --pid /tmp/gunicorn.pid


# === CONTRÔLE (SIGNAUX) ===

# Reload graceful (recharge code)
kill -HUP $(cat /tmp/gunicorn.pid)

# Stop graceful (attend fin requêtes)
kill -QUIT $(cat /tmp/gunicorn.pid)

# Stop immédiat
kill -TERM $(cat /tmp/gunicorn.pid)

# Ajouter 1 worker
kill -TTIN $(cat /tmp/gunicorn.pid)

# Retirer 1 worker
kill -TTOU $(cat /tmp/gunicorn.pid)


# === SYSTEMD ===

# Démarrer
sudo systemctl start myblog

# Arrêter
sudo systemctl stop myblog

# Redémarrer
sudo systemctl restart myblog

# Reload graceful
sudo systemctl reload myblog

# Status
sudo systemctl status myblog

# Logs
sudo journalctl -u myblog -f


# === DOCKER ===

# Build
docker build -t myblog .

# Run
docker run -d -p 8000:8000 myblog

# Logs
docker logs -f myblog

# Avec docker-compose
docker-compose up -d
docker-compose logs -f
docker-compose down


# === DEBUGGING ===

# Tester config
gunicorn app:app -c gunicorn.conf.py --check-config

# Voir processus
ps aux | grep gunicorn

# Voir ports
sudo netstat -tlnp | grep gunicorn

# Tester endpoint
curl http://localhost:8000/health

# Voir logs en direct
tail -f logs/access.log
tail -f logs/error.log


[OK] RESSOURCES & AIDE


# === DOCUMENTATION OFFICIELLE ===

# Gunicorn
https://docs.gunicorn.org/

# Configuration settings
https://docs.gunicorn.org/en/stable/settings.html

# Design overview
https://docs.gunicorn.org/en/stable/design.html


# === TUTORIELS RECOMMANDÉS ===

# Digital Ocean - Flask + Gunicorn + Nginx
https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-20-04

# Real Python - Deploying Flask
https://realpython.com/flask-by-example-part-3-text-processing-with-requests-beautifulsoup-nltk/

# Django deployment checklist
https://docs.djangoproject.com/en/stable/howto/deployment/checklist/


# === OUTILS COMPLÉMENTAIRES ===

# Supervisor - Process manager
https://supervisord.org/

# Circus - Process watcher
https://circus.readthedocs.io/

# Prometheus - Monitoring
https://prometheus.io/

# Grafana - Dashboards
https://grafana.com/


# === COMPARAISON SERVEURS WSGI ===

# Gunicorn
# [OK] Simple, stable, éprouvé
# [OK] Configuration facile
# [OK] Bon pour la plupart des cas
# [X] Pas le plus performant

# uWSGI
# [OK] Très performant
# [OK] Beaucoup de fonctionnalités
# [X] Configuration complexe
# [X] Documentation difficile

# Waitress
# [OK] Pur Python (Windows)
# [OK] Simple
# [X] Moins performant

# mod_wsgi (Apache)
# [OK] Intégration Apache
# [X] Configuration complexe
# [X] Moins moderne


# === AIDE COMMUNAUTAIRE ===

# Stack Overflow
Tag: [gunicorn]

# GitHub Issues
https://github.com/benoitc/gunicorn/issues

# Reddit
r/django
r/flask
r/python


# === CHECKLIST FINALE ===

# Avant de déployer en production, vérifier:

[WHITE_SQUARE] Environnement virtuel activé
[WHITE_SQUARE] Requirements.txt à jour
[WHITE_SQUARE] gunicorn.conf.py configuré
[WHITE_SQUARE] Nombre workers approprié
[WHITE_SQUARE] Timeouts adaptés à votre app
[WHITE_SQUARE] Logs configurés et rotationnés
[WHITE_SQUARE] Systemd service créé et testé
[WHITE_SQUARE] Nginx configuré comme reverse proxy
[WHITE_SQUARE] SSL/TLS actif (Let's Encrypt)
[WHITE_SQUARE] Firewall configuré (ufw)
[WHITE_SQUARE] Utilisateur non-root
[WHITE_SQUARE] Variables d'environnement sécurisées
[WHITE_SQUARE] Health endpoint implémenté
[WHITE_SQUARE] Monitoring en place
[WHITE_SQUARE] Backups configurés
[WHITE_SQUARE] Documentation à jour
[WHITE_SQUARE] Load testing effectué
[WHITE_SQUARE] Plan de rollback prêt


# FIN DE LA CHEATSHEET GUNICORN
# Vous êtes maintenant prêt à déployer en production! [RAPIDE]# Fichier: python_cheats/cheatsheets/gunicorn.txt
# Cheatsheet Gunicorn - Guide Complet du Débutant à l'Expert


[OK] COMPRENDRE GUNICORN - EXPLICATIONS POUR DÉBUTANTS

# === QU'EST-CE QUE GUNICORN? ===

# Gunicorn (Green Unicorn) est un serveur d'application Python pour la production.

# ANALOGIE SIMPLE:
# Imaginez un restaurant:
# - Votre code Python (Flask/Django) = Les recettes et la cuisine
# - Gunicorn = Le système de gestion du restaurant (maître d'hôtel + serveurs)
# - Les clients = Les requêtes HTTP qui arrivent
# - Les workers = Les serveurs qui prennent les commandes et servent
# - Nginx (reverse proxy) = L'hôtesse d'accueil qui dirige les clients

# POURQUOI GUNICORN?
# Quand vous développez en local, vous faites:
python app.py                    # Flask dev server
python manage.py runserver       # Django dev server

# [ATTENTION] CES SERVEURS NE SONT PAS POUR LA PRODUCTION!
# Problèmes:
# - Gèrent une seule requête à la fois (lent!)
# - Pas de gestion de crash
# - Pas optimisés pour la performance
# - Pas sécurisés

# [OK] GUNICORN RÉSOUT CES PROBLÈMES:
# - Gère plusieurs requêtes simultanément (workers)
# - Redémarre automatiquement si un worker crash
# - Optimisé pour la production
# - Stable et testé


# === CONCEPTS DE BASE ===

# 1. WSGI (Web Server Gateway Interface)
# - Interface standard entre serveur web et application Python
# - Flask, Django, Pyramid utilisent WSGI
# - Gunicorn est un serveur WSGI

# 2. MASTER PROCESS (Processus Maître)
# - Gère les workers
# - Ne traite PAS les requêtes lui-même
# - Surveille et redémarre les workers si nécessaire

# 3. WORKERS (Travailleurs)
# - Processus qui traitent réellement les requêtes
# - Exemple: 4 workers = 4 requêtes simultanées minimum
# - Chaque worker est un processus Python indépendant

# 4. WORKER CLASSES (Types de workers)
# - sync: Un worker traite une requête à la fois (défaut)
# - gthread: Un worker avec plusieurs threads (parallélisme)
# - gevent/eventlet: Workers asynchrones (beaucoup de connexions)

# VISUALISATION:
#
#     [Nginx] <- Clients HTTP
#        v
#   [Gunicorn Master]
#        v
#   ├─ [Worker 1] <- Traite requête A
#   ├─ [Worker 2] <- Traite requête B
#   ├─ [Worker 3] <- Traite requête C
#   └─ [Worker 4] <- Traite requête D


[OK] INSTALLATION - PREMIERS PAS

# === PRÉREQUIS ===

# Avant d'installer Gunicorn, assurez-vous d'avoir:
# 1. Python 3.7+ installé
python --version                # Vérifier version Python

# 2. Un environnement virtuel (FORTEMENT RECOMMANDÉ)
python -m venv venv             # Créer environnement virtuel
source venv/bin/activate        # Linux/Mac
venv\Scripts\activate           # Windows

# 3. Une application Python (Flask, Django, etc.)


# === INSTALLATION BASIQUE ===

# Installation simple (dans votre environnement virtuel)
pip install gunicorn

# Vérifier l'installation
gunicorn --version
# Output: gunicorn (version 21.2.0)

# Où Gunicorn est installé?
which gunicorn                  # Linux/Mac
where gunicorn                  # Windows
# Output: /path/to/venv/bin/gunicorn

# Installation avec extras
pip install gunicorn[gevent]        # Support gevent workers
pip install gunicorn[eventlet]      # Support eventlet workers
pip install gunicorn[tornado]       # Support tornado workers
pip install gunicorn[gthread]       # Support threaded workers (inclus par défaut)

# Installation pour ASGI (FastAPI, Starlette)
pip install "uvicorn[standard]" gunicorn

# Vérifier installation
gunicorn --version
gunicorn -v


[OK] UTILISATION DE BASE


# === STRUCTURE APPLICATION SIMPLE ===

# app.py (Flask)
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello World!'

if __name__ == '__main__':
    app.run()

# app.py (Django)
# Pas besoin de code spécial, Django crée wsgi.py automatiquement

# myproject/wsgi.py (Django)
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
application = get_wsgi_application()


# === LANCER GUNICORN ===

# Syntaxe de base
gunicorn [OPTIONS] APP_MODULE

# Flask - Lancer app simple
gunicorn app:app
# Format: module:variable

# Flask - Lancer avec fonction factory
gunicorn "app:create_app()"

# Django
gunicorn myproject.wsgi:application

# Spécifier host et port
gunicorn app:app --bind 0.0.0.0:8000
gunicorn app:app -b 0.0.0.0:8000            # Version courte

# Plusieurs binds
gunicorn app:app -b 0.0.0.0:8000 -b 0.0.0.0:8001

# Bind sur socket Unix
gunicorn app:app --bind unix:/tmp/gunicorn.sock

# Écouter sur toutes les interfaces
gunicorn app:app -b :8000


[OK] GESTION DES WORKERS


# === TYPES DE WORKERS ===

# sync (défaut)
# - Workers synchrones traditionnels
# - Un worker = un processus
# - Bon pour CPU-bound tasks
gunicorn app:app --worker-class sync
gunicorn app:app -k sync

# gthread
# - Workers avec threads
# - Gère plusieurs requêtes simultanées par worker
# - Bon équilibre performance/ressources
gunicorn app:app --worker-class gthread --threads 4
gunicorn app:app -k gthread --threads 4

# gevent
# - Workers asynchrones (coroutines)
# - Très efficace pour I/O-bound tasks
# - Nécessite: pip install gunicorn[gevent]
gunicorn app:app --worker-class gevent
gunicorn app:app -k gevent

# eventlet
# - Similaire à gevent
# - Workers asynchrones
# - Nécessite: pip install gunicorn[eventlet]
gunicorn app:app --worker-class eventlet
gunicorn app:app -k eventlet

# tornado
# - Utilise Tornado framework
# - Workers asynchrones
# - Nécessite: pip install gunicorn[tornado]
gunicorn app:app --worker-class tornado
gunicorn app:app -k tornado


# === NOMBRE DE WORKERS ===

# Formule recommandée: (2 x CPU) + 1
# Exemple: 4 CPUs -> 9 workers

# Spécifier nombre de workers
gunicorn app:app --workers 4
gunicorn app:app -w 4

# Auto-détection basée sur CPU
# Pas d'option auto, mais script Python:
import multiprocessing
workers = multiprocessing.cpu_count() * 2 + 1
# Utiliser dans config: workers = workers

# Workers avec threads (gthread)
gunicorn app:app -w 4 -k gthread --threads 2
# Total: 4 workers × 2 threads = 8 threads

# Connections simultanées (gevent/eventlet)
gunicorn app:app -w 4 -k gevent --worker-connections 1000
# Total: 4 workers × 1000 connections = 4000 connections


[OK] CONFIGURATION AVANCÉE


# === OPTIONS DE PERFORMANCE ===

# Timeout requête (secondes)
gunicorn app:app --timeout 30
gunicorn app:app -t 30

# Graceful timeout (arrêt propre)
gunicorn app:app --graceful-timeout 30

# Keep-alive (connexions persistantes)
gunicorn app:app --keep-alive 5

# Max requests par worker (redémarre après)
gunicorn app:app --max-requests 1000

# Max requests jitter (variabilité aléatoire)
gunicorn app:app --max-requests 1000 --max-requests-jitter 100
# Worker redémarre entre 900-1100 requêtes


# === LOGGING ===

# Niveau de log
gunicorn app:app --log-level debug
gunicorn app:app --log-level info       # défaut
gunicorn app:app --log-level warning
gunicorn app:app --log-level error
gunicorn app:app --log-level critical

# Fichier de log
gunicorn app:app --access-logfile access.log
gunicorn app:app --error-logfile error.log

# Log vers stdout/stderr (Docker friendly)
gunicorn app:app --access-logfile -
gunicorn app:app --error-logfile -

# Désactiver access log
gunicorn app:app --access-logfile /dev/null

# Format de log personnalisé
gunicorn app:app --access-logformat '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"'

# Variables disponibles pour access-logformat:
# %(h)s - IP client
# %(l)s - '-'
# %(u)s - Utilisateur
# %(t)s - Date/heure
# %(r)s - Ligne de requête
# %(s)s - Status code
# %(b)s - Taille réponse
# %(f)s - Referer
# %(a)s - User agent
# %(T)s - Temps requête (secondes)
# %(D)s - Temps requête (microsecondes)
# %(L)s - Temps requête (secondes décimales)
# %(p)s - PID du worker


# === DAEMON & PROCESS ===

# Lancer en daemon (arrière-plan)
gunicorn app:app --daemon

# PID file
gunicorn app:app --pid /var/run/gunicorn.pid

# User/Group
gunicorn app:app --user www-data --group www-data

# Umask
gunicorn app:app --umask 0007

# Changer répertoire de travail
gunicorn app:app --chdir /path/to/app


# === SÉCURITÉ ===

# Limite taille des headers
gunicorn app:app --limit-request-line 4096      # Taille ligne requête
gunicorn app:app --limit-request-fields 100     # Nombre de headers
gunicorn app:app --limit-request-field-size 8190  # Taille header

# SSL/TLS
gunicorn app:app --certfile=/path/to/cert.pem --keyfile=/path/to/key.pem

# SSL avec CA cert
gunicorn app:app --certfile=cert.pem --keyfile=key.pem --ca-certs=ca.pem

# Cipher suite SSL
gunicorn app:app --certfile=cert.pem --keyfile=key.pem --ciphers TLSv1.2

# Désactiver SSL compression
gunicorn app:app --certfile=cert.pem --keyfile=key.pem --suppress-ragged-eofs


[OK] FICHIER DE CONFIGURATION


# === CRÉER gunicorn_config.py ===

# gunicorn_config.py
import multiprocessing

# Server Socket
bind = "0.0.0.0:8000"
backlog = 2048

# Worker Processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 50
timeout = 30
graceful_timeout = 30
keepalive = 5

# Logging
accesslog = "-"
errorlog = "-"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Process Naming
proc_name = "myapp"

# Server Mechanics
daemon = False
pidfile = "/var/run/gunicorn.pid"
user = None
group = None
tmp_upload_dir = None

# SSL
keyfile = None
certfile = None


# === UTILISER LE FICHIER DE CONFIG ===

# Charger config
gunicorn app:app --config gunicorn_config.py
gunicorn app:app -c gunicorn_config.py

# Config + options CLI (CLI override config)
gunicorn app:app -c gunicorn_config.py --workers 8


# === CONFIGURATION AVANCÉE ===

# gunicorn_advanced.py

# Fonction de pré-chargement de l'app
def on_starting(server):
    """Appelé juste avant le démarrage du master"""
    print("Gunicorn démarrage...")

def on_reload(server):
    """Appelé lors d'un reload"""
    print("Gunicorn reload...")

def when_ready(server):
    """Appelé après démarrage, avant accepter requêtes"""
    print("Gunicorn prêt!")

def pre_fork(server, worker):
    """Appelé avant fork d'un worker"""
    pass

def post_fork(server, worker):
    """Appelé après fork d'un worker"""
    print(f"Worker {worker.pid} spawned")

def post_worker_init(worker):
    """Appelé dans le worker après init"""
    print(f"Worker {worker.pid} initialized")

def worker_int(worker):
    """Appelé quand worker reçoit SIGINT/SIGQUIT"""
    print(f"Worker {worker.pid} interrupted")

def worker_abort(worker):
    """Appelé quand worker reçoit SIGABRT"""
    print(f"Worker {worker.pid} aborted")

def pre_exec(server):
    """Appelé avant re-exec du master"""
    print("Gunicorn re-exec...")

def pre_request(worker, req):
    """Appelé avant traitement requête"""
    worker.log.debug(f"{req.method} {req.path}")

def post_request(worker, req, environ, resp):
    """Appelé après traitement requête"""
    pass

def child_exit(server, worker):
    """Appelé dans le master après exit d'un worker"""
    print(f"Worker {worker.pid} exited")

def worker_exit(server, worker):
    """Appelé dans le worker avant exit"""
    pass

def nworkers_changed(server, new_value, old_value):
    """Appelé après changement nombre workers"""
    print(f"Workers: {old_value} -> {new_value}")

def on_exit(server):
    """Appelé avant arrêt complet"""
    print("Gunicorn shutdown")


# === CONFIGURATION ENVIRONNEMENT ===

# Charger depuis variables d'environnement
import os

bind = os.getenv("GUNICORN_BIND", "0.0.0.0:8000")
workers = int(os.getenv("GUNICORN_WORKERS", multiprocessing.cpu_count() * 2 + 1))
worker_class = os.getenv("GUNICORN_WORKER_CLASS", "sync")
timeout = int(os.getenv("GUNICORN_TIMEOUT", 30))
loglevel = os.getenv("GUNICORN_LOG_LEVEL", "info")

# Utiliser
export GUNICORN_WORKERS=4
gunicorn app:app -c gunicorn_config.py


[OK] SIGNAUX & CONTRÔLE


# === SIGNAUX MASTER PROCESS ===

# QUIT - Graceful shutdown (attend requêtes en cours)
kill -QUIT $(cat /var/run/gunicorn.pid)
# Ou
pkill -QUIT gunicorn

# TERM - Fast shutdown (termine immédiatement)
kill -TERM $(cat /var/run/gunicorn.pid)

# INT - Fast shutdown (Ctrl+C)
kill -INT $(cat /var/run/gunicorn.pid)

# HUP - Reload config et redémarre workers gracefully
kill -HUP $(cat /var/run/gunicorn.pid)

# USR1 - Réouvre les log files (rotation logs)
kill -USR1 $(cat /var/run/gunicorn.pid)

# USR2 - Upgrade Gunicorn (nouveau master, ancien shutdown après)
kill -USR2 $(cat /var/run/gunicorn.pid)

# TTIN - Augmente nombre de workers de 1
kill -TTIN $(cat /var/run/gunicorn.pid)

# TTOU - Diminue nombre de workers de 1
kill -TTOU $(cat /var/run/gunicorn.pid)

# WINCH - Gracefully shutdown tous les workers
kill -WINCH $(cat /var/run/gunicorn.pid)


# === SIGNAUX WORKER PROCESS ===

# QUIT - Graceful shutdown du worker
kill -QUIT <worker_pid>

# TERM - Fast shutdown du worker
kill -TERM <worker_pid>

# USR1 - Réouvre les log files
kill -USR1 <worker_pid>


# === SCRIPTS DE CONTRÔLE ===

# Reload graceful (Bash)
#!/bin/bash
# reload.sh
PID_FILE=/var/run/gunicorn.pid
if [ -f $PID_FILE ]; then
    kill -HUP $(cat $PID_FILE)
    echo "Gunicorn reloaded"
else
    echo "PID file not found"
fi

# Stop graceful (Bash)
#!/bin/bash
# stop.sh
PID_FILE=/var/run/gunicorn.pid
if [ -f $PID_FILE ]; then
    kill -QUIT $(cat $PID_FILE)
    echo "Gunicorn stopping..."
    # Attendre arrêt
    while [ -f $PID_FILE ]; do
        sleep 1
    done
    echo "Gunicorn stopped"
else
    echo "Gunicorn not running"
fi

# Upgrade zero-downtime
#!/bin/bash
# upgrade.sh
PID_FILE=/var/run/gunicorn.pid
OLD_PID=$(cat $PID_FILE)

# Nouveau master
kill -USR2 $OLD_PID
sleep 5

# Vérifier nouveau master
NEW_PID=$(cat $PID_FILE)
if [ "$NEW_PID" != "$OLD_PID" ]; then
    # Shutdown ancien master gracefully
    kill -QUIT $OLD_PID
    echo "Upgrade successful"
else
    echo "Upgrade failed"
fi


[OK] INTÉGRATION NGINX


# === CONFIGURATION NGINX ===

# /etc/nginx/sites-available/myapp

upstream app_server {
    # Socket Unix (recommandé)
    server unix:/tmp/gunicorn.sock fail_timeout=0;
    
    # Ou TCP
    # server 127.0.0.1:8000 fail_timeout=0;
    
    # Ou multiple workers
    # server 127.0.0.1:8000;
    # server 127.0.0.1:8001;
    # server 127.0.0.1:8002;
}

server {
    listen 80;
    server_name example.com www.example.com;
    
    client_max_body_size 4G;
    
    # Path to static files
    root /path/to/app/static;
    
    # Logs
    access_log /var/log/nginx/myapp-access.log;
    error_log /var/log/nginx/myapp-error.log;
    
    # Static files
    location /static/ {
        alias /path/to/app/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
    
    location /media/ {
        alias /path/to/app/media/;
        expires 30d;
    }
    
    # Proxy to Gunicorn
    location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_buffering off;
        
        proxy_pass http://app_server;
    }
    
    # Security headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
}

# Activer site
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx


# === GUNICORN AVEC NGINX ===

# Lancer Gunicorn avec socket Unix
gunicorn app:app --bind unix:/tmp/gunicorn.sock --workers 4

# Permissions socket
gunicorn app:app --bind unix:/tmp/gunicorn.sock --workers 4 --umask 0007

# Config pour Nginx
# gunicorn_config.py
bind = "unix:/tmp/gunicorn.sock"
workers = 4
worker_class = "sync"
timeout = 30
keepalive = 5
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"


[OK] SYSTEMD SERVICE


# === CRÉER SERVICE SYSTEMD ===

# /etc/systemd/system/gunicorn.service

[Unit]
Description=Gunicorn daemon for myapp
Requires=gunicorn.socket
After=network.target

[Service]
Type=notify
# User/Group
User=www-data
Group=www-data

# Working directory
WorkingDirectory=/path/to/app

# Virtual environment
Environment="PATH=/path/to/app/venv/bin"

# Gunicorn command
ExecStart=/path/to/app/venv/bin/gunicorn \
          --config /path/to/app/gunicorn_config.py \
          myproject.wsgi:application

ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true

[Install]
WantedBy=multi-user.target


# === CRÉER SOCKET SYSTEMD ===

# /etc/systemd/system/gunicorn.socket

[Unit]
Description=gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660

[Install]
WantedBy=sockets.target


# === GÉRER LE SERVICE ===

# Recharger systemd
sudo systemctl daemon-reload

# Activer au démarrage
sudo systemctl enable gunicorn.socket
sudo systemctl enable gunicorn.service

# Démarrer
sudo systemctl start gunicorn.socket
sudo systemctl start gunicorn.service

# Arrêter
sudo systemctl stop gunicorn.service

# Redémarrer
sudo systemctl restart gunicorn.service

# Reload config (graceful)
sudo systemctl reload gunicorn.service

# Status
sudo systemctl status gunicorn.service

# Logs
sudo journalctl -u gunicorn.service
sudo journalctl -u gunicorn.service -f          # Follow
sudo journalctl -u gunicorn.service --since today


# === SERVICE AVEC CONFIG ENVIRONNEMENT ===

# /etc/systemd/system/gunicorn.service

[Service]
EnvironmentFile=/etc/gunicorn/env
ExecStart=/path/to/venv/bin/gunicorn \
          --bind unix:/run/gunicorn.sock \
          --workers ${GUNICORN_WORKERS} \
          --worker-class ${GUNICORN_WORKER_CLASS} \
          myproject.wsgi:application

# /etc/gunicorn/env
GUNICORN_WORKERS=4
GUNICORN_WORKER_CLASS=sync
DJANGO_SETTINGS_MODULE=myproject.settings.production


[OK] DOCKER & CONTAINERISATION


# === DOCKERFILE ===

# Dockerfile
FROM python:3.11-slim

# Variables
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1

# Working directory
WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY . .

# User non-root
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# Expose port
EXPOSE 8000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

# Run Gunicorn
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000", "--workers", "4"]


# === DOCKER-COMPOSE ===

# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    command: gunicorn app:app --bind 0.0.0.0:8000 --workers 4 --log-level info
    volumes:
      - .:/app
      - static:/app/static
      - media:/app/media
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://user:pass@db:5432/mydb
      - DJANGO_SETTINGS_MODULE=myproject.settings.production
    depends_on:
      - db
    restart: unless-stopped
  
  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - static:/static:ro
      - media:/media:ro
    ports:
      - "80:80"
    depends_on:
      - web
    restart: unless-stopped
  
  db:
    image: postgres:15
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=mydb
    restart: unless-stopped

volumes:
  postgres_data:
  static:
  media:


# === MULTI-STAGE BUILD (PRODUCTION) ===

# Dockerfile
FROM python:3.11-slim as builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

FROM python:3.11-slim

WORKDIR /app

# Copy dependencies from builder
COPY --from=builder /root/.local /root/.local
ENV PATH=/root/.local/bin:$PATH

# Copy app
COPY . .

# Non-root user
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

EXPOSE 8000

CMD ["gunicorn", "app:app", \
     "--bind", "0.0.0.0:8000", \
     "--workers", "4", \
     "--worker-class", "gthread", \
     "--threads", "2", \
     "--timeout", "30", \
     "--access-logfile", "-", \
     "--error-logfile", "-", \
     "--log-level", "info"]


[OK] MONITORING & MÉTRIQUES


# === STATSDAT (INTÉGRÉ) ===

# Activer StatsD
gunicorn app:app --statsd-host localhost:8125 --statsd-prefix myapp

# Métriques envoyées:
# - gunicorn.requests
# - gunicorn.request.duration
# - gunicorn.workers
# - gunicorn.failures


# === PROMETHEUS ===

# Installer
pip install prometheus-flask-exporter      # Pour Flask
pip install django-prometheus              # Pour Django

# Flask avec prometheus
from flask import Flask
from prometheus_flask_exporter import PrometheusMetrics

app = Flask(__name__)
metrics = PrometheusMetrics(app)

# Endpoint /metrics automatiquement créé

# Gunicorn config avec prometheus
# gunicorn_config.py
from prometheus_client import multiprocess
from prometheus_client import generate_latest, CollectorRegistry, CONTENT_TYPE_LATEST

def child_exit(server, worker):
    multiprocess.mark_process_dead(worker.pid)


# === HEALTH CHECKS ===

# Flask health endpoint
@app.route('/health')
def health():
    return {'status': 'healthy'}, 200

@app.route('/ready')
def ready():
    # Vérifier DB, Redis, etc.
    try:
        db.session.execute('SELECT 1')
        return {'status': 'ready'}, 200
    except:
        return {'status': 'not ready'}, 503

# Django health endpoint
# myapp/views.py
from django.http import JsonResponse
from django.db import connection

def health(request):
    return JsonResponse({'status': 'healthy'})

def ready(request):
    try:
        with connection.cursor() as cursor:
            cursor.execute('SELECT 1')
        return JsonResponse({'status': 'ready'})
    except:
        return JsonResponse({'status': 'not ready'}, status=503)

# urls.py
urlpatterns = [
    path('health/', health),
    path('ready/', ready),
]


# === LOGGING STRUCTURÉ ===

# gunicorn_config.py avec structlog
import structlog

# Logger
logger = structlog.get_logger()

def pre_request(worker, req):
    logger.info(
        "request_started",
        method=req.method,
        path=req.path,
        worker_pid=worker.pid
    )

def post_request(worker, req, environ, resp):
    logger.info(
        "request_completed",
        method=req.method,
        path=req.path,
        status=resp.status_code,
        worker_pid=worker.pid
    )


[OK] PERFORMANCE TUNING


# === OPTIMISATION WORKERS ===

# Formule de base
# workers = (2 × CPU) + 1

# CPU-bound (calculs intensifs)
gunicorn app:app -w 4 -k sync

# I/O-bound (requêtes DB, API externes)
gunicorn app:app -w 4 -k gevent --worker-connections 1000

# Mixed (CPU + I/O)
gunicorn app:app -w 4 -k gthread --threads 4

# Benchmark différentes configs
ab -n 1000 -c 100 http://localhost:8000/
wrk -t12 -c400 -d30s http://localhost:8000/


# === OPTIMISATION TIMEOUTS ===

# Timeout adapté à votre app
gunicorn app:app --timeout 30               # API rapide
gunicorn app:app --timeout 120              # Traitement long
gunicorn app:app --timeout 300              # Upload fichiers

# Graceful timeout (shutdown propre)
gunicorn app:app --graceful-timeout 30

# Keep-alive (connexions persistantes)
gunicorn app:app --keep-alive 5             # Réutilise connexions


# === OPTIMISATION MÉMOIRE ===

# Max requests (redémarre worker après N requêtes)
# Évite memory leaks
gunicorn app:app --max-requests 1000 --max-requests-jitter 100

# Preload app (charge app avant fork)
# Économise mémoire (Copy-On-Write)
gunicorn app:app --preload

# ATTENTION: --preload désactive hot reload!


# === OPTIMISATION RÉSEAU ===

# Backlog (queue connexions)
gunicorn app:app --backlog 2048

# Worker connections (gevent/eventlet)
gunicorn app:app -k gevent --worker-connections 1000


# === CONFIGURATION OPTIMISÉE ===

# gunicorn_production.py
import multiprocessing

# CPU cores
cores = multiprocessing.cpu_count()

# Bind
bind = "0.0.0.0:8000"
backlog = 2048

# Workers (CPU-bound)
workers = cores * 2 + 1
worker_class = "sync"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 100

# Timeouts
timeout = 30
graceful_timeout = 30
keepalive = 5

# Preload (économise RAM)
preload_app = True

# Logging
accesslog = "-"
errorlog = "-"
loglevel = "warning"  # Production: warning/error
capture_output = True

# Process
proc_name = "myapp"
daemon = False


# === CONFIGURATION I/O-BOUND ===

# gunicorn_io_bound.py
import multiprocessing

cores = multiprocessing.cpu_count()

bind = "0.0.0.0:8000"
backlog = 2048

# Workers async
workers = cores * 2 + 1
worker_class = "gevent"
worker_connections = 1000

# Timeouts plus longs (I/O)
timeout = 60
graceful_timeout = 60
keepalive = 5

# Pas de preload avec gevent
preload_app = False

max_requests = 5000
max_requests_jitter = 500


[OK] DÉPLOIEMENT AVANCÉ


# === STRATÉGIES DE DÉPLOIEMENT ===

# Blue-Green Deployment
# 1. Nouveau code sur port différent
gunicorn app:app --bind 0.0.0.0:8001 -c new_config.py &

# 2. Health check
curl http://localhost:8001/health

# 3. Basculer Nginx vers nouveau port
# Modifier upstream dans nginx.conf

# 4. Reload Nginx
sudo nginx -s reload

# 5. Arrêter ancien Gunicorn
kill -QUIT $(cat /var/run/gunicorn_old.pid)


# Rolling Deployment avec systemd
# Utilise automatiquement reload graceful
sudo systemctl reload gunicorn


# Canary Deployment (Nginx)
upstream app_server {
    server 127.0.0.1:8000 weight=9;    # Version stable (90%)
    server 127.0.0.1:8001 weight=1;    # Canary (10%)
}


# === ZERO-DOWNTIME DEPLOYMENT ===

# Script de déploiement
#!/bin/bash
# deploy.sh

set -e

APP_DIR=/path/to/app
PID_FILE=/var/run/gunicorn.pid
CONFIG_FILE=$APP_DIR/gunicorn_config.py

echo "Déploiement démarré..."

# 1. Pull nouveau code
cd $APP_DIR
git pull origin main

# 2. Installer dépendances
source venv/bin/activate
pip install -r requirements.txt

# 3. Migrations DB (Django)
python manage.py migrate --noinput

# 4. Collecter static files
python manage.py collectstatic --noinput

# 5. Reload Gunicorn (graceful)
if [ -f $PID_FILE ]; then
    echo "Reload Gunicorn..."
    kill -HUP $(cat $PID_FILE)
else
    echo "Démarrage Gunicorn..."
    gunicorn -c $CONFIG_FILE myproject.wsgi:application
fi

echo "Déploiement terminé!"


# === HOT RELOAD (DÉVELOPPEMENT) ===

# Reload auto sur changement fichiers
gunicorn app:app --reload

# Reload avec fichiers spécifiques
gunicorn app:app --reload --reload-extra-file config.py

# ATTENTION: --reload pour dev uniquement!


[OK] SÉCURITÉ AVANCÉE


# === SSL/TLS ===

# SSL simple
gunicorn app:app \
    --certfile=/etc/ssl/certs/cert.pem \
    --keyfile=/etc/ssl/private/key.pem \
    --bind 0.0.0.0:443

# SSL avec CA cert
gunicorn app:app \
    --certfile=/etc/ssl/certs/cert.pem \
    --keyfile=/etc/ssl/private/key.pem \
    --ca-certs=/etc/ssl/certs/ca-bundle.crt

# Protocoles SSL modernes uniquement
gunicorn app:app \
    --certfile=cert.pem \
    --keyfile=key.pem \
    --ssl-version=TLSv1_2

# Ciphers sécurisés
gunicorn app:app \
    --certfile=cert.pem \
    --keyfile=key.pem \
    --ciphers="ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS"


# === LIMITES DE SÉCURITÉ ===

# gunicorn_secure.py

# Limites requêtes
limit_request_line = 4096           # Taille max ligne requête
limit_request_fields = 100          # Nombre max headers
limit_request_field_size = 8190     # Taille max header

# Proxy headers (derrière reverse proxy)
forwarded_allow_ips = "127.0.0.1"   # IPs autorisées pour X-Forwarded-*
proxy_protocol = True               # Support PROXY protocol
proxy_allow_ips = "127.0.0.1"       # IPs autorisées pour PROXY


# === CONFIGURATION DJANGO SECURITY ===

# settings.py (Django)
import os

# HTTPS
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True

# HSTS
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Autres headers
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'

# Proxy headers
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

# Allowed hosts
ALLOWED_HOSTS = ['example.com', 'www.example.com']


# === RATE LIMITING ===

# Avec Nginx
# nginx.conf
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

server {
    location / {
        limit_req zone=mylimit burst=20 nodelay;
        proxy_pass http://app_server;
    }
}

# Avec Flask-Limiter
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

app = Flask(__name__)
limiter = Limiter(
    app,
    key_func=get_remote_address,
    default_limits=["200 per day", "50 per hour"]
)

@app.route("/api/endpoint")
@limiter.limit("5 per minute")
def endpoint():
    return "data"


[OK] DEBUGGING & TROUBLESHOOTING


# === MODES DE DEBUG ===

# Lancer en mode debug
gunicorn app:app --log-level debug

# Logs verbeux
gunicorn app:app --log-level debug --access-logfile - --error-logfile -

# Tracer les requêtes
gunicorn app:app --log-level debug --capture-output


# === VÉRIFIER CONFIGURATION ===

# Test config sans démarrer
gunicorn app:app -c gunicorn_config.py --check-config

# Afficher config
gunicorn app:app -c gunicorn_config.py --print-config


# === PROBLÈMES COURANTS ===

# === PROBLÈME: Worker timeout ===
# Symptôme: [CRITICAL] WORKER TIMEOUT
# Solution: Augmenter timeout
gunicorn app:app --timeout 60

# Ou identifier requêtes lentes
# Ajouter dans app:
import time
start_time = time.time()
# ... traitement ...
duration = time.time() - start_time
logger.info(f"Request took {duration}s")


# === PROBLÈME: Memory leaks ===
# Solution: Redémarrer workers régulièrement
gunicorn app:app --max-requests 1000 --max-requests-jitter 100

# Ou surveiller mémoire
import psutil
import os

def post_worker_init(worker):
    process = psutil.Process(os.getpid())
    worker.log.info(f"Worker memory: {process.memory_info().rss / 1024 / 1024:.2f} MB")


# === PROBLÈME: Too many open files ===
# Solution: Augmenter limites système
# /etc/security/limits.conf
www-data soft nofile 65536
www-data hard nofile 65536

# Vérifier
ulimit -n

# Systemd
# /etc/systemd/system/gunicorn.service
[Service]
LimitNOFILE=65536


# === PROBLÈME: Workers meurent aléatoirement ===
# Vérifier logs
sudo journalctl -u gunicorn -n 100

# Vérifier OOM (Out Of Memory)
dmesg | grep -i "out of memory"

# Solution: Réduire workers ou augmenter RAM
gunicorn app:app --workers 2


# === PROBLÈME: Slow performance ===
# Profiler avec py-spy
pip install py-spy

# Profiler process
sudo py-spy record -o profile.svg --pid <gunicorn_worker_pid>

# Ou avec cProfile dans app
import cProfile
import pstats

profiler = cProfile.Profile()
profiler.enable()
# ... code ...
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats()


# === PROBLÈME: Connection refused ===
# Vérifier bind
netstat -tlnp | grep gunicorn
ss -tlnp | grep gunicorn

# Vérifier firewall
sudo ufw status
sudo iptables -L

# Tester connexion
curl http://localhost:8000
telnet localhost 8000


# === PROBLÈME: Static files not served ===
# Gunicorn ne sert PAS les static files!
# Solution: Utiliser Nginx ou WhiteNoise

# WhiteNoise (Django)
pip install whitenoise

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # Juste après Security
    # ...
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'


[OK] TESTS & BENCHMARKING


# === LOAD TESTING ===

# Apache Bench
ab -n 10000 -c 100 http://localhost:8000/

# Options utiles
ab -n 10000 -c 100 -k http://localhost:8000/         # Keep-alive
ab -n 10000 -c 100 -H "Accept: application/json" http://localhost:8000/
ab -n 10000 -c 100 -p data.json -T "application/json" http://localhost:8000/


# wrk (plus moderne)
wrk -t12 -c400 -d30s http://localhost:8000/

# Avec script Lua
wrk -t12 -c400 -d30s -s script.lua http://localhost:8000/

# script.lua
wrk.method = "POST"
wrk.body   = '{"key":"value"}'
wrk.headers["Content-Type"] = "application/json"


# Locust (Python)
# locustfile.py
from locust import HttpUser, task, between

class WebsiteUser(HttpUser):
    wait_time = between(1, 5)
    
    @task
    def index(self):
        self.client.get("/")
    
    @task(3)
    def api(self):
        self.client.get("/api/data")

# Lancer
locust -f locustfile.py --host=http://localhost:8000


# === STRESS TESTING ===

# siege
siege -c 100 -t 60s http://localhost:8000/

# Options
siege -c 100 -r 10 http://localhost:8000/           # 100 users, 10 reps
siege -c 100 -t 60s -f urls.txt                     # Depuis fichier URLs


# === MONITORING PERFORMANCE ===

# Installer py-spy
pip install py-spy

# Top en temps réel
sudo py-spy top --pid <pid>

# Flame graph
sudo py-spy record -o profile.svg --pid <pid> --duration 60


[OK] FASTAPI & ASGI


# === FASTAPI AVEC GUNICORN ===

# FastAPI utilise ASGI, pas WSGI
# Utiliser Uvicorn workers

# Installation
pip install fastapi uvicorn[standard]

# app.py (FastAPI)
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}


# Lancer avec Uvicorn seul (dev)
uvicorn app:app --reload

# Lancer avec Gunicorn + Uvicorn workers (production)
gunicorn app:app \
    --workers 4 \
    --worker-class uvicorn.workers.UvicornWorker \
    --bind 0.0.0.0:8000

# Version courte
gunicorn app:app -w 4 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000


# === CONFIGURATION FASTAPI ===

# gunicorn_fastapi.py
import multiprocessing

bind = "0.0.0.0:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"

# WebSockets support
timeout = 120
keepalive = 5

# Logging
accesslog = "-"
errorlog = "-"
loglevel = "info"


# === FASTAPI AVEC WEBSOCKETS ===

# app.py
from fastapi import FastAPI, WebSocket

app = FastAPI()

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    await websocket.accept()
    while True:
        data = await websocket.receive_text()
        await websocket.send_text(f"Message: {data}")

# Lancer
gunicorn app:app \
    -w 4 \
    -k uvicorn.workers.UvicornWorker \
    --timeout 120


# === UVICORN H11 vs HTTPTOOLS ===

# H11 (pur Python, plus lent)
gunicorn app:app -k uvicorn.workers.UvicornH11Worker

# HTTPTools (compilé, plus rapide)
pip install uvicorn[standard]
gunicorn app:app -k uvicorn.workers.UvicornWorker


[OK] EXEMPLES DE CONFIGURATIONS COMPLÈTES


# === CONFIGURATION FLASK PRODUCTION ===

# gunicorn_flask_prod.py
import multiprocessing
import os

# Server
bind = "unix:/run/gunicorn.sock"
backlog = 2048

# Workers
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
worker_connections = 1000
max_requests = 1000
max_requests_jitter = 100

# Timeouts
timeout = 30
graceful_timeout = 30
keepalive = 5

# Performance
preload_app = True

# Logging
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "warning"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'

# Process
proc_name = "flask_app"
pidfile = "/var/run/gunicorn.pid"
user = "www-data"
group = "www-data"
umask = 0o007

# Security
limit_request_line = 4096
limit_request_fields = 100
limit_request_field_size = 8190

# Hooks
def on_starting(server):
    print("[RAPIDE] Gunicorn starting...")

def when_ready(server):
    print("[OK] Gunicorn ready!")

def on_exit(server):
    print("[WAVING_HAND_SIGN] Gunicorn shutdown")


# === CONFIGURATION DJANGO PRODUCTION ===

# gunicorn_django_prod.py
import multiprocessing
import os

# Django settings
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings.production')

# Server
bind = "unix:/run/gunicorn.sock"
backlog = 2048

# Workers (CPU-bound pour Django)
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
threads = 1

# Memory management
max_requests = 1000
max_requests_jitter = 100

# Timeouts
timeout = 30
graceful_timeout = 30
keepalive = 5

# Performance
preload_app = True

# Logging
accesslog = "-"
errorlog = "-"
loglevel = "info"
capture_output = True

# Process
proc_name = "django_app"
pidfile = "/var/run/gunicorn.pid"
user = "www-data"
group = "www-data"

# Security
forwarded_allow_ips = "127.0.0.1"

# Hooks
def post_fork(server, worker):
    """Close DB connections after fork"""
    try:
        from django.db import connections
        for conn in connections.all():
            conn.close()
    except Exception as e:
        worker.log.error(f"Error closing DB connections: {e}")


# === CONFIGURATION FASTAPI PRODUCTION ===

# gunicorn_fastapi_prod.py
import multiprocessing

# Server
bind = "0.0.0.0:8000"
backlog = 2048

# Workers (ASGI async)
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000

# Timeouts (plus longs pour async)
timeout = 60
graceful_timeout = 60
keepalive = 5

# Memory
max_requests = 5000
max_requests_jitter = 500

# Logging
accesslog = "-"
errorlog = "-"
loglevel = "info"

# Process
proc_name = "fastapi_app"


# === CONFIGURATION MICROSERVICE ===

# gunicorn_microservice.py
import multiprocessing
import os

# Environment
env = os.getenv('ENVIRONMENT', 'production')

# Server
bind = f"0.0.0.0:{os.getenv('PORT', 8000)}"
backlog = 2048

# Workers (scalable)
workers = int(os.getenv('GUNICORN_WORKERS', multiprocessing.cpu_count() * 2 + 1))
worker_class = os.getenv('GUNICORN_WORKER_CLASS', 'sync')
threads = int(os.getenv('GUNICORN_THREADS', 2))

# Timeouts
timeout = int(os.getenv('GUNICORN_TIMEOUT', 30))
graceful_timeout = 30
keepalive = 5

# Memory
max_requests = 1000
max_requests_jitter = 100

# Logging (JSON pour parsing)
import json

def json_access_log(status, environ):
    return json.dumps({
        'method': environ.get('REQUEST_METHOD'),
        'path': environ.get('PATH_INFO'),
        'status': status,
        'ip': environ.get('REMOTE_ADDR'),
        'user_agent': environ.get('HTTP_USER_AGENT')
    })

accesslog = "-"
errorlog = "-"
loglevel = "info"

# Process
proc_name = os.getenv('SERVICE_NAME', 'microservice')


[OK] MIGRATION & UPGRADE


# === MIGRER DE WSGI À ASGI ===

# Avant (WSGI - Flask/Django)
gunicorn app:app -w 4 -k sync

# Après (ASGI - FastAPI/Starlette)
gunicorn app:app -w 4 -k uvicorn.workers.UvicornWorker


# === UPGRADE GUNICORN ===

# Vérifier version actuelle
gunicorn --version

# Upgrade pip
pip install --upgrade gunicorn

# Upgrade avec rollback possible
pip install gunicorn==20.1.0    # Version stable connue

# Test nouvelle version
gunicorn app:app --check-config

# Upgrade zero-downtime (systemd)
sudo systemctl reload gunicorn


# === MIGRER DE UWSGI À GUNICORN ===

# uWSGI config
# uwsgi.ini
[uwsgi]
module = app:app
master = true
processes = 4
socket = /tmp/uwsgi.sock

# Équivalent Gunicorn
gunicorn app:app \
    --workers 4 \
    --bind unix:/tmp/gunicorn.sock


[OK] OUTILS & RESSOURCES


# === OUTILS COMPLÉMENTAIRES ===

# Supervisor (alternative systemd)
pip install supervisor

# supervisord.conf
[program:gunicorn]
command=/path/to/venv/bin/gunicorn app:app -c gunicorn_config.py
directory=/path/to/app
user=www-data
autostart=true
autorestart=true
redirect_stderr=true
stdout_logfile=/var/log/gunicorn/supervisor.log


# Circus (process manager)
pip install circus

# circus.ini
[watcher:webapp]
cmd = /path/to/venv/bin/gunicorn app:app
working_dir = /path/to/app
numprocesses = 1
stdout_stream.class = FileStream
stdout_stream.filename = /var/log/circus/webapp.log


# === MONITORING STACK ===

# Prometheus + Grafana
# prometheus.yml
scrape_configs:
  - job_name: 'gunicorn'
    static_configs:
      - targets: ['localhost:8000']


# === SCRIPTS UTILES ===

# Health check script
#!/bin/bash
# health_check.sh
ENDPOINT="http://localhost:8000/health"
MAX_RETRIES=5
RETRY=0

while [ $RETRY -lt $MAX_RETRIES ]; do
    if curl -f -s $ENDPOINT > /dev/null; then
        echo "[OK] Service healthy"
        exit 0
    fi
    RETRY=$((RETRY + 1))
    echo "[HOURGLASS_WITH_FLOWING_SAND] Retry $RETRY/$MAX_RETRIES..."
    sleep 2
done

echo "[X] Service unhealthy"
exit 1


# Auto-scaling script
#!/bin/bash
# scale_workers.sh
PID_FILE=/var/run/gunicorn.pid

if [ ! -f $PID_FILE ]; then
    echo "Gunicorn not running"
    exit 1
fi

MASTER_PID=$(cat $PID_FILE)

# Scale up
scale_up() {
    kill -TTIN $MASTER_PID
    echo "Worker added"
}

# Scale down
scale_down() {
    kill -TTOU $MASTER_PID
    echo "Worker removed"
}

case "$1" in
    up)
        scale_up
        ;;
    down)
        scale_down
        ;;
    *)
        echo "Usage: $0 {up|down}"
        exit 1
        ;;
esac


[OK] BONNES PRATIQUES PRODUCTION


# 1. [OK] TOUJOURS utiliser reverse proxy (Nginx)
# Gunicorn n'est PAS un serveur web complet

# 2. [OK] JAMAIS --reload en production
# Seulement pour développement

# 3. [OK] Utiliser systemd pour gérer le service
# Auto-restart, logs centralisés

# 4. [OK] Configurer timeouts appropriés
# Basé sur votre application

# 5. [OK] Monitorer les workers
# CPU, mémoire, requêtes

# 6. [OK] Logs structurés
# Facilite parsing et monitoring

# 7. [OK] Max requests pour éviter memory leaks
# Redémarre workers régulièrement

# 8. [OK] Preload app en production
# Économise RAM avec Copy-On-Write

# 9. [OK] Health checks obligatoires
# Pour load balancer et monitoring

# 10. [OK] Graceful shutdown
# QUIT signal, pas KILL

# 11. [OK] Security headers via Nginx
# Pas Gunicorn

# 12. [OK] Static files via Nginx
# Jamais Gunicorn

# 13. [OK] SSL/TLS terminaison sur Nginx
# Sauf cas spécifiques

# 14. [OK] Backup configuration
# Version control (git)

# 15. [OK] Test avant déploiement
# --check-config


[OK] CHECKLIST DÉPLOIEMENT


[WHITE_SQUARE] Configuration validée (--check-config)
[WHITE_SQUARE] Workers adaptés au hardware
[WHITE_SQUARE] Timeouts configurés
[WHITE_SQUARE] Logs configurés et rotationnés
[WHITE_SQUARE] Systemd service créé
[WHITE_SQUARE] Nginx configuré comme reverse proxy
[WHITE_SQUARE] SSL/TLS configuré (Let's Encrypt)
[WHITE_SQUARE] Health endpoints implémentés
[WHITE_SQUARE] Monitoring en place (Prometheus/Grafana)
[WHITE_SQUARE] Alertes configurées
[WHITE_SQUARE] Backup strategy
[WHITE_SQUARE] Deployment script testé
[WHITE_SQUARE] Rollback plan ready
[WHITE_SQUARE] Documentation à jour
[WHITE_SQUARE] Load testing effectué


[OK] COMMANDES RAPIDES


# Démarrer
gunicorn app:app -w 4 -b 0.0.0.0:8000

# Daemon
gunicorn app:app -w 4 -b 0.0.0.0:8000 --daemon --pid /tmp/gunicorn.pid

# Reload
kill -HUP $(cat /tmp/gunicorn.pid)

# Stop graceful
kill -QUIT $(cat /tmp/gunicorn.pid)

# Stop immédiat
kill -TERM $(cat /tmp/gunicorn.pid)

# Add worker
kill -TTIN $(cat /tmp/gunicorn.pid)

# Remove worker
kill -TTOU $(cat /tmp/gunicorn.pid)

# Status (systemd)
sudo systemctl status gunicorn

# Logs (systemd)
sudo journalctl -u gunicorn -f

# Test Nginx config
sudo nginx -t

# Reload Nginx
sudo nginx -s reload


[OK] RESSOURCES


# Documentation officielle
# https://docs.gunicorn.org/

# GitHub
# https://github.com/benoitc/gunicorn

# Comparaison WSGI servers
# https://www.appdynamics.com/blog/engineering/a-performance-analysis-of-python-wsgi-servers-part-2/

# Tuning guide
# https://medium.com/building-the-system/gunicorn-3-means-of-concurrency-efbb547674b7

# Best practices
# https://www.digitalocean.com/community/tutorials/how-to-serve-flask-applications-with-gunicorn-and-nginx-on-ubuntu-20-04