An ordered procedure with a checkpoint after each step. Written so that a person or an agent can run it start to finish without guessing. Every command is complete; the only inputs are the ones in the first section.
Collect these before starting. Nothing else is needed.
| Input | Where it comes from |
|---|---|
<tenant> |
Your Perfloop tenant name, the segment after /t/ in the app URL. |
<namespace> |
The Kubernetes namespace the relay will run in. |
<upstream-name> |
A name you choose for the source, one lowercase DNS label, for example vm. |
<kind> |
One of the kinds routes.go lists: today prometheus, victoriametrics, loki, or pprof. |
<upstream-url> |
The provider base URL as reachable from inside the cluster, for example http://vmselect.monitoring.svc:8481/select/0/prometheus. No trailing slash. A pprof upstream has targets instead, one such URL per Go process (configuration.md, "pprof upstreams"). |
<upstream-read-token> |
A read-only token for that upstream, if it needs one. Query-scoped. |
<digest> |
The relay image digest to pin. See "Choose the image" below. |
| A Perfloop admin | Creating the relay token and registering the source happen in Perfloop Setup, which needs an admin login in the browser. |
Access needed: kubectl against the cluster with rights to create a
ConfigMap, Secret, Deployment, and NetworkPolicy in <namespace>; outbound
HTTPS from that namespace to app.perfloop.ai on port 443; and network
reachability from the namespace to the upstream.
Every push to main in this repository publishes
ghcr.io/perfloop/relay tagged with the commit SHA. Pick the commit you
read, and resolve its digest:
docker buildx imagetools inspect ghcr.io/perfloop/relay:<commit sha> --format '{{json .Manifest.Digest}}'Checkpoint: the command prints one sha256:… string. That is <digest>.
To verify the signature and the binary, see image.md.
In the Perfloop app, open https://app.perfloop.ai/t/<tenant>/setup, and in
Model inputs, at the end of the Telemetry section, find
Relay tokens. Enter a name for the token (for example relay <namespace>)
and press Create relay token. Copy the token now; it is shown once. It
starts with plf_.
Checkpoint: the token's name appears in the Relay tokens list.
Hold the token in an environment variable in your shell, never in a file or on a command line that shell history records:
read -rs PERFLOOP_RELAY_TOKEN # paste, then EnterWrite relay.yaml:
api: https://app.perfloop.ai
token: ${PERFLOOP_RELAY_TOKEN}
upstreams:
- name: <upstream-name>
kind: <kind>
url: <upstream-url>
headers:
Authorization: Bearer ${UPSTREAM_READ_TOKEN}Drop the headers block if the upstream needs no token. Every field is
described in configuration.md.
Validate it and print exactly what it permits, using the same image you will
deploy. -print-routes only checks the file; placeholders are fine for the
secrets here:
docker run --rm -v "$PWD/relay.yaml:/etc/perfloop-relay/relay.yaml:ro" \
-e PERFLOOP_RELAY_TOKEN=placeholder -e UPSTREAM_READ_TOKEN=placeholder \
ghcr.io/perfloop/relay@<digest> -print-routesCheckpoint: exit code 0 and a listing whose forwards URLs are the ones you
expect. Any other exit code is a configuration error, printed on the last
line. Keep the listing with your change record: it is everything the relay
can be asked to do.
From the variables, not from literals:
read -rs UPSTREAM_READ_TOKEN # paste, then Enter (skip if the upstream needs none)
kubectl -n <namespace> create secret generic perfloop-relay \
--from-literal=token="$PERFLOOP_RELAY_TOKEN" \
--from-literal=upstream-read-token="${UPSTREAM_READ_TOKEN:-}"
unset PERFLOOP_RELAY_TOKEN UPSTREAM_READ_TOKENCheckpoint: kubectl -n <namespace> get secret perfloop-relay lists it with
two keys.
Take examples/kubernetes/relay.yaml,
delete its Secret document (you created the real one in step 4), replace
every <placeholder> including <digest>, put your relay.yaml content in
the ConfigMap, and apply:
kubectl -n <namespace> apply -f relay.yaml
kubectl -n <namespace> rollout status deploy/perfloop-relay --timeout=120sCheckpoint: the rollout completes, and within ten seconds the log shows two open tunnels:
kubectl -n <namespace> logs deploy/perfloop-relay | grep '"tunnel open"' | head -2Two lines with "msg":"tunnel open","api":"app.perfloop.ai".
In https://app.perfloop.ai/t/<tenant>/setup, in the Model inputs group,
press Connect for your kind (Prometheus, VictoriaMetrics, Loki,
or Go pprof). In the Endpoint field enter:
relay://<upstream-name>
Leave the credential empty, and for Loki leave the tenant empty too; Perfloop
refuses both for a relay source. A multi-tenant Loki takes its tenant from an
X-Scope-OrgID entry in the upstream's headers in relay.yaml (step 3).
For a pprof upstream, register one selector per target name from its
targets; one connection holds at most eight. Complete the connection.
Checkpoint: Setup shows the source as connected, and the relay log shows the connection check as a read:
kubectl -n <namespace> logs deploy/perfloop-relay | grep '"relay read"' | tail -1One line with "status":200.
The first Perfloop Session or research that uses this source produces reads with a request id:
kubectl -n <namespace> logs deploy/perfloop-relay | grep '"request_id":"[^"]' | tail -3Each line's request_id is <session id>/<tool call ref>; it names the
Perfloop transcript event that asked. That is your join key between this log
and Perfloop.
| What you see | Meaning | What to check |
|---|---|---|
-print-routes exits non-zero |
The file is invalid. | The last line names the field and the rule. |
No tunnel open; tunnel ended with a dial or TLS error |
The relay cannot reach app.perfloop.ai:443. |
Egress from the namespace, the NetworkPolicy, DNS, an egress proxy (HTTPS_PROXY must carry WebSockets), api_ca if TLS is re-signed. |
perfloop refused tunnel: 401 Unauthorized |
The token is wrong or revoked. | Create a new token in Setup and replace the Secret's token key; the Deployment restarts on its own only if you roll it. |
perfloop refused tunnel: 429 |
This tenant already has the maximum number of open tunnels. | Another relay of yours is connected; stop the extra one or wait for it to end. |
Setup connection fails; relay log shows "decision":"upstream-error" |
The relay reached the tunnel but not the upstream. | url, the upstream's TLS (ca), and network reachability from the namespace to the upstream. |
Setup connection fails; relay log shows "status":404,"decision":"unknown-upstream" |
The endpoint name in Setup does not match upstreams[].name. |
Use exactly relay://<upstream-name>. |
| Setup connection fails; relay log shows no line at all | The read never reached this relay. | Confirm the tunnels are open; the tenant in Setup is the tenant the token belongs to. |
"decision":"truncated" |
An upstream response exceeded 32 MiB and was cut. | Narrow the query on the Perfloop side; the relay does not buffer. |
Create the new token in Setup, update the Secret's token key, roll the
Deployment, confirm two tunnel open lines, then revoke the old token in
Setup. Tunnels on the old token end within 40 seconds of revocation, so do
it in that order to avoid a gap. To end them at once, stop the relays that
still use the old token.