This commit is contained in:
@@ -0,0 +1,33 @@
|
|||||||
|
FROM python:3.9-slim
|
||||||
|
|
||||||
|
# Install required system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
curl \
|
||||||
|
docker.io \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy requirements first to leverage Docker cache
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy the healthcheck script
|
||||||
|
COPY healthcheck.py .
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
ENV BASE_URL="https://puppeteer.workwithkora.com"
|
||||||
|
ENV TEST_URL="https://www.google.com"
|
||||||
|
ENV API_KEY="Q7Sd#hhFkyHy*T"
|
||||||
|
ENV TARGET_CONTAINER="puppeteer-api"
|
||||||
|
ENV CHECK_INTERVAL=60
|
||||||
|
|
||||||
|
# Create a non-root user
|
||||||
|
RUN useradd -m healthcheck && chown -R healthcheck:healthcheck /app
|
||||||
|
|
||||||
|
USER healthcheck
|
||||||
|
|
||||||
|
# Run the healthcheck script
|
||||||
|
CMD ["python", "healthcheck.py"]
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
# Puppeteer API Healthcheck
|
||||||
|
|
||||||
|
A Docker container that monitors the Puppeteer API and automatically restarts the target container when the API becomes unresponsive.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Monitors the Puppeteer API endpoint every minute
|
||||||
|
- Automatically restarts the target container after 3 consecutive failures
|
||||||
|
- Configurable via environment variables
|
||||||
|
- Comprehensive logging
|
||||||
|
- Docker socket access for container management
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
| ------------------ | ------------------------------------ | -------------------------------- |
|
||||||
|
| `BASE_URL` | `https://puppeteer.workwithkora.com` | Base URL of the Puppeteer API |
|
||||||
|
| `TEST_URL` | `https://www.google.com` | URL to test the API with |
|
||||||
|
| `API_KEY` | `Q7Sd#hhFkyHy*T` | API key for authentication |
|
||||||
|
| `TARGET_CONTAINER` | `puppeteer-api` | Name of the container to restart |
|
||||||
|
| `CHECK_INTERVAL` | `60` | Health check interval in seconds |
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Docker Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d \
|
||||||
|
--name puppeteer-healthcheck \
|
||||||
|
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||||
|
-e BASE_URL="https://puppeteer.workwithkora.com" \
|
||||||
|
-e TEST_URL="https://www.google.com" \
|
||||||
|
-e API_KEY="your-api-key" \
|
||||||
|
-e TARGET_CONTAINER="puppeteer-api" \
|
||||||
|
-e CHECK_INTERVAL="60" \
|
||||||
|
your-registry/puppeteer-healthcheck:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
puppeteer-healthcheck:
|
||||||
|
build: .
|
||||||
|
container_name: puppeteer-healthcheck
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
- BASE_URL=https://puppeteer.workwithkora.com
|
||||||
|
- TEST_URL=https://www.google.com
|
||||||
|
- API_KEY=your-api-key
|
||||||
|
- TARGET_CONTAINER=puppeteer-api
|
||||||
|
- CHECK_INTERVAL=60
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- puppeteer-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. The healthcheck container makes a GET request to the Puppeteer API every minute
|
||||||
|
2. It uses the configured test URL and API key for authentication
|
||||||
|
3. If the request fails (non-200 status or timeout), it increments a failure counter
|
||||||
|
4. After 3 consecutive failures, it attempts to restart the target container
|
||||||
|
5. If the restart is successful, the failure counter is reset
|
||||||
|
6. The process continues indefinitely
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
The container logs all health check activities to both stdout and a log file (`/app/healthcheck.log`). Log levels include:
|
||||||
|
|
||||||
|
- INFO: Normal operations and successful health checks
|
||||||
|
- WARNING: Failed health checks
|
||||||
|
- ERROR: Container restart attempts and failures
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
- The container requires access to the Docker socket to restart other containers
|
||||||
|
- Ensure proper API key management
|
||||||
|
- Consider running with limited privileges where possible
|
||||||
|
- The container runs as a non-root user for security
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Container not found
|
||||||
|
|
||||||
|
- Ensure the `TARGET_CONTAINER` environment variable matches the exact name of your puppeteer-api container
|
||||||
|
- Verify the container is running and accessible
|
||||||
|
|
||||||
|
### Permission denied
|
||||||
|
|
||||||
|
- Ensure the Docker socket is properly mounted
|
||||||
|
- Check that the container has the necessary permissions to access the Docker daemon
|
||||||
|
|
||||||
|
### API key issues
|
||||||
|
|
||||||
|
- Verify the API key is correct and has the necessary permissions
|
||||||
|
- Check that the base URL is accessible from the container
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t puppeteer-healthcheck .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Version
|
||||||
|
|
||||||
|
Current version: 1.0.0
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
#!/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
|
||||||
|
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
|
||||||
|
try:
|
||||||
|
self.docker_client = docker.from_env()
|
||||||
|
logger.info("Docker client initialized successfully")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Failed to initialize Docker client: {e}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
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 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}"
|
||||||
|
|
||||||
|
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:
|
||||||
|
logger.info("Health check passed - API is responding correctly")
|
||||||
|
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()
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
requests==2.31.0
|
||||||
|
docker==6.1.3
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
latest
|
||||||
Reference in New Issue
Block a user