fixed coolify health checks
This commit is contained in:
@@ -325,7 +325,7 @@ docker compose restart
|
||||
# health-check.sh erstellen
|
||||
cat > health-check.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8000/login | grep -q "200"; then
|
||||
if curl -s -o /dev/null -w "%{http_code}" http://localhost:8001/health | grep -q "200"; then
|
||||
echo "$(date): OK"
|
||||
else
|
||||
echo "$(date): FEHLER - Neustart..."
|
||||
|
||||
@@ -41,7 +41,7 @@ EXPOSE 8001
|
||||
|
||||
# Health check (uses PORT env var with 8001 fallback)
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD python -c "import os, httpx; port = os.getenv('PORT', '8001'); httpx.get(f'http://localhost:{port}/login', timeout=5)" || exit 1
|
||||
CMD python -c "import os, httpx; port = os.getenv('PORT', '8001'); r = httpx.get(f'http://127.0.0.1:{port}/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)" || exit 1
|
||||
|
||||
# Run the application (uses PORT env var with 8001 fallback)
|
||||
CMD sh -c "python -m uvicorn src.web.app:app --host 0.0.0.0 --port ${PORT:-8001}"
|
||||
|
||||
@@ -27,6 +27,7 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
|
||||
linkedin-scheduler:
|
||||
build: .
|
||||
@@ -34,10 +35,17 @@ services:
|
||||
command: python -m src.services.scheduler_runner
|
||||
labels:
|
||||
- traefik.enable=false
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import os, time; p=os.getenv('SCHEDULER_HEALTH_FILE','/tmp/scheduler_heartbeat'); raise SystemExit(0 if os.path.exists(p) and time.time() - os.path.getmtime(p) < 120 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
environment:
|
||||
- PYTHONPATH=/app
|
||||
- SCHEDULER_ENABLED=true
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- SCHEDULER_HEALTH_FILE=/tmp/scheduler_heartbeat
|
||||
volumes:
|
||||
- logs:/app/logs
|
||||
depends_on:
|
||||
@@ -61,7 +69,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
|
||||
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -11,17 +11,25 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
|
||||
linkedin-scheduler:
|
||||
build: .
|
||||
container_name: linkedin-scheduler
|
||||
restart: unless-stopped
|
||||
command: sh -c "python -m src.services.scheduler_runner"
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import os, time; p=os.getenv('SCHEDULER_HEALTH_FILE','/tmp/scheduler_heartbeat'); raise SystemExit(0 if os.path.exists(p) and time.time() - os.path.getmtime(p) < 120 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 20s
|
||||
env_file: .env
|
||||
environment:
|
||||
- PYTHONPATH=/app
|
||||
- SCHEDULER_ENABLED=true
|
||||
- REDIS_URL=redis://redis:6379/0
|
||||
- SCHEDULER_HEALTH_FILE=/tmp/scheduler_heartbeat
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
depends_on:
|
||||
@@ -47,7 +55,7 @@ services:
|
||||
redis:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
|
||||
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
volumes:
|
||||
- ./logs:/app/logs
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
|
||||
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
# Optional: Mount logs directory
|
||||
- ./logs:/app/logs
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import httpx; httpx.get('http://localhost:8001/login', timeout=5)"]
|
||||
test: ["CMD", "python", "-c", "import httpx; r = httpx.get('http://127.0.0.1:8001/health', timeout=5); raise SystemExit(0 if r.status_code == 200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -8,16 +8,29 @@ SchedulerService so it can run in its own container without duplicating work in
|
||||
the main web-worker containers (which set SCHEDULER_ENABLED=false).
|
||||
"""
|
||||
import asyncio
|
||||
import os
|
||||
import signal
|
||||
from pathlib import Path
|
||||
from loguru import logger
|
||||
|
||||
from src.database.client import DatabaseClient
|
||||
from src.services.scheduler_service import init_scheduler
|
||||
|
||||
|
||||
async def heartbeat_loop(path: Path, interval_seconds: int = 30):
|
||||
"""Write a heartbeat file so Docker can healthcheck the scheduler process."""
|
||||
while True:
|
||||
try:
|
||||
path.write_text(str(asyncio.get_running_loop().time()), encoding="utf-8")
|
||||
except Exception as exc:
|
||||
logger.warning(f"Failed to write scheduler heartbeat: {exc}")
|
||||
await asyncio.sleep(interval_seconds)
|
||||
|
||||
|
||||
async def main():
|
||||
db = DatabaseClient()
|
||||
scheduler = init_scheduler(db, check_interval=60)
|
||||
health_file = Path(os.getenv("SCHEDULER_HEALTH_FILE", "/tmp/scheduler_heartbeat"))
|
||||
|
||||
stop_event = asyncio.Event()
|
||||
|
||||
@@ -30,10 +43,16 @@ async def main():
|
||||
loop.add_signal_handler(sig, handle_signal)
|
||||
|
||||
await scheduler.start()
|
||||
heartbeat_task = asyncio.create_task(heartbeat_loop(health_file))
|
||||
logger.info("Scheduler started (dedicated process)")
|
||||
|
||||
await stop_event.wait()
|
||||
|
||||
heartbeat_task.cancel()
|
||||
try:
|
||||
await heartbeat_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
await scheduler.stop()
|
||||
logger.info("Scheduler stopped")
|
||||
|
||||
|
||||
@@ -116,6 +116,17 @@ app.add_middleware(GZipMiddleware, minimum_size=500)
|
||||
app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static")
|
||||
|
||||
|
||||
@app.api_route("/health", methods=["GET", "HEAD"], include_in_schema=False)
|
||||
async def health():
|
||||
"""Container health endpoint for Docker/Coolify.
|
||||
|
||||
Keep this endpoint intentionally shallow: it verifies the ASGI app is
|
||||
serving requests without depending on auth, templates, Redis, Supabase or
|
||||
external APIs.
|
||||
"""
|
||||
return {"status": "ok"}
|
||||
|
||||
|
||||
def _load_env_file(path: Path) -> dict:
|
||||
"""Load key=value pairs from a simple .env file."""
|
||||
data = {}
|
||||
|
||||
Reference in New Issue
Block a user