PiHole-Data in grafana importieren / anzeigen

Ziel – Doku  [ Pi-Hole (100er) ]

Ein Pi-hole v6 soll ohne Docker, ohne Go-Exporter, ohne Fremdcontainer direkt in Prometheus und Grafana sichtbar werden – stabil, updatefest und auf ARMv6 lauffähig.

Ergebnis:

Pi-hole → node_exporter (Textfile Collector) → Prometheus → Grafana

Hintergrund: Es laufen 2 PiHoles im Netzwerk, damit bei einem Ausfall die Redundanz gegeben ist und weiterhin auf das Internet zugegriffen werden kann.

1. Prinzip

Pi-hole v6 stellt keine Prometheus-Metriken bereit und verwendet eine session-basierte API (SID + CSRF).

Statt eines klassischen Exporters wird der node_exporter Textfile Collector genutzt:

 Pi-hole API → Bash-Script → *.prom Datei → node_exporter → Prometheus → Grafana

2. API-Ermittlung

Erfolgreicher API-Endpoint für Pi-hole v6:

 /api/stats/summary

Dieser liefert alle relevanten Query-Statistiken.

3. Export-Mechanismus

Ein Bash-Script wurde implementiert, das:

  1. Sich bei Pi-hole authentifiziert (/api/auth)

  2. SID & CSRF auswertet

  3. /api/stats/summary abruft

  4. Die JSON-Antwort in echte Prometheus-Metriken umwandelt

  5. Die Daten atomar nach: /var/lib/node_exporter/textfile_collector/pihole.prom schreibt.

4. Exportierte Metriken (Auszug)

Metrik

Bedeutung

pihole_dns_queries_total

Gesamtanzahl DNS-Anfragen

pihole_dns_queries_blocked

Blockierte Anfragen

pihole_dns_percent_blocked

Blockquote

pihole_unique_domains

Anzahl angefragter Domains

pihole_queries_forwarded

Weitergeleitete Anfragen

pihole_queries_cached

Cache-Treffer

pihole_clients_active

Aktive Clients

pihole_query_type_total{type=…}

DNS-Typ-Verteilung

pihole_query_status_total{status=…}

Blockursachen

pihole_reply_type_total{reply=…}

Antworttypen

Alle Werte sind Prometheus-konform (keine JSON-Blöcke, keine Strings).

5. Automatisierung

systemd-Timer erzeugt die Metriken jede Minute:

pihole-textfile-exporter.service
pihole-textfile-exporter.timer

Damit bleiben die Werte dauerhaft aktuell.

6. Grafana

Ein Dashboard wurde erstellt mit:

  • Umschaltbarer $pihole-Variable (mehrere Pi-holes)

  • Live-Panels:

    • Gesamt-Queries

    • Blockierte Queries

    • Blockquote

    • Unique Domains

    • Cached vs Forwarded

    • DNS-Typen

    • Blockursachen

    • Antworttypen

Ergebnis

Pi-hole v6 ist jetzt:

• exporterlos

• containerfrei

• ARMv6-kompatibel

• upgradefest

• vollständig Prometheus-/Grafana-fähig

Damit bleiben die Werte dauerhaft aktuell.

Nun das 200er Pi-Hole (aarch64):

Schritt 1 – node_exporter prüfen (falls noch nicht da)

Auf 192.168.2.200:

sudo apt install -y prometheus-node-exporter
sudo systemctl enable --now prometheus-node-exporter

Prüfen:

curl http://127.0.0.1:9100/metrics | head

Schritt 2 – Export-Verzeichnis anlegen

sudo install -d -m 0755 /var/lib/node_exporter/textfile_collector

Schritt 3 – Passwort-File

ssudo install -d -m 0750 /etc/pihole-exporter
sudo bash -c 'umask 077; read -rsp "Pi-hole Web Passwort: " PW; echo; printf "%s" "$PW" > /etc/pihole-exporter/password'
sudo chmod 0600 /etc/pihole-exporter/password

Schritt 4 – Exporter-Script installieren

Auf dem zweiten Pi exakt dieses Script (identisch zum ersten):

sudo tee /usr/local/bin/pihole_textfile_exporter.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

HOST_HEADER="pi.hole"
BASE_URL="http://127.0.0.1"
PASS_FILE="/etc/pihole-exporter/password"

OUT_DIR="/var/lib/node_exporter/textfile_collector"
OUT_FILE="${OUT_DIR}/pihole.prom"

PW="$(cat "$PASS_FILE" | tr -d '\r\n')"
tmp="$(mktemp)"
trap 'rm -f "$tmp"' EXIT

declare -A _declared

