updated structure and app
This commit is contained in:
@@ -1,72 +1,165 @@
|
||||
# Gunicorn Configuration File for Trasabilitate Application
|
||||
# Production-ready WSGI server configuration
|
||||
# Docker-optimized Production WSGI server configuration
|
||||
|
||||
import multiprocessing
|
||||
import os
|
||||
|
||||
# Server socket
|
||||
bind = "0.0.0.0:8781"
|
||||
backlog = 2048
|
||||
# ============================================================================
|
||||
# SERVER SOCKET CONFIGURATION
|
||||
# ============================================================================
|
||||
# Bind to all interfaces on port from environment or default
|
||||
bind = os.getenv("GUNICORN_BIND", "0.0.0.0:8781")
|
||||
backlog = int(os.getenv("GUNICORN_BACKLOG", "2048"))
|
||||
|
||||
# Worker processes
|
||||
workers = multiprocessing.cpu_count() * 2 + 1
|
||||
worker_class = "sync"
|
||||
worker_connections = 1000
|
||||
timeout = 30
|
||||
keepalive = 2
|
||||
# ============================================================================
|
||||
# WORKER PROCESSES CONFIGURATION
|
||||
# ============================================================================
|
||||
# Calculate workers: For Docker, use CPU count * 2 + 1 (but allow override)
|
||||
# In Docker, cpu_count() returns container CPU limit if set
|
||||
workers = int(os.getenv("GUNICORN_WORKERS", multiprocessing.cpu_count() * 2 + 1))
|
||||
|
||||
# Restart workers after this many requests, to prevent memory leaks
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 50
|
||||
# Worker class - 'sync' is stable for most use cases
|
||||
# Alternative: 'gevent' or 'gthread' for better concurrency
|
||||
worker_class = os.getenv("GUNICORN_WORKER_CLASS", "sync")
|
||||
|
||||
# Logging
|
||||
accesslog = "/srv/quality_recticel/logs/access.log"
|
||||
errorlog = "/srv/quality_recticel/logs/error.log"
|
||||
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'
|
||||
# Max simultaneous connections per worker
|
||||
worker_connections = int(os.getenv("GUNICORN_WORKER_CONNECTIONS", "1000"))
|
||||
|
||||
# Process naming
|
||||
proc_name = 'trasabilitate_app'
|
||||
# Workers silent for more than this many seconds are killed and restarted
|
||||
# Increase for long-running requests (file uploads, reports)
|
||||
timeout = int(os.getenv("GUNICORN_TIMEOUT", "120"))
|
||||
|
||||
# Daemon mode (set to True for production deployment)
|
||||
# Keep-alive for reusing connections
|
||||
keepalive = int(os.getenv("GUNICORN_KEEPALIVE", "5"))
|
||||
|
||||
# Graceful timeout - time to wait for workers to finish during shutdown
|
||||
graceful_timeout = int(os.getenv("GUNICORN_GRACEFUL_TIMEOUT", "30"))
|
||||
|
||||
# ============================================================================
|
||||
# WORKER LIFECYCLE - PREVENT MEMORY LEAKS
|
||||
# ============================================================================
|
||||
# Restart workers after this many requests to prevent memory leaks
|
||||
max_requests = int(os.getenv("GUNICORN_MAX_REQUESTS", "1000"))
|
||||
max_requests_jitter = int(os.getenv("GUNICORN_MAX_REQUESTS_JITTER", "100"))
|
||||
|
||||
# ============================================================================
|
||||
# LOGGING CONFIGURATION
|
||||
# ============================================================================
|
||||
# Docker-friendly: logs to stdout/stderr by default, but allow file logging
|
||||
accesslog = os.getenv("GUNICORN_ACCESS_LOG", "/srv/quality_recticel/logs/access.log")
|
||||
errorlog = os.getenv("GUNICORN_ERROR_LOG", "/srv/quality_recticel/logs/error.log")
|
||||
|
||||
# For pure Docker logging (12-factor app), use:
|
||||
# accesslog = "-" # stdout
|
||||
# errorlog = "-" # stderr
|
||||
|
||||
loglevel = os.getenv("GUNICORN_LOG_LEVEL", "info")
|
||||
|
||||
# Enhanced access log format with timing and user agent
|
||||
access_log_format = (
|
||||
'%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s '
|
||||
'"%(f)s" "%(a)s" %(D)s µs'
|
||||
)
|
||||
|
||||
# Capture stdout/stderr in log (useful for print statements)
|
||||
capture_output = os.getenv("GUNICORN_CAPTURE_OUTPUT", "true").lower() == "true"
|
||||
|
||||
# ============================================================================
|
||||
# PROCESS NAMING & DAEMON
|
||||
# ============================================================================
|
||||
proc_name = os.getenv("GUNICORN_PROC_NAME", "trasabilitate_app")
|
||||
|
||||
# CRITICAL FOR DOCKER: Never use daemon mode in containers
|
||||
# Docker needs the process to run in foreground
|
||||
daemon = False
|
||||
|
||||
# User/group to run worker processes
|
||||
# user = "www-data"
|
||||
# group = "www-data"
|
||||
# ============================================================================
|
||||
# SECURITY & LIMITS
|
||||
# ============================================================================
|
||||
# Request line size limit (protect against large headers)
|
||||
limit_request_line = int(os.getenv("GUNICORN_LIMIT_REQUEST_LINE", "4094"))
|
||||
limit_request_fields = int(os.getenv("GUNICORN_LIMIT_REQUEST_FIELDS", "100"))
|
||||
limit_request_field_size = int(os.getenv("GUNICORN_LIMIT_REQUEST_FIELD_SIZE", "8190"))
|
||||
|
||||
# Preload application for better performance
|
||||
preload_app = True
|
||||
# ============================================================================
|
||||
# PERFORMANCE OPTIMIZATION
|
||||
# ============================================================================
|
||||
# Preload application before forking workers
|
||||
# Pros: Faster worker spawn, less memory if using copy-on-write
|
||||
# Cons: Code changes require full restart
|
||||
preload_app = os.getenv("GUNICORN_PRELOAD_APP", "true").lower() == "true"
|
||||
|
||||
# Enable automatic worker restarts
|
||||
max_requests = 1000
|
||||
max_requests_jitter = 100
|
||||
# Pseudo-random number for load balancing
|
||||
worker_tmp_dir = os.getenv("GUNICORN_WORKER_TMP_DIR", "/dev/shm")
|
||||
|
||||
# SSL Configuration (uncomment if using HTTPS)
|
||||
# keyfile = "/path/to/ssl/private.key"
|
||||
# certfile = "/path/to/ssl/certificate.crt"
|
||||
# ============================================================================
|
||||
# SSL CONFIGURATION (if needed)
|
||||
# ============================================================================
|
||||
# Uncomment and set environment variables if using HTTPS
|
||||
# keyfile = os.getenv("SSL_KEY_FILE")
|
||||
# certfile = os.getenv("SSL_CERT_FILE")
|
||||
# ca_certs = os.getenv("SSL_CA_CERTS")
|
||||
|
||||
# ============================================================================
|
||||
# SERVER HOOKS - LIFECYCLE CALLBACKS
|
||||
# ============================================================================
|
||||
|
||||
def on_starting(server):
|
||||
"""Called just before the master process is initialized."""
|
||||
server.log.info("=" * 60)
|
||||
server.log.info("🚀 Trasabilitate Application - Starting Server")
|
||||
server.log.info("=" * 60)
|
||||
server.log.info("📍 Configuration:")
|
||||
server.log.info(f" • Workers: {workers}")
|
||||
server.log.info(f" • Worker Class: {worker_class}")
|
||||
server.log.info(f" • Timeout: {timeout}s")
|
||||
server.log.info(f" • Bind: {bind}")
|
||||
server.log.info(f" • Preload App: {preload_app}")
|
||||
server.log.info(f" • Max Requests: {max_requests} (+/- {max_requests_jitter})")
|
||||
server.log.info("=" * 60)
|
||||
|
||||
# Security
|
||||
limit_request_line = 4094
|
||||
limit_request_fields = 100
|
||||
limit_request_field_size = 8190
|
||||
|
||||
def when_ready(server):
|
||||
"""Called just after the server is started."""
|
||||
server.log.info("Trasabilitate Application server is ready. Listening on: %s", server.address)
|
||||
server.log.info("=" * 60)
|
||||
server.log.info("✅ Trasabilitate Application Server is READY!")
|
||||
server.log.info(f"📡 Listening on: {server.address}")
|
||||
server.log.info(f"🌐 Access the application at: http://{bind}")
|
||||
server.log.info("=" * 60)
|
||||
|
||||
|
||||
def on_exit(server):
|
||||
"""Called just before exiting Gunicorn."""
|
||||
server.log.info("=" * 60)
|
||||
server.log.info("👋 Trasabilitate Application - Shutting Down")
|
||||
server.log.info("=" * 60)
|
||||
|
||||
|
||||
def worker_int(worker):
|
||||
"""Called just after a worker exited on SIGINT or SIGQUIT."""
|
||||
worker.log.info("Worker received INT or QUIT signal")
|
||||
worker.log.info("⚠️ Worker %s received INT or QUIT signal", worker.pid)
|
||||
|
||||
|
||||
def pre_fork(server, worker):
|
||||
"""Called just before a worker is forked."""
|
||||
server.log.info("Worker spawned (pid: %s)", worker.pid)
|
||||
server.log.info("🔄 Forking new worker (pid: %s)", worker.pid)
|
||||
|
||||
|
||||
def post_fork(server, worker):
|
||||
"""Called just after a worker has been forked."""
|
||||
server.log.info("Worker spawned (pid: %s)", worker.pid)
|
||||
server.log.info("✨ Worker spawned successfully (pid: %s)", worker.pid)
|
||||
|
||||
|
||||
def pre_exec(server):
|
||||
"""Called just before a new master process is forked."""
|
||||
server.log.info("🔄 Master process forking...")
|
||||
|
||||
|
||||
def worker_abort(worker):
|
||||
"""Called when a worker received the SIGABRT signal."""
|
||||
worker.log.info("Worker received SIGABRT signal")
|
||||
worker.log.warning("🚨 Worker %s received SIGABRT signal - ABORTING!", worker.pid)
|
||||
|
||||
|
||||
def child_exit(server, worker):
|
||||
"""Called just after a worker has been exited, in the master process."""
|
||||
server.log.info("👋 Worker %s exited (exit code: %s)", worker.pid, worker.tmp.last_mtime)
|
||||
Reference in New Issue
Block a user