Sign inSign up

immersivefusion/parallax

By immersivefusion

•Updated 9 days ago

Real, repeat-safe HTTP and gRPC health calls through your services, with W3C trace context.

Image
Developer tools
Monitoring & observability
0

217

immersivefusion/parallax repository overview

⁠Parallax

Real calls through your own services, paced and repeat-safe.

Parallax walks a graph of your services and calls them for real: HTTP GET and HEAD on the routes you list, and the standard gRPC health check. Each call starts a trace and passes W3C trace context, so if your service reads W3C trace context, its own spans appear under Parallax's client span. Point Parallax at an OTLP/gRPC endpoint and it exports its side of that trace too.

Use it as a continuous check that your services answer, or as a CI step that fails when one does not.

  • Repeat-safe only. Only GET, HEAD and the gRPC health check run. Any other HTTP method is refused and listed in the dry run. Parallax goes by the method, so a GET that writes on your server will still be called: list only routes that are safe to repeat.
  • Only calls nodes you mark owned. A node must be marked owned: true before Parallax sends it anything, and redirects are never followed. This is a guardrail against mistakes, not an access control: whoever writes the manifest decides what is owned.
  • Paced and bounded. In continuous mode, calls are spread across the interval with jitter. A single pass (-once) is paced by run.maxCallsPerSecond.
  • Exports to any OTLP/gRPC receiver, such as an OpenTelemetry Collector.

No account or API key is needed to run it. If your OTLP backend needs a key, pass it as a header (see below).

Status: early release. The manifest format and flags may still change between minor versions, so pin by digest rather than relying on latest (see Tags).

⁠The image

  • Multi-arch: linux/amd64 and linux/arm64.
  • Distroless and static, running as a non-root user (UID 65532).
  • The entrypoint is the parallax binary, so the image takes the same flags as the CLI.
  • Exposes port 8080 for /readyz and /healthz in continuous mode.
  • Each platform carries an SBOM and a build provenance attestation, generated by BuildKit (unsigned). Inspect them with docker buildx imagetools inspect immersivefusion/parallax.
  • The manifest is read as UID 65532, so a mounted file must be readable by that user.

⁠Quick start

Save a manifest as parallax.yaml. This one has a route that writes and a node that is not marked owned, so you can see both refused. The addresses are placeholders: point them at your own services.

apiVersion: parallax/v1
kind: Manifest
metadata:
  name: shop
graph:
  nodes:
    - name: checkout
      owned: true
      http:
        baseURL: http://checkout.shop.svc.cluster.local:8080
        routes:
          - {method: GET, path: /healthz}
          - {method: POST, path: /api/orders}
      grpc:
        target: checkout.shop.svc.cluster.local:9090
        insecure: true
        health: true
    - name: payment-provider
      http:
        baseURL: https://api.payments.example.com
        routes:
          - {method: GET, path: /v1/status}
run:
  interval: 60s

See exactly what would run, and what is refused, without sending anything:

docker run --rm -v "$PWD/parallax.yaml:/etc/parallax/parallax.yaml:ro" \
  immersivefusion/parallax -manifest /etc/parallax/parallax.yaml -dry-run
Parallax dry run: shop
  blast radius: 1 node(s), 2 call(s) per pass
  cadence: every 1m0s, spread across the interval (about 0.03 calls/s, limit 20.00)
  per-call timeout: 5s
  load: none (repeat-safe probes only)

Calls:
  + checkout GET http://checkout.shop.svc.cluster.local:8080/healthz
  + checkout grpc checkout.shop.svc.cluster.local:9090 /grpc.health.v1.Health/Check (server)

Refused:
  - checkout POST http://checkout.shop.svc.cluster.local:8080/api/orders
      not repeat-safe: Parallax only calls GET, HEAD and gRPC health automatically
  - payment-provider GET https://api.payments.example.com/v1/status
      node is not marked owned: Parallax never calls a system you don't own

"Don't own" in that refusal means "not marked owned: true in this manifest": it is the guardrail described above, not a check of who owns the system.

One pass to a hosted OTLP backend over TLS (the default), with the key read from your shell's environment so it stays off the command line:

export OTEL_EXPORTER_OTLP_HEADERS="api-key=YOUR_KEY"
docker run --rm -v "$PWD/parallax.yaml:/etc/parallax/parallax.yaml:ro" \
  -e OTEL_EXPORTER_OTLP_HEADERS \
  immersivefusion/parallax -manifest /etc/parallax/parallax.yaml -once -endpoint otlp.example.com:443

