Skip to content

Tautulli exporter

Purpose

kubernetes/apps/observability/tautulli-exporter scrapes Tautulli's stats (active streams, transcodes, LAN/WAN bandwidth) as Prometheus plex_* gauges and ships a hand-built Grafana dashboard for them. The exporter's upstream project, mm503/tautulli-exporter on GitHub, ships no dashboard of its own, and every public "Tautulli" dashboard targets a different exporter's metric schema, so importing one renders blank against this exporter's series. The deployed image, docker.io/mm404/tautulli-exporter, is that project's published build.

Metric namespace collision with the Plex exporter

This exporter and the cluster's Plex exporter (the "PPE Plex exporter") both emit metrics under the plex_* prefix, from unrelated schemas. servicemonitor.yaml relabels job and instance on scrape so Prometheus and the Grafana dashboard can tell the two series sets apart; the dashboard's PromQL is pinned to job="tautulli-exporter" for the same reason.

Alert design: a v1.0.0 breaking change

Before the exporter's v1.0.0 Go rewrite, TautulliUnreachable used absent(plex_active_streams_total{job="tautulli-exporter"}), since the active-streams gauge disappeared whenever the exporter lost its connection to the Tautulli API.

v1.0.0 changed that behavior: on a failed scrape, only plex_up and plex_scrape_failures_total still move. The stream and bandwidth gauges freeze at their last value instead of disappearing, so absent() never fires again. The alert now reads plex_up{job="tautulli-exporter"} == 0 instead, which the rewrite's /ready endpoint change also made necessary: /ready stopped returning a 503 when Tautulli is unreachable, which is what an earlier pod-readiness alert had relied on.

Secrets and API scope

The ExternalSecret reuses the existing tautulli 1Password item rather than creating one for this exporter. A read-only Tautulli API key is enough, because the exporter only calls the get_activity endpoint. TAUTULLI_URL points at http://tautulli.media.svc.cluster.local without an /api/v2 suffix; the exporter appends that path itself.

Dashboard macro protection

The dashboard JSON (app/dashboards/tautulli.json) uses Grafana's ${DS_PROMETHEUS} template macro to bind its datasource. Flux postBuild substitution treats any undefined ${VAR} as empty, which would blank that macro, so app/kustomization.yaml's configMapGenerator carries kustomize.toolkit.fluxcd.io/substitute: disabled to keep it intact. The GrafanaDashboard resource maps that macro to the cluster's prometheus datasource through spec.datasources.