Real, repeat-safe HTTP and gRPC health calls through your services, with W3C trace context.
217
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.
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.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.-once) is paced by run.maxCallsPerSecond.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).
linux/amd64 and linux/arm64.parallax binary, so the image takes the same flags as the CLI./readyz and /healthz in continuous mode.docker buildx imagetools inspect immersivefusion/parallax.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.
| Flag | Default | Meaning |
|---|---|---|
-manifest | (required) | Path to a parallax/v1 manifest, or - to read it from stdin |
-dry-run | false | Print the plan (blast radius, calls and refusals), then exit without sending anything |
-once | false | One pass, then exit with 0, 1, 2 or 3 |
-spread | false | With -once, spread the calls across the interval with jitter |
-max-in-flight | 64 | Most calls open at once (1 to 4096); a call beyond it is skipped and reported, never queued |
-endpoint | empty | OTLP/gRPC host:port for Parallax's own spans. Empty: trace context is still propagated, nothing is exported |
-headers | empty | OTLP headers k=v,k=v. Falls back to OTEL_EXPORTER_OTLP_HEADERS; prefer the variable so keys stay out of process lists. Never logged |
-insecure | false | Plaintext to the OTLP endpoint; refused with headers unless the endpoint is loopback (override with -allow-insecure-headers) |
-results | empty | jsonl: one JSON object per call and per pass on stdout |
-health-addr | :8080 | Address 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.
latest follows the newest release; each release also publishes a matching semantic-version tag. Pin by digest for reproducible runs.github.com/ImmersiveFusion/parallax
Apache-2.0. Built by Immersive Fusion.
Content type
Image
Digest
sha256:56db51799…
Size
6.2 MB
Last updated
9 days ago
docker pull immersivefusion/parallax