declare_metric() {
local name="$1" help="$2"
if [[ -z "${_declared[$name]+x}" ]]; then
echo "# HELP ${name} ${help}" >>"$tmp"
echo "# TYPE ${name} gauge" >>"$tmp"
_declared[$name]=1
fi
}

emit_gauge() {
local name="$1" help="$2" value="$3"
declare_metric "$name" "$help"
echo "${name} ${value}" >>"$tmp"
}

emit_gauge_labeled() {
local name="$1" help="$2" lkey="$3" lval="$4" value="$5"
declare_metric "$name" "$help"
lval="${lval//\\/\\\\}"
lval="${lval//\"/\\\"}"
echo "${name}{${lkey}=\"${lval}\"} ${value}" >>"$tmp"
}

# Auth
auth_json="$(curl -fsS -H "Host: ${HOST_HEADER}" -H "Content-Type: application/json" \
-X POST "${BASE_URL}/api/auth" \
--data "{\"password\":\"${PW}\"}")"

sid="$(echo "$auth_json" | jq -r '.session.sid')"
csrf="$(echo "$auth_json" | jq -r '.session.csrf')"

# Summary
summary="$(curl -fsS -H "Host: ${HOST_HEADER}" \
-H "X-CSRF-Token: ${csrf}" \
-H "Origin: http://pi.hole" \
-H "Referer: http://pi.hole/" \
-b "sid=${sid}" \
"${BASE_URL}/api/stats/summary")"

# Metrics
emit_gauge "pihole_dns_queries_total" "Total DNS queries" "$(echo "$summary" | jq -r '.queries.total')"
emit_gauge "pihole_dns_queries_blocked" "Blocked DNS queries" "$(echo "$summary" | jq -r '.queries.blocked')"
emit_gauge "pihole_dns_percent_blocked" "Blocked percent" "$(echo "$summary" | jq -r '.queries.percent_blocked')"
emit_gauge "pihole_unique_domains" "Unique domains" "$(echo "$summary" | jq -r '.queries.unique_domains')"
emit_gauge "pihole_queries_forwarded" "Forwarded queries" "$(echo "$summary" | jq -r '.queries.forwarded')"
emit_gauge "pihole_queries_cached" "Cached queries" "$(echo "$summary" | jq -r '.queries.cached')"
emit_gauge "pihole_query_frequency" "Query frequency" "$(echo "$summary" | jq -r '.queries.frequency')"
emit_gauge "pihole_clients_active" "Active clients" "$(echo "$summary" | jq -r '.clients.active')"
emit_gauge "pihole_clients_total" "Total clients" "$(echo "$summary" | jq -r '.clients.total')"

echo "$summary" | jq -r '.queries.types | to_entries[] | "\(.key)\t\(.value)"' |
while IFS=$'\t' read k v; do
emit_gauge_labeled "pihole_query_type_total" "Query types" "type" "$k" "$v"
done

echo "$summary" | jq -r '.queries.status | to_entries[] | "\(.key)\t\(.value)"' |
while IFS=$'\t' read k v; do
emit_gauge_labeled "pihole_query_status_total" "Query status" "status" "$k" "$v"
done

echo "$summary" | jq -r '.queries.replies | to_entries[] | "\(.key)\t\(.value)"' |
while IFS=$'\t' read k v; do
emit_gauge_labeled "pihole_reply_type_total" "Reply types" "reply" "$k" "$v"
done

install -m 0644 "$tmp" "$OUT_FILE"
EOF

sudo chmod +x /usr/local/bin/pihole_textfile_exporter.sh

Schritt 5 – Test

sudo /usr/local/bin/pihole_textfile_exporter.sh
curl http://127.0.0.1:9100/metrics | grep pihole_ | head

Schritt 6 – systemd Timer

sudo tee /etc/systemd/system/pihole-textfile-exporter.service >/dev/null <<'EOF'
[Unit]
Description=Pi-hole textfile exporter
After=network-online.target pihole-FTL.service

[Service]
Type=oneshot
ExecStart=/usr/local/bin/pihole_textfile_exporter.sh
EOF

sudo tee /etc/systemd/system/pihole-textfile-exporter.timer >/dev/null <<'EOF'
[Timer]
OnBootSec=30
OnUnitActiveSec=60

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now pihole-textfile-exporter.timer

Danach taucht der zweite Pi automatisch im gleichen Grafana-Dashboard auf.

Grafana läuft auf einem MacMini 2019 in einem Docker Container, inkl. Prometheus.

Keine weitere Grafana-Arbeit nötig. Du hast jetzt Dual-Pi-hole-Telemetrie auf Enterprise-Niveau.

Mit voller Einsicht in das tatsächliche DNS-Verhalten des Netzwerks.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert