Declarative Grafana / Prometheus / Loki / Tempo observability stack

Deploys the whole stack into the observability namespace of the local
cluster with Kustomize alone — no helm install, no dashboard created by
hand in the UI. The Makefile carries every command: deploy, dashboards,
diagnostics, PVC flush, teardown.

Dashboards are files, never UI objects. Each carries a stable uid, which
is what keeps URLs, cross-links and alerts alive across redeployments, and
references datasources by stable uid rather than by name. The dashboard
ConfigMap is generated without a name-suffix hash so applying it updates
it in place and Grafana reloads without a restart, while the provisioning
ConfigMaps keep their hash precisely so that a change there does force one.

Prometheus scrapes the kubelet /metrics/resource endpoint rather than
/metrics/cadvisor: on this cluster cadvisor exposes machine_* series only
and no container_* at all, which would leave every CPU and memory panel
empty.

Alloy ships every pod's logs to Loki through the Kubernetes API, so no
/var/log mount is needed. Tempo's metrics_generator feeds span metrics and
the service graph back into Prometheus, and the datasources are wired so a
log line's trace_id opens the matching trace.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Stéphane Tailland
2026-08-25 07:27:38 +02:00
commit 3937b4f366
42 changed files with 7597 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
# Grafana dashboards -- declarative provisioning.
#
# Rules:
# * one .json file per dashboard, named after its UID;
# * every JSON carries a STABLE "uid" field (never regenerated): that is what
# keeps URLs, cross-links and alerts alive across redeployments;
# * `disableNameSuffixHash: true` -> the ConfigMap always keeps the same name,
# so `kubectl apply` updates it in place and Grafana reloads WITHOUT
# restarting the pod, in about 70s (see dashboard-providers.yaml).
#
# Adding a dashboard = drop the .json here and add it to `files:` below.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: observability
configMapGenerator:
- name: grafana-dashboards
files:
# >>> dashboards (generated by `make dashboards-sync`) >>>
- obs-classic-models-api.json
- obs-keycloak.json
- obs-librechat.json
- obs-stack-health.json
# <<< dashboards <<<
generatorOptions:
disableNameSuffixHash: true
labels:
app.kubernetes.io/name: grafana
app.kubernetes.io/component: dashboards
app.kubernetes.io/part-of: observability
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,437 @@
{
"uid": "obs-stack-health",
"title": "Observability — Stack Health",
"description": "Health of the Prometheus / Loki / Tempo / Alloy stack. Reference dashboard: stable UID, declaratively provisioned.",
"tags": [
"observability",
"stack",
"provisioned"
],
"editable": false,
"schemaVersion": 39,
"version": 1,
"refresh": "30s",
"time": {
"from": "now-1h",
"to": "now"
},
"timezone": "browser",
"templating": {
"list": [
{
"name": "namespace",
"label": "Namespace",
"type": "query",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"query": "label_values(kube_pod_info, namespace)",
"refresh": 2,
"includeAll": true,
"multi": true,
"current": {
"text": "observability",
"value": "observability"
}
}
]
},
"panels": [
{
"id": 1,
"type": "row",
"title": "Collection",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 0
},
"collapsed": false,
"panels": []
},
{
"id": 2,
"type": "stat",
"title": "Prometheus targets UP",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 5,
"w": 6,
"x": 0,
"y": 1
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum(up)",
"legendFormat": "up"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "green",
"value": 1
}
]
}
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "area",
"textMode": "auto"
}
},
{
"id": 3,
"type": "stat",
"title": "Prometheus targets DOWN",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 5,
"w": 6,
"x": 6,
"y": 1
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum(up == bool 0)",
"legendFormat": "down"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"color": {
"mode": "thresholds"
},
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
},
{
"color": "red",
"value": 1
}
]
}
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "none",
"textMode": "auto"
}
},
{
"id": 4,
"type": "stat",
"title": "Log lines ingested (/s)",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 5,
"w": 6,
"x": 12,
"y": 1
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum(rate(loki_distributor_lines_received_total[5m]))",
"legendFormat": "lines/s"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"decimals": 1,
"color": {
"mode": "fixed",
"fixedColor": "blue"
}
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "area",
"textMode": "auto"
}
},
{
"id": 5,
"type": "stat",
"title": "Spans ingested (/s)",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 5,
"w": 6,
"x": 18,
"y": 1
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum(rate(tempo_distributor_spans_received_total[5m]))",
"legendFormat": "spans/s"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"decimals": 1,
"color": {
"mode": "fixed",
"fixedColor": "purple"
}
},
"overrides": []
},
"options": {
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"colorMode": "value",
"graphMode": "area",
"textMode": "auto"
}
},
{
"id": 6,
"type": "row",
"title": "Stack resources",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 6
},
"collapsed": false,
"panels": []
},
{
"id": 7,
"type": "timeseries",
"title": "CPU per component (observability namespace)",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 7
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum by (pod) (rate(container_cpu_usage_seconds_total{namespace=\"observability\", container!=\"\"}[$__rate_interval]))",
"legendFormat": "{{pod}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "short",
"custom": {
"drawStyle": "line",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": [
"mean",
"max"
]
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"id": 8,
"type": "timeseries",
"title": "Memory per component (observability namespace)",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 7
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "prometheus",
"uid": "prometheus"
},
"expr": "sum by (pod) (container_memory_working_set_bytes{namespace=\"observability\", container!=\"\"})",
"legendFormat": "{{pod}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "bytes",
"custom": {
"drawStyle": "line",
"lineWidth": 1,
"fillOpacity": 10,
"showPoints": "never"
}
},
"overrides": []
},
"options": {
"legend": {
"displayMode": "table",
"placement": "bottom",
"calcs": [
"mean",
"max"
]
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
}
},
{
"id": 9,
"type": "row",
"title": "Logs",
"gridPos": {
"h": 1,
"w": 24,
"x": 0,
"y": 15
},
"collapsed": false,
"panels": []
},
{
"id": 10,
"type": "logs",
"title": "Recent stack errors",
"datasource": {
"type": "loki",
"uid": "loki"
},
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 16
},
"targets": [
{
"refId": "A",
"datasource": {
"type": "loki",
"uid": "loki"
},
"expr": "{namespace=\"observability\"} |~ \"(?i)(error|level=error|fatal)\"",
"queryType": "range"
}
],
"options": {
"showTime": true,
"showLabels": false,
"wrapLogMessage": true,
"sortOrder": "Descending",
"dedupStrategy": "none",
"enableLogDetails": true
}
}
]
}