8.2 KiB
AGENTS.md — Monitoring Folder Guide for AI Agents
Overview
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.
Folder Structure
monitoring/
├── AGENTS.md ← You are here
├── README.md ← Human-oriented deployment guide
├── DASHBOARD_CREATION.md ← How to create/update dashboards (read this!)
├── prometheus-operator.secret.yml ← Helm values for kube-prometheus-stack
├── alertmanagerconfig.secret.yaml ← Alertmanager config (Telegram alerts)
├── servicemonitor.secret.yml ← ServiceMonitor examples (commented out)
├── 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
└── scripts/ ← Discovery & helper scripts
├── discover-influxdb.sh ← Query InfluxDB schema
└── discover-home-assistant.sh ← Query Home Assistant entities
Key Infrastructure Context
Namespaces & Services
| Namespace | Service | Purpose |
|---|---|---|
monitoring |
kube-prometheus-stack | Prometheus, Alertmanager, Grafana |
influxdb |
InfluxDB2 | Time-series storage for Home Assistant sensor data |
home-assistant |
Home Assistant | Smart home hub, writes sensor data to InfluxDB |
Data Flow
Home Assistant sensors → InfluxDB (bucket: default, org: influxdata)
↓
Grafana (Flux queries via datasource)
Grafana Datasources
| Name | UID | Type | URL |
|---|---|---|---|
| InfluxDB Home Assistant | P2AB959DC95E5519F |
influxdb (Flux) | http://influxdb-influxdb2.influxdb.svc.cluster.local:80 |
| InfluxDB Home Assistant InfluxQL | P86455D1023ECC470 |
influxdb (InfluxQL) | same |
| Prometheus | prometheus |
prometheus | internal |
⚠️ IMPORTANT: The datasource UIDs above are the current values. If the Grafana deployment is recreated, these UIDs will change. Always re-discover UIDs via
kubectl -n monitoring exec deploy/prometheus-operator-grafana -c grafana -- curl -s -u "admin:<password>" http://localhost:3000/api/datasourcesbefore creating new dashboards.
Grafana Admin Credentials
- URL:
https://grafana.haumdaucher.de - User:
admin - Password: stored in
prometheus-operator.secret.ymlundergrafana.adminPassword
Home Assistant API
- URL:
https://hass.moritzgraf.de - Token: stored in
../home-assistant/tmp_long_lived_token.secret.yml
InfluxDB API
- Internal URL:
http://influxdb-influxdb2.influxdb.svc.cluster.local:80 - Org:
influxdata - Bucket:
default - Token: stored in
prometheus-operator.secret.ymlundergrafana.additionalDataSources[0].secureJsonData.token
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.
Key Design Decisions
- One file per dashboard — Each ConfigMap YAML contains the dashboard JSON embedded directly in
data. No separate JSON files (avoids duplication). - Strict GitOps — Dashboards are
editable: falsein Grafana UI. All changes must be made to the YAML files and re-applied. - Namespace-scoped — Sidecar only watches the
monitoringnamespace (searchNamespace: monitoring). - Folder annotation — Use
grafana_dashboard_folderannotation to organize dashboards in Grafana folders.
ConfigMap Template
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: nosensor.prefix!),domain(e.g.,sensor)
Flux Query Pattern (MUST follow this exactly)
Exact match on entity_id:
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/):
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)andaggregateWindow(every: v.windowPeriod, ...) - Always end with
|> yield(name: "mean") - entity_id in InfluxDB does NOT have the
sensor.prefix — usehome_wohnzimmer_temperature, NOTsensor.home_wohnzimmer_temperature - For "current value" stat panels, use
range(start: -15m)withlast()instead of aggregateWindow - Regex operator is
=~with/pattern/syntax (e.g.,=~ /^home_/matches all entities starting withhome_)
Common Pitfalls (Lessons Learned)
- Entity IDs have no
sensor.prefix in InfluxDB — HA stores domain separately. Usehome_wohnzimmer_temperature, notsensor.home_wohnzimmer_temperature. This is the #1 cause of "no data". - 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. - Datasource reference must use actual UID — Discover via
kubectl exec+ Grafana API. Current UID:P2AB959DC95E5519F. - Don't add
uidto helm datasource config — Addinguidto existing datasources inadditionalDataSourcescauses Grafana provisioning to fail with "data source not found". Let UIDs be auto-generated. - Always use bracket notation for
_measurementand_field— User["_measurement"]andr["_field"], not dot notation. Fields starting with underscore may not resolve with dot notation in Flux. - Always use Grafana time variables — Never hardcode
range(start: -24h)oraggregateWindow(every: 5m). Always usev.timeRangeStart,v.timeRangeStop,v.windowPeriod. - Always add
yield(name: "mean")— Required for Grafana to properly parse Flux results. - InfluxDB has multiple fields per measurement — Always filter
r["_field"] == "value"to get numeric data. - Sidecar container name is
grafana-sc-dashboard(singular), notgrafana-sc-dashboards. - Use Wandthermostat entities for temperature/humidity — Query HA to filter by device type. Wandthermostat entities have
wandthermostatorthermostatin their entity_id.
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