Files
PythonTemplateProject/app_example.py

162 lines
3.9 KiB
Python
Raw Normal View History

2025-11-28 23:21:07 +01:00
#!/usr/bin/env python3
"""
Example Flask application with proper health endpoint for Nomad/Consul.
This template shows:
- Health check endpoint (CRITICAL for Nomad!)
- Graceful shutdown handling
- Environment variable configuration
- Logging setup
- Error handling
"""
import os
import sys
import logging
import signal
from datetime import datetime
from flask import Flask, jsonify, request
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(sys.stdout)
]
)
logger = logging.getLogger(__name__)
# Create Flask app
app = Flask(__name__)
# Configuration from environment
PORT = int(os.getenv('PORT', 5000))
APP_ENV = os.getenv('APP_ENV', 'development')
# Global state for graceful shutdown
is_shutting_down = False
@app.route('/')
def index():
"""Main endpoint - replace with your application logic."""
return jsonify({
'message': 'Hello from Flask!',
'environment': APP_ENV,
'timestamp': datetime.utcnow().isoformat()
})
@app.route('/health')
def health():
"""
Health check endpoint - CRITICAL for Nomad/Consul!
This endpoint is called by Consul every 10 seconds.
If this returns non-200 status, Consul marks service as unhealthy.
Returns:
200 OK: Service is healthy
503 Service Unavailable: Service is shutting down or unhealthy
"""
if is_shutting_down:
logger.warning("Health check called during shutdown")
return jsonify({
'status': 'shutting_down',
'timestamp': datetime.utcnow().isoformat()
}), 503
# Add your health checks here
health_status = {
'status': 'healthy',
'timestamp': datetime.utcnow().isoformat(),
'environment': APP_ENV,
'checks': {
'app': 'ok',
# Add more checks as needed:
# 'database': check_database(),
# 'cache': check_cache(),
}
}
return jsonify(health_status), 200
@app.route('/ready')
def ready():
"""
Readiness check endpoint (optional).
Use this for more complex readiness checks (DB connections, etc.)
Nomad can use this as additional check.
"""
return jsonify({
'ready': True,
'timestamp': datetime.utcnow().isoformat()
}), 200
@app.route('/metrics')
def metrics():
"""
Metrics endpoint for monitoring (optional).
Can be scraped by Prometheus if you set it up.
"""
# Example basic metrics
return jsonify({
'requests_total': 0, # Implement counter
'uptime_seconds': 0, # Implement uptime tracking
'timestamp': datetime.utcnow().isoformat()
}), 200
@app.errorhandler(404)
def not_found(error):
"""Handle 404 errors."""
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(500)
def internal_error(error):
"""Handle 500 errors."""
logger.error(f"Internal error: {error}")
return jsonify({'error': 'Internal server error'}), 500
def shutdown_handler(signum, frame):
"""
Handle shutdown signals gracefully.
When Nomad stops the job, it sends SIGTERM.
This gives the app time to finish current requests.
"""
global is_shutting_down
logger.info(f"Received signal {signum}, initiating graceful shutdown...")
is_shutting_down = True
# Perform cleanup here
# - Close database connections
# - Finish pending requests
# - Save state if needed
logger.info("Cleanup complete, exiting...")
sys.exit(0)
# Register signal handlers
signal.signal(signal.SIGTERM, shutdown_handler)
signal.signal(signal.SIGINT, shutdown_handler)
if __name__ == '__main__':
logger.info(f"Starting Flask app on port {PORT} in {APP_ENV} mode")
# Run Flask app
app.run(
host='0.0.0.0',
port=PORT,
debug=(APP_ENV == 'development')
)