From 6688c62522c8eab45e1e16870feb1be5a284d6fa Mon Sep 17 00:00:00 2001 From: Jens Holdgaard Pedersen Date: Sun, 31 May 2026 15:51:29 +0200 Subject: [PATCH 1/3] fix(bench): bind-mount a writable capture dir for the demo collector (PR-N3.1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First capture dispatch (run 26714187216) failed: the fileexporter couldn't open /tmp/otel-corpus.jsonl ("no such file or directory") and crash-looped the collector. The demo's collector is a distroless image with no writable /tmp, and the fileexporter does not create a missing parent directory. Fix: bind-mount a runner-side directory into the collector at /capture via a compose override layered with COMPOSE_FILE, and point the exporter at /capture/logs.jsonl. The directory is mkdir'd on the host before `up` so the mount target exists, and the corpus now lands straight on the runner filesystem — the docker cp extract step is replaced by a non-empty verification + teardown. The rest of the demo came up fine on the runner (all ~20 images pulled, collector started, OTLP receiver bound) — this was the only blocker. Co-Authored-By: Claude Opus 4.8 --- .../otel-demo-capture-compose-override.yml | 22 ++++++++++ .github/otel-demo-capture-extras.yml | 17 +++++--- .../workflows/capture-otel-demo-corpus.yml | 40 ++++++++++++++----- 3 files changed, 64 insertions(+), 15 deletions(-) create mode 100644 .github/otel-demo-capture-compose-override.yml diff --git a/.github/otel-demo-capture-compose-override.yml b/.github/otel-demo-capture-compose-override.yml new file mode 100644 index 000000000..9dcfdb339 --- /dev/null +++ b/.github/otel-demo-capture-compose-override.yml @@ -0,0 +1,22 @@ +# Compose override for the OTel Demo corpus capture +# (`capture-otel-demo-corpus.yml`). Layered onto the demo's +# docker-compose.yml via `-f docker-compose.yml -f `. +# +# Bind-mounts a runner-side directory into the collector at +# `/capture`, the path the file/corpus exporter writes to (see +# otel-demo-capture-extras.yml). The demo's collector is a +# distroless image with no writable `/tmp`, so the exporter needs +# a directory that demonstrably exists — a host bind-mount +# guarantees that and lands the corpus straight on the runner +# filesystem, no `docker cp`. +# +# `${OURIOS_CAPTURE_DIR}` is an absolute host path the workflow +# exports + `mkdir`s before `up`. Absolute (not relative) so it +# doesn't depend on Compose's project-directory resolution when +# the override is supplied from a different directory than the +# base compose file. + +services: + otel-collector: + volumes: + - ${OURIOS_CAPTURE_DIR}:/capture diff --git a/.github/otel-demo-capture-extras.yml b/.github/otel-demo-capture-extras.yml index e9ef5306b..b581c0641 100644 --- a/.github/otel-demo-capture-extras.yml +++ b/.github/otel-demo-capture-extras.yml @@ -18,14 +18,21 @@ # # `format: json` makes the fileexporter emit one OTLP/JSON # `LogsData` object per line — the exact `*.jsonl` shape -# `ourios-bench`'s corpus loader reads (RFC 0006 §3.1). The path -# is inside the collector container; the workflow `docker cp`s it -# out after the capture window rather than bind-mounting (dodges -# any compose volume-merge ambiguity). +# `ourios-bench`'s corpus loader reads (RFC 0006 §3.1). +# +# `/capture` is a host directory bind-mounted into the collector +# by the capture workflow's compose override. It must be a +# **mounted, pre-existing** directory, not an arbitrary +# in-container path: the demo's collector is a distroless image +# with no writable `/tmp`, and the fileexporter does not create a +# missing parent dir — pointing it at `/tmp/...` fails the +# pipeline with `open … no such file or directory`. The mount +# also lands the corpus straight on the runner filesystem (no +# `docker cp`). exporters: file/corpus: - path: /tmp/otel-corpus.jsonl + path: /capture/logs.jsonl format: json flush_interval: 1s diff --git a/.github/workflows/capture-otel-demo-corpus.yml b/.github/workflows/capture-otel-demo-corpus.yml index d59533398..7b2edb95e 100644 --- a/.github/workflows/capture-otel-demo-corpus.yml +++ b/.github/workflows/capture-otel-demo-corpus.yml @@ -57,6 +57,18 @@ jobs: # is a generous ceiling; a hung step shouldn't burn toward # GitHub's 6h job limit. timeout-minutes: 45 + env: + # Host directory bind-mounted into the collector at + # `/capture` (see the compose override). The fileexporter + # writes `logs.jsonl` here, so the corpus lands straight on + # the runner filesystem. + OURIOS_CAPTURE_DIR: ${{ github.workspace }}/captured + # `COMPOSE_FILE` (colon-separated on Linux) layers our + # override onto the demo's base compose for every + # `docker compose` call without repeating `-f`. The base + # path is relative (resolved from the `demo` working-dir); + # the override is absolute so it's project-dir-independent. + COMPOSE_FILE: docker-compose.yml:${{ github.workspace }}/.github/otel-demo-capture-compose-override.yml steps: - name: Checkout ourios (for the collector overlay) uses: actions/checkout@v4 @@ -86,8 +98,13 @@ jobs: working-directory: demo run: | set -euxo pipefail + # The capture dir must exist before `up` so the bind + # mount points at a real host directory; the collector + # (running as root) then creates logs.jsonl inside it. + mkdir -p "$OURIOS_CAPTURE_DIR" # Released demo versions reference prebuilt ghcr images, - # so `up -d` pulls rather than builds. + # so `up -d` pulls rather than builds. COMPOSE_FILE (job + # env) layers in our /capture mount override. docker compose up -d # Wait for the collector container to be running before # the warmup clock starts. Image pulls dominate here. @@ -121,18 +138,21 @@ jobs: echo "capturing for ${DURATION}s" sleep "$DURATION" - - name: Extract the captured corpus + - name: Verify capture + tear down working-directory: demo run: | set -euxo pipefail - mkdir -p "$GITHUB_WORKSPACE/captured" - # `docker compose cp` pulls the file the fileexporter - # wrote inside the collector container out to the runner. - docker compose cp \ - otel-collector:/tmp/otel-corpus.jsonl \ - "$GITHUB_WORKSPACE/captured/logs.jsonl" - # Tear the stack down before we do anything slow with the - # artifact, freeing the runner. + # The bind mount means the corpus is already on the host + # at $OURIOS_CAPTURE_DIR/logs.jsonl — no docker cp. + if [ ! -s "$OURIOS_CAPTURE_DIR/logs.jsonl" ]; then + echo "::error::no corpus captured at $OURIOS_CAPTURE_DIR/logs.jsonl (file missing or empty)" + docker compose ps + docker compose logs otel-collector | tail -50 || true + docker compose down -v || true + exit 1 + fi + # Tear the stack down before the (slower) manifest + + # upload steps, freeing the runner. docker compose down -v || true - name: Build diversity manifest From 45684d11c3d1e943d175aa8c3a4a3d50ffe1119c Mon Sep 17 00:00:00 2001 From: Jens Holdgaard Pedersen Date: Sun, 31 May 2026 16:20:56 +0200 Subject: [PATCH 2/3] fixup! fix(bench): bind-mount a writable capture dir for the demo collector (PR-N3.1) --- .github/workflows/capture-otel-demo-corpus.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/capture-otel-demo-corpus.yml b/.github/workflows/capture-otel-demo-corpus.yml index 7b2edb95e..9530c8df3 100644 --- a/.github/workflows/capture-otel-demo-corpus.yml +++ b/.github/workflows/capture-otel-demo-corpus.yml @@ -100,8 +100,16 @@ jobs: set -euxo pipefail # The capture dir must exist before `up` so the bind # mount points at a real host directory; the collector - # (running as root) then creates logs.jsonl inside it. + # then creates logs.jsonl inside it. mkdir -p "$OURIOS_CAPTURE_DIR" + # World-writable so the fileexporter can create the file + # regardless of the collector's UID. The demo pins + # `user: 0:0` (root) on otel-collector today, but the + # otelcol-contrib image's own default is UID 10001 — if + # the demo ever drops the override the bind mount would + # otherwise fail with EACCES, and a wrong guess here + # costs a full ~15-min dispatch to discover. + chmod 0777 "$OURIOS_CAPTURE_DIR" # Released demo versions reference prebuilt ghcr images, # so `up -d` pulls rather than builds. COMPOSE_FILE (job # env) layers in our /capture mount override. From 2a1b5aa1c6e809f6408c01d7dcbc4bb51d5c08c8 Mon Sep 17 00:00:00 2001 From: Jens Holdgaard Pedersen Date: Sun, 31 May 2026 16:40:39 +0200 Subject: [PATCH 3/3] fixup! fix(bench): bind-mount a writable capture dir for the demo collector (PR-N3.1) --- .github/otel-demo-capture-compose-override.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/otel-demo-capture-compose-override.yml b/.github/otel-demo-capture-compose-override.yml index 9dcfdb339..8219e6e68 100644 --- a/.github/otel-demo-capture-compose-override.yml +++ b/.github/otel-demo-capture-compose-override.yml @@ -1,6 +1,8 @@ # Compose override for the OTel Demo corpus capture # (`capture-otel-demo-corpus.yml`). Layered onto the demo's -# docker-compose.yml via `-f docker-compose.yml -f `. +# docker-compose.yml via the workflow's `COMPOSE_FILE` job env +# (`docker-compose.yml:`), so every `docker compose` call +# picks it up without repeating `-f`. # # Bind-mounts a runner-side directory into the collector at # `/capture`, the path the file/corpus exporter writes to (see