For a plaintext Collector on your own machine, add -insecure and use an address the container can reach: localhost inside the container is the container itself. On Docker Desktop that is host.docker.internal:4317; on Linux, run with --network host and use localhost:4317. Never combine -insecure with an API key over a network you do not control.

Exit code 0 means the pass completed and no call was unanswered, 1 means at least one call was unanswered, 2 means a bad manifest or configuration, and 3 means the pass was cut short by docker stop or Ctrl-C. A failed span export is logged but never changes the exit code, so 0 says no call went unanswered, not that the spans arrived. Calls skipped because -max-in-flight calls were already open do not change the exit code, so in CI also fail the job when the pass line reports skipped above 0, or raise -max-in-flight. In -once, calls go out at up to run.maxCallsPerSecond (default 20) with jitter, much faster than the continuous cadence the dry run reports; add -spread to spread them across the interval instead.

A call counts as unanswered if it errors or times out, if an HTTP route returns status 400 or above (other than 401 and 403), or if a gRPC health check returns anything other than SERVING. A 3xx counts as answered, because redirects are not followed: a route that redirects to a login page passes. Parallax adds no credentials of its own: a 401 or 403 did answer, so it counts as answered and is flagged needsAuth in -results. A call not started because -max-in-flight calls are already open is reported as skipped, and does not change the exit code. Keep secrets out of the manifest: a baseURL with credentials (https://user:pass@host), a query string or a fragment is rejected, and every URL is printed as-is in the dry run, the logs and -results. HTTP calls honor HTTP_PROXY, HTTPS_PROXY and NO_PROXY.

Parallax refuses to connect to link-local and cloud metadata addresses (such as 169.254.169.254), checked on the address it actually connects to, after DNS resolution; pass -allow-link-local to allow them. When HTTP calls go through a proxy, that address is the proxy's, so the proxy decides whether a metadata address is reached. Loopback and private ranges are not restricted. gRPC health checks do not use the proxy, so in a network where only the proxy can reach your services they will fail.

Without -once or -dry-run, Parallax walks the graph continuously (every 60s by default) and serves /readyz and /healthz on port 8080 (publish it with -p 8080:8080 if you need it outside the container; the image defines no Docker HEALTHCHECK). /healthz reports stale after three intervals without a completed pass, so a hung container can be restarted. Both endpoints report Parallax's own health, not your services': /healthz stays green when every call goes unanswered. In continuous mode, unanswered calls show up as warnings on stderr, as error-status probe spans, and in -results, never in the exit code or the health endpoints.

⁠Common flags

FlagDefaultMeaning
-manifest(required)Path to a parallax/v1 manifest, or - to read it from stdin
-dry-runfalsePrint the plan (blast radius, calls and refusals), then exit without sending anything
-oncefalseOne pass, then exit with 0, 1, 2 or 3
-spreadfalseWith -once, spread the calls across the interval with jitter
-max-in-flight64Most calls open at once (1 to 4096); a call beyond it is skipped and reported, never queued
-endpointemptyOTLP/gRPC host:port for Parallax's own spans. Empty: trace context is still propagated, nothing is exported
-headersemptyOTLP headers k=v,k=v. Falls back to OTEL_EXPORTER_OTLP_HEADERS; prefer the variable so keys stay out of process lists. Never logged
-insecurefalsePlaintext to the OTLP endpoint; refused with headers unless the endpoint is loopback (override with -allow-insecure-headers)
-resultsemptyjsonl: one JSON object per call and per pass on stdout
-health-addr:8080Address for /readyz and /healthz in continuous mode. Empty disables the server

run.maxCallsPerSecond (default 20, at most 1000) is an average budget: a graph whose calls cannot fit the interval at that rate is rejected when the plan is built. Hard limits no manifest can raise: 16 MiB per manifest, 10,000 calls per pass, 1,000 gRPC targets, and an interval of at most 24 hours.

The full flag list, manifest reference, span attributes and results format are in the README on GitHub⁠.

⁠Tags

  • latest follows the newest release; each release also publishes a matching semantic-version tag. Pin by digest for reproducible runs.

⁠Source, issues, full docs

github.com/ImmersiveFusion/parallax⁠

Apache-2.0. Built by Immersive Fusion.

Tag summary

Content type

Image

Digest

sha256:56db51799…

Size

6.2 MB

Last updated

9 days ago

docker pull immersivefusion/parallax