feat(ops): web + worker container healthchecks (#20)
docker-compose only health-checked db. Add: - web: an unauthenticated GET /api/health (excluded from the auth middleware) + a compose healthcheck that hits it (node global fetch — no curl/wget in image). - worker: a background daemon thread touches /tmp/worker.alive every ~15s (independent of the main loop, so it stays fresh through a long download) + a compose healthcheck on the file's mtime (unhealthy if >90s stale). Now `docker compose ps` reports real health for all services. worker 223 tests, web 154, tsc + build + compose config clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
|
||||
@@ -203,8 +204,25 @@ def _require_secret_key() -> None:
|
||||
print(f"\n{banner}\nWARNING: {msg}.\n{banner}\n", flush=True)
|
||||
|
||||
|
||||
HEARTBEAT_FILE = "/tmp/worker.alive"
|
||||
|
||||
|
||||
def _liveness_heartbeat() -> None:
|
||||
"""Touch HEARTBEAT_FILE every HEARTBEAT_SECONDS from a background daemon thread, so the
|
||||
compose healthcheck sees the process as alive even while the main loop is blocked in a
|
||||
long download (a loop-driven heartbeat would go stale and falsely report unhealthy)."""
|
||||
while True:
|
||||
try:
|
||||
with open(HEARTBEAT_FILE, "w") as fh:
|
||||
fh.write("1")
|
||||
except OSError:
|
||||
pass
|
||||
time.sleep(HEARTBEAT_SECONDS)
|
||||
|
||||
|
||||
def run_forever() -> None:
|
||||
_require_secret_key()
|
||||
threading.Thread(target=_liveness_heartbeat, daemon=True).start()
|
||||
conn = wait_for_db()
|
||||
clear_staging_root(STAGING_ROOT) # sweep any staging dirs orphaned by a prior crash
|
||||
reclaimed = reclaim_stuck_jobs(conn) # requeue jobs a prior crash left mid-pipeline
|
||||
|
||||
Reference in New Issue
Block a user