Skip to content

fix(ci): Run LocalStack-backed Rust tests on self-hosted runners. - #2449

Open
jackluo923 wants to merge 1 commit into
ci-drop-docs-macosfrom
ci-localstack-local-runner-fix
Open

fix(ci): Run LocalStack-backed Rust tests on self-hosted runners.#2449
jackluo923 wants to merge 1 commit into
ci-drop-docs-macosfrom
ci-localstack-local-runner-fix

Conversation

@jackluo923

@jackluo923 jackluo923 commented Aug 1, 2026

Copy link
Copy Markdown
Member

Description

tests:rust-all starts LocalStack with --publish <port>:4566 and connects to it over loopback. Our self-hosted runners mount the host's Docker socket, so that container is a sibling on the host: the port lands in the host's network namespace rather than the runner's, and the job can't reach it. This is why the job was pinned to GH_RUNNER_LINUX_LIGHT_X64 (["ubuntu-24.04"], GitHub-hosted).

start.py now checks whether it's running inside a container the Docker daemon can resolve. If so, it starts LocalStack with --network container:<id> and GATEWAY_LISTEN=0.0.0.0:<port> instead of publishing a port, so LocalStack shares the job's network namespace and loopback reaches it. No extra privileges are needed, and because each runner has its own namespace, concurrent runners on one machine can't contend over ports.

GitHub-hosted runners and dev machines fail the check and keep today's publishing behaviour, so AWS_ENDPOINT_URL and the steps in components/log-ingestor/README.md are unchanged.

The job then moves to GH_RUNNER_LINUX_HEAVY_X64 (self-hosted), which suits it anyway since tests:rust-all builds the workspace in release mode.

Two smaller changes:

  • Address LocalStack as 127.0.0.1 rather than localhost. Inside a container localhost can resolve to ::1 alone, which LocalStack's IPv4 gateway doesn't bind, and it fails identically to the bug above. tests/aws_config.rs already used 127.0.0.1.
  • start.py waits for /_localstack/health before returning. Nothing waited for readiness before; boto3 retries happened to cover it.

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

Simulated a runner with the host Docker socket mounted, matching the y-scope/ci setup:

  • LocalStack was reachable over loopback, with no port published on the host.
  • Two such runners ran LocalStack on the same port without interfering.
  • The runner kept DNS and egress with LocalStack in its namespace.

Also re-ran publishing mode on the host end to end through create-bucket.py. ruff check, ruff format, and yamllint --strict are clean.

@jackluo923
jackluo923 requested a review from a team as a code owner August 1, 2026 23:43
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: b156f076-a770-4f8a-89fa-5ae4ef1b2969

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

`tests:rust-all` starts LocalStack with `--publish <port>:4566` and connects to
it over loopback. Our self-hosted runners mount the host's Docker socket, so the
container `start.py` starts is a sibling on the host, and the port is published
in the host's network namespace rather than the runner's, where it is
unreachable from the job. The port `get-free-port.py` picks is also probed in
the runner's namespace while being reserved in the host's, so it can collide
with an unrelated listener or with another runner on the same machine.
`clp-rust-checks` therefore targeted `GH_RUNNER_LINUX_LIGHT_X64`
(`["ubuntu-24.04"]`, GitHub-hosted) to stay off the self-hosted pool.

`start.py` now detects whether it is running inside a container the Docker
daemon can resolve (`/.dockerenv` plus `docker inspect $(hostname)`) and, if so,
starts LocalStack with `--network container:<id>` and
`GATEWAY_LISTEN=0.0.0.0:<port>` instead of publishing. LocalStack then shares
the job's network namespace, so loopback reaches it, nothing is published to the
host, and the chosen port is probed in the namespace it is bound in. No
elevated privileges are needed: `--network container:` is an ordinary network
mode requiring only the socket access the runners already have.

With that, `clp-rust-checks` moves to `GH_RUNNER_LINUX_HEAVY_X64`
(`["self-hosted","x64","ubuntu-noble","docker"]`), which also suits the job
better: `tests:rust-all` builds the workspace in release mode. GitHub-hosted
runners and dev machines fail the detection and keep the existing publishing
behaviour, so `AWS_ENDPOINT_URL` and the developer instructions in
`components/log-ingestor/README.md` are unchanged.

Address LocalStack by `127.0.0.1` rather than `localhost`: inside a container
`localhost` can resolve to `::1` alone, which LocalStack's IPv4 gateway doesn't
bind, so the name form fails with a connection refusal indistinguishable from
the bug above. `tests/aws_config.rs` already defaulted to `127.0.0.1`; this
aligns the taskfile and `create-bucket.py` with it.

Also wait for `/_localstack/health` before returning from `start.py`. Nothing
waited for readiness previously; `create-bucket.py` happened to paper over it
via boto3 retries.

Verified against a runner simulated with the host socket mounted: LocalStack was
reachable on loopback with no host port published; two such runners ran
concurrent LocalStacks on the same port without interfering; and the runner
retained DNS and egress with LocalStack in its namespace. Publishing mode was
re-verified end to end on the host through `create-bucket.py`. `ruff check`,
`ruff format`, and `yamllint --strict` are clean.

Co-Authored-By: Claude <noreply@anthropic.com>
@jackluo923
jackluo923 force-pushed the ci-localstack-local-runner-fix branch from c1d43ce to 1ab20d3 Compare August 1, 2026 23:48
@jackluo923 jackluo923 changed the title fix(ci): Make LocalStack tests work on Docker-socket-mounting self-hosted runners. fix(ci): Run LocalStack-backed Rust tests on self-hosted runners. Aug 1, 2026
@jackluo923

Copy link
Copy Markdown
Member Author

Follow-ups noted while working on this, not addressed here:

  • A hard-cancelled job leaks a sibling LocalStack container on the host, since the defer: stop never runs. A --label plus a periodic prune on the runner machines would cover it.
  • get-free-port.py closes its probe socket before LocalStack binds, leaving a narrow TOCTOU window. Retrying start.py on bind failure would close it.
  • docs/src/dev-docs/tooling-gh-workflows.md:99-110 still documents the owner-based runner selection and ["self-hosted", "x64", "ubuntu-noble"] tags removed in d12530e.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant