epok

Send metrics & infrastructure to Epok

Updated Jul 28, 2026 · 1d ago

Epok accepts metrics over two open protocols: OTLP and Prometheus remote_write. Anything that speaks either — the OpenTelemetry collector, node_exporter, kube-state-metrics, cAdvisor, Telegraf, or a database exporter — works with no proprietary agent.

The one thing that matters for correlation: give each metric a service label (or an OTLP service.name resource attribute). Epok normalizes the common aliases automatically — node_exporter instance becomes host, KSM pod/namespace carry through — so the metric lands on the right service in the investigation instead of a separate dashboard.

Endpoints: /v1/metrics (OTLP) · /api/v1/write (remote_write) · API key: app.getepok.dev → Settings → API Keys

OpenTelemetry hostmetrics (recommended)

The hostmetrics receiver already emits system.cpu/memory/filesystem.utilization as 0–1 fractions — exactly the shape Epok's saturation detection wants. Add a service.name so the host joins the service it runs.

otel-collector-config.yaml
yaml
receivers:
  hostmetrics:
    collection_interval: 30s
    scrapers: { cpu: {}, memory: {}, filesystem: {}, load: {}, network: {} }

processors:
  resource:
    attributes:
      - key: service.name        # joins these host metrics to your service
        value: checkout-service
        action: upsert

exporters:
  otlphttp/epok:
    metrics_endpoint: https://ingest.getepok.dev/v1/metrics
    encoding: json                 # set this — accepted by every build (see note below)
    headers: { Authorization: "Bearer ${EPOK_API_KEY}" }

service:
  pipelines:
    metrics:
      receivers: [hostmetrics]
      processors: [resource]
      exporters: [otlphttp/epok]

On encoding: json. Set it. OTLP/JSON is accepted by every build of the metrics endpoint, so this line is the one setting that is correct everywhere. A stock collector defaults to protobuf instead — newer builds also accept that, but older ones reject it, and the rejection is quiet in a way that costs you an afternoon: traces and logs keep flowing while metrics alone are dropped, so it reads as a missing-metrics problem rather than an encoding one. Keeping encoding: json in your config removes the question.

Prometheus node_exporter

node_exporter emits raw bytes and seconds, not utilization — so add two recording rules that derive the system_*_utilization series Epok's saturation detection and the investigation infra panel read. Attach a service label at scrape time.

prometheus.yml
yaml
scrape_configs:
  - job_name: node
    static_configs:
      - targets: ["node-exporter:9100"]
        labels:
          service: checkout-service     # joins this service in Epok

rule_files: [epok_recording.yml]

remote_write:
  - url: https://ingest.getepok.dev/api/v1/write
    authorization:
      type: Bearer
      credentials: YOUR_API_KEY     # paste your key literally — Prometheus does not expand env vars
epok_recording.yml — derive 0-1 utilization from raw node_exporter
yaml
groups:
  - name: epok-derived-utilization
    rules:
      - record: system_memory_utilization
        expr: 1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
      - record: system_filesystem_utilization
        expr: 1 - (node_filesystem_avail_bytes / node_filesystem_size_bytes)
      - record: system_cpu_utilization
        expr: 1 - avg by (instance, service) (rate(node_cpu_seconds_total{mode="idle"}[5m]))

Kubernetes (kube-state-metrics)

Scrape kube-state-metrics and remote_write it to Epok. This is what powers the pod-Pending, restart, and node-pressure signals that pair with Epok's log-based Kubernetes detection — pod and namespace labels carry through automatically.

prometheus.yml (KSM scrape → remote_write)
yaml
scrape_configs:
  - job_name: kube-state-metrics
    static_configs:
      - targets: ["kube-state-metrics.kube-system.svc:8080"]

remote_write:
  - url: https://ingest.getepok.dev/api/v1/write
    authorization:
      type: Bearer
      credentials: YOUR_API_KEY     # paste your key literally — Prometheus does not expand env vars

Database & middleware exporters

postgres_exporter, kafka_exporter, redis_exporter, and friends correlate directly with the service that depends on them — replication lag, consumer lag, and pool saturation become evidence in the incident. Scrape them with a service label and remote_write, exactly like node_exporter above.

Confirm it's working

  1. Wait ~1–2 minutes, then open Metrics in Epok (app.getepok.dev → Metrics) — your host series (system_cpu_utilization, memory, filesystem) appear in the metric list.
  2. Open the Service page for the serviceyou set — the infra strip shows live CPU and memory for that service.
  3. The metric detectors (saturation, anomaly, slow-drift, reporting-gap) arm automatically for those series — no rules to write. They hold fire for roughly the first 3 hours after your first metric arrives: a baseline built from minutes of startup noise generates false pages, so the gate is deliberate.

Nothing showing up? Check your collector / Prometheus logs for a non-2xx on the ingest call. 400means the body did not parse — the payload does not match the content type it was sent under (a truncated batch, a bad gzip, or an encoding the build in front of you does not decode); 403means a key arrived and was rejected — wrong, rotated, or missing the ingest scope, and the usual cause is Prometheus shipping ${EPOK_API_KEY} literally, since it does not expand env vars; 401 means no credential was found at all, so the auth block itself is missing or malformed; 429 means you are over your daily volume or rate limit; 503means metrics ingest isn't switched on for your account — a gate, not a fault, and nothing you can fix from the config.

Your first look

Wait one to two minutes after the first write, then open Infrastructure → Metrics. It opens filtered to infrastructure-origin series. Type system_into the metric filter box: if you followed the hostmetrics or recording-rule sections above, the three utilization series are there — system_cpu_utilization, system_memory_utilization, system_filesystem_utilization.

The one thing worth doing first: select system_filesystem_utilization and use Break down by to split it by host. Every label you attached is a chip. A fleet of flat lines with one climbing away from the rest is exactly the shape capacity forecasting is built to catch — and seeing it split cleanly is your proof that the service and host labels landed, which is what decides whether these metrics show up inside an investigation or sit in a corner as their own dashboard.

If the breakdown chips are missing or everything collapses into one line, the labels did not make it — go back to the service label on the scrape config or the resource processor before going further. Then open Infrastructure → Host Map for the same data as one row per host.

Next