162 lines
3.9 KiB
Python
162 lines
3.9 KiB
Python
#!/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')
|
|
)
|