Files
projects/Dockers/puppeteer-healthcheck/healthcheck.py
T
Bram 4c0c785160
Build and Push Docker Images / build-and-push (push) Successful in 14s
set back
2025-07-07 13:14:03 +02:00

186 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""
Healthcheck script for Puppeteer API
Monitors the API endpoint and restarts the container if it fails
"""
import os
import time
import logging
import requests
import docker
import json
from urllib.parse import quote
from datetime import datetime
# Configure logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler(),
logging.FileHandler('/app/healthcheck.log')
]
)
logger = logging.getLogger(__name__)
class PuppeteerHealthcheck:
def __init__(self):
self.base_url = os.getenv('BASE_URL', 'https://puppeteer.workwithkora.com')
self.test_url = os.getenv('TEST_URL', 'https://www.google.com')
self.api_key = os.getenv('API_KEY', 'Q7Sd#hhFkyHy*T')
self.target_container = os.getenv('TARGET_CONTAINER', 'puppeteer-api')
self.check_interval = int(os.getenv('CHECK_INTERVAL', '60'))
# Initialize Docker client with proper error handling
self.docker_client = self._initialize_docker_client()
logger.info(f"Healthcheck initialized with:")
logger.info(f" Base URL: {self.base_url}")
logger.info(f" Test URL: {self.test_url}")
logger.info(f" Target Container: {self.target_container}")
logger.info(f" Check Interval: {self.check_interval} seconds")
def _initialize_docker_client(self):
"""Initialize Docker client with proper error handling"""
try:
# Try to initialize Docker client
client = docker.from_env()
# Test the connection by getting Docker info
client.info()
logger.info("Docker client initialized successfully")
return client
except docker.errors.DockerException as e:
logger.error(f"Docker client initialization failed: {e}")
logger.error("This usually means the Docker socket is not accessible or permissions are incorrect.")
logger.error("Make sure to:")
logger.error(" 1. Mount the Docker socket: -v /var/run/docker.sock:/var/run/docker.sock")
logger.error(" 2. Run with proper permissions or add the container to the docker group")
raise
except Exception as e:
logger.error(f"Unexpected error initializing Docker client: {e}")
raise
def perform_health_check(self):
"""Perform the health check by making a request to the puppeteer API"""
try:
# Construct the URL with the test URL as a parameter
encoded_test_url = quote(self.test_url, safe='')
url = f"{self.base_url}/?url={encoded_test_url}&skipCache=true"
headers = {
'x-api-key': self.api_key,
'User-Agent': 'Puppeteer-Healthcheck/1.0'
}
logger.info(f"Performing health check: {url}")
response = requests.get(
url,
headers=headers,
timeout=30,
allow_redirects=True
)
if response.status_code == 200:
# Check if the response body indicates an error
try:
response_data = response.json()
if response_data.get('status') == 'error':
logger.warning(f"Health check failed - API returned status: error")
logger.warning(f"Response: {response.text[:200]}...")
return False
else:
logger.info("Health check passed - API is responding correctly")
return True
except json.JSONDecodeError:
# If response is not JSON, treat as success (backward compatibility)
logger.info("Health check passed - API is responding correctly (non-JSON response)")
return True
else:
logger.warning(f"Health check failed - Status code: {response.status_code}")
logger.warning(f"Response: {response.text[:200]}...")
return False
except requests.exceptions.RequestException as e:
logger.error(f"Health check failed - Request error: {e}")
return False
except Exception as e:
logger.error(f"Health check failed - Unexpected error: {e}")
return False
def restart_container(self):
"""Restart the target puppeteer-api container"""
try:
logger.info(f"Attempting to restart container: {self.target_container}")
# Get the container
container = self.docker_client.containers.get(self.target_container)
# Restart the container
container.restart(timeout=30)
logger.info(f"Successfully restarted container: {self.target_container}")
return True
except docker.errors.NotFound:
logger.error(f"Container not found: {self.target_container}")
return False
except docker.errors.APIError as e:
logger.error(f"Docker API error while restarting container: {e}")
return False
except Exception as e:
logger.error(f"Unexpected error while restarting container: {e}")
return False
def run(self):
"""Main loop for the healthcheck"""
logger.info("Starting Puppeteer API healthcheck service")
consecutive_failures = 0
max_consecutive_failures = 3 # Restart after 3 consecutive failures
while True:
try:
# Perform health check
if self.perform_health_check():
consecutive_failures = 0
logger.info("Health check passed - resetting failure counter")
else:
consecutive_failures += 1
logger.warning(f"Health check failed - consecutive failures: {consecutive_failures}")
# Restart container if we've had too many consecutive failures
if consecutive_failures >= max_consecutive_failures:
logger.error(f"Health check failed {consecutive_failures} times consecutively - restarting container")
if self.restart_container():
consecutive_failures = 0
logger.info("Container restarted successfully - resetting failure counter")
else:
logger.error("Failed to restart container")
# Wait before next check
logger.info(f"Waiting {self.check_interval} seconds before next health check...")
time.sleep(self.check_interval)
except KeyboardInterrupt:
logger.info("Healthcheck service stopped by user")
break
except Exception as e:
logger.error(f"Unexpected error in main loop: {e}")
time.sleep(self.check_interval)
def main():
"""Main entry point"""
try:
healthcheck = PuppeteerHealthcheck()
healthcheck.run()
except Exception as e:
logger.error(f"Failed to start healthcheck service: {e}")
exit(1)
if __name__ == "__main__":
main()