refactor(monitoring): rearchitect taupi-fan dashboard & clean up docs
Dashboard changes: - Merge 3 separate correlation graphs into one full-width panel - Left Y axis: temperatures + dewpoints (°C) - Right Y axis: humidity + mold threshold (%) - Boolean signals (fan/mold/activation) as colored fill bands - graphTooltip: 2 for shared crosshair + tooltip - Add collapsed Deep Dive row with delta graphs - Collapse BLE diagnostics row Documentation changes: - DASHBOARD_CREATION.md: remove content now covered by Grafana skills (dashboarding, grafana-oss, promql), eliminate duplicated pitfalls section, add Prometheus dashboard guidance, add Grafana unit codes - AGENTS.md: add skills prerequisite table, update folder structure (add taupi-fan.yaml), add Prometheus data flow, remove duplicated InfluxDB/Flux/pitfalls sections (canonical in DASHBOARD_CREATION.md)
This commit is contained in:
+29
-82
@@ -4,22 +4,36 @@
|
||||
|
||||
This folder manages the **kube-prometheus-stack** deployment and **Grafana dashboards as code** for the `haumdaucher` Kubernetes cluster. The monitoring stack runs in the `monitoring` namespace.
|
||||
|
||||
## Prerequisites — Grafana Skills
|
||||
|
||||
Before creating or modifying dashboards, **read these skills first** — they cover general Grafana topics (JSON schema, panel types, PromQL, provisioning) that this document does not repeat:
|
||||
|
||||
| Skill | Covers |
|
||||
|---|---|
|
||||
| **`dashboarding`** | Dashboard JSON schema, panel types, units, template variables, transformations, annotations |
|
||||
| **`grafana-oss`** | Dashboard/datasource provisioning, RBAC, plugin config, health checks |
|
||||
| **`promql`** | PromQL query writing, rate/irate, histograms, recording rules, cardinality |
|
||||
|
||||
For project-specific dashboard creation workflows (ConfigMap template, InfluxDB Flux queries, deploy/verify steps), see **[DASHBOARD_CREATION.md](DASHBOARD_CREATION.md)**.
|
||||
|
||||
## Folder Structure
|
||||
|
||||
```
|
||||
monitoring/
|
||||
├── AGENTS.md ← You are here
|
||||
├── README.md ← Human-oriented deployment guide
|
||||
├── DASHBOARD_CREATION.md ← How to create/update dashboards (read this!)
|
||||
├── DASHBOARD_CREATION.md ← Project-specific dashboard creation guide
|
||||
├── prometheus-operator.secret.yml ← Helm values for kube-prometheus-stack
|
||||
├── alertmanagerconfig.secret.yaml ← Alertmanager config (Telegram alerts)
|
||||
├── servicemonitor.secret.yml ← ServiceMonitor examples (commented out)
|
||||
├── taupi-fan.yml ← Cellar fan exporter (Service, Endpoints, ServiceMonitor, Alerts)
|
||||
├── tankerkoenig.yml ← Tankerkoenig fuel price exporter
|
||||
├── dashboards/ ← Grafana dashboards as ConfigMaps
|
||||
│ ├── grafana-dashboard-home-climate.yaml
|
||||
│ ├── grafana-dashboard-energy-monitor.yaml
|
||||
│ ├── grafana-dashboard-heating-gas.yaml
|
||||
│ └── grafana-dashboard-outdoor-weather.yaml
|
||||
│ ├── grafana-dashboard-outdoor-weather.yaml
|
||||
│ └── grafana-dashboard-taupi-fan.yaml
|
||||
└── scripts/ ← Discovery & helper scripts
|
||||
├── discover-influxdb.sh ← Query InfluxDB schema
|
||||
└── discover-home-assistant.sh ← Query Home Assistant entities
|
||||
@@ -41,6 +55,10 @@ monitoring/
|
||||
Home Assistant sensors → InfluxDB (bucket: default, org: influxdata)
|
||||
↓
|
||||
Grafana (Flux queries via datasource)
|
||||
|
||||
Custom exporters (taupi-fan, etc.) → Prometheus (scraped via ServiceMonitor)
|
||||
↓
|
||||
Grafana (PromQL queries)
|
||||
```
|
||||
|
||||
### Grafana Datasources
|
||||
@@ -73,92 +91,21 @@ Home Assistant sensors → InfluxDB (bucket: default, org: influxdata)
|
||||
|
||||
## Dashboard Architecture
|
||||
|
||||
Dashboards are managed as **Kubernetes ConfigMaps** with the label `grafana_dashboard: "1"`. The kube-prometheus-stack's **Grafana Dashboard Sidecar** (`grafana-sc-dashboard` container) watches for these ConfigMaps and auto-loads them into Grafana.
|
||||
Dashboards are managed as **Kubernetes ConfigMaps** with the label `grafana_dashboard: "1"`. The kube-prometheus-stack's **Grafana Dashboard Sidecar** (`grafana-sc-dashboard` container, singular!) watches for these ConfigMaps and auto-loads them into Grafana.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
1. **One file per dashboard** — Each ConfigMap YAML contains the dashboard JSON embedded directly in `data`. No separate JSON files (avoids duplication).
|
||||
2. **Strict GitOps** — Dashboards are `editable: false` in Grafana UI. All changes must be made to the YAML files and re-applied.
|
||||
3. **Namespace-scoped** — Sidecar only watches the `monitoring` namespace (`searchNamespace: monitoring`).
|
||||
4. **Folder annotation** — Use `grafana_dashboard_folder` annotation to organize dashboards in Grafana folders.
|
||||
1. **One file per dashboard** — Each ConfigMap YAML contains the dashboard JSON embedded directly in `data`.
|
||||
2. **Strict GitOps** — Dashboards are `editable: false` in Grafana UI. All changes go through YAML files.
|
||||
3. **Namespace-scoped** — Sidecar only watches `monitoring` namespace (`searchNamespace: monitoring`).
|
||||
4. **Folder annotation** — Use `grafana_dashboard_folder` annotation to organize dashboards.
|
||||
5. **No `"dashboard"` wrapper** — File provisioning requires top-level JSON properties. The `{"dashboard": {...}}` wrapper is only for the HTTP API.
|
||||
6. **Never add `uid` to helm datasource config** — Let Grafana auto-generate UIDs. Adding `uid` to `additionalDataSources` crashes provisioning.
|
||||
|
||||
### ConfigMap Template
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-<name>
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
annotations:
|
||||
grafana_dashboard_folder: "🏠 Home"
|
||||
data:
|
||||
<name>.json: |
|
||||
{
|
||||
"uid": "<unique-uid>",
|
||||
"title": "<Dashboard Title>",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## InfluxDB Data Schema
|
||||
|
||||
Home Assistant writes sensor data to InfluxDB with this structure:
|
||||
|
||||
- **Measurement name** = unit of measurement (e.g., `°C`, `%`, `W`, `kWh`, `m³`, `hPa`, `km/h`, `mm/h`)
|
||||
- **Fields**: `value` (numeric), plus metadata fields (`friendly_name_str`, `device_class_str`, `id_str`, etc.)
|
||||
- **Tags**: `entity_id` (e.g., `home_wohnzimmer_temperature` — **NOTE: no `sensor.` prefix!**), `domain` (e.g., `sensor`)
|
||||
|
||||
### Flux Query Pattern (MUST follow this exactly)
|
||||
|
||||
**Exact match on entity_id:**
|
||||
```flux
|
||||
from(bucket: "default")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "°C")
|
||||
|> filter(fn: (r) => r["_field"] == "value")
|
||||
|> filter(fn: (r) => r.entity_id == "home_wandthermostat_bad_temperature")
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
**Regex match on entity_id** (use `=~` operator with `/pattern/`):
|
||||
```flux
|
||||
from(bucket: "default")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "°C")
|
||||
|> filter(fn: (r) => r["_field"] == "value")
|
||||
|> filter(fn: (r) => r.entity_id =~ /^home_/)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
**Critical rules:**
|
||||
- Always use bracket notation: `r["_measurement"]`, `r["_field"]` (fields starting with `_`)
|
||||
- Always use dynamic time: `range(start: v.timeRangeStart, stop: v.timeRangeStop)` and `aggregateWindow(every: v.windowPeriod, ...)`
|
||||
- Always end with `|> yield(name: "mean")`
|
||||
- **entity_id in InfluxDB does NOT have the `sensor.` prefix** — use `home_wohnzimmer_temperature`, NOT `sensor.home_wohnzimmer_temperature`
|
||||
- For "current value" stat panels, use `range(start: -15m)` with `last()` instead of aggregateWindow
|
||||
- Regex operator is `=~` with `/pattern/` syntax (e.g., `=~ /^home_/` matches all entities starting with `home_`)
|
||||
|
||||
## Common Pitfalls (Lessons Learned)
|
||||
|
||||
1. **Entity IDs have no `sensor.` prefix in InfluxDB** — HA stores domain separately. Use `home_wohnzimmer_temperature`, not `sensor.home_wohnzimmer_temperature`. This is the #1 cause of "no data".
|
||||
2. **Dashboard JSON must NOT be wrapped in `"dashboard"` key** — Grafana file provisioning expects top-level properties (`uid`, `title`, `panels`, etc.). The `"dashboard"` wrapper is only used in the HTTP API.
|
||||
3. **Datasource reference must use actual UID** — Discover via `kubectl exec` + Grafana API. Current UID: `P2AB959DC95E5519F`.
|
||||
4. **Don't add `uid` to helm datasource config** — Adding `uid` to existing datasources in `additionalDataSources` causes Grafana provisioning to fail with "data source not found". Let UIDs be auto-generated.
|
||||
5. **Always use bracket notation for `_measurement` and `_field`** — Use `r["_measurement"]` and `r["_field"]`, not dot notation. Fields starting with underscore may not resolve with dot notation in Flux.
|
||||
6. **Always use Grafana time variables** — Never hardcode `range(start: -24h)` or `aggregateWindow(every: 5m)`. Always use `v.timeRangeStart`, `v.timeRangeStop`, `v.windowPeriod`.
|
||||
7. **Always add `yield(name: "mean")`** — Required for Grafana to properly parse Flux results.
|
||||
8. **InfluxDB has multiple fields per measurement** — Always filter `r["_field"] == "value"` to get numeric data.
|
||||
9. **Sidecar container name is `grafana-sc-dashboard`** (singular), not `grafana-sc-dashboards`.
|
||||
10. **Use Wandthermostat entities for temperature/humidity** — Query HA to filter by device type. Wandthermostat entities have `wandthermostat` or `thermostat` in their entity_id.
|
||||
For the full creation workflow, Flux query templates, and InfluxDB conventions, see **[DASHBOARD_CREATION.md](DASHBOARD_CREATION.md)**.
|
||||
|
||||
## Related Files Outside This Folder
|
||||
|
||||
- `../home-assistant/home-assistant.secret.yaml` — HA helm values (InfluxDB integration config)
|
||||
- `../home-assistant/tmp_long_lived_token.secret.yml` — HA API token for discovery
|
||||
- `../influxdb/influxdb2.secret.yml` — InfluxDB helm values
|
||||
- `../../kochbuch/plans/grafana-dashboards-as-code.md` — Original architecture plan
|
||||
- `../influxdb/influxdb2.secret.yml` — InfluxDB helm values
|
||||
Reference in New Issue
Block a user