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:
-
Sich bei Pi-hole authentifiziert (/api/auth)
-
SID & CSRF auswertet
-
/api/stats/summary abruft
-
Die JSON-Antwort in echte Prometheus-Metriken umwandelt
-
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.
