Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ x-sentry-defaults: &sentry_defaults
- "./sentry:/etc/sentry"
- "./geoip:/geoip:ro"
- "./certificates:/usr/local/share/ca-certificates:ro"
- "./healthcheck:/healthcheck:ro"
x-snuba-defaults: &snuba_defaults
<<: [*restart_policy, *pull_policy]
depends_on:
Expand Down Expand Up @@ -307,10 +308,10 @@ services:
snuba-api:
<<: *snuba_defaults
volumes:
- "./snuba/api_healthcheck.py:/usr/src/snuba/api_healthcheck.py:ro"
- "./healthcheck:/healthcheck:ro"
healthcheck:
<<: *healthcheck_defaults
test: ["CMD", "python3", "/usr/src/snuba/api_healthcheck.py"]
test: ["CMD", "python3", "/healthcheck/check_http.py", "http://127.0.0.1:1218/health"]
# Kafka consumer responsible for feeding events into Clickhouse
snuba-errors-consumer:
<<: *snuba_defaults
Expand Down Expand Up @@ -465,12 +466,7 @@ services:
healthcheck:
<<: *healthcheck_defaults
start_period: 5m
test:
- "CMD"
- "/bin/bash"
- "-c"
# Courtesy of https://unix.stackexchange.com/a/234089/108960
- 'exec 3<>/dev/tcp/127.0.0.1/9000 && echo -e "GET /_health/ HTTP/1.1\r\nhost: 127.0.0.1\r\n\r\n" >&3 && grep ok -s -m 1 <&3'
test: ["CMD", "python3", "/healthcheck/check_http.py", "http://127.0.0.1:9000/_health/"]
events-consumer:
<<: *sentry_defaults
command: run consumer ingest-events --consumer-group ingest-consumer --auto-offset-reset=earliest --no-strict-offset-reset --healthcheck-file-path /tmp/health.txt --max-poll-interval-ms ${SENTRY_KAFKA_MAX_POLL_INTERVAL_MS:-300000}
Expand Down
58 changes: 58 additions & 0 deletions healthcheck/check_http.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env python3
"""
HTTP healthcheck for self-hosted services.

Usage: python3 /healthcheck/check_http.py <url>

GETs the URL and exits 0 if the response body contains "ok", else 1. On
failure, prints a one-line description to stderr rather than a full Python
traceback, so `docker inspect` output stays readable.

Uses only the standard library, so it works in any image with python3 on
PATH, including distroless ones. docker-compose.yml mounts this directory
read-only at /healthcheck.
"""

import sys
import urllib.error
import urllib.request

TIMEOUT = 2 # seconds


def main(url: str) -> int:
try:
# Ignore HTTP(S)_PROXY, which Docker may inject into containers: the
# target is always the container itself.
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}))
body = opener.open(url, timeout=TIMEOUT).read().decode()
except urllib.error.HTTPError as exc:
print(f"HTTP {exc.code} from {url}", file=sys.stderr)
return 1
except urllib.error.URLError as exc:
# urlopen() wraps connection-phase failures (refused, DNS, etc.) here.
print(f"{url} unreachable: {exc.reason}", file=sys.stderr)
return 1
except TimeoutError:
# A timeout firing during .read() (after urlopen returns) bubbles up
# as a bare TimeoutError from the underlying socket — not wrapped in
# URLError. Catch it explicitly so the message stays one-line.
print(f"timed out reading {url} after {TIMEOUT}s", file=sys.stderr)
return 1
except OSError as exc:
# ConnectionResetError, etc. — anything else from the socket layer.
print(f"error against {url}: {exc}", file=sys.stderr)
return 1

if "ok" not in body:
print(f"response from {url} missing 'ok': {body!r}", file=sys.stderr)
return 1

return 0


if __name__ == "__main__":
if len(sys.argv) != 2:
print("usage: check_http.py <url>", file=sys.stderr)
sys.exit(2)
sys.exit(main(sys.argv[1]))
58 changes: 0 additions & 58 deletions snuba/api_healthcheck.py

This file was deleted.

Loading