Skip to main content

satellite-observability

Use before querying production Storj satellite telemetry in Grafana — which datasource holds satellite metrics, the mandatory label selectors per peer, k8s pod metrics, where prod logs live (ClickHouse otel_logs) and how to query them, what is NOT available (traces, profiles), and the traps that make queries silently return wrong data. Pair with monkit-metrics (metric shapes) and satellite-peers (per-peer signals).

Datos de origen

Repositorio
storj/claude-plugins
Última actividad en el origen
21 de octubre de 2026 a las 19:48
Idioma detectado de SKILL.md
inglés
Estrellas
3
Forks
0

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
satellite-observability
description
Use before querying production Storj satellite telemetry in Grafana — which datasource holds satellite metrics, the mandatory label selectors per peer, k8s pod metrics, where prod logs live (ClickHouse otel_logs) and how to query them, what is NOT available (traces, profiles), and the traps that make queries silently return wrong data. Pair with monkit-metrics (metric shapes) and satellite-peers (per-peer signals).
# Observing production satellites Facts verified 2026-10-09. Observability infra is being migrated; if a datasource below fails, run `list_datasources` once and say what changed in your report. ## What exists (and what does not) | Signal | Status | Where | |---|---|---| | App metrics (monkit) | **Yes**, all prod regions | VictoriaMetrics `ffqrvx0pyhp8gf` (Grafana default) | | k8s pod metrics | Yes | same datasource | | Prod satellite **logs** | **Yes** (most peers) | ClickHouse `otel.otel_logs` via `cfqvdb4b1y96oa` — see Logs below | | Traces / profiles | No satellite data in Tempo/Pyroscope | live pprof via `satellite-debug-port` skill | | Eventkit events | Not in Grafana | ClickHouse warehouse (if you have access) | | Satellite DB (nodes, reputation, ...) | Yes, read-only SELECT | Grafana Postgres datasources — see Satellite DB below | | Select storage node metrics | Yes (`storj-select`, `select-eu`) | same VictoriaMetrics — see Select fleet below | | Prod config, version | git | `satellite-infra` skill | | Live process state | debug port: `/mon/stats`, `/debug/pprof`, `/metrics` | `satellite-debug-port` skill | ## Logs (ClickHouse `otel.otel_logs`) Prod satellite logs are in **ClickHouse**, table `otel.otel_logs`, through the Grafana datasource `cfqvdb4b1y96oa` (grafana-clickhouse-datasource). They are **not** in VictoriaLogs. Human view: dashboard `otel-logs-explorer` (https://grafana.storj.tools/d/otel-logs-explorer). Verified 2026-10-09: all four regions, near real time, **about 9 days of retention** (oldest rows on 2026-10-09 were from 2026-09-30). Run SQL through the Grafana API (read-only SELECT): ``` grafana_api_request POST /api/ds/query {"from":"now-1h","to":"now","queries":[{"refId":"A","format":1, "datasource":{"uid":"cfqvdb4b1y96oa","type":"grafana-clickhouse-datasource"}, "rawSql":"SELECT ..."}]} jq: .results.A.frames[0].data.values ``` Tip: build one string column with `concat(...)` and read `.values[0]` — easier than many columns. Columns that matter: - `Timestamp` — always filter on it first (huge table: us1 alone is millions of lines per hour). - `ResourceAttributes['environment_name']` — `storj-prod-satellite-us1` etc. - `` `__otel_materialized_k8s.namespace.name` ``, `` `__otel_materialized_k8s.pod.name` ``, `` `__otel_materialized_k8s.container.name` `` (= `satellite` for the app container). Use these materialized columns, not the map, for speed. Pod name = `<app>-<hash>-<id>`, so filter a peer with `pod.name LIKE 'satellite-api-%'`. - `ResourceAttributes['server_name']` — the host (bare-metal repairers have no k8s fields). - `Body` — the line. `SeverityText`, `ServiceName`, `ScopeName` are **empty** for satellite logs. **Two line formats** — your query must handle both: 1. rke2 clusters (most peers): zap console text with tabs: `08:25:00.032<TAB>warn<TAB>metainfo:endpoint<TAB>Storage limit exceeded<TAB>limit=2000000000000<TAB>public_id=...` → `splitByChar('\t', Body)`: `[2]` level, `[3]` logger, `[4]` message, `[5..]` `key=value` fields. An error with a stack trace has it inside the message part. 2. legacy k3s clusters (`rh-ewr-satellite-repair`, `cb-ash-satellite-repair`): GCP JSON. Parsed into `LogAttributes['severity']` (`WARNING`, `ERROR`), `LogAttributes['message']`, and `LogAttributes['logging.googleapis.com/labels']` (JSON with `error`, `node_id`, ...). No logger name. Top errors and warnings per peer (verified, handles both formats): ```sql WITH splitByChar('\t', Body) AS p, LogAttributes['message'] != '' AS is_json, replaceOne(lower(if(is_json, LogAttributes['severity'], p[2])), 'warning', 'warn') AS level, if(is_json, '', p[3]) AS logger, if(is_json, LogAttributes['message'], p[4]) AS msg, extract(`__otel_materialized_k8s.pod.name`, '^(.*?)-[a-z0-9]+(-[a-z0-9]+)?$') AS deploy SELECT concat(toString(count()), ' | ', deploy, ' | ', level, ' | ', logger, ' | ', substring(msg, 1, 120)) FROM otel.otel_logs WHERE Timestamp > now() - INTERVAL 1 HOUR AND ResourceAttributes['environment_name'] = 'storj-prod-satellite-us1' AND `__otel_materialized_k8s.namespace.name` = 'satellite' AND `__otel_materialized_k8s.container.name` = 'satellite' AND level IN ('error', 'warn', 'fatal') GROUP BY deploy, level, logger, msg ORDER BY count() DESC LIMIT 20 ``` (In the JSON body for `/api/ds/query` write the tab as `'\\t'`.) Rules: count with `GROUP BY`, never pull raw lines without `LIMIT`; keep windows short (≤ 1h) for raw lines; find one example line only after you know which message matters. Log fields can hold customer data (bucket names, project ids) — don't copy them into reports unless needed. **Coverage gaps (2026-10-09):** us1 `satellite-repair` (the dp-lax k8s repairers) has **no logs**; the other us1 repair sites do. No logs for `satellite-change-stream`. gc-bf and gc-sender log in all four regions (gc-bf only while a job runs). `satellite-ranged-loop` logs ~12 lines/day (low verbosity, not missing). `satellite-repair` replicas outside k8s (bare metal) ship nothing. Also: us1 `satellite-api` warns "Storage limit exceeded" ~170k/h — normal. **Shipped log levels differ per peer** (us1, 2026-10-09): repair (`rh-ewr`, `cb-ash`) **warn + error only**; gc-sender **warn only**; api, core, auditor, admin, console info + warn + error; ranged-loop info; gc-bf debug + info. So an info message from repair (e.g. "audit failed" with the node ID) is not in ClickHouse — say the data is not available instead of searching. Check first: `groupUniqArray(level)` per deployment over 24h. **Stack traces are split into rows**: each frame of a Go stack trace is its own log row (Body like `\t/go/satellite/...go:164`). They pollute `GROUP BY` on the message column — filter on `p[2] IN ('error','warn',...)` (frames have no level). zap error fields appear in Body as `exception.message=...` and `exception.stacktrace=...`, **not** `error=` — e.g. `arrayFirst(x -> startsWith(x, 'exception.message='), p)`. The cause can also be the **last row after the stack frames** (same timestamp +~10 ms, no tab, no level, e.g. `Error 8175 (HY000): Your query has been cancelled due to exceeding the allowed memory limit...`). **The cause is in separate stderr rows starting with `---`** (`--- context canceled`, `--- metabase: failed to scan segments: ...`), with `LogAttributes['log.iostream'] = 'stderr'` and no level — so a level filter hides them. Recipe: fetch all raw rows of that pod for ±2 s around the error (`ORDER BY Timestamp`), or `WHERE Body LIKE '---%' GROUP BY Body` for a few minutes around it. **Any SQL error comes back as a bare HTTP 400** with no message (bad syntax, positional `GROUP BY 1` / `GROUP BY 2,3` — group by the real expressions —, too much raw data). Simplify the query to find the problem. Aggregate over long windows; fetch raw lines only for a window of minutes around a known timestamp. The satellite-api pods (us1) export no Go runtime metrics (GC, heap, goroutines); for those you need pprof via the debug port. Dead or empty datasources (do not use): `afqbg5eubyqyob` (502), `cfpjntp1kao00d`, `de9dc57qvnegwc`, `feghujtjgimtcd` (gone), `bfx67nh13k5j4f` (502), `efsa9dkqirrwgd` (VictoriaLogs, empty since 2026-09-22). There is no Thanos datasource any more. ## Satellite DB (read-only SQL, verified 2026-10-21) Grafana Postgres datasources: us1 `afrrqq1u668sge`, eu1 `ffx4danf5xm9sd`, ap1 `cfx4dkk6xcbuod`, slc `efx4drsst3pc0c`. Use them for the **current state of nodes** — this is the source of truth, logs and metrics cannot give it: `nodes.last_contact_success`, `disqualified`, `disqualification_reason` (1 = audit, 3 = offline / stray node), `unknown_audit_suspended`, `offline_suspended`, `exit_initiated_at`, `exit_finished_at`, `piece_count` (stale), `email`, `last_net`; host and server group in `node_tags`. Successful node check-ins are **not logged**, so "did the node come back?" can only be answered here. ``` grafana_api_request POST /api/ds/query {"from":"now-1h","to":"now","queries":[{"refId":"A","format":"table", "datasource":{"uid":"afrrqq1u668sge"},"rawSql":"SELECT ..."}]} jq: .results.A.frames[0].data.values ``` - Node IDs are `bytea`: filter with `id IN ('\x<hex>', ...)`, print with `encode(id,'hex')`. Logs use base58 node IDs. - Select nodes: `email = 'ops@storj.io'`. Online now: `last_contact_success > now() - interval '4 hours'`. - Host of a node: `(SELECT convert_from(value,'UTF8') FROM node_tags t WHERE t.node_id = n.id AND t.name = 'host')`. - Emails and wallets are personal data: group by `md5(email)` in reports. ## Select fleet (storage nodes run by Storj, verified 2026-10-21) Select hosts (`cb-select-storage-*`, `rh-select-storage-*`) run many storagenode containers each and report metrics with `environment_name="storj-select"` (EU: `select-eu`), label `server_name` = host. There is no `app`/`satellite` label. A Select outage looks like a **satellite repair problem**: Select placements use a tight RS scheme, so one lost piece already needs repair. **Host metrics hide dead nodes** — node-exporter stays up while containers are stopped. Count per node: ```promql # storagenode processes reporting, per host (drop = nodes down) count by (server_name) (used_space{environment_name="storj-select",field="sum"}) # running containers per host (includes cadvisor/otelcol, so a few more than nodes) count by (server_name) (container_cpu_utilization_ratio{server_name=~"cb-select-storage-.*"}) ``` If container count is flat but `used_space` count dropped, the process is hung, not stopped. Compare both over 2–3 days at a 15m step and look for steps down. Kernel/disk trouble on a host: `select-kernel-log` skill. ## Mandatory selectors Every satellite query must pin **all three**: ``` environment_name="storj-prod-satellite-us1" # or -eu1, -ap1, -slc app="satellite-api" # the peer kubernetes_namespace="satellite" # slc also runs QA in "satellite-qa" with the SAME app names ``` Regions: `us1` (largest), `eu1`, `ap1`, `slc`. All four: `environment_name=~"storj-prod-satellite-(us1|eu1|ap1|slc)"`. `app` values: `satellite-api`, `satellite-console`, `satellite-core`, `satellite-auditor`, `satellite-repair`, `satellite-ranged-loop`, `satellite-gc-bf`, `satellite-gc-sender`, `satellite-admin`, `satellite-change-stream` (us1, eu1), `jobq`. **Repair has many apps.** us1 runs `rh-ewr-satellite-repair`, `cb-ash-satellite-repair`, `slc-satellite-repair`, `ap1-satellite-repair` (namespace `satellite-us1`), and more. eu1 has `hn-prod-storage-*-repair`. For "all repair workers" use `app=~".*repair"` and drop the namespace pin. Never filter on the `service` label — it is not reliable across peers. ## Metric shape (monkit) Load the `monkit-metrics` skill for full detail. The parts that break queries most often: - Method names carry a receiver prefix: `name="__Endpoint__GetObject"`, `__SegmentRepairer__Repair`. Plain `name="GetObject"` returns nothing. Discover: `count by (name) (function{scope="...", field="total", environment_name="...", app="..."})`. - Scope = Go package path with `.` and `/` → `_`: `storj_io_storj_satellite_metainfo`. - `function` fields: `total` = all calls, `successes`, `failures` (= `errors`). Error ratio = `failures/total`. **`count` is NOT calls**: it is the failure counter split by `error_name` (`drpc_ResourceExhausted`, `drpc_NotFound`, ...). `sum by (error_name) (rate(function{field="count",...}[5m]))` tells you *which* errors.
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub