diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d004a9b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,20 @@ +# Instructions for agents + +You are setting up or operating the Perfloop relay in a customer's network. + +- Follow [docs/setup.md](docs/setup.md) in order. Do not skip a checkpoint; + each one is the observable that proves the step worked. +- Never print, log, or write a token to a file. Tokens go from the operator's + paste into a Secret through a shell variable, as the procedure shows. +- Before applying anything, run `-print-routes` (step 3) and keep its output. + It is the complete list of what the relay can be asked to do. +- The relay token and the source registration are created in Perfloop Setup + by a tenant admin in the browser. If you are not that admin, stop at those + steps and ask for the token and the confirmation, do not work around them. +- Pin the image by digest. Never use a mutable tag in a manifest. +- What the relay does and refuses is in [docs/security.md](docs/security.md); + configuration fields in [docs/configuration.md](docs/configuration.md); + logs and `-print-routes` in [docs/operations.md](docs/operations.md); + image verification in [docs/image.md](docs/image.md). +- When something fails, use the table at the end of `docs/setup.md` before + changing anything else. diff --git a/README.md b/README.md index 0cbe4ff..35ddebd 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,10 @@ The full model, with the code that enforces each point: 5. In Perfloop Setup, register the source as `relay://vm`. +The same steps as an ordered procedure with a checkpoint after each one, and +a table of what each failure means: [docs/setup.md](docs/setup.md). Agents +start there ([AGENTS.md](AGENTS.md)). + Every configuration field: [docs/configuration.md](docs/configuration.md). Running, `-print-routes`, and the audit log: [docs/operations.md](docs/operations.md). diff --git a/docs/operations.md b/docs/operations.md index b3f4a50..fbc4b84 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -8,7 +8,8 @@ perfloop-relay -config /etc/perfloop-relay/relay.yaml on standard output. A Kubernetes starting point, with a hardened pod and a NetworkPolicy that -allows only the egress the relay needs, is in `examples/kubernetes/`. +allows only the egress the relay needs, is in `examples/kubernetes/`. The +ordered setup procedure with checkpoints is [setup.md](setup.md). ## `-print-routes` diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000..a0ccb05 --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,178 @@ +# Setting up the relay + +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. + +## Inputs + +Collect these before starting. Nothing else is needed. + +| Input | Where it comes from | +| --- | --- | +| `` | Your Perfloop tenant name, the segment after `/t/` in the app URL. | +| `` | The Kubernetes namespace the relay will run in. | +| `` | A name you choose for the source, one lowercase DNS label, for example `vm`. | +| `` | `prometheus`, `victoriametrics`, or `loki`. | +| `` | The provider base URL as reachable from inside the cluster, for example `http://vmselect.monitoring.svc:8481/select/0/prometheus`. No trailing slash. | +| `` | A read-only token for that upstream, if it needs one. Query-scoped. | +| `` | 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 ``; outbound +HTTPS from that namespace to `app.perfloop.ai` on port 443; and network +reachability from the namespace to the upstream. + +## 1. Choose the image + +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: + +```sh +docker buildx imagetools inspect ghcr.io/perfloop/relay: --format '{{json .Manifest.Digest}}' +``` + +Checkpoint: the command prints one `sha256:…` string. That is ``. +To verify the signature and the binary, see [image.md](image.md). + +## 2. Create the relay token + +In the Perfloop app, open `https://app.perfloop.ai/t//setup`, find the +**API keys** group, enter a name for the token (for example +`relay `), and press **Create relay token**. Copy the token now; +it is shown once. It starts with `plf_`. + +Checkpoint: the token appears in the live keys table with scope `relay`. + +Hold the token in an environment variable in your shell, never in a file +or on a command line that shell history records: + +```sh +read -rs PERFLOOP_RELAY_TOKEN # paste, then Enter +``` + +## 3. Write the configuration and validate it before deploying + +Write `relay.yaml`: + +```yaml +api: https://app.perfloop.ai +token: ${PERFLOOP_RELAY_TOKEN} +upstreams: + - name: + kind: + url: + headers: + Authorization: Bearer ${UPSTREAM_READ_TOKEN} +``` + +Drop the `headers` block if the upstream needs no token. Every field is +described in [configuration.md](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: + +```sh +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@ -print-routes +``` + +Checkpoint: 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. + +## 4. Create the Secret + +From the variables, not from literals: + +```sh +read -rs UPSTREAM_READ_TOKEN # paste, then Enter (skip if the upstream needs none) +kubectl -n 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_TOKEN +``` + +Checkpoint: `kubectl -n get secret perfloop-relay` lists it with +two keys. + +## 5. Deploy + +Take [examples/kubernetes/relay.yaml](../examples/kubernetes/relay.yaml), +delete its `Secret` document (you created the real one in step 4), replace +every `` including ``, put your `relay.yaml` content in +the ConfigMap, and apply: + +```sh +kubectl -n apply -f relay.yaml +kubectl -n rollout status deploy/perfloop-relay --timeout=120s +``` + +Checkpoint: the rollout completes, and within ten seconds the log shows two +open tunnels: + +```sh +kubectl -n logs deploy/perfloop-relay | grep '"tunnel open"' | head -2 +``` + +Two lines with `"msg":"tunnel open","api":"app.perfloop.ai"`. + +## 6. Register the source in Perfloop + +In `https://app.perfloop.ai/t//setup`, in the **Model inputs** group, +press **Connect ** for your kind (Prometheus, VictoriaMetrics, or +Loki). In the **Endpoint** field enter: + +``` +relay:// +``` + +Leave the credential empty; a relay source has none. For Loki, enter the +tenant id if your Loki is multi-tenant. Complete the connection. + +Checkpoint: Setup shows the source as connected, and the relay log shows the +connection check as a read: + +```sh +kubectl -n logs deploy/perfloop-relay | grep '"relay read"' | tail -1 +``` + +One line with `"status":200`. + +## 7. Confirm a real read + +The first Perfloop Session or research that uses this source produces reads +with a request id: + +```sh +kubectl -n logs deploy/perfloop-relay | grep '"request_id":"[^"]' | tail -3 +``` + +Each line's `request_id` is `/`; it names the +Perfloop transcript event that asked. That is your join key between this log +and Perfloop. + +## If a checkpoint fails + +| 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 tunnels on that API replica. | 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://`. | +| 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. | + +## Rotating the token + +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 thirty seconds of revocation, so +do it in that order to avoid a gap.