Sprache: Deutsch
IR-11 · observability
Pushkar Kumar 11 Min. Lesezeit
Datasource ${DS_PROMETHEUS} was not found: Ursachen & Fixes
Warum provisionierte Grafana-Dashboards Datasource ${DS_PROMETHEUS} was not found melden, wie jq jeden Platzhalter findet und welche vier Fixes CI überstehen.
- observability
- prometheus
Auf dieser Seite
- Warum meldet Grafana „Datasource $${DS_PROMETHEUS} was not found”?
- Woher kommt der Name DS_PROMETHEUS?
- Wie prüfst du, welche Platzhalter in der Datei stecken?
- Welche Ursache ist deine?
- Wie behebst du es für Provisioning und die API?
- Ist das Löschen von __inputs der Fix, der keiner ist?
- Wie verhinderst du, dass er in CI zurückkommt?
- Was solltest du prüfen, bevor du eine Dashboard-Datei auslieferst?
Du hast ein Dashboard aus Staging exportiert, das JSON neben deine Provisioning-Config committet und Grafana neu ausgerollt. Das Dashboard taucht im richtigen Ordner auf. Jedes Panel ist leer, und jedes trägt dieselbe rote Ecke:
Datasource ${DS_PROMETHEUS} was not foundDeine Prometheus-Datasource existiert und funktioniert in Explore. Dieser Grafana-Fehler „datasource not found”, oft auch als „DS_PROMETHEUS not found” gesucht, bedeutet nicht, dass die Datasource fehlt. Grafana gibt wörtlich den Lookup-Schlüssel aus, den es bekommen hat – und dieser Schlüssel ist ein Platzhalter, den niemand ausgefüllt hat. Wer das einmal so sieht, für den ist der Fix reine Mechanik.
TL;DR
- Der Export-Schalter zum Teilen („Export for sharing externally” in Grafana 10 bis 11) ersetzt jede Datasource durch
${DS_<NAME>}und listet sie in__inputsauf. Ausgefüllt werden sie nur vom Import-Dialog und von der Import-API.- File Provisioning,
POST /api/dashboards/dbund Terraform speichern die Datei unverändert, also landet der Platzhalter im Panel.- So findest du sie:
jq -r 'paths(type == "string" and startswith("${DS_")) | map(tostring) | join(".")' dashboard.json.- Die Fixes: ein echtes
{ type, uid }einsetzen, diese uid im Datasource-Provisioning festnageln, eine Datasource-Variable namensDS_PROMETHEUSanlegen oder mitinputsan/api/dashboards/importposten.
Warum meldet Grafana „Datasource ${DS_PROMETHEUS} was not found”?
Die Datasource-Referenz eines Panels läuft durch DatasourceSrv.get(), das die uid vor dem Lookup durch den Template-Service schickt. Der Template-Service gibt jedes ${...}, für das er keine Variable kennt, unverändert zurück. Der Schlüssel bleibt also der wörtliche String ${DS_PROMETHEUS}, keine Datasource hat diese uid oder diesen Namen, und loadDatasource() bricht mit genau der Meldung ab, die du siehst.
Hineingekommen ist der Platzhalter beim Export. Mit aktiviertem Export-Schalter schreibt Grafana Folgendes:
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"panels": [
{
"type": "timeseries",
"title": "Request rate",
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
"targets": [
{
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
"expr": "sum(rate(http_requests_total{job=\"$job\"}[5m]))"
}
]
}
],
"templating": {
"list": [
{
"type": "query",
"name": "job",
"datasource": { "type": "prometheus", "uid": "${DS_PROMETHEUS}" },
"query": "label_values(up, job)"
}
]
}
}__inputs ist eine Frage an den Importierenden: Welche deiner Datasources soll DS_PROMETHEUS ersetzen? Der Import-Dialog stellt sie. Der serverseitige Template-Evaluator ersetzt danach jedes deklarierte ${name} durch den gewählten Wert und entfernt __inputs.
Sonst fragt niemand. Grafanas eigener Quellcode-Kommentar in export_inputs.go hält fest, dass ein Template, das über Terraform, Provisioning oder einen direkten POST geschrieben wird, „is stored verbatim” und dass „DS_* inputs carry no value to substitute”. Der Wunsch, das im Provisioning zu unterstützen, grafana/grafana#10786, ist seit 2018 offen.
Wichtig: Seit Grafana 13.2.0 (#129213) werden
${VAR_*}-Konstanten-Inputs in Dashboards aufgelöst, die über Provisioning oder einen direkten POST gespeichert werden.DS_*-Inputs löst Grafana ausdrücklich nicht auf – auf 13.2.2 verhält sich dieser Fehler also genau wie vorher.
In Grafana 7.x und frühen 8.x lautete derselbe Fehler Datasource named ${DS_PROMETHEUS} was not found – die Formulierung, die die meisten Forenthreads zitieren. Zeigt eine Query-Variable auf den Platzhalter, erscheint stattdessen ein Templating-Toast, in Threads berichtet als Error updating options: Datasource named ${DS_PROMETHEUS} was not found oder Failed to upgrade legacy queries Datasource ${DS_PROMETHEUS} was not found.
Woher kommt der Name DS_PROMETHEUS?
Vom Anzeigenamen der Datasource, nicht von ihrem Typ. Der Exporter baut ihn als 'DS_' + ds.name.replace(' ', '_').toUpperCase(). Du bekommst DS_PROMETHEUS nur, weil der Standardname „Prometheus” ist. Eine Datasource namens „Thanos” wird als ${DS_THANOS} exportiert.
JavaScripts replace mit einem String-Muster ersetzt nur das erste Leerzeichen. Eine Datasource namens „Prometheus Prod EU” wird deshalb als ${DS_PROMETHEUS_PROD EU} exportiert, inklusive Leerzeichen. Referenzen auf Library Panels bekommen das Suffix -FOR-LIBRARY-PANEL, und Konstanten-Variablen werden zu VAR_<NAME>-Inputs.
Der Schalter, der all das erzeugt, ist zwischen den Versionen gewandert:
- Grafana 10 bis 11: Export for sharing externally.
- Grafana 11.2 bis 12.3 beschriften ihn zusätzlich mit Export the dashboard to use in another instance (12.1 bis 12.3 erreichen ihn über Export → Export as code).
- Grafana 12.4 und 13: Export → Export as code, Schalter Share dashboard with another instance, mit einem Classic- oder V2-Resource-Modell unter Advanced options.
Wenn du für dein eigenes Provisioning-Repo exportierst, lass den Schalter aus. Die Sharing-Doku beschreibt die Optionen pro Version.
Wie prüfst du, welche Platzhalter in der Datei stecken?
Drei Prüfungen, in dieser Reihenfolge. Erstens: Liste auf, was die Datei ausgefüllt haben möchte:
jq -c '.__inputs[] | {name, pluginId}' dashboard.json{"name":"DS_PROMETHEUS","pluginId":"prometheus"}Zweitens: Finde jeden String, der noch einen Platzhalter enthält, auch die in Query-Variablen. Lass den Filter in einfachen Anführungszeichen, damit die Shell ${DS_...} nicht expandiert:
jq -r 'paths(type == "string" and startswith("${DS_")) | map(tostring) | join(".")' dashboard.jsonpanels.0.datasource.uid
panels.0.targets.0.datasource.uid
templating.list.0.datasource.uidgrep -nF '${DS_' dashboard.json liefert dieselbe Antwort mit Zeilennummern. Drittens: Liste die uids auf, die auf der Zielinstanz wirklich existieren. GET /api/datasources gibt laut Data-Source-API-Doku pro Eintrag id, uid, orgId, name und type zurück:
curl -sS -H "Authorization: Bearer $GRAFANA_TOKEN" "$GRAFANA_URL/api/datasources" \
| jq -r '.[] | [.uid, .name, .type] | @tsv'Wenn du dir das jq sparen willst, füge das Dashboard-JSON in den Grafana Dashboard Validator ein. Für die exportierte Datei oben meldet er einen Fehler, unter der Regel unresolved-ds-input. Die Warnungen daneben betreffen nur den gekürzten Ausschnitt, dem uid, title, gridPos und schemaVersion fehlen:
[error] unresolved-ds-input @ __inputs[0]
"__inputs" declares "DS_PROMETHEUS", an import placeholder that only the Grafana import dialog fills in.Seine Grenze: Er liest nur die Datei. Ein Dashboard mit einer echt aussehenden uid wie P1809F7CD0C75ACF3 besteht die Prüfung, ob diese uid auf deiner Instanz existiert oder nicht – gleiche sie also mit /api/datasources ab.
Welche Ursache ist deine?
| Was du siehst | Wahrscheinlichste Ursache |
|---|---|
__inputs vorhanden, Datei per Provisioning oder Terraform geladen | Reine Import-Datei als provisionierte Datei verwendet |
Kein __inputs, aber ${DS_...} noch in den Panels | Platzhalter übrig, nachdem __inputs entfernt wurde |
| Fehler nennt eine echt aussehende uid, keinen Platzhalter | Uid unterscheidet sich zwischen Instanzen |
Variable DS_PROMETHEUS existiert, ist aber nicht type: datasource | Variable mit falschem Typ |
1. Eine exportierte Datei wurde provisioniert. Das Erkennungszeichen: __inputs ist vorhanden, und das Dashboard kam über den File Provider, /api/dashboards/db oder Terraform herein. Die Lösung: eine echte uid einsetzen oder die Datei über die Import-API laden. Prüfe, dass der Platzhalter-Zähler weiter unten 0 ergibt.
2. Platzhalter ohne __inputs. Das Erkennungszeichen: Jemand hat __inputs gelöscht, oder ein Chart hat das Dashboard ohne ausgeliefert. Der Import-Dialog hat dann nichts zu fragen, und der Fehler übersteht sogar einen Import über die UI. Die Lösung: ersetzen oder die Datasource-Variable anlegen. Prüfe, dass die Panels nach einem Reload rendern.
3. Die uid existiert hier nicht. Das Erkennungszeichen: Nach dem Ersetzen nennt der Fehler eine uid. Grafana generiert automatisch eine uid, wenn das Provisioning keine setzt, also sind sich Staging und Produktion uneinig. Die Lösung: die uid im Datasource-Provisioning festnageln. Prüfe, dass /api/datasources sie auflistet.
4. Eine Variable mit dem richtigen Namen, aber dem falschen Typ. Das Erkennungszeichen: templating.list enthält DS_PROMETHEUS, aber als Custom-Variable. Sie löst sich zu ihrem eigenen Wert auf, nicht zu einer Datasource-uid. Die Lösung: Mach daraus type: datasource. Prüfe, dass der CI-Guard weiter unten für die Datei 0 ausgibt.
Steht in der Meldung ein Doppelpunkt, Datasource: ${DS_PROMETHEUS} was not found, dann konnte das Plugin nicht geladen werden. Das ist ein anderes Problem.
Wie behebst du es für Provisioning und die API?
Eine echte uid einsetzen. Dieses walk ersetzt jedes Platzhalter-Objekt, auch das der Query-Variable, und entfernt die Schlüssel, die nur für den Import gedacht sind:
jq --arg uid P1809F7CD0C75ACF3 \
'walk(if type == "object" and .uid == "${DS_PROMETHEUS}"
then .uid = $uid else . end)
| del(.__inputs, .__requires)' \
dashboard.json > dashboard.provisioned.jsonProbier den Filter mit deiner eigenen Datei im jq Playground aus, der echtes jq ausführt.
Die uid festnageln, damit sie überall passt. Setz uid im Datasource-Provisioning, dann stimmt der eingesetzte Wert auf jeder Instanz:
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
uid: prometheus-main
access: proxy
url: http://prometheus:9090Der Dashboard-Provider braucht nichts Besonderes – du solltest nur wissen, dass er __inputs nie liest:
apiVersion: 1
providers:
- name: platform
orgId: 1
folder: Platform
type: file
disableDeletion: true
updateIntervalSeconds: 30
allowUiUpdates: false
options:
path: /var/lib/grafana/dashboardsBeide Formate stehen in der Provisioning-Doku.
Den Platzhalter behalten und definieren. Eine Variable vom Typ Datasource, die exakt wie der Platzhalter heißt, löst ${DS_PROMETHEUS} zu einer echten uid auf. Ihre query ist die Plugin-ID:
{
"templating": {
"list": [
{ "type": "datasource", "name": "DS_PROMETHEUS", "query": "prometheus" }
]
}
}Die Import-API verwenden. POST /api/dashboards/import ist der Endpunkt, an den der Import-Dialog postet. Er braucht dashboards:create und fehlt in der aktuellen HTTP-API-Doku, seine Form stammt also aus dem Quellcode. Ersetze <folder-uid> durch die echte uid des Zielordners: Der Provider oben legt den Ordner über seinen Titel an, seine uid ist also nicht zwingend platform.
jq -n --slurpfile dash dashboard.json \
--arg uid prometheus-main --arg folder '<folder-uid>' \
'{dashboard: $dash[0], overwrite: true, folderUid: $folder,
inputs: [{name: "DS_PROMETHEUS", type: "datasource",
pluginId: "prometheus", value: $uid}]}' \
> payload.json
curl -sS -X POST \
-H "Authorization: Bearer $GRAFANA_TOKEN" \
-H "Content-Type: application/json" \
--data @payload.json "$GRAFANA_URL/api/dashboards/import"Lässt du einen Input weg, schlägt der Aufruf mit HTTP 400 fehl, die Meldung steckt in einem JSON-Body:
{"message":"dashboard import failed: missing dashboard input variable DS_PROMETHEUS"}Ist das Löschen von __inputs der Fix, der keiner ist?
Ja. Wer __inputs löscht, lässt die Datei wie ein normales Dashboard aussehen, aber jedes ${DS_PROMETHEUS} bleibt stehen. Der Fehler wandert von „falsch provisioniert” zu Ursache 2, und jetzt kann ihn nicht einmal mehr der Import-Dialog reparieren, weil der Evaluator nur Namen ersetzt, die in __inputs deklariert sind.
Der zweite Schein-Fix: den Platzhalter durch den Namen der Datasource ersetzen, "Prometheus". Grafana sucht einen Schlüssel erst über die uid und dann über den Namen, also funktioniert das – bis jemand die Datasource umbenennt oder eine andere Instanz sie anders nennt. Genau deshalb markiert der Validator die reine String-Form als datasource-by-name.
Achtung: Die Datasource so umzubenennen, dass sie zum Platzhalter passt, ändert nichts. Das Panel sucht eine Datasource, deren uid oder Name wörtlich
${DS_PROMETHEUS}ist.
Wie verhinderst du, dass er in CI zurückkommt?
Lass die Pipeline scheitern, wenn ein committetes Dashboard noch einen Platzhalter enthält, den keine Datasource-Variable definiert:
status=0
for f in dashboards/*.json; do
n=$(jq '[.templating.list[]? | select(.type == "datasource") | .name] as $vars
| [.. | strings | select(startswith("${DS_"))
| ltrimstr("${") | rtrimstr("}")
| select(IN($vars[]) | not)] | length' "$f")
if [ "$n" -ne 0 ]; then echo "$f: $n unresolved placeholder(s)"; status=1; fi
done
exit "$status"Für die exportierte Datei gibt er bei diesem Dashboard 3 aus. Nach dem Ersetzen per walk oder mit der DS_PROMETHEUS-Datasource-Variable an Ort und Stelle gibt er 0 aus.
Geh pro Datei in einer Schleife durch, statt jq -e einen Glob zu übergeben – jq -e setzt seinen Exit-Code nur anhand der letzten Ausgabe. Führe den Guard im selben Job aus, der die Dashboards in den Provisioning-Pfad kopiert, damit eine fehlerhafte Datei Grafana nie erreicht.
Kombiniere ihn mit der uid-Prüfung, denn auch eine saubere Datei kann eine uid nennen, die einer Umgebung fehlt. Dieselbe Disziplin gilt für die Abfragen darin: PromQL lesen erklärt, was die Panels eigentlich fragen, und LogQL vs. PromQL hilft, wenn ein Loki-Panel daneben sitzt.
Was solltest du prüfen, bevor du eine Dashboard-Datei auslieferst?
- Exportiere mit ausgeschaltetem Sharing-Schalter, wenn die Datei für dein eigenes Provisioning-Repo ist.
- Führe den
paths(...)-Filter aus und lies jede Stelle, die er ausgibt. - Liste die uids mit
GET /api/datasourcesauf jeder Zielinstanz auf. - Nagle diese uids im Datasource-Provisioning fest, damit alle Umgebungen übereinstimmen.
- Ersetze mit
walk, oder lege pro Platzhalter eine Datasource-Variable an. - Entferne
__inputsund__requireserst nach dem Ersetzen. - Nutze
/api/dashboards/importmitinputs, wenn du die teilbare Datei behalten musst. - Füge den CI-Guard hinzu und mach ihn vor dem Deploy zur Pflicht.
Bevor das nächste Dashboard in deinem Provisioning-Ordner landet, füge es in den Grafana Dashboard Validator ein und behebe, was er markiert.
Hältst du exportierte, teilbare Dashboards und provisionierte als getrennte Dateien vor, oder erzeugst du das eine in CI aus dem anderen?
Ähnliche Beiträge
-
10 Min. Lesezeit
Warum Prometheus mein Target verwarf: relabel_configs
Ein Target ist verschwunden oder ein Label ist nach dem Relabeling weg. relabel_configs vs. metric_relabel_configs debuggen, Regex-Anker und keep/drop.
-
10 Min. Lesezeit
Prometheus-Relabel-Actions: keep, labelmap, hashmod
Feldreferenz für alle 11 Prometheus-Relabel-Actions — replace, keep, drop, labelmap, labeldrop, hashmod, keepequal, lowercase — jeweils mit Beispiel.
-
5 Min. Lesezeit
Wie man eine PromQL-Abfrage liest
Eine PromQL-Abfrage liest man von innen nach außen, nicht von links nach rechts. Die vier Schichten – Selektoren, Bereiche, Funktionen, Aggregationen – erklärt.