From 1edb648489bf41dcc46ae2dd5d43c04bf09a93f0 Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 10:10:01 +0100 Subject: [PATCH 01/10] test nightly-distroless --- devservices/config.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/devservices/config.yml b/devservices/config.yml index 16aa137158b..6d9d3a0bb8e 100644 --- a/devservices/config.yml +++ b/devservices/config.yml @@ -75,7 +75,7 @@ services: restart: unless-stopped snuba: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless ports: - 127.0.0.1:1218:1218 - 127.0.0.1:1219:1219 @@ -114,7 +114,7 @@ services: - orchestrator=devservices restart: unless-stopped profiles-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=profiles, @@ -143,7 +143,7 @@ services: - orchestrator=devservices restart: unless-stopped profile-chunks-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=profile_chunks, @@ -172,7 +172,7 @@ services: - orchestrator=devservices restart: unless-stopped functions-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=functions_raw, @@ -201,7 +201,7 @@ services: - orchestrator=devservices restart: unless-stopped metrics-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=metrics_raw, @@ -230,7 +230,7 @@ services: - orchestrator=devservices restart: unless-stopped generic-metrics-distributions-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=generic_metrics_distributions_raw, @@ -259,7 +259,7 @@ services: - orchestrator=devservices restart: unless-stopped generic-metrics-sets-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=generic_metrics_sets_raw, @@ -288,7 +288,7 @@ services: - orchestrator=devservices restart: unless-stopped generic-metrics-counters-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=generic_metrics_counters_raw, @@ -317,7 +317,7 @@ services: - orchestrator=devservices restart: unless-stopped generic-metrics-gauges-consumer: - image: ghcr.io/getsentry/snuba:nightly + image: ghcr.io/getsentry/snuba:nightly-distroless command: [ rust-consumer, --storage=generic_metrics_gauges_raw, From 90473639dad4391c314976e09c18344f2ea0d8d1 Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 10:44:01 +0100 Subject: [PATCH 02/10] move honcho to prod image --- .github/workflows/image.yml | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index ce6e049679d..b3552768c76 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -136,7 +136,7 @@ jobs: assemble-distroless: needs: [build-distroless-multiplatform] - if: ${{ (github.ref_name == 'master' || startsWith(github.ref_name, 'release/')) && github.event_name != 'pull_request' }} + if: ${{ github.repository_owner == 'getsentry' && (github.ref_name == 'master' || startsWith(github.ref_name, 'release/') || github.ref_name == 'test/nightly-distroless') }} runs-on: ubuntu-latest permissions: contents: read diff --git a/pyproject.toml b/pyproject.toml index 42c04c9f530..c0258121330 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,6 +36,7 @@ dependencies = [ "sentry-relay>=0.9.25", "sentry-sdk>=2.35.0", "sentry-usage-accountant>=0.0.11", + "honcho>=1.1.0", "simplejson>=3.17.6", "snuba-sdk>=3.0.39", "sql-metadata>=2.11.0", @@ -78,7 +79,6 @@ snuba = "snuba.cli:main" dev = [ "devservices>=1.2.1", "freezegun>=1.5.5", - "honcho>=1.1.0", "mypy>=1.1.1", "pre-commit>=4.2.0", "pytest>=8.3.3", diff --git a/uv.lock b/uv.lock index 9e33136ccda..fec7f32e820 100644 --- a/uv.lock +++ b/uv.lock @@ -1069,6 +1069,7 @@ dependencies = [ { name = "google-cloud-storage", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "googleapis-common-protos", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "granian", extra = ["pname"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, + { name = "honcho", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "jsonschema", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "parsimonious", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -1104,7 +1105,6 @@ dependencies = [ dev = [ { name = "devservices", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "freezegun", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, - { name = "honcho", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "mypy", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "pre-commit", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "pytest", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -1140,6 +1140,7 @@ requires-dist = [ { name = "google-cloud-storage", specifier = ">=2.18.0" }, { name = "googleapis-common-protos", specifier = ">=1.63.2" }, { name = "granian", extras = ["pname"], specifier = ">=2.7" }, + { name = "honcho", specifier = ">=1.1.0" }, { name = "jsonschema", specifier = ">=4.23.0" }, { name = "packaging", specifier = ">=24.1" }, { name = "parsimonious", specifier = ">=0.10.0" }, @@ -1175,7 +1176,6 @@ requires-dist = [ dev = [ { name = "devservices", specifier = ">=1.2.1" }, { name = "freezegun", specifier = ">=1.5.5" }, - { name = "honcho", specifier = ">=1.1.0" }, { name = "mypy", specifier = ">=1.1.1" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "pytest", specifier = ">=8.3.3" }, From 233e3118a45466ba3e234c12ff92a59c6ff0f454 Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 11:01:31 +0100 Subject: [PATCH 03/10] assemble on test/nightly-distroless --- .github/workflows/image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index b3552768c76..46d009dd89a 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -4,6 +4,7 @@ on: branches: - master - release/** + - test/nightly-distroless jobs: build-multiplatform: @@ -136,7 +137,7 @@ jobs: assemble-distroless: needs: [build-distroless-multiplatform] - if: ${{ github.repository_owner == 'getsentry' && (github.ref_name == 'master' || startsWith(github.ref_name, 'release/') || github.ref_name == 'test/nightly-distroless') }} + if: ${{ github.repository_owner == 'getsentry' && (github.ref_name == 'master' || startsWith(github.ref_name, 'release/') || github.ref_name == 'test/nightly-distroless' || github.head_ref == 'test/nightly-distroless') }} runs-on: ubuntu-latest permissions: contents: read From 16ec8119ef4e0b51909c0052b67adbfe8b1fce2d Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 11:21:53 +0100 Subject: [PATCH 04/10] publish on pr test/nightly-distroless --- .github/workflows/image.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 46d009dd89a..6ecf4032d38 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -82,6 +82,7 @@ jobs: ghcr: true tag_nightly: false tag_latest: false + publish_on_pr: ${{ github.head_ref == 'test/nightly-distroless' }} # Debug distroless image — with busybox for troubleshooting build-debug-multiplatform: @@ -137,7 +138,7 @@ jobs: assemble-distroless: needs: [build-distroless-multiplatform] - if: ${{ github.repository_owner == 'getsentry' && (github.ref_name == 'master' || startsWith(github.ref_name, 'release/') || github.ref_name == 'test/nightly-distroless' || github.head_ref == 'test/nightly-distroless') }} + if: ${{ needs.build-distroless-multiplatform.result == 'success' && github.repository_owner == 'getsentry' && (github.ref_name == 'master' || startsWith(github.ref_name, 'release/') || github.ref_name == 'test/nightly-distroless' || github.head_ref == 'test/nightly-distroless') }} runs-on: ubuntu-latest permissions: contents: read From 09a32d49d5c33ba6339b0b8a86f207523e2d306e Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 11:53:55 +0100 Subject: [PATCH 05/10] one more try just for the test --- .github/workflows/image.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/image.yml b/.github/workflows/image.yml index 6ecf4032d38..e5d3a021f6d 100644 --- a/.github/workflows/image.yml +++ b/.github/workflows/image.yml @@ -153,12 +153,14 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Create distroless multiplatform manifests + env: + IMAGE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} run: | docker buildx imagetools create \ - --tag ghcr.io/getsentry/snuba:${{ github.sha }}-distroless \ + --tag ghcr.io/getsentry/snuba:${IMAGE_SHA}-distroless \ --tag ghcr.io/getsentry/snuba:nightly-distroless \ - ghcr.io/getsentry/snuba:${{ github.sha }}-distroless-amd64 \ - ghcr.io/getsentry/snuba:${{ github.sha }}-distroless-arm64 + ghcr.io/getsentry/snuba:${IMAGE_SHA}-distroless-amd64 \ + ghcr.io/getsentry/snuba:${IMAGE_SHA}-distroless-arm64 assemble-debug: needs: [build-debug-multiplatform] From cb00a7468195df6d250ce485478fa3bac77f60be Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 12:21:59 +0100 Subject: [PATCH 06/10] replace honcho with subprocess --- pyproject.toml | 2 -- snuba/cli/devserver.py | 54 +++++++++++++++++++++++++++++++++--------- uv.lock | 10 -------- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c0258121330..69bab7335a0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,6 @@ dependencies = [ "sentry-relay>=0.9.25", "sentry-sdk>=2.35.0", "sentry-usage-accountant>=0.0.11", - "honcho>=1.1.0", "simplejson>=3.17.6", "snuba-sdk>=3.0.39", "sql-metadata>=2.11.0", @@ -136,7 +135,6 @@ module = [ "fastjsonschema", "fastjsonschema.exceptions", "granian", - "honcho.manager", "jsonschema", "jsonschema.exceptions", "jsonschema2md", diff --git a/snuba/cli/devserver.py b/snuba/cli/devserver.py index f7e1128828e..4fb34243d5b 100644 --- a/snuba/cli/devserver.py +++ b/snuba/cli/devserver.py @@ -1,6 +1,9 @@ import os +import signal +import subprocess import sys -from subprocess import call, list2cmdline +import threading +from subprocess import call import click @@ -21,8 +24,6 @@ def devserver(*, bootstrap: bool, workers: bool, log_level: str) -> None: "Starts all Snuba processes for local development." - from honcho.manager import Manager - os.environ["PYTHONUNBUFFERED"] = "1" if bootstrap: @@ -518,13 +519,44 @@ def devserver(*, bootstrap: bool, workers: bool, log_level: str) -> None: ), ] - manager = Manager() + sys.exit(_run_daemons(daemons)) + + +def _run_daemons(daemons: list[tuple[str, list[str]]]) -> int: + procs: dict[str, subprocess.Popen[bytes]] = {} + first_failure: list[int] = [] + done = threading.Event() + + def stream(name: str, proc: subprocess.Popen[bytes]) -> None: + assert proc.stdout is not None + for line in proc.stdout: + sys.stdout.write(f"{name} | {line.decode(errors='replace')}") + sys.stdout.flush() + rc = proc.wait() + if rc != 0 and not first_failure: + first_failure.append(rc) + done.set() + for name, cmd in daemons: - manager.add_process( - name, - list2cmdline(cmd), - quiet=False, - ) + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + procs[name] = proc + threading.Thread(target=stream, args=(name, proc), daemon=True).start() + + def shutdown(signum: int, frame: object) -> None: + for proc in procs.values(): + if proc.poll() is None: + proc.terminate() + + signal.signal(signal.SIGINT, shutdown) + signal.signal(signal.SIGTERM, shutdown) + + done.wait() + if first_failure: + for proc in procs.values(): + if proc.poll() is None: + proc.terminate() + + for proc in procs.values(): + proc.wait() - manager.loop() - sys.exit(manager.returncode) + return first_failure[0] if first_failure else 0 diff --git a/uv.lock b/uv.lock index fec7f32e820..9190a0043c0 100644 --- a/uv.lock +++ b/uv.lock @@ -416,14 +416,6 @@ wheels = [ { url = "https://pypi.devinfra.sentry.io/wheels/grpcio-1.73.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab860d5bfa788c5a021fba264802e2593688cd965d1374d31d2b1a34cacd854" }, ] -[[package]] -name = "honcho" -version = "1.1.0" -source = { registry = "https://pypi.devinfra.sentry.io/simple" } -wheels = [ - { url = "https://pypi.devinfra.sentry.io/wheels/honcho-1.1.0-py2.py3-none-any.whl", hash = "sha256:a4d6e3a88a7b51b66351ecfc6e9d79d8f4b87351db9ad7e923f5632cc498122f" }, -] - [[package]] name = "httplib2" version = "0.22.0" @@ -1069,7 +1061,6 @@ dependencies = [ { name = "google-cloud-storage", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "googleapis-common-protos", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "granian", extra = ["pname"], marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, - { name = "honcho", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "jsonschema", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "packaging", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, { name = "parsimonious", marker = "sys_platform == 'darwin' or sys_platform == 'linux'" }, @@ -1140,7 +1131,6 @@ requires-dist = [ { name = "google-cloud-storage", specifier = ">=2.18.0" }, { name = "googleapis-common-protos", specifier = ">=1.63.2" }, { name = "granian", extras = ["pname"], specifier = ">=2.7" }, - { name = "honcho", specifier = ">=1.1.0" }, { name = "jsonschema", specifier = ">=4.23.0" }, { name = "packaging", specifier = ">=24.1" }, { name = "parsimonious", specifier = ">=0.10.0" }, From 51fe720b8df3cb7bb13a17c147685674bdcd909f Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 12:22:33 +0100 Subject: [PATCH 07/10] granian_entrypoint.py --- granian_entrypoint.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 granian_entrypoint.py diff --git a/granian_entrypoint.py b/granian_entrypoint.py new file mode 100644 index 00000000000..ceaf9cc2a36 --- /dev/null +++ b/granian_entrypoint.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +""" +Python replacement for granian_entrypoint.sh — compatible with distroless images. +""" + +import os +import time +import urllib.error +import urllib.request + + +def wait_for_envoy() -> None: + port = os.environ.get("ENVOY_ADMIN_PORT") + if not port: + print("ENVOY_ADMIN_PORT env var is unset") + print("Fall through to snuba start without check") + return + + url = f"http://localhost:{port}/ready" + print(f"Check envoy readiness on {url}") + while True: + time.sleep(1) + try: + with urllib.request.urlopen(url, timeout=2) as resp: + if resp.read().decode().strip() == "LIVE": + print("Envoy is ready") + return + except Exception: + pass + print("Envoy not ready, looping..") + + +def main() -> None: + wait_for_envoy() + + wsgi_target = os.environ.get("SNUBA_WSGI_TARGET", "snuba.web.wsgi:application") + args = ["granian", "--interface", "wsgi", "--host", "0.0.0.0", "--http", "auto", wsgi_target] + os.execvp(args[0], args) + + +if __name__ == "__main__": + main() From d8a24bbe892ed98f554c725cbba0fe36c7ab42f1 Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 12:45:32 +0100 Subject: [PATCH 08/10] pythonic healthcheck --- devservices/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devservices/config.yml b/devservices/config.yml index 6d9d3a0bb8e..22569cf5b08 100644 --- a/devservices/config.yml +++ b/devservices/config.yml @@ -83,7 +83,7 @@ services: - devserver - --${SNUBA_NO_WORKERS:+no-workers} healthcheck: - test: curl -f http://localhost:1218/health_envoy + test: python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:1218/health_envoy')" interval: 5s timeout: 5s retries: 3 From 17f86bad91c8f480299acd9f076f4a5412f658a0 Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 12:52:56 +0100 Subject: [PATCH 09/10] aargh --- devservices/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devservices/config.yml b/devservices/config.yml index 22569cf5b08..729b201c567 100644 --- a/devservices/config.yml +++ b/devservices/config.yml @@ -83,7 +83,7 @@ services: - devserver - --${SNUBA_NO_WORKERS:+no-workers} healthcheck: - test: python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:1218/health_envoy')" + test: ["python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:1218/health_envoy')"] interval: 5s timeout: 5s retries: 3 From db84b83046ad488577bec645db548112562929ec Mon Sep 17 00:00:00 2001 From: Alexander Tarasov Date: Mon, 23 Mar 2026 14:47:37 +0100 Subject: [PATCH 10/10] aargh 2 --- devservices/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devservices/config.yml b/devservices/config.yml index 729b201c567..1ddbd3131a2 100644 --- a/devservices/config.yml +++ b/devservices/config.yml @@ -83,7 +83,7 @@ services: - devserver - --${SNUBA_NO_WORKERS:+no-workers} healthcheck: - test: ["python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:1218/health_envoy')"] + test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:1218/health_envoy')"] interval: 5s timeout: 5s retries: 3