#!/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') )