I have added a lot of changes from various ai sessions
This commit is contained in:
parent
68345648d9
commit
15e802bebc
|
|
@ -122,6 +122,9 @@ kube_proxy_mode: iptables
|
|||
# Moritz: changed to iptables for tegra
|
||||
#kube_proxy_mode: ipvs
|
||||
|
||||
# Expose kube-proxy metrics on all interfaces so Prometheus can scrape port 10249
|
||||
kube_proxy_metrics_bind_address: "0.0.0.0:10249"
|
||||
|
||||
# configure arp_ignore and arp_announce to avoid answering ARP queries from kube-ipvs0 interface
|
||||
# must be set to true for MetalLB to work
|
||||
kube_proxy_strict_arp: false
|
||||
|
|
|
|||
|
|
@ -121,19 +121,24 @@ Backup tool. See chart [README](https://github.com/vmware-tanzu/helm-charts/blob
|
|||
```sh
|
||||
helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts
|
||||
helm repo update
|
||||
helm upgrade --install --create-namespace --namespace velero -f ./velero/velero.secret.yaml velero vmware-tanzu/velero --version 5.3.0
|
||||
kubectl create secret generic rclone-config --from-file=./velero/rclone.secret
|
||||
kubectl apply -f velero/dropbox_sync.yml
|
||||
# #
|
||||
# Apply CRDs first (required on upgrades across major versions)
|
||||
helm show crds vmware-tanzu/velero --version 12.1.0 | kubectl apply -f -
|
||||
helm upgrade --install --create-namespace --namespace velero -f ./velero/velero.secret.yaml velero vmware-tanzu/velero --version 12.1.0
|
||||
# Secrets for rclone (minio + gdrive)
|
||||
kubectl create secret generic rclone-config --from-file=./velero/rclone.secret -n velero
|
||||
kubectl create secret generic gdrive-sa-key --from-file=gdrive_sa.json.secret=./velero/gdrive_sa.json.secret -n velero
|
||||
# Deploy GDrive sync cronjob
|
||||
kubectl apply -f velero/gdrive_sync.yml
|
||||
# Teardown
|
||||
helm delete velero -n velero
|
||||
kubectl delete ns velero
|
||||
```
|
||||
|
||||
A manual backup may be created executing the following command. **Note: Keep backuped namespaces in sync with config from helm chart!!!**
|
||||
A manual backup may be created executing the following command. **Note: Keep backed-up namespaces in sync with config from helm chart!!!**
|
||||
|
||||
```sh
|
||||
DATE=$( date +%Y%m%d )
|
||||
velero backup create $DATE --include-namespaces datalab,development,nextcloud,tt-rss,mailu --wait
|
||||
velero backup create $DATE --include-namespaces coder,home-assistant,n8n,influxdb --wait
|
||||
```
|
||||
|
||||
## coder
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ spec:
|
|||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
ingressClassName: traefik
|
||||
|
|
|
|||
|
|
@ -18,4 +18,4 @@ spec:
|
|||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
ingressClassName: traefik
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -0,0 +1,2 @@
|
|||
# this token has been generated to enable AI to access HASS
|
||||
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2NDMyZTI1MDIzZjU0ZmFiYTU3OTc0ODYyMzg3M2ExZSIsImlhdCI6MTc4MjczMTY4NiwiZXhwIjoyMDk4MDkxNjg2fQ.u8kFHoYL2kdhVenfe3BiWcNkN_en3yn91MRGSVJvbmw
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
# kind: ConfigMap
|
||||
# metadata:
|
||||
# creationTimestamp: null
|
||||
# name: rclone-dropbox-config
|
||||
# namespace: mailu
|
||||
# apiVersion: v1
|
||||
# data:
|
||||
# rclone.conf: |
|
||||
# [dropbox]
|
||||
# type = dropbox
|
||||
# token = {"access_token":"17hvEArIh3cAAAAAAAGTXUfwkGdz8EC_rDcEseLmSRltQoUz_AVceuUewx0sGMQ1","token_type":"bearer","expiry":"0001-01-01T00:00:00Z"}
|
||||
# ---
|
||||
# apiVersion: batch/v1beta1
|
||||
# kind: CronJob
|
||||
# metadata:
|
||||
# name: backup-mailu
|
||||
# namespace: mailu
|
||||
# spec:
|
||||
# schedule: "30 4 * * *"
|
||||
# jobTemplate:
|
||||
# spec:
|
||||
# template:
|
||||
# spec:
|
||||
# containers:
|
||||
# - name: backup-mailu
|
||||
# image: iptizer/swiss
|
||||
# volumeMounts:
|
||||
# - name: rclone-dropbox-config
|
||||
# mountPath: /rclone.conf
|
||||
# subPath: "rclone.conf"
|
||||
# - mountPath: /data/
|
||||
# name: data
|
||||
# command:
|
||||
# - "/bin/bash"
|
||||
# - "-c"
|
||||
# args:
|
||||
# - "cd / && tar zcvf - /data/ | /usr/bin/rclone --config /rclone.conf -v rcat dropbox:server_backup/$( /bin/expr $( date +%m ) % 3 )_$( /bin/expr $( date +%j ) % 3 )_mailu.tar.gz"
|
||||
# volumes:
|
||||
# - name: rclone-dropbox-config
|
||||
# configMap:
|
||||
# name: rclone-dropbox-config
|
||||
# - name: data
|
||||
# persistentVolumeClaim:
|
||||
# claimName: mailu-storage
|
||||
# restartPolicy: Never
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
# DO NOT USE MASTER!!!!
|
||||
#mailuVersion: master
|
||||
# DO NOT USE MASTER!!!
|
||||
|
||||
hostnames:
|
||||
- mail.moritzgraf.de
|
||||
domain: moritzgraf.de
|
||||
secretKey: fa5faeD9aegietaesahbiequ5Pe9au
|
||||
subnet: 10.233.0.0/16
|
||||
|
||||
ingress:
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
|
||||
clamav:
|
||||
enabled: false
|
||||
front:
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 200Mi
|
||||
cpu: 200m
|
||||
admin:
|
||||
resources:
|
||||
requests:
|
||||
memory: 20Mi
|
||||
cpu: 50m
|
||||
limits:
|
||||
memory: 500Mi
|
||||
cpu: 500m
|
||||
|
||||
dovecot:
|
||||
resources:
|
||||
requests:
|
||||
memory: 50Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 500Mi
|
||||
cpu: 500m
|
||||
postfix:
|
||||
resources:
|
||||
requests:
|
||||
memory: 1Gi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 2Gi
|
||||
cpu: 500m
|
||||
|
||||
redis:
|
||||
resources:
|
||||
requests:
|
||||
memory: 20Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 300Mi
|
||||
cpu: 200m
|
||||
roundcube:
|
||||
enabled: true
|
||||
logLevel: INFO
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 200Mi
|
||||
cpu: 200m
|
||||
rspamd:
|
||||
resources:
|
||||
requests:
|
||||
memory: 10Mi
|
||||
cpu: 10m
|
||||
limits:
|
||||
memory: 2000Mi
|
||||
cpu: 600m
|
||||
certmanager:
|
||||
issuerType: ClusterIssuer
|
||||
issuerName: "letsencrypt-prod"
|
||||
apiVersion: cert-manager.io/v1
|
||||
persistence:
|
||||
storageClass: openebs-hostpath
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: restore
|
||||
name: restore
|
||||
spec:
|
||||
containers:
|
||||
- image: iptizer/swiss
|
||||
name: restore
|
||||
resources: {}
|
||||
volumeMounts:
|
||||
- mountPath: /data/
|
||||
name: data
|
||||
command:
|
||||
- "sleep"
|
||||
- "3600"
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Never
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: mailu-storage
|
||||
status: {}
|
||||
Binary file not shown.
|
|
@ -0,0 +1,164 @@
|
|||
# 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/datasources` before creating new dashboards.
|
||||
|
||||
### Grafana Admin Credentials
|
||||
|
||||
- URL: `https://grafana.haumdaucher.de`
|
||||
- User: `admin`
|
||||
- Password: stored in `prometheus-operator.secret.yml` under `grafana.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.yml` under `grafana.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
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
## 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
|
||||
|
|
@ -0,0 +1,326 @@
|
|||
# DASHBOARD_CREATION.md — How to Create & Update Grafana Dashboards
|
||||
|
||||
> **Linked from**: [AGENTS.md](AGENTS.md)
|
||||
|
||||
This document captures the exact process used to create the Grafana dashboards in this folder, including all pitfalls encountered and lessons learned. Use this as a reference when creating new dashboards or updating existing ones.
|
||||
|
||||
---
|
||||
|
||||
## Step-by-Step Dashboard Creation Process
|
||||
|
||||
### Step 1: Discovery — Find Available Metrics
|
||||
|
||||
Before creating any dashboard, discover what data actually exists:
|
||||
|
||||
#### 1a. Query Home Assistant for Entity Metadata
|
||||
|
||||
```bash
|
||||
# Get all entities with their friendly names, units, and device classes
|
||||
curl -s -H "Authorization: Bearer <HA_TOKEN>" \
|
||||
https://hass.moritzgraf.de/api/states | \
|
||||
jq '[.[] | select(.attributes.unit_of_measurement != null) | {
|
||||
entity_id,
|
||||
unit: .attributes.unit_of_measurement,
|
||||
friendly_name: (.attributes.friendly_name // ""),
|
||||
device_class: (.attributes.device_class // "")
|
||||
}] | group_by(.unit)'
|
||||
```
|
||||
|
||||
Or use the helper script: `./scripts/discover-home-assistant.sh`
|
||||
|
||||
#### 1b. Query InfluxDB for Measurements and Schema
|
||||
|
||||
```bash
|
||||
# Port-forward to InfluxDB (no ingress configured)
|
||||
kubectl -n influxdb port-forward svc/influxdb-influxdb2 8086:80
|
||||
|
||||
# List measurements with data in last 6 months
|
||||
curl -s "http://localhost:8086/api/v2/query?org=influxdata" \
|
||||
-H "Authorization: Token <INFLUXDB_TOKEN>" \
|
||||
-H "Content-Type: application/vnd.flux" \
|
||||
-d 'from(bucket: "default")
|
||||
|> range(start: -6mo)
|
||||
|> keep(columns: ["_measurement"])
|
||||
|> distinct()
|
||||
|> sort()'
|
||||
|
||||
# For a specific measurement, get fields and tags
|
||||
curl -s "http://localhost:8086/api/v2/query?org=influxdata" \
|
||||
-H "Authorization: Token <INFLUXDB_TOKEN>" \
|
||||
-H "Content-Type: application/vnd.flux" \
|
||||
-d 'import "influxdata/influxdb/schema"
|
||||
schema.measurementFieldKeys(bucket: "default", measurement: "°C")'
|
||||
```
|
||||
|
||||
Or use: `./scripts/discover-influxdb.sh`
|
||||
|
||||
#### 1c. Verify Data Exists
|
||||
|
||||
Always test a sample query before building dashboards:
|
||||
|
||||
```bash
|
||||
curl -s "http://localhost:8086/api/v2/query?org=influxdata" \
|
||||
-H "Authorization: Token <INFLUXDB_TOKEN>" \
|
||||
-H "Content-Type: application/vnd.flux" \
|
||||
-d 'from(bucket: "default")
|
||||
|> range(start: -1h)
|
||||
|> filter(fn: (r) => r._measurement == "°C" and r._field == "value")
|
||||
|> limit(n: 5)'
|
||||
```
|
||||
|
||||
### Step 2: Discover Grafana Datasource UIDs
|
||||
|
||||
Datasource UIDs change when Grafana is redeployed. Always discover the current UIDs:
|
||||
|
||||
```bash
|
||||
kubectl -n monitoring exec deploy/prometheus-operator-grafana -c grafana -- \
|
||||
curl -s -u "admin:<GRAFANA_PASSWORD>" \
|
||||
http://localhost:3000/api/datasources | \
|
||||
jq '.[] | {name, uid, type}'
|
||||
```
|
||||
|
||||
The Grafana admin password is in [`prometheus-operator.secret.yml`](prometheus-operator.secret.yml) under `grafana.adminPassword`.
|
||||
|
||||
### Step 3: Select the Right Entities
|
||||
|
||||
**Important**: Not all sensors of the same type are equal. For example:
|
||||
- Use **Wandthermostat** (wall thermostat) entities for temperature/humidity, not raw sensor entities. Wandthermostats provide more reliable, calibrated readings.
|
||||
- Query Home Assistant to filter by entity name pattern or device type.
|
||||
|
||||
```bash
|
||||
# Find all Wandthermostat temperature sensors
|
||||
curl -s -H "Authorization: Bearer <HA_TOKEN>" \
|
||||
https://hass.moritzgraf.de/api/states | \
|
||||
jq '[.[] | select(.attributes.unit_of_measurement == "°C" and
|
||||
(.entity_id | test("wandthermostat|thermostat"; "i")))]'
|
||||
```
|
||||
|
||||
### Step 4: Create the Dashboard ConfigMap YAML
|
||||
|
||||
Create a new file in `dashboards/` following this 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>",
|
||||
"tags": ["home", "<category>", "auto-generated"],
|
||||
"timezone": "browser",
|
||||
"schemaVersion": 39,
|
||||
"version": 1,
|
||||
"refresh": "5m",
|
||||
"editable": false,
|
||||
"graphTooltip": 1,
|
||||
"time": { "from": "now-24h", "to": "now" },
|
||||
"panels": [ ... ]
|
||||
}
|
||||
```
|
||||
|
||||
#### Critical JSON Structure Rules
|
||||
|
||||
**✅ CORRECT** (top-level properties, no wrapper):
|
||||
```json
|
||||
{
|
||||
"uid": "my-dashboard",
|
||||
"title": "My Dashboard",
|
||||
"panels": [...]
|
||||
}
|
||||
```
|
||||
|
||||
**❌ WRONG** (wrapped in "dashboard" key — causes "Dashboard title cannot be empty"):
|
||||
```json
|
||||
{
|
||||
"dashboard": {
|
||||
"uid": "my-dashboard",
|
||||
"title": "My Dashboard",
|
||||
"panels": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `"dashboard"` wrapper is **only** used in the Grafana HTTP API (`/api/dashboards/db`). For file-based provisioning via the sidecar, properties must be at the top level.
|
||||
|
||||
#### Datasource Reference
|
||||
|
||||
**✅ CORRECT** (by actual UID):
|
||||
```json
|
||||
"datasource": { "type": "influxdb", "uid": "P2AB959DC95E5519F" }
|
||||
```
|
||||
|
||||
**❌ WRONG** (by name — won't resolve):
|
||||
```json
|
||||
"datasource": { "type": "influxdb", "uid": "InfluxDB Home Assistant" }
|
||||
```
|
||||
|
||||
#### Flux Query Template
|
||||
|
||||
**Exact match on entity_id:**
|
||||
```flux
|
||||
from(bucket: "default")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "<UNIT>" and r["_field"] == "value")
|
||||
|> filter(fn: (r) => r.entity_id == "<ENTITY_ID>")
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
**Regex match on entity_id** (for panels showing multiple entities matching a pattern):
|
||||
```flux
|
||||
from(bucket: "default")
|
||||
|> range(start: v.timeRangeStart, stop: v.timeRangeStop)
|
||||
|> filter(fn: (r) => r["_measurement"] == "<UNIT>" and r["_field"] == "value")
|
||||
|> filter(fn: (r) => r.entity_id =~ /^home_/)
|
||||
|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
**Current value** (for stat panels showing last reading):
|
||||
```flux
|
||||
from(bucket: "default")
|
||||
|> range(start: -15m)
|
||||
|> filter(fn: (r) => r["_measurement"] == "<UNIT>" and r["_field"] == "value")
|
||||
|> filter(fn: (r) => r.entity_id == "<ENTITY_ID>")
|
||||
|> last()
|
||||
|> yield(name: "mean")
|
||||
```
|
||||
|
||||
**Key points**:
|
||||
- Use bracket notation `r["_measurement"]`, `r["_field"]` for fields starting with underscore.
|
||||
- **entity_id in InfluxDB has NO `sensor.` prefix** — HA stores domain in a separate `domain` tag. Use `home_wohnzimmer_temperature`, not `sensor.home_wohnzimmer_temperature`.
|
||||
- Always use `v.timeRangeStart`, `v.timeRangeStop`, `v.windowPeriod` — never hardcode time values.
|
||||
- Always end with `|> yield(name: "mean")` — required for Grafana to parse Flux results.
|
||||
- Use regex `=~ /pattern/` for multi-entity panels (e.g., `=~ /^home_/` matches all home entities).
|
||||
- Always filter `r["_field"] == "value"` — InfluxDB stores metadata strings in other fields.
|
||||
|
||||
### Step 5: Deploy
|
||||
|
||||
```bash
|
||||
# Apply all dashboards
|
||||
kubectl apply -f dashboards/
|
||||
|
||||
# Apply a single dashboard
|
||||
kubectl apply -f dashboards/grafana-dashboard-<name>.yaml
|
||||
```
|
||||
|
||||
### Step 6: Verify
|
||||
|
||||
```bash
|
||||
# Check ConfigMaps are labeled correctly
|
||||
kubectl get configmaps -n monitoring -l grafana_dashboard=1
|
||||
|
||||
# Check sidecar logs (container name is grafana-sc-dashboard, singular!)
|
||||
kubectl -n monitoring logs -l "app.kubernetes.io/name=grafana" \
|
||||
-c grafana-sc-dashboard --tail=20
|
||||
|
||||
# Check Grafana main logs for errors
|
||||
kubectl -n monitoring logs -l "app.kubernetes.io/name=grafana" \
|
||||
-c grafana --tail=30 | grep -i "error\|failed"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Update Process (Modifying an Existing Dashboard)
|
||||
|
||||
1. Edit the YAML file in `dashboards/`
|
||||
2. Increment the `version` field in the dashboard JSON
|
||||
3. Run `kubectl apply -f dashboards/grafana-dashboard-<name>.yaml`
|
||||
4. The sidecar will detect the change and reload within ~60 seconds
|
||||
5. Verify in Grafana UI
|
||||
|
||||
**Never edit dashboards in the Grafana UI** — they are `editable: false`. All changes must go through the YAML files.
|
||||
|
||||
---
|
||||
|
||||
## Removing a Dashboard
|
||||
|
||||
```bash
|
||||
kubectl delete configmap -n monitoring grafana-dashboard-<name>
|
||||
```
|
||||
|
||||
The sidecar will remove it from Grafana within ~60 seconds.
|
||||
|
||||
---
|
||||
|
||||
## Pitfalls Encountered in This Conversation
|
||||
|
||||
### Pitfall 1: Entity IDs lack `sensor.` prefix in InfluxDB
|
||||
**Problem**: Used `sensor.home_wohnzimmer_temperature` in queries — no data appeared. Home Assistant stores the domain (`sensor`) in a separate `domain` tag, not in `entity_id`.
|
||||
**Fix**: Use `home_wohnzimmer_temperature` (without prefix). Always query InfluxDB directly to verify entity_id format before building queries.
|
||||
|
||||
### Pitfall 2: Hardcoded time ranges
|
||||
**Problem**: Used `range(start: -24h)` and `aggregateWindow(every: 5m)` — dashboard time picker had no effect, data not loaded properly.
|
||||
**Fix**: Always use `v.timeRangeStart`, `v.timeRangeStop`, `v.windowPeriod`. These Grafana variables adapt to the dashboard time picker.
|
||||
|
||||
### Pitfall 3: Missing `yield()`
|
||||
**Problem**: Flux queries missing `yield(name: "mean")` — Grafana couldn't parse results.
|
||||
**Fix**: Always end Flux queries with `|> yield(name: "mean")`.
|
||||
|
||||
### Pitfall 4: Dot notation for underscore fields
|
||||
**Problem**: Used `r._measurement` and `r._field` — Flux may not resolve fields starting with `_` via dot notation.
|
||||
**Fix**: Use bracket notation: `r["_measurement"]`, `r["_field"]`.
|
||||
|
||||
### Pitfall 5: Datasource UID vs Name
|
||||
**Problem**: Used datasource name as UID (`"uid": "InfluxDB Home Assistant"`). Dashboards loaded but showed "no data".
|
||||
**Fix**: Discover actual UID via `kubectl exec` + Grafana API. Current: `P2AB959DC95E5519F`.
|
||||
|
||||
### Pitfall 6: `"dashboard"` JSON Wrapper
|
||||
**Problem**: Dashboard JSON wrapped in `{"dashboard": {...}}`. Grafana showed "Dashboard title cannot be empty".
|
||||
**Fix**: Top-level properties only. The `"dashboard"` wrapper is for HTTP API, not file provisioning.
|
||||
|
||||
### Pitfall 7: Adding `uid` to Helm Datasource Config
|
||||
**Problem**: Added `uid: influxdb-ha-flux` to helm values — Grafana pod crashed.
|
||||
**Fix**: Let Grafana auto-generate UIDs. Never add `uid` to existing datasources.
|
||||
|
||||
### Pitfall 8: Multi-entity panels using `or` in filter
|
||||
**Problem**: Used `filter(fn: (r) => r.entity_id == "A" or r.entity_id == "B")` — Flux `or` inside a single filter call doesn't reliably work.
|
||||
**Fix**: Use regex: `filter(fn: (r) => r.entity_id =~ /^home_/)` for matching multiple entities.
|
||||
|
||||
### Pitfall 9: Wrong Sensor Selection
|
||||
**Problem**: Used raw temperature sensors instead of Wandthermostat sensors.
|
||||
**Fix**: Query HA to filter entities with `wandthermostat` or `thermostat` in entity_id.
|
||||
|
||||
### Pitfall 10: Sidecar Container Name
|
||||
**Problem**: `kubectl logs -c grafana-sc-dashboards` (plural).
|
||||
**Fix**: Container name is `grafana-sc-dashboard` (singular).
|
||||
|
||||
---
|
||||
|
||||
## Dashboard Design Guidelines
|
||||
|
||||
1. **Use semantic thresholds with colors**: Blue=cold, green=comfort, orange=warm, red=hot for temperature; reverse for grid import (green=low, red=high).
|
||||
2. **Use Stat panels with sparklines** for current values — they show the 24h mini-trend.
|
||||
3. **Use BarGauge for day comparisons** (today vs yesterday vs avg).
|
||||
4. **Use Gauge for ranged values** (battery SoC 0-100%, cloud cover 0-100%).
|
||||
5. **Color-code panel backgrounds** with `"colorMode": "background"` for at-a-glance status.
|
||||
6. **Set `"editable": false`** for strict GitOps.
|
||||
7. **Use emoji in dashboard and panel titles** for visual appeal (🏠 ⚡ 🔥 🌡️ 💧).
|
||||
8. **Add `"auto-generated"` tag** to distinguish from manually created dashboards.
|
||||
9. **Include calcs in legend** (`mean`, `max`, `min`, `lastNotNull`) for quick statistics.
|
||||
10. **Use `"spanNulls": true`** in timeseries to handle gaps in sensor data.
|
||||
|
||||
---
|
||||
|
||||
## InfluxDB Measurement Reference
|
||||
|
||||
| Measurement | Unit | Typical Entities |
|
||||
|-------------|------|------------------|
|
||||
| `°C` | celsius | Wandthermostat temperatures, outdoor temp, battery temp |
|
||||
| `%` | percent | Humidity, battery levels, heating valve position, cloud cover |
|
||||
| `W` | watt | Solar production, self-consumption, grid import/export, house consumption |
|
||||
| `kWh` | kilowatt-hour | Daily energy totals (production, consumption, import, export) |
|
||||
| `m³` | cubic meter | Gas volume counter |
|
||||
| `m³/h` | cubic meter/hour | Current gas flow rate |
|
||||
| `hPa` | hectopascal | Barometric pressure |
|
||||
| `km/h` | kilometer/hour | Wind speed |
|
||||
| `mm/h` | millimeter/hour | Precipitation rate |
|
||||
| `EUR` | euro | Gas cost, electricity cost |
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
# Grafana Dashboards as Code
|
||||
|
||||
This directory contains Grafana dashboards managed as Kubernetes ConfigMaps. Dashboards are automatically loaded into Grafana via the dashboard sidecar.
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
dashboards/*.yaml (ConfigMap with embedded JSON)
|
||||
│
|
||||
│ kubectl apply
|
||||
▼
|
||||
Kubernetes ConfigMap (label: grafana_dashboard=1)
|
||||
│
|
||||
│ Sidecar watches for ConfigMaps
|
||||
▼
|
||||
Grafana Dashboard (read-only, GitOps managed)
|
||||
```
|
||||
|
||||
## Quick Deploy
|
||||
|
||||
```bash
|
||||
# Apply all dashboards
|
||||
kubectl apply -f dashboards/
|
||||
|
||||
# Apply a single dashboard
|
||||
kubectl apply -f dashboards/grafana-dashboard-home-climate.yaml
|
||||
```
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
# Check ConfigMaps are created and labeled correctly
|
||||
kubectl get configmaps -n monitoring -l grafana_dashboard=1
|
||||
|
||||
# Check sidecar logs to confirm dashboards were loaded
|
||||
kubectl logs -n monitoring deployment/kube-prometheus-stack-grafana -c grafana-sc-dashboards --tail=50
|
||||
|
||||
# List dashboards via Grafana API
|
||||
kubectl -n monitoring exec deploy/kube-prometheus-stack-grafana -- \
|
||||
curl -s -u admin:$(kubectl -n monitoring get secret kube-prometheus-stack-grafana -o jsonpath='{.data.admin-password}' | base64 -d) \
|
||||
http://localhost:3000/api/search?type=dash-db | jq '.[].title'
|
||||
```
|
||||
|
||||
## Discovery
|
||||
|
||||
Before generating new dashboards, discover what metrics are available:
|
||||
|
||||
```bash
|
||||
# Discover InfluxDB measurements, fields, and tags
|
||||
./scripts/discover-influxdb.sh
|
||||
|
||||
# Discover Home Assistant entities and their metadata
|
||||
./scripts/discover-home-assistant.sh
|
||||
```
|
||||
|
||||
## Dashboard Structure
|
||||
|
||||
Each dashboard is a single YAML file containing a Kubernetes ConfigMap with the dashboard JSON embedded:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-<name>
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: "1"
|
||||
annotations:
|
||||
grafana_dashboard_folder: "<Folder Name>"
|
||||
data:
|
||||
<name>.json: |
|
||||
{
|
||||
"dashboard": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
## Adding a New Dashboard
|
||||
|
||||
1. Run discovery scripts to understand available metrics
|
||||
2. Create a new ConfigMap YAML in `dashboards/`
|
||||
3. Apply: `kubectl apply -f dashboards/grafana-dashboard-<name>.yaml`
|
||||
4. Verify the dashboard appears in Grafana at https://grafana.haumdaucher.de
|
||||
|
||||
## Removing a Dashboard
|
||||
|
||||
```bash
|
||||
kubectl delete configmap -n monitoring grafana-dashboard-<name>
|
||||
```
|
||||
|
||||
The sidecar will automatically remove the dashboard from Grafana within ~60 seconds.
|
||||
Binary file not shown.
|
|
@ -0,0 +1,92 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-energy-monitor
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: '1'
|
||||
annotations:
|
||||
grafana_dashboard_folder: 🏠 Home
|
||||
data:
|
||||
energy-monitor.json: '{"uid": "energy-monitor", "title": "⚡ Energy Monitor", "tags": ["home", "energy", "solar", "power", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh":
|
||||
"5m", "editable": false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "⚡ Live Power", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}, "collapsed": false},
|
||||
{"title": "☀️ Solar", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals":
|
||||
0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#FFD700", "value": 100}, {"color": "#73BF69", "value": 500}, {"color": "#37872D", "value": 2000}]}, "color":
|
||||
{"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket:
|
||||
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemleistung\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🏠 Self-Use", "type": "stat", "datasource": {"type": "influxdb",
|
||||
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value":
|
||||
null}, {"color": "#73BF69", "value": 100}, {"color": "#FFD700", "value": 1000}, {"color": "#FF7383", "value": 3000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode":
|
||||
"background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||
filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_eigene_leistung\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||
fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🔌 Grid Import", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||
"gridPos": {"h": 4, "w": 4, "x": 8, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700",
|
||||
"value": 500}, {"color": "#FF7383", "value": 2000}, {"color": "#C4162A", "value": 5000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode":
|
||||
"auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r)
|
||||
=> r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_bezugsleistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty:
|
||||
false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "↗️ Grid Export", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4,
|
||||
"w": 4, "x": 12, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#5794F2", "value": 100},
|
||||
{"color": "#1F60C4", "value": 500}, {"color": "#3274D9", "value": 2000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions":
|
||||
{"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||
== \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_einspeiseleistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |>
|
||||
yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🔋 Battery SoC", "type": "gauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x":
|
||||
16, "y": 1}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "min": 0, "max": 100, "thresholds": {"mode": "absolute", "steps": [{"color": "#C4162A", "value": null}, {"color": "#FF7383",
|
||||
"value": 20}, {"color": "#FFD700", "value": 40}, {"color": "#73BF69", "value": 60}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation":
|
||||
"auto", "showThresholdLabels": true, "showThresholdMarkers": true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and
|
||||
r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"ladeaufstellung_soc\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🏡 Total House", "type":
|
||||
"stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 20, "y": 1}, "fieldConfig": {"defaults": {"unit": "watt", "decimals": 0, "thresholds": {"mode":
|
||||
"absolute", "steps": [{"color": "#555", "value": null}, {"color": "#73BF69", "value": 200}, {"color": "#FFD700", "value": 1000}, {"color": "#FF7383", "value": 3000}, {"color": "#C4162A", "value": 5000}]},
|
||||
"color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query":
|
||||
"from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||
== \"home_strommesserat_currentpowerconsumption\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title":
|
||||
"📈 Power Flow", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title": "Power Timeline", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||
"gridPos": {"h": 10, "w": 24, "x": 0, "y": 6}, "fieldConfig": {"defaults": {"unit": "watt", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher":
|
||||
{"id": "byName", "options": "Solar"}, "properties": [{"id": "color", "value": {"fixedColor": "#FFD700", "mode": "fixed"}}, {"id": "custom.fillOpacity", "value": 15}]}, {"matcher": {"id": "byName", "options":
|
||||
"Self-Use"}, "properties": [{"id": "color", "value": {"fixedColor": "#73BF69", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid Import"}, "properties": [{"id": "color", "value": {"fixedColor":
|
||||
"#FF7383", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid Export"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}, {"id": "custom.lineStyle",
|
||||
"value": {"fill": "dash", "dash": [8, 4]}}]}, {"matcher": {"id": "byName", "options": "Local Load"}, "properties": [{"id": "color", "value": {"fixedColor": "#C4162A", "mode": "fixed"}}]}]}, "options":
|
||||
{"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["max", "mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "SOLAR", "query": "from(bucket:
|
||||
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemleistung\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Solar\")", "resultFormat": "time_series"}, {"refId": "SELF", "query":
|
||||
"from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||
== \"garten_bzp3n7g1b0_eigene_leistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Self-Use\")", "resultFormat":
|
||||
"time_series"}, {"refId": "IMPORT", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"]
|
||||
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_bezugsleistung\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |>
|
||||
set(key: \"source\", value: \"Grid Import\")", "resultFormat": "time_series"}, {"refId": "EXPORT", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||
filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_einspeiseleistung\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||
fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Grid Export\")", "resultFormat": "time_series"}, {"refId": "LOAD", "query": "from(bucket: \"default\")\n |>
|
||||
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"W\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_lokale_lastleistung\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"source\", value: \"Local Load\")", "resultFormat": "time_series"}]}, {"title": "📊 Energy
|
||||
Summary (Today)", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 16}, "collapsed": false}, {"title": "☀️ Solar Produced", "type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals": 1, "min": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color":
|
||||
"#FFD700", "value": 5}, {"color": "#73BF69", "value": 15}, {"color": "#37872D", "value": 30}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]},
|
||||
"orientation": "horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\"
|
||||
and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemerzeugung_heute\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title":
|
||||
"🏠 Self-Consumed", "type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 6, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals":
|
||||
1, "min": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#73BF69", "value": 5}, {"color": "#FFD700", "value": 15}, {"color": "#FF7383", "value": 30}]},
|
||||
"color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation": "horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query":
|
||||
"from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_eigenverbrauch_heute\")\n |>
|
||||
last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "🔌 Grid Imported", "type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h":
|
||||
4, "w": 6, "x": 12, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals": 1, "min": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700",
|
||||
"value": 2}, {"color": "#FF7383", "value": 10}, {"color": "#C4162A", "value": 20}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation":
|
||||
"horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"]
|
||||
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_netzbezug_heute\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "↗️ Grid Exported",
|
||||
"type": "bargauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 18, "y": 17}, "fieldConfig": {"defaults": {"unit": "kwatth", "decimals": 1, "min":
|
||||
0, "thresholds": {"mode": "absolute", "steps": [{"color": "#555", "value": null}, {"color": "#5794F2", "value": 2}, {"color": "#1F60C4", "value": 10}, {"color": "#3274D9", "value": 20}]}, "color": {"mode":
|
||||
"thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation": "horizontal", "displayMode": "basic", "showUnfilled": true}, "targets": [{"query": "from(bucket:
|
||||
\"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"export_ins_netz_heute\")\n |> last()\n |>
|
||||
yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Energy Trend", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 21}, "collapsed": false}, {"title": "Daily Energy", "type":
|
||||
"timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 24, "x": 0, "y": 22}, "fieldConfig": {"defaults": {"unit": "kwatth", "custom": {"lineWidth": 2,
|
||||
"fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Solar"}, "properties": [{"id": "color", "value": {"fixedColor": "#FFD700", "mode":
|
||||
"fixed"}}]}, {"matcher": {"id": "byName", "options": "Self-Use"}, "properties": [{"id": "color", "value": {"fixedColor": "#73BF69", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid
|
||||
Import"}, "properties": [{"id": "color", "value": {"fixedColor": "#FF7383", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Grid Export"}, "properties": [{"id": "color", "value": {"fixedColor":
|
||||
"#5794F2", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["sum", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId":
|
||||
"SOLAR", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |>
|
||||
filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_systemerzeugung_heute\")\n |> aggregateWindow(every: 1d, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"type\", value:
|
||||
\"Solar\")", "resultFormat": "time_series"}, {"refId": "SELF", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||
== \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_eigenverbrauch_heute\")\n |> aggregateWindow(every: 1d, fn: mean, createEmpty: false)\n |> yield(name:
|
||||
\"mean\") |> set(key: \"type\", value: \"Self-Use\")", "resultFormat": "time_series"}, {"refId": "IMPORT", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||
filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"garten_bzp3n7g1b0_netzbezug_heute\")\n |> aggregateWindow(every: 1d, fn: mean,
|
||||
createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"type\", value: \"Grid Import\")", "resultFormat": "time_series"}, {"refId": "EXPORT", "query": "from(bucket: \"default\")\n |> range(start:
|
||||
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"kWh\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"export_ins_netz_heute\")\n |>
|
||||
aggregateWindow(every: 1d, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"type\", value: \"Grid Export\")", "resultFormat": "time_series"}]}]}'
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-heating-gas
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: '1'
|
||||
annotations:
|
||||
grafana_dashboard_folder: 🏠 Home
|
||||
data:
|
||||
heating-gas.json: '{"uid": "heating-gas", "title": "🔥 Heating & Gas", "tags": ["home", "heating", "gas", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh": "5m", "editable":
|
||||
false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "⛽ Gas Overview", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}, "collapsed": false}, {"title":
|
||||
"Current Gas Flow", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 0, "y": 1}, "fieldConfig": {"defaults": {"unit": "cms", "decimals":
|
||||
0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 0.5}, {"color": "#FF7383", "value": 1.5}, {"color": "#C4162A", "value": 3}]}, "color":
|
||||
{"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket:
|
||||
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"m³/h\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id ==
|
||||
\"home_gaszahler_currentgasflow\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Total Gas Volume",
|
||||
"type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 6, "y": 1}, "fieldConfig": {"defaults": {"unit": "cms", "decimals": 0, "thresholds":
|
||||
{"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 500}, {"color": "#FF7383", "value": 1000}, {"color": "#C4162A", "value": 2000}]}, "color": {"mode":
|
||||
"thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |>
|
||||
range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"m³\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_gaszahler_gasvolume\")\n |> last()\n |> yield(name:
|
||||
\"mean\")", "resultFormat": "time_series"}]}, {"title": "Gas Cost (EUR)", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 12, "y": 1},
|
||||
"fieldConfig": {"defaults": {"unit": "currencyEUR", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 200}, {"color": "#FF7383",
|
||||
"value": 500}, {"color": "#C4162A", "value": 1000}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false,
|
||||
"calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"EUR\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||
(r) => r.entity_id == \"home_gaszahler_gasvolume_cost\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Electricity Cost (EUR)", "type": "stat", "datasource":
|
||||
{"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 6, "x": 18, "y": 1}, "fieldConfig": {"defaults": {"unit": "currencyEUR", "decimals": 0, "thresholds": {"mode": "absolute",
|
||||
"steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 100}, {"color": "#FF7383", "value": 300}, {"color": "#C4162A", "value": 500}]}, "color": {"mode": "thresholds"}}}, "options":
|
||||
{"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start:
|
||||
-15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"EUR\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_strommesserat_energy_counter_usage_high_tariff_cost\")\n |>
|
||||
last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Gas Consumption Trend", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title":
|
||||
"Gas Flow", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 16, "x": 0, "y": 6}, "fieldConfig": {"defaults": {"unit": "cms", "custom":
|
||||
{"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Gas Flow"}, "properties": [{"id": "color", "value": {"fixedColor":
|
||||
"#FF7383", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["max", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId":
|
||||
"FLOW", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"m³/h\" and r[\"_field\"] == \"value\")\n |>
|
||||
filter(fn: (r) => r.entity_id == \"home_gaszahler_currentgasflow\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value:
|
||||
\"Gas Flow\")", "resultFormat": "time_series"}]}, {"title": "Gas Volume", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 8, "x": 16, "y":
|
||||
6}, "fieldConfig": {"defaults": {"unit": "cms", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Gas Volume"},
|
||||
"properties": [{"id": "color", "value": {"fixedColor": "#C4162A", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip":
|
||||
{"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "VOL", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||
== \"m³\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_gaszahler_gasvolume\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name:
|
||||
\"mean\") |> set(key: \"metric\", value: \"Gas Volume\")", "resultFormat": "time_series"}]}, {"title": "🏠 Heating Activity", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 14}, "collapsed":
|
||||
false}, {"title": "Heating Valve Position", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 24, "x": 0, "y": 15}, "fieldConfig": {"defaults":
|
||||
{"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Wohnzimmer"}, "properties": [{"id":
|
||||
"color", "value": {"fixedColor": "#FF7383", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Wohnzimmer #2"}, "properties": [{"id": "color", "value": {"fixedColor": "#FF9830", "mode": "fixed"}}]},
|
||||
{"matcher": {"id": "byName", "options": "Arbeitszimmer"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Schlafzimmer"},
|
||||
"properties": [{"id": "color", "value": {"fixedColor": "#8F3BB8", "mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Küche"}, "properties": [{"id": "color", "value": {"fixedColor": "#73BF69",
|
||||
"mode": "fixed"}}]}, {"matcher": {"id": "byName", "options": "Bad"}, "properties": [{"id": "color", "value": {"fixedColor": "#1F60C4", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table",
|
||||
"placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "Wo", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_heating\")\n |> aggregateWindow(every:
|
||||
v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Wohnzimmer\")", "resultFormat": "time_series"}, {"refId": "Wo", "query": "from(bucket: \"default\")\n |>
|
||||
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_heating\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Wohnzimmer #2\")", "resultFormat": "time_series"}, {"refId": "Ar",
|
||||
"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||
(r) => r.entity_id == \"home_arbeitszimmer_heating\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Arbeitszimmer\")",
|
||||
"resultFormat": "time_series"}, {"refId": "Sc", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and
|
||||
r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_schlafzimmer_og_heating\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |>
|
||||
set(key: \"room\", value: \"Schlafzimmer\")", "resultFormat": "time_series"}, {"refId": "Kü", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:
|
||||
(r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_vorne_heating\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty:
|
||||
false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Küche\")", "resultFormat": "time_series"}, {"refId": "Ba", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_badezimmer_thermostat_heating\")\n |> aggregateWindow(every:
|
||||
v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"room\", value: \"Bad\")", "resultFormat": "time_series"}]}]}'
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-home-climate
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: '1'
|
||||
annotations:
|
||||
grafana_dashboard_folder: 🏠 Home
|
||||
data:
|
||||
home-climate.json: '{"uid": "home-climate", "title": "🏠 Home Climate", "tags": ["home", "climate", "temperature", "humidity", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version":
|
||||
1, "refresh": "5m", "editable": false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "🌡️ Current Temperatures", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0,
|
||||
"y": 0}, "collapsed": false}, {"title": "Wohnzimmer", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 1}, "fieldConfig": {"defaults":
|
||||
{"unit": "celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red",
|
||||
"value": 26}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets":
|
||||
[{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||
(r) => r.entity_id == \"home_wall_mounted_thermostat_pro_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]},
|
||||
{"title": "Wohnzimmer #2", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius",
|
||||
"decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color":
|
||||
{"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket:
|
||||
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id ==
|
||||
\"home_wohnzimmer_2_thermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Arbeitszimmer",
|
||||
"type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 8, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius", "decimals": 1, "thresholds":
|
||||
{"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color": {"mode": "thresholds"}}},
|
||||
"options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |>
|
||||
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_arbeitszimmer_thermostat_temperature\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Küche", "type": "stat", "datasource": {"type": "influxdb",
|
||||
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 12, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue",
|
||||
"value": null}, {"color": "green", "value": 18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background",
|
||||
"textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||
filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||
fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Bad", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos":
|
||||
{"h": 4, "w": 4, "x": 16, "y": 1}, "fieldConfig": {"defaults": {"unit": "celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "blue", "value": null}, {"color": "green", "value":
|
||||
18}, {"color": "orange", "value": 22}, {"color": "red", "value": 26}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions":
|
||||
{"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||
== \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |>
|
||||
yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Temperature Trends", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title": "All Rooms Temperature",
|
||||
"type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 16, "x": 0, "y": 6}, "fieldConfig": {"defaults": {"unit": "celsius", "custom": {"lineWidth":
|
||||
2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull", "min", "max"]}, "tooltip": {"mode":
|
||||
"multi", "sort": "desc"}}, "targets": [{"refId": "Wo", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] ==
|
||||
\"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wall_mounted_thermostat_pro_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |>
|
||||
set(key: \"room\", value: \"Wohnzimmer\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Wo2", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop:
|
||||
v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_thermostat_temperature\")\n |> aggregateWindow(every:
|
||||
v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Wohnzimmer #2\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Ar", "query": "from(bucket:
|
||||
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id ==
|
||||
\"home_arbeitszimmer_thermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Arbeitszimmer\")\n |> yield(name: \"mean\")",
|
||||
"resultFormat": "time_series"}, {"refId": "Kü", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\"
|
||||
and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_temperature\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Küche\")\n |> yield(name:
|
||||
\"mean\")", "resultFormat": "time_series"}, {"refId": "Ba", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||
filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_temperature\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||
fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Bad\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Indoor vs Outdoor", "type": "timeseries", "datasource":
|
||||
{"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 10, "w": 8, "x": 16, "y": 6}, "fieldConfig": {"defaults": {"unit": "celsius", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls":
|
||||
true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Outdoor"}, "properties": [{"id": "color", "value": {"fixedColor": "light-blue", "mode": "fixed"}}, {"id": "custom.lineStyle",
|
||||
"value": {"fill": "dash", "dash": [10, 5]}}]}, {"matcher": {"id": "byName", "options": "Indoor Avg"}, "properties": [{"id": "color", "value": {"fixedColor": "orange", "mode": "fixed"}}]}]}, "options":
|
||||
{"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "OUT", "query": "from(bucket: \"default\")\n |>
|
||||
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_temperatur\")\n |>
|
||||
aggregateWindow(every: 1h, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> set(key: \"location\", value: \"Outdoor\")", "resultFormat": "time_series"}, {"refId": "IN", "query": "from(bucket:
|
||||
\"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id =~
|
||||
/^home_/)\n |> aggregateWindow(every: 1h, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")\n |> group()\n |> mean()\n |> set(key: \"location\", value: \"Indoor Avg\")", "resultFormat":
|
||||
"time_series"}]}, {"title": "💧 Humidity", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 16}, "collapsed": false}, {"title": "Wohnzimmer", "type": "stat", "datasource": {"type": "influxdb",
|
||||
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "red",
|
||||
"value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode":
|
||||
"area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wall_mounted_thermostat_pro_humidity\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Arbeitszimmer", "type": "stat", "datasource": {"type":
|
||||
"influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color":
|
||||
"red", "value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options":
|
||||
{"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start:
|
||||
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_arbeitszimmer_thermostat_humidity\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Wohnzimmer #2", "type": "stat", "datasource": {"type":
|
||||
"influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 8, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color":
|
||||
"red", "value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options":
|
||||
{"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start:
|
||||
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_thermostat_humidity\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Bad", "type": "stat", "datasource": {"type": "influxdb",
|
||||
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 12, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "red",
|
||||
"value": null}, {"color": "orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode":
|
||||
"area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_humidity\")\n |> aggregateWindow(every:
|
||||
v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Küche", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||
"gridPos": {"h": 4, "w": 4, "x": 16, "y": 17}, "fieldConfig": {"defaults": {"unit": "percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "red", "value": null}, {"color":
|
||||
"orange", "value": 30}, {"color": "green", "value": 40}, {"color": "orange", "value": 60}, {"color": "red", "value": 70}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode":
|
||||
"background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||
filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_humidity\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||
fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "Humidity Trends", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"},
|
||||
"gridPos": {"h": 8, "w": 24, "x": 0, "y": 20}, "fieldConfig": {"defaults": {"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}}, "options": {"legend":
|
||||
{"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "Wo", "query": "from(bucket: \"default\")\n |>
|
||||
range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wall_mounted_thermostat_pro_humidity\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Wohnzimmer\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Ar", "query":
|
||||
"from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||
== \"home_arbeitszimmer_thermostat_humidity\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Arbeitszimmer\")\n |> yield(name: \"mean\")",
|
||||
"resultFormat": "time_series"}, {"refId": "Wo2", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and
|
||||
r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wohnzimmer_2_thermostat_humidity\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Wohnzimmer #2\")\n |> yield(name:
|
||||
\"mean\")", "resultFormat": "time_series"}, {"refId": "Ba", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |>
|
||||
filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_wandthermostat_bad_humidity\")\n |> aggregateWindow(every: v.windowPeriod,
|
||||
fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Bad\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}, {"refId": "Kü", "query": "from(bucket: \"default\")\n |> range(start:
|
||||
v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"home_kuche_wandthermostat_humidity\")\n |>
|
||||
aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> set(key: \"room\", value: \"Küche\")\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}]}'
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboard-outdoor-weather
|
||||
namespace: monitoring
|
||||
labels:
|
||||
grafana_dashboard: '1'
|
||||
annotations:
|
||||
grafana_dashboard_folder: 🏠 Home
|
||||
data:
|
||||
outdoor-weather.json: '{"uid": "outdoor-weather", "title": "🌡️ Outdoor & Weather", "tags": ["home", "weather", "outdoor", "auto-generated"], "timezone": "browser", "schemaVersion": 39, "version": 1, "refresh":
|
||||
"5m", "editable": false, "graphTooltip": 1, "time": {"from": "now-24h", "to": "now"}, "panels": [{"title": "🌤️ Current Conditions", "type": "row", "gridPos": {"h": 1, "w": 24, "x": 0, "y": 0}, "collapsed":
|
||||
false}, {"title": "🌡️ Temperature", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 0, "y": 1}, "fieldConfig": {"defaults": {"unit":
|
||||
"celsius", "decimals": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "#5794F2", "value": null}, {"color": "#73BF69", "value": 0}, {"color": "#FFD700", "value": 15}, {"color": "#FF7383",
|
||||
"value": 25}, {"color": "#C4162A", "value": 30}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false,
|
||||
"calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"°C\" and r[\"_field\"]
|
||||
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_temperatur\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat":
|
||||
"time_series"}]}, {"title": "💧 Humidity", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 4, "y": 1}, "fieldConfig": {"defaults": {"unit":
|
||||
"percent", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#FF7383", "value": null}, {"color": "#FFD700", "value": 30}, {"color": "#73BF69", "value": 40}, {"color": "#FFD700",
|
||||
"value": 70}, {"color": "#5794F2", "value": 90}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false,
|
||||
"calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"]
|
||||
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_luftfeuchtigkeit\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"resultFormat": "time_series"}]}, {"title": "🔵 Pressure", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 8, "y": 1}, "fieldConfig":
|
||||
{"defaults": {"unit": "pressurehpa", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#FF7383", "value": null}, {"color": "#FFD700", "value": 990}, {"color": "#73BF69", "value":
|
||||
1010}, {"color": "#FFD700", "value": 1030}, {"color": "#FF7383", "value": 1040}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto",
|
||||
"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||
== \"hPa\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_druck\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name:
|
||||
\"mean\")", "resultFormat": "time_series"}]}, {"title": "💨 Wind", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 12, "y": 1}, "fieldConfig":
|
||||
{"defaults": {"unit": "velocitykmh", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#FFD700", "value": 20}, {"color": "#FF7383", "value":
|
||||
40}, {"color": "#C4162A", "value": 60}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs":
|
||||
["lastNotNull"]}}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"km/h\" and r[\"_field\"]
|
||||
== \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_windgeschwindigkeit\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")",
|
||||
"resultFormat": "time_series"}]}, {"title": "🌧️ Rain", "type": "stat", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 16, "y": 1}, "fieldConfig": {"defaults":
|
||||
{"unit": "mm/h", "decimals": 0, "thresholds": {"mode": "absolute", "steps": [{"color": "#73BF69", "value": null}, {"color": "#5794F2", "value": 0.1}, {"color": "#1F60C4", "value": 1}, {"color": "#3274D9",
|
||||
"value": 5}]}, "color": {"mode": "thresholds"}}}, "options": {"graphMode": "area", "colorMode": "background", "textMode": "auto", "reduceOptions": {"values": false, "calcs": ["lastNotNull"]}}, "targets":
|
||||
[{"query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"mm/h\" and r[\"_field\"] == \"value\")\n |> filter(fn:
|
||||
(r) => r.entity_id == \"regensburg_regensburg_niederschlag\")\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]},
|
||||
{"title": "☁️ Cloud Cover", "type": "gauge", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 4, "w": 4, "x": 20, "y": 1}, "fieldConfig": {"defaults": {"unit": "percent",
|
||||
"decimals": 0, "min": 0, "max": 100, "thresholds": {"mode": "absolute", "steps": [{"color": "#5794F2", "value": null}, {"color": "#73BF69", "value": 20}, {"color": "#FFD700", "value": 50}, {"color":
|
||||
"#808080", "value": 80}]}, "color": {"mode": "thresholds"}}}, "options": {"reduceOptions": {"values": false, "calcs": ["lastNotNull"]}, "orientation": "auto", "showThresholdLabels": true, "showThresholdMarkers":
|
||||
true}, "targets": [{"query": "from(bucket: \"default\")\n |> range(start: -15m)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id
|
||||
== \"regensburg_regensburg_bewolkungsgrad\")\n |> last()\n |> yield(name: \"mean\")", "resultFormat": "time_series"}]}, {"title": "📈 Temperature & Humidity Trend", "type": "row", "gridPos": {"h":
|
||||
1, "w": 24, "x": 0, "y": 5}, "collapsed": false}, {"title": "Temperature", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x": 0,
|
||||
"y": 6}, "fieldConfig": {"defaults": {"unit": "celsius", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options":
|
||||
"Temperature"}, "properties": [{"id": "color", "value": {"fixedColor": "#FF7383", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["min", "max",
|
||||
"mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "T", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r)
|
||||
=> r[\"_measurement\"] == \"°C\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_temperatur\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty:
|
||||
false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Temperature\")", "resultFormat": "time_series"}]}, {"title": "Humidity", "type": "timeseries", "datasource": {"type": "influxdb",
|
||||
"uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x": 12, "y": 6}, "fieldConfig": {"defaults": {"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints":
|
||||
"never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Humidity"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode":
|
||||
"table", "placement": "bottom", "calcs": ["min", "max", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "H", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart,
|
||||
stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_luftfeuchtigkeit\")\n |> aggregateWindow(every:
|
||||
30m, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Humidity\")", "resultFormat": "time_series"}]}, {"title": "📊 Pressure & Wind", "type": "row", "gridPos":
|
||||
{"h": 1, "w": 24, "x": 0, "y": 14}, "collapsed": false}, {"title": "Pressure", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x":
|
||||
0, "y": 15}, "fieldConfig": {"defaults": {"unit": "pressurehpa", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options":
|
||||
"Pressure"}, "properties": [{"id": "color", "value": {"fixedColor": "#8F3BB8", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["min", "max", "mean"]},
|
||||
"tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "P", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"]
|
||||
== \"hPa\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_druck\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |>
|
||||
set(key: \"metric\", value: \"Pressure\")", "resultFormat": "time_series"}]}, {"title": "Wind Speed", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos":
|
||||
{"h": 8, "w": 12, "x": 12, "y": 15}, "fieldConfig": {"defaults": {"unit": "velocitykmh", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher":
|
||||
{"id": "byName", "options": "Wind"}, "properties": [{"id": "color", "value": {"fixedColor": "#5794F2", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs":
|
||||
["max", "mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "W", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:
|
||||
(r) => r[\"_measurement\"] == \"km/h\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_windgeschwindigkeit\")\n |> aggregateWindow(every: 30m, fn: mean,
|
||||
createEmpty: false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Wind\")", "resultFormat": "time_series"}]}, {"title": "🌧️ Precipitation & Cloud", "type": "row", "gridPos": {"h": 1,
|
||||
"w": 24, "x": 0, "y": 23}, "collapsed": false}, {"title": "Rain", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h": 8, "w": 12, "x": 0, "y": 24},
|
||||
"fieldConfig": {"defaults": {"unit": "mm/h", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id": "byName", "options": "Rain"}, "properties":
|
||||
[{"id": "color", "value": {"fixedColor": "#3274D9", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs": ["mean", "lastNotNull"]}, "tooltip": {"mode":
|
||||
"multi", "sort": "desc"}}, "targets": [{"refId": "R", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn: (r) => r[\"_measurement\"] == \"mm/h\"
|
||||
and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_niederschlag\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty: false)\n |> yield(name: \"mean\") |>
|
||||
set(key: \"metric\", value: \"Rain\")", "resultFormat": "time_series"}]}, {"title": "Cloud Cover", "type": "timeseries", "datasource": {"type": "influxdb", "uid": "P2AB959DC95E5519F"}, "gridPos": {"h":
|
||||
8, "w": 12, "x": 12, "y": 24}, "fieldConfig": {"defaults": {"unit": "percent", "custom": {"lineWidth": 2, "fillOpacity": 5, "spanNulls": true, "showPoints": "never"}}, "overrides": [{"matcher": {"id":
|
||||
"byName", "options": "Cloud Cover"}, "properties": [{"id": "color", "value": {"fixedColor": "#808080", "mode": "fixed"}}]}]}, "options": {"legend": {"displayMode": "table", "placement": "bottom", "calcs":
|
||||
["mean"]}, "tooltip": {"mode": "multi", "sort": "desc"}}, "targets": [{"refId": "C", "query": "from(bucket: \"default\")\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\n |> filter(fn:
|
||||
(r) => r[\"_measurement\"] == \"%\" and r[\"_field\"] == \"value\")\n |> filter(fn: (r) => r.entity_id == \"regensburg_regensburg_bewolkungsgrad\")\n |> aggregateWindow(every: 30m, fn: mean, createEmpty:
|
||||
false)\n |> yield(name: \"mean\") |> set(key: \"metric\", value: \"Cloud Cover\")", "resultFormat": "time_series"}]}]}'
|
||||
|
|
@ -7,12 +7,11 @@ kubeControllerManager:
|
|||
serviceMonitor:
|
||||
https: true
|
||||
insecureSkipVerify: true
|
||||
# proxy not working, svc not exposed by port
|
||||
kubeProxy:
|
||||
service:
|
||||
targetPort: 10249
|
||||
serviceMonitor:
|
||||
https: true
|
||||
https: false
|
||||
insecureSkipVerify: true
|
||||
##################
|
||||
|
||||
|
|
@ -61,6 +60,7 @@ prometheus:
|
|||
storage: 50Gi
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: traefik
|
||||
hosts:
|
||||
- prometheus.haumdaucher.de
|
||||
tls:
|
||||
|
|
@ -68,7 +68,7 @@ prometheus:
|
|||
- "prometheus.haumdaucher.de"
|
||||
secretName: prometheus-haumdaucher-de
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
kubernetes.io/ingress.class: "traefik"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
# see https://kubernetes.github.io/ingress-nginx/examples/auth/basic/
|
||||
|
|
@ -89,6 +89,7 @@ alertmanager:
|
|||
enabled: true
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: traefik
|
||||
hosts:
|
||||
- alertmanager.haumdaucher.de
|
||||
tls:
|
||||
|
|
@ -96,7 +97,7 @@ alertmanager:
|
|||
- "alertmanager.haumdaucher.de"
|
||||
secretName: alertmanager-haumdaucher-de
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
kubernetes.io/ingress.class: "traefik"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
# see https://kubernetes.github.io/ingress-nginx/examples/auth/basic/
|
||||
|
|
@ -126,8 +127,31 @@ grafana:
|
|||
imageRenderer:
|
||||
enabled: true
|
||||
adminPassword: kohkohh5sah1Do3ize2x
|
||||
additionalDataSources:
|
||||
- name: InfluxDB Home Assistant
|
||||
type: influxdb
|
||||
access: proxy
|
||||
url: http://influxdb-influxdb2.influxdb.svc.cluster.local:80
|
||||
jsonData:
|
||||
version: Flux
|
||||
organization: influxdata
|
||||
defaultBucket: default
|
||||
httpMode: POST
|
||||
secureJsonData:
|
||||
token: enaiY9yaiWi6ahv0phoph3FaiphoGh
|
||||
- name: InfluxDB Home Assistant InfluxQL
|
||||
type: influxdb
|
||||
access: proxy
|
||||
url: http://influxdb-influxdb2.influxdb.svc.cluster.local:80
|
||||
jsonData:
|
||||
dbName: default
|
||||
httpHeaderName1: 'Authorization'
|
||||
httpMode: POST
|
||||
secureJsonData:
|
||||
httpHeaderValue1: 'Token enaiY9yaiWi6ahv0phoph3FaiphoGh'
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: traefik
|
||||
hosts:
|
||||
- grafana.haumdaucher.de
|
||||
tls:
|
||||
|
|
@ -135,7 +159,7 @@ grafana:
|
|||
- "grafana.haumdaucher.de"
|
||||
secretName: grafana-haumdaucher-de
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
kubernetes.io/ingress.class: "traefik"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
env:
|
||||
|
|
@ -149,4 +173,22 @@ grafana:
|
|||
# interval_day: "MMM YYYY"
|
||||
# interval_month: "YYYY-MM"
|
||||
# interval_year: "YYYY"
|
||||
sidecar:
|
||||
dashboards:
|
||||
enabled: true
|
||||
label: grafana_dashboard
|
||||
labelValue: "1"
|
||||
searchNamespace: monitoring
|
||||
folder: /tmp/dashboards
|
||||
defaultFolderName: null
|
||||
provider:
|
||||
name: sidecarProvider
|
||||
orgId: 1
|
||||
folder: ""
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: false
|
||||
allowUiUpdates: false
|
||||
options:
|
||||
path: /tmp/dashboards
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
#!/bin/bash
|
||||
# discover-home-assistant.sh — Query Home Assistant REST API for entity metadata
|
||||
# Usage: ./discover-home-assistant.sh
|
||||
# Requires: curl, jq
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
HA_URL="https://hass.moritzgraf.de"
|
||||
HA_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiI2NDMyZTI1MDIzZjU0ZmFiYTU3OTc0ODYyMzg3M2ExZSIsImlhdCI6MTc4MjczMTY4NiwiZXhwIjoyMDk4MDkxNjg2fQ.u8kFHoYL2kdhVenfe3BiWcNkN_en3yn91MRGSVJvbmw"
|
||||
|
||||
echo "=== Home Assistant Entity Discovery ==="
|
||||
echo "URL: $HA_URL"
|
||||
echo ""
|
||||
|
||||
# Get all entities
|
||||
echo "→ Fetching all entities..."
|
||||
ENTITIES=$(curl -s -H "Authorization: Bearer $HA_TOKEN" "$HA_URL/api/states")
|
||||
|
||||
if [ -z "$ENTITIES" ] || echo "$ENTITIES" | jq -e '. == null' >/dev/null 2>&1; then
|
||||
echo "ERROR: Failed to fetch entities. Check token and connectivity."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=== Entity Summary ==="
|
||||
echo "$ENTITIES" | jq -r '
|
||||
[.[] | {
|
||||
entity_id,
|
||||
state,
|
||||
friendly_name: (.attributes.friendly_name // ""),
|
||||
unit: (.attributes.unit_of_measurement // ""),
|
||||
device_class: (.attributes.device_class // ""),
|
||||
domain: (.entity_id | split(".")[0])
|
||||
}]
|
||||
| sort_by(.domain, .entity_id)
|
||||
| .[]
|
||||
| "\(.entity_id) | state=\(.state) | name=\(.friendly_name) | unit=\(.unit) | class=\(.device_class) | domain=\(.domain)"
|
||||
'
|
||||
|
||||
echo ""
|
||||
echo "=== Domains (entity types) ==="
|
||||
echo "$ENTITIES" | jq -r '[.[].entity_id | split(".")[0]] | unique | .[]'
|
||||
|
||||
echo ""
|
||||
echo "=== Entities with unit_of_measurement (likely sensor data in InfluxDB) ==="
|
||||
echo "$ENTITIES" | jq -r '
|
||||
[.[] | select(.attributes.unit_of_measurement != null) | {
|
||||
entity_id,
|
||||
unit: .attributes.unit_of_measurement,
|
||||
friendly_name: (.attributes.friendly_name // ""),
|
||||
device_class: (.attributes.device_class // "")
|
||||
}]
|
||||
| sort_by(.unit, .entity_id)
|
||||
| .[]
|
||||
| "\(.entity_id) | \(.unit) | \(.device_class) | \(.friendly_name)"
|
||||
'
|
||||
|
||||
echo ""
|
||||
echo "=== Device classes ==="
|
||||
echo "$ENTITIES" | jq -r '[.[].attributes.device_class | select(. != null)] | unique | .[]'
|
||||
|
||||
echo ""
|
||||
echo "=== Discovery complete ==="
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
#!/bin/bash
|
||||
# discover-influxdb.sh — Query InfluxDB to discover measurements, fields, and tags
|
||||
# Usage: ./discover-influxdb.sh [namespace]
|
||||
# Requires: kubectl, curl, jq
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
NAMESPACE="${1:-influxdb}"
|
||||
INFLUXDB_TOKEN="enaiY9yaiWi6ahv0phoph3FaiphoGh"
|
||||
INFLUXDB_ORG="influxdata"
|
||||
INFLUXDB_BUCKET="default"
|
||||
LOCAL_PORT="8086"
|
||||
|
||||
echo "=== InfluxDB Discovery ==="
|
||||
echo "Namespace: $NAMESPACE"
|
||||
echo ""
|
||||
|
||||
# Start port-forward in background
|
||||
echo "→ Starting port-forward to influxdb-influxdb2..."
|
||||
kubectl -n "$NAMESPACE" port-forward "svc/influxdb-influxdb2" "$LOCAL_PORT:80" &>/dev/null &
|
||||
PF_PID=$!
|
||||
sleep 2
|
||||
|
||||
# Ensure cleanup on exit
|
||||
cleanup() {
|
||||
kill "$PF_PID" 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
API="http://localhost:$LOCAL_PORT/api/v2/query?org=$INFLUXDB_ORG"
|
||||
AUTH="Authorization: Token $INFLUXDB_TOKEN"
|
||||
CT="Content-Type: application/vnd.flux"
|
||||
|
||||
echo ""
|
||||
echo "=== Measurements (tables) in bucket '$INFLUXDB_BUCKET' ==="
|
||||
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||
-d "import \"influxdata/influxdb/schema\"
|
||||
schema.measurements(bucket: \"$INFLUXDB_BUCKET\")" \
|
||||
| jq -r '.[] | ._value' 2>/dev/null || echo "(no results or parse error)"
|
||||
|
||||
echo ""
|
||||
echo "=== Measurements with data in last 6 months ==="
|
||||
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||
-d "from(bucket: \"$INFLUXDB_BUCKET\")
|
||||
|> range(start: -6mo)
|
||||
|> keep(columns: [\"_measurement\"])
|
||||
|> distinct()
|
||||
|> sort()" \
|
||||
| jq -r '.[] | ._value' 2>/dev/null || echo "(no results or parse error)"
|
||||
|
||||
echo ""
|
||||
echo "=== For each measurement: fields, tags, and sample count ==="
|
||||
|
||||
# Get measurements
|
||||
MEASUREMENTS=$(curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||
-d "import \"influxdata/influxdb/schema\"
|
||||
schema.measurements(bucket: \"$INFLUXDB_BUCKET\")" \
|
||||
| jq -r '.[] | ._value' 2>/dev/null)
|
||||
|
||||
for m in $MEASUREMENTS; do
|
||||
echo ""
|
||||
echo "--- Measurement: $m ---"
|
||||
|
||||
# Fields
|
||||
echo " Fields:"
|
||||
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||
-d "import \"influxdata/influxdb/schema\"
|
||||
schema.measurementFieldKeys(bucket: \"$INFLUXDB_BUCKET\", measurement: \"$m\")" \
|
||||
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||
|
||||
# Tags
|
||||
echo " Tags:"
|
||||
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||
-d "import \"influxdata/influxdb/schema\"
|
||||
schema.measurementTagKeys(bucket: \"$INFLUXDB_BUCKET\", measurement: \"$m\")" \
|
||||
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||
|
||||
# Tag values for entity_id (most common tag in HA data)
|
||||
echo " entity_id values (sample):"
|
||||
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||
-d "import \"influxdata/influxdb/schema\"
|
||||
schema.tagValues(bucket: \"$INFLUXDB_BUCKET\", tag: \"entity_id\")
|
||||
|> filter(fn: (r) => r._measurement == \"$m\")
|
||||
|> limit(n: 20)" \
|
||||
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||
|
||||
# Sample count in last 6 months
|
||||
echo " Data points (last 6 months):"
|
||||
curl -s "$API" -H "$AUTH" -H "$CT" \
|
||||
-d "from(bucket: \"$INFLUXDB_BUCKET\")
|
||||
|> range(start: -6mo)
|
||||
|> filter(fn: (r) => r._measurement == \"$m\")
|
||||
|> count()" \
|
||||
| jq -r '.[] | " - \(._value)"' 2>/dev/null || echo " (none)"
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== Discovery complete ==="
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
rootUser:
|
||||
password: cah6eepai1Saepho8tho
|
||||
forcePassword: true
|
||||
db:
|
||||
name: tt-rss
|
||||
user: tt-rss
|
||||
password: AhS5cai7oph3ahN4Aile
|
||||
forcePassword: true
|
||||
replication:
|
||||
enabled: false
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
db:
|
||||
host: tt-rss-mariadb
|
||||
ingress:
|
||||
enabled: true
|
||||
hosts:
|
||||
- path: "/"
|
||||
tls: true
|
||||
name: "tt-rss.phpmyadmin.haumdaucher.de"
|
||||
tlsSecret: "tt-rss-phpmyadmin-haumdaucher-de"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "0"
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
env:
|
||||
# -- External URL you use to connect to the RSS (the one you enter in your browser)
|
||||
TTRSS_SELF_URL_PATH: "tt-rss.moritzgraf.de"
|
||||
# -- Postgres database hostname
|
||||
# @default -- internal postgresql URL
|
||||
TTRSS_DB_HOST: "tt-rss-mariadb"
|
||||
# -- Postgres database port.
|
||||
TTRSS_DB_PORT: "3306"
|
||||
# -- Postgres database user name
|
||||
# @default -- postgresql.postgresqlUsername value
|
||||
TTRSS_DB_USER: "tt-rss"
|
||||
# -- Postgres database password
|
||||
# @default -- postgresql.postgresqlPassword value
|
||||
TTRSS_DB_PASS: "AhS5cai7oph3ahN4Aile"
|
||||
# -- Postgres database password
|
||||
# @default -- postgresql.postgresqlDatabase value
|
||||
TTRSS_DB_NAME: "tt-rss"
|
||||
|
||||
ingress:
|
||||
main:
|
||||
enabled: true
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: "nginx"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
tls:
|
||||
- hosts:
|
||||
- "tt-rss.moritzgraf.de"
|
||||
secretName: tt-rss-moritzgraf-de
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
Binary file not shown.
|
|
@ -1,29 +0,0 @@
|
|||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: dropbox-sync
|
||||
namespace: velero
|
||||
spec:
|
||||
schedule: "0 5 * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: default
|
||||
containers:
|
||||
- name: dopybox-sync
|
||||
image: iptizer/swiss
|
||||
volumeMounts:
|
||||
- name: rclone-config
|
||||
mountPath: /rclone.secret
|
||||
subPath: "rclone.secret"
|
||||
command:
|
||||
- "devbox"
|
||||
- "run"
|
||||
args:
|
||||
- "rclone --config /rclone.secret -v copy minio:backup/ dropbox:server_backup/velero/"
|
||||
volumes:
|
||||
- name: rclone-config
|
||||
secret:
|
||||
secretName: rclone-config
|
||||
restartPolicy: Never
|
||||
Binary file not shown.
|
|
@ -0,0 +1,39 @@
|
|||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: gdrive-sync
|
||||
namespace: velero
|
||||
spec:
|
||||
schedule: "0 5 * * *"
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: default
|
||||
containers:
|
||||
- name: gdrive-sync
|
||||
image: rclone/rclone:latest
|
||||
volumeMounts:
|
||||
- name: rclone-config
|
||||
mountPath: /rclone.secret
|
||||
subPath: "rclone.secret"
|
||||
- name: gdrive-sa-key
|
||||
mountPath: /gdrive_sa.json.secret
|
||||
subPath: "gdrive_sa.json.secret"
|
||||
command:
|
||||
- "rclone"
|
||||
args:
|
||||
- "--config"
|
||||
- "/rclone.secret"
|
||||
- "-v"
|
||||
- "sync"
|
||||
- "minio:backup/"
|
||||
- "gdrivemoritz:"
|
||||
volumes:
|
||||
- name: rclone-config
|
||||
secret:
|
||||
secretName: rclone-config
|
||||
- name: gdrive-sa-key
|
||||
secret:
|
||||
secretName: gdrive-sa-key
|
||||
restartPolicy: Never
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: gdrive-sync-manual
|
||||
namespace: velero
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
serviceAccountName: default
|
||||
containers:
|
||||
- name: gdrive-sync
|
||||
image: rclone/rclone:latest
|
||||
volumeMounts:
|
||||
- name: rclone-config
|
||||
mountPath: /rclone.secret
|
||||
subPath: "rclone.secret"
|
||||
- name: gdrive-sa-key
|
||||
mountPath: /gdrive_sa.json.secret
|
||||
subPath: "gdrive_sa.json.secret"
|
||||
command:
|
||||
- "rclone"
|
||||
args:
|
||||
- "--config"
|
||||
- "/rclone.secret"
|
||||
- "-v"
|
||||
- "sync"
|
||||
- "minio:backup/"
|
||||
- "gdrivemoritz:"
|
||||
volumes:
|
||||
- name: rclone-config
|
||||
secret:
|
||||
secretName: rclone-config
|
||||
- name: gdrive-sa-key
|
||||
secret:
|
||||
secretName: gdrive-sa-key
|
||||
restartPolicy: Never
|
||||
Binary file not shown.
|
|
@ -0,0 +1,828 @@
|
|||
##
|
||||
## Configuration settings related to Velero installation namespace
|
||||
##
|
||||
|
||||
# Labels settings in namespace
|
||||
namespace:
|
||||
labels: {}
|
||||
# Enforce Pod Security Standards with Namespace Labels
|
||||
# https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-namespace-labels/
|
||||
# pod-security.kubernetes.io/enforce: privileged
|
||||
# pod-security.kubernetes.io/enforce-version: latest
|
||||
# pod-security.kubernetes.io/audit: privileged
|
||||
# pod-security.kubernetes.io/audit-version: latest
|
||||
# pod-security.kubernetes.io/warn: privileged
|
||||
# pod-security.kubernetes.io/warn-version: latest
|
||||
|
||||
##
|
||||
## End of namespace-related settings.
|
||||
##
|
||||
|
||||
|
||||
##
|
||||
## Configuration settings that directly affect the Velero deployment YAML.
|
||||
##
|
||||
|
||||
# Details of the container image to use in the Velero deployment & daemonset (if
|
||||
# enabling node-agent). Required.
|
||||
image:
|
||||
repository: docker.io/velero/velero
|
||||
tag: v1.18.1
|
||||
# Digest value example: sha256:73266e6bd7e2fe3f65fb20677d36c4a8df76d417d9ba76bd9932e0d983a776ec.
|
||||
# If used, it will take precedence over the image.tag.
|
||||
# digest:
|
||||
pullPolicy: IfNotPresent
|
||||
# One or more secrets to be used when pulling images
|
||||
imagePullSecrets: []
|
||||
# - registrySecretName
|
||||
|
||||
nameOverride: ""
|
||||
fullnameOverride: ""
|
||||
|
||||
# Annotations to add to the Velero deployment's. Optional.
|
||||
#
|
||||
# If you are using reloader use the following annotation with your VELERO_SECRET_NAME
|
||||
annotations: {}
|
||||
# secret.reloader.stakater.com/reload: "<VELERO_SECRET_NAME>"
|
||||
|
||||
# Annotations to add to secret
|
||||
secretAnnotations: {}
|
||||
|
||||
# Labels to add to the Velero deployment's. Optional.
|
||||
labels: {}
|
||||
|
||||
# Annotations to add to the Velero deployment's pod template. Optional.
|
||||
#
|
||||
# If using kube2iam or kiam, use the following annotation with your AWS_ACCOUNT_ID
|
||||
# and VELERO_ROLE_NAME filled in:
|
||||
podAnnotations: {}
|
||||
# iam.amazonaws.com/role: "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<VELERO_ROLE_NAME>"
|
||||
|
||||
# Additional pod labels for Velero deployment's template. Optional
|
||||
# ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
podLabels: {}
|
||||
|
||||
# Number of old history to retain to allow rollback (If not set, default Kubernetes value is set to 10)
|
||||
# revisionHistoryLimit: 1
|
||||
|
||||
# Resource requests/limits to specify for the Velero deployment.
|
||||
# https://velero.io/docs/v1.6/customize-installation/#customize-resource-requests-and-limits
|
||||
resources: {}
|
||||
# requests:
|
||||
# cpu: 500m
|
||||
# memory: 128Mi
|
||||
# limits:
|
||||
# cpu: 1000m
|
||||
# memory: 512Mi
|
||||
|
||||
# Container resize policy for the Velero deployment.
|
||||
# See: https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/
|
||||
resizePolicy: []
|
||||
# - resourceName: cpu
|
||||
# restartPolicy: NotRequired
|
||||
# - resourceName: memory
|
||||
# restartPolicy: RestartContainer
|
||||
|
||||
# Configure hostAliases for Velero deployment. Optional
|
||||
# For more information, check: https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/
|
||||
hostAliases: []
|
||||
# - ip: "127.0.0.1"
|
||||
# hostnames:
|
||||
# - "foo.local"
|
||||
# - "bar.local"
|
||||
|
||||
# Resource requests/limits to specify for the upgradeCRDs job pod. Need to be adjusted by user accordingly.
|
||||
upgradeJobResources: {}
|
||||
# requests:
|
||||
# cpu: 50m
|
||||
# memory: 128Mi
|
||||
# limits:
|
||||
# cpu: 100m
|
||||
# memory: 256Mi
|
||||
upgradeCRDsJob:
|
||||
# Extra volumes for the Upgrade CRDs Job. Optional.
|
||||
extraVolumes: []
|
||||
# Extra volumeMounts for the Upgrade CRDs Job. Optional.
|
||||
extraVolumeMounts: []
|
||||
# Additional values to be used as environment variables. Optional.
|
||||
extraEnvVars: []
|
||||
# Simple value
|
||||
# - name: SIMPLE_VAR
|
||||
# value: "simple-value"
|
||||
|
||||
# FieldRef example
|
||||
# - name: MY_POD_LABEL
|
||||
# valueFrom:
|
||||
# fieldRef:
|
||||
# fieldPath: metadata.labels['my_label']
|
||||
|
||||
# Configure if API credential for Service Account is automounted.
|
||||
automountServiceAccountToken: true
|
||||
# Configure the shell cmd in case you are using custom image
|
||||
# shellCmd: /tmp/sh
|
||||
# updateCmd: /velero install --crds-only --dry-run -o yaml | /tmp/kubectl apply -f -
|
||||
|
||||
# Configure the dnsPolicy of the Velero deployment
|
||||
# See: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
|
||||
dnsPolicy: ClusterFirst
|
||||
|
||||
# Init containers to add to the Velero deployment's pod spec. At least one plugin provider image is required.
|
||||
# If the value is a string then it is evaluated as a template.
|
||||
initContainers:
|
||||
# - name: velero-plugin-for-aws
|
||||
# image: velero/velero-plugin-for-aws:v1.13.1
|
||||
# imagePullPolicy: IfNotPresent
|
||||
# volumeMounts:
|
||||
# - mountPath: /target
|
||||
# name: plugins
|
||||
|
||||
# SecurityContext to use for the Velero deployment. Optional.
|
||||
# Set fsGroup for `AWS IAM Roles for Service Accounts`
|
||||
# see more informations at: https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
|
||||
podSecurityContext: {}
|
||||
# fsGroup: 1337
|
||||
|
||||
# Container Level Security Context for the 'velero' container of the Velero deployment. Optional.
|
||||
# See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||
containerSecurityContext: {}
|
||||
# allowPrivilegeEscalation: false
|
||||
# capabilities:
|
||||
# drop: ["ALL"]
|
||||
# add: []
|
||||
# readOnlyRootFilesystem: true
|
||||
|
||||
# Container Lifecycle Hooks to use for the Velero deployment. Optional.
|
||||
lifecycle: {}
|
||||
|
||||
# Pod priority class name to use for the Velero deployment. Optional.
|
||||
priorityClassName: ""
|
||||
|
||||
# Pod runtime class name to use for the Velero deployment. Optional.
|
||||
runtimeClassName: ""
|
||||
|
||||
# The number of seconds to allow for graceful termination of the pod. Optional.
|
||||
terminationGracePeriodSeconds: 3600
|
||||
|
||||
# Liveness probe of the pod
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: http-monitoring
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
|
||||
# Readiness probe of the pod
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /metrics
|
||||
port: http-monitoring
|
||||
scheme: HTTP
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
successThreshold: 1
|
||||
failureThreshold: 5
|
||||
|
||||
# Tolerations to use for the Velero deployment. Optional.
|
||||
tolerations: []
|
||||
|
||||
# Affinity to use for the Velero deployment. Optional.
|
||||
affinity: {}
|
||||
|
||||
# Node selector to use for the Velero deployment. Optional.
|
||||
nodeSelector: {}
|
||||
|
||||
# DNS configuration to use for the Velero deployment. Optional.
|
||||
dnsConfig: {}
|
||||
|
||||
# Pod Disruption Budget for the Velero deployment. Optional.
|
||||
# Velero runs a single replica with a Recreate strategy by default, so this is
|
||||
# disabled by default. Enable it when running Velero with multiple replicas.
|
||||
podDisruptionBudget:
|
||||
enabled: false
|
||||
# minAvailable and maxUnavailable are mutually exclusive; set only one.
|
||||
minAvailable: 1
|
||||
maxUnavailable: ""
|
||||
# See https://kubernetes.io/docs/tasks/run-application/configure-pdb/
|
||||
# unhealthyPodEvictionPolicy: IfHealthyBudget
|
||||
annotations: {}
|
||||
labels: {}
|
||||
|
||||
# Extra volumes for the Velero deployment. Optional.
|
||||
extraVolumes: []
|
||||
|
||||
# Extra volumeMounts for the Velero deployment. Optional.
|
||||
extraVolumeMounts: []
|
||||
|
||||
# Extra K8s manifests to deploy
|
||||
extraObjects: []
|
||||
# - apiVersion: secrets-store.csi.x-k8s.io/v1
|
||||
# kind: SecretProviderClass
|
||||
# metadata:
|
||||
# name: velero-secrets-store
|
||||
# spec:
|
||||
# provider: aws
|
||||
# parameters:
|
||||
# objects: |
|
||||
# - objectName: "velero"
|
||||
# objectType: "secretsmanager"
|
||||
# jmesPath:
|
||||
# - path: "access_key"
|
||||
# objectAlias: "access_key"
|
||||
# - path: "secret_key"
|
||||
# objectAlias: "secret_key"
|
||||
# secretObjects:
|
||||
# - data:
|
||||
# - key: access_key
|
||||
# objectName: client-id
|
||||
# - key: client-secret
|
||||
# objectName: client-secret
|
||||
# secretName: velero-secrets-store
|
||||
# type: Opaque
|
||||
|
||||
# Settings for Velero's prometheus metrics. Enabled by default.
|
||||
metrics:
|
||||
enabled: true
|
||||
scrapeInterval: 30s
|
||||
scrapeTimeout: 10s
|
||||
|
||||
# service metdata if metrics are enabled
|
||||
service:
|
||||
annotations: {}
|
||||
type: ClusterIP
|
||||
labels: {}
|
||||
nodePort: null
|
||||
|
||||
# External/Internal traffic policy setting (Cluster, Local)
|
||||
# https://kubernetes.io/docs/reference/networking/virtual-ips/#traffic-policies
|
||||
externalTrafficPolicy: ""
|
||||
internalTrafficPolicy: ""
|
||||
|
||||
# the IP family policy for the metrics Service to be able to configure dual-stack; see [Configure dual-stack](https://kubernetes.io/docs/concepts/services-networking/dual-stack/#services).
|
||||
ipFamilyPolicy: ""
|
||||
# a list of IP families for the metrics Service that should be supported, in the order in which they should be applied to ClusterIP. Can be "IPv4" and/or "IPv6".
|
||||
ipFamilies: []
|
||||
|
||||
# Pod annotations for Prometheus
|
||||
podAnnotations:
|
||||
prometheus.io/scrape: "true"
|
||||
prometheus.io/port: "8085"
|
||||
prometheus.io/path: "/metrics"
|
||||
|
||||
serviceMonitor:
|
||||
autodetect: true
|
||||
enabled: false
|
||||
annotations: {}
|
||||
additionalLabels: {}
|
||||
|
||||
# metrics.serviceMonitor.metricRelabelings Specify Metric Relabelings to add to the scrape endpoint
|
||||
# ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#relabelconfig
|
||||
# metricRelabelings: []
|
||||
# metrics.serviceMonitor.relabelings [array] Prometheus relabeling rules
|
||||
# relabelings: []
|
||||
# ServiceMonitor namespace. Default to Velero namespace.
|
||||
# namespace:
|
||||
# ServiceMonitor connection scheme. Defaults to HTTP.
|
||||
# scheme: ""
|
||||
# ServiceMonitor connection tlsConfig. Defaults to {}.
|
||||
# tlsConfig: {}
|
||||
nodeAgentPodMonitor:
|
||||
autodetect: true
|
||||
enabled: false
|
||||
annotations: {}
|
||||
additionalLabels: {}
|
||||
# metrics.nodeAgentPodMonitor.metricRelabelings Specify Metric Relabelings to add to the scrape endpoint
|
||||
# ref: https://github.com/coreos/prometheus-operator/blob/master/Documentation/api.md#relabelconfig
|
||||
# metricRelabelings: []
|
||||
# metrics.nodeAgentPodMonitor.relabelings [array] Prometheus relabeling rules
|
||||
# relabelings: []
|
||||
# PodMonitor namespace. Default to Velero namespace.
|
||||
# namespace:
|
||||
# PodMonitor connection scheme. Defaults to HTTP.
|
||||
# scheme: ""
|
||||
# PodMonitor connection tlsConfig. Defaults to {}.
|
||||
# tlsConfig: {}
|
||||
|
||||
prometheusRule:
|
||||
autodetect: true
|
||||
enabled: false
|
||||
# Additional labels to add to deployed PrometheusRule
|
||||
additionalLabels: {}
|
||||
# PrometheusRule namespace. Defaults to Velero namespace.
|
||||
# namespace: ""
|
||||
# Rules to be deployed
|
||||
spec: []
|
||||
# - alert: VeleroBackupFailed
|
||||
# annotations:
|
||||
# message: Velero backup {{ $labels.schedule }} has failed
|
||||
# expr: |-
|
||||
# velero_backup_last_status{schedule!=""} != 1
|
||||
# for: 15m
|
||||
# labels:
|
||||
# severity: warning
|
||||
# - alert: VeleroBackupFailing
|
||||
# annotations:
|
||||
# message: Velero backup {{ $labels.schedule }} has been failing for the last 12h
|
||||
# expr: |-
|
||||
# velero_backup_last_status{schedule!=""} != 1
|
||||
# for: 12h
|
||||
# labels:
|
||||
# severity: critical
|
||||
# - alert: VeleroNoNewBackup
|
||||
# annotations:
|
||||
# message: Velero backup {{ $labels.schedule }} has not run successfully in the last 25h
|
||||
# expr: |-
|
||||
# (
|
||||
# (time() - velero_backup_last_successful_timestamp{schedule!=""}) >bool (25 * 3600)
|
||||
# or
|
||||
# absent(velero_backup_last_successful_timestamp{schedule!=""})
|
||||
# ) == 1
|
||||
# for: 1h
|
||||
# labels:
|
||||
# severity: critical
|
||||
# - alert: VeleroBackupPartialFailures
|
||||
# annotations:
|
||||
# message: Velero backup {{ $labels.schedule }} has {{ $value | humanizePercentage }} partialy failed backups
|
||||
# expr: |-
|
||||
# rate(velero_backup_partial_failure_total{schedule!=""}[25m])
|
||||
# / rate(velero_backup_attempt_total{schedule!=""}[25m]) > 0.5
|
||||
# for: 15m
|
||||
# labels:
|
||||
# severity: warning
|
||||
|
||||
kubectl:
|
||||
image:
|
||||
repository: registry.k8s.io/kubectl
|
||||
# Digest value example: sha256:c7bf8cf6e79f474fda2623a7b98572a1b909c4d912b05062b444ffe79883fdbe.
|
||||
# If used, it will take precedence over the kubectl.image.tag.
|
||||
# digest:
|
||||
# kubectl image tag. If used, it will take precedence over the cluster Kubernetes version.
|
||||
# tag: v1.34.5
|
||||
# Container Level Security Context for the 'kubectl' container of the crd jobs. Optional.
|
||||
# See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||
containerSecurityContext: {}
|
||||
# Resource requests/limits to specify for the upgrade/cleanup job. Optional
|
||||
resources: {}
|
||||
# Annotations to set for the upgrade/cleanup job. Optional.
|
||||
annotations: {}
|
||||
# Labels to set for the upgrade/cleanup job. Optional.
|
||||
labels: {}
|
||||
# Extra volumes for the upgrade/cleanup job. Optional.
|
||||
extraVolumes: []
|
||||
# Extra volumeMounts for the upgrade/cleanup job. Optional.
|
||||
extraVolumeMounts: []
|
||||
|
||||
# This job upgrades the CRDs.
|
||||
upgradeCRDs: true
|
||||
|
||||
# This job is meant primarily for cleaning up CRDs on CI systems.
|
||||
# Using this on production systems, especially those that have multiple releases of Velero, will be destructive.
|
||||
cleanUpCRDs: false
|
||||
|
||||
##
|
||||
## End of deployment-related settings.
|
||||
##
|
||||
|
||||
|
||||
##
|
||||
## Parameters for the `default` BackupStorageLocation and VolumeSnapshotLocation,
|
||||
## and additional server settings.
|
||||
##
|
||||
configuration:
|
||||
# Parameters for the BackupStorageLocation(s). Configure multiple by adding other element(s) to the backupStorageLocation slice.
|
||||
# See https://velero.io/docs/v1.6/api-types/backupstoragelocation/
|
||||
backupStorageLocation:
|
||||
# name is the name of the backup storage location where backups should be stored. If a name is not provided,
|
||||
# a backup storage location will be created with the name "default". Optional.
|
||||
- name:
|
||||
# provider is the name for the backup storage location provider.
|
||||
provider: ""
|
||||
# bucket is the name of the bucket to store backups in. Required.
|
||||
bucket: ""
|
||||
# caCert defines a base64 encoded CA bundle to use when verifying TLS connections to the provider. Optional.
|
||||
caCert:
|
||||
# prefix is the directory under which all Velero data should be stored within the bucket. Optional.
|
||||
prefix:
|
||||
# default indicates this location is the default backup storage location. Optional.
|
||||
default: false
|
||||
# validationFrequency defines how frequently Velero should validate the object storage. Optional.
|
||||
validationFrequency:
|
||||
# backupSyncPeriod defines how frequently Velero should synchronize backups in object storage. Optional.
|
||||
backupSyncPeriod:
|
||||
# accessMode determines if velero can write to this backup storage location. Optional.
|
||||
# default to ReadWrite, ReadOnly is used during migrations and restores.
|
||||
accessMode: ReadWrite
|
||||
credential:
|
||||
# name of the secret used by this backupStorageLocation.
|
||||
name:
|
||||
# name of key that contains the secret data to be used.
|
||||
key:
|
||||
# Additional provider-specific configuration. See link above
|
||||
# for details of required/optional fields for your provider.
|
||||
config: {}
|
||||
# region:
|
||||
# s3ForcePathStyle:
|
||||
# s3Url:
|
||||
# kmsKeyId:
|
||||
# resourceGroup:
|
||||
# The ID of the subscription containing the storage account, if different from the cluster’s subscription. (Azure only)
|
||||
# subscriptionId:
|
||||
# storageAccount:
|
||||
# publicUrl:
|
||||
# Name of the GCP service account to use for this backup storage location. Specify the
|
||||
# service account here if you want to use workload identity instead of providing the key file.(GCP only)
|
||||
# serviceAccount:
|
||||
# Option to skip certificate validation or not if insecureSkipTLSVerify is set to be true, the client side should set the
|
||||
# flag. For Velero client Command like velero backup describe, velero backup logs needs to add the flag --insecure-skip-tls-verify
|
||||
# insecureSkipTLSVerify:
|
||||
|
||||
# annotations allows adding arbitrary annotations to this BackupStorageLocation resource. Optional.
|
||||
annotations: {}
|
||||
|
||||
# Parameters for the VolumeSnapshotLocation(s). Configure multiple by adding other element(s) to the volumeSnapshotLocation slice.
|
||||
# See https://velero.io/docs/v1.6/api-types/volumesnapshotlocation/
|
||||
volumeSnapshotLocation:
|
||||
# name is the name of the volume snapshot location where snapshots are being taken. If a name is not provided,
|
||||
# a volume snapshot location will be created with the name "default". Optional.
|
||||
- name:
|
||||
# provider is the name for the volume snapshot provider.
|
||||
provider: ""
|
||||
credential:
|
||||
# name of the secret used by this volumeSnapshotLocation.
|
||||
name:
|
||||
# name of key that contains the secret data to be used.
|
||||
key:
|
||||
# Additional provider-specific configuration. See link above
|
||||
# for details of required/optional fields for your provider.
|
||||
config: {}
|
||||
# region:
|
||||
# apiTimeout:
|
||||
# resourceGroup:
|
||||
# The ID of the subscription where volume snapshots should be stored, if different from the cluster’s subscription. If specified, also requires `configuration.volumeSnapshotLocation.config.resourceGroup`to be set. (Azure only)
|
||||
# subscriptionId:
|
||||
# incremental:
|
||||
# snapshotLocation:
|
||||
# project:
|
||||
|
||||
# annotations allows adding arbitrary annotations to this VolumeSnapshotLocation resource. Optional.
|
||||
annotations: {}
|
||||
|
||||
# These are server-level settings passed as CLI flags to the `velero server` command. Velero
|
||||
# uses default values if they're not passed in, so they only need to be explicitly specified
|
||||
# here if using a non-default value. The `velero server` default values are shown in the
|
||||
# comments below.
|
||||
# --------------------
|
||||
# `velero server` default: kopia
|
||||
uploaderType:
|
||||
# `velero server` default: 1m
|
||||
backupSyncPeriod:
|
||||
# `velero server` default: 4h
|
||||
fsBackupTimeout:
|
||||
# `velero server` default: 30
|
||||
clientBurst:
|
||||
# `velero server` default: 500
|
||||
clientPageSize:
|
||||
# `velero server` default: 20.0
|
||||
clientQPS:
|
||||
# Name of the default backup storage location. Default: default
|
||||
defaultBackupStorageLocation:
|
||||
# The default duration any single item operation can take before timing out, especially important for large volume schedules. Default 4h
|
||||
defaultItemOperationTimeout:
|
||||
# How long to wait by default before backups can be garbage collected. Default: 72h
|
||||
defaultBackupTTL:
|
||||
# Name of the default volume snapshot location.
|
||||
defaultVolumeSnapshotLocations:
|
||||
# `velero server` default: empty
|
||||
disableControllers:
|
||||
# `velero server` default: false
|
||||
disableInformerCache: false
|
||||
# `velero server` default: 1h
|
||||
garbageCollectionFrequency:
|
||||
# `velero server` default: 1
|
||||
itemBlockWorkerCount:
|
||||
# Set log-format for Velero pod. Default: text. Other option: json.
|
||||
logFormat:
|
||||
# Set log-level for Velero pod. Default: info. Other options: debug, warning, error, fatal, panic.
|
||||
logLevel:
|
||||
# The address to expose prometheus metrics. Default: :8085
|
||||
metricsAddress:
|
||||
# Directory containing Velero plugins. Default: /plugins
|
||||
pluginDir:
|
||||
# The address to expose the pprof profiler. Default: localhost:6060
|
||||
profilerAddress:
|
||||
# `velero server` default: false
|
||||
restoreOnlyMode:
|
||||
# `velero server` default: customresourcedefinitions,namespaces,storageclasses,volumesnapshotclass.snapshot.storage.k8s.io,volumesnapshotcontents.snapshot.storage.k8s.io,volumesnapshots.snapshot.storage.k8s.io,persistentvolumes,persistentvolumeclaims,secrets,configmaps,serviceaccounts,limitranges,pods,replicasets.apps,clusterclasses.cluster.x-k8s.io,clusters.cluster.x-k8s.io,clusterresourcesets.addons.cluster.x-k8s.io
|
||||
restoreResourcePriorities:
|
||||
# `velero server` default: 1m
|
||||
storeValidationFrequency:
|
||||
# How long to wait on persistent volumes and namespaces to terminate during a restore before timing out. Default: 10m
|
||||
terminatingResourceTimeout:
|
||||
# Bool flag to configure Velero server to move data by default for all snapshots supporting data movement. Default: false
|
||||
defaultSnapshotMoveData:
|
||||
# Comma separated list of velero feature flags. default: empty
|
||||
# features: EnableCSI
|
||||
features:
|
||||
# Configures the timeout for provisioning the volume created from the CSI snapshot. Default: 30m
|
||||
dataMoverPrepareTimeout:
|
||||
# Resource requests/limits to specify for the repository-maintenance job. Optional.
|
||||
# https://velero.io/docs/v1.14/repository-maintenance/#resource-limitation
|
||||
repositoryMaintenanceJob:
|
||||
# Per-repository resource settings ConfigMap
|
||||
# This ConfigMap allows specifying different settings for different repositories
|
||||
# See: https://velero.io/docs/main/repository-maintenance/
|
||||
repositoryConfigData:
|
||||
# Name of the ConfigMap to create. If not provided, will use "velero-repo-maintenance"
|
||||
name: "velero-repo-maintenance"
|
||||
# Global configuration applied to all repositories
|
||||
# This configuration is used when no specific repository configuration is found
|
||||
# global:
|
||||
# podResources:
|
||||
# cpuRequest: "100m"
|
||||
# cpuLimit: "200m"
|
||||
# memoryRequest: "100Mi"
|
||||
# memoryLimit: "200Mi"
|
||||
# keepLatestMaintenanceJobs: 1
|
||||
# loadAffinity:
|
||||
# - nodeSelector:
|
||||
# matchExpressions:
|
||||
# - key: "cloud.google.com/machine-family"
|
||||
# operator: "In"
|
||||
# values: ["e2"]
|
||||
# - nodeSelector:
|
||||
# matchExpressions:
|
||||
# - key: "topology.kubernetes.io/zone"
|
||||
# operator: "In"
|
||||
# values: ["us-central1-a", "us-central1-b", "us-central1-c"]
|
||||
# priorityClassName: "low-priority" # Note: priorityClassName is only supported in global configuration
|
||||
global:
|
||||
keepLatestMaintenanceJobs: 3
|
||||
# Repository-specific configurations
|
||||
# Repository keys are formed as: "{namespace}-{storageLocation}-{repositoryType}"
|
||||
# For example: "default-default-kopia" or "prod-s3-backup-kopia"
|
||||
# Note: priorityClassName is NOT supported in repository-specific configurations
|
||||
# repositories:
|
||||
# "kibishii-default-kopia":
|
||||
# podResources:
|
||||
# cpuRequest: "200m"
|
||||
# cpuLimit: "400m"
|
||||
# memoryRequest: "200Mi"
|
||||
# memoryLimit: "400Mi"
|
||||
# keepLatestMaintenanceJobs: 2
|
||||
repositories: {}
|
||||
# `velero server` default: velero
|
||||
namespace:
|
||||
# additional command-line arguments that will be passed to the `velero server`
|
||||
# e.g.: extraArgs: ["--foo=bar"]
|
||||
extraArgs: []
|
||||
|
||||
# Additional values to be used as environment variables. Optional.
|
||||
extraEnvVars: []
|
||||
# Simple value
|
||||
# - name: SIMPLE_VAR
|
||||
# value: "simple-value"
|
||||
|
||||
# FieldRef example
|
||||
# - name: MY_POD_LABEL
|
||||
# valueFrom:
|
||||
# fieldRef:
|
||||
# fieldPath: metadata.labels['my_label']
|
||||
|
||||
# Set true for backup all pod volumes without having to apply annotation on the pod when used file system backup Default: false.
|
||||
defaultVolumesToFsBackup:
|
||||
|
||||
# How often repository maintain is run for repositories by default.
|
||||
defaultRepoMaintainFrequency:
|
||||
|
||||
##
|
||||
## End of backup/snapshot location settings.
|
||||
##
|
||||
|
||||
|
||||
##
|
||||
## Settings for additional Velero resources.
|
||||
##
|
||||
|
||||
rbac:
|
||||
# Whether to create the Velero role and role binding to give all permissions to the namespace to Velero.
|
||||
create: true
|
||||
# Whether to create the cluster role binding to give administrator permissions to Velero
|
||||
clusterAdministrator: true
|
||||
# Name of the ClusterRole.
|
||||
clusterAdministratorName: cluster-admin
|
||||
|
||||
# Information about the Kubernetes service account Velero uses.
|
||||
serviceAccount:
|
||||
server:
|
||||
create: true
|
||||
name:
|
||||
annotations:
|
||||
labels:
|
||||
imagePullSecrets: []
|
||||
# - registrySecretName
|
||||
# Configure if API credential for Service Account is automounted.
|
||||
automountServiceAccountToken: true
|
||||
|
||||
# Info about the secret to be used by the Velero deployment, which
|
||||
# should contain credentials for the cloud provider IAM account you've
|
||||
# set up for Velero.
|
||||
credentials:
|
||||
# Whether a secret should be used. Set to false if, for examples:
|
||||
# - using kube2iam or kiam to provide AWS IAM credentials instead of providing the key file. (AWS only)
|
||||
# - using workload identity instead of providing the key file. (Azure/GCP only)
|
||||
useSecret: true
|
||||
# Name of the secret to create if `useSecret` is true and `existingSecret` is empty
|
||||
name:
|
||||
# Name of a pre-existing secret (if any) in the Velero namespace
|
||||
# that should be used to get IAM account credentials. Optional.
|
||||
existingSecret:
|
||||
# Data to be stored in the Velero secret, if `useSecret` is true and `existingSecret` is empty.
|
||||
# As of the current Velero release, Velero only uses one secret key/value at a time.
|
||||
# The key must be named `cloud`, and the value corresponds to the entire content of your IAM credentials file.
|
||||
# Note that the format will be different for different providers, please check their documentation.
|
||||
# Here is a list of documentation for plugins maintained by the Velero team:
|
||||
# [AWS] https://github.com/vmware-tanzu/velero-plugin-for-aws/blob/main/README.md
|
||||
# [GCP] https://github.com/vmware-tanzu/velero-plugin-for-gcp/blob/main/README.md
|
||||
# [Azure] https://github.com/vmware-tanzu/velero-plugin-for-microsoft-azure/blob/main/README.md
|
||||
secretContents: {}
|
||||
# cloud: |
|
||||
# [default]
|
||||
# aws_access_key_id=<REDACTED>
|
||||
# aws_secret_access_key=<REDACTED>
|
||||
# additional key/value pairs to be used as environment variables such as "DIGITALOCEAN_TOKEN: <your-key>". Values will be stored in the secret.
|
||||
extraEnvVars: {}
|
||||
# Name of a pre-existing secret (if any) in the Velero namespace
|
||||
# that will be used to load environment variables into velero and node-agent.
|
||||
# Secret should be in format - https://kubernetes.io/docs/concepts/configuration/secret/#use-case-as-container-environment-variables
|
||||
extraSecretRef: ""
|
||||
|
||||
# Whether to create backupstoragelocation crd, if false => do not create a default backup location
|
||||
backupsEnabled: true
|
||||
# Whether to create volumesnapshotlocation crd, if false => disable snapshot feature
|
||||
snapshotsEnabled: true
|
||||
|
||||
# Whether to deploy the node-agent daemonset.
|
||||
deployNodeAgent: false
|
||||
|
||||
nodeAgent:
|
||||
# Disables Host Path volumes for node-agent.
|
||||
disableHostPath: false
|
||||
podVolumePath: /var/lib/kubelet/pods
|
||||
pluginVolumePath: /var/lib/kubelet/plugins
|
||||
# Pod priority class name to use for the node-agent daemonset. Optional.
|
||||
priorityClassName: ""
|
||||
# Pod runtime class name to use for the node-agent daemonset. Optional.
|
||||
runtimeClassName: ""
|
||||
# Resource requests/limits to specify for the node-agent daemonset deployment. Optional.
|
||||
# https://velero.io/docs/v1.6/customize-installation/#customize-resource-requests-and-limits
|
||||
resources: {}
|
||||
# requests:
|
||||
# cpu: 500m
|
||||
# memory: 512Mi
|
||||
# limits:
|
||||
# cpu: 1000m
|
||||
# memory: 1024Mi
|
||||
# Container resize policy for the node-agent daemonset.
|
||||
# See: https://kubernetes.io/docs/tasks/configure-pod-container/resize-container-resources/
|
||||
resizePolicy: []
|
||||
# - resourceName: cpu
|
||||
# restartPolicy: NotRequired
|
||||
# - resourceName: memory
|
||||
# restartPolicy: RestartContainer
|
||||
|
||||
# Tolerations to use for the node-agent daemonset. Optional.
|
||||
tolerations: []
|
||||
|
||||
# Annotations to set for the node-agent daemonset. Optional.
|
||||
annotations: {}
|
||||
|
||||
# labels to set for the node-agent daemonset. Optional.
|
||||
labels: {}
|
||||
|
||||
# Additional pod labels for the node-agent daemonset. Optional
|
||||
# ref: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/
|
||||
podLabels: {}
|
||||
|
||||
# will map /scratch to emptyDir. Set to false and specify your own volume
|
||||
# via extraVolumes and extraVolumeMounts that maps to /scratch
|
||||
# if you don't want to use emptyDir.
|
||||
useScratchEmptyDir: true
|
||||
|
||||
# Extra volumes for the node-agent daemonset. Optional.
|
||||
extraVolumes: []
|
||||
|
||||
# Extra volumeMounts for the node-agent daemonset. Optional.
|
||||
extraVolumeMounts: []
|
||||
|
||||
# Additional values to be used as environment variables for node-agent daemonset. Optional.
|
||||
extraEnvVars: []
|
||||
# Simple key/value
|
||||
# - name: SIMPLE_VAR
|
||||
# value: "simple-value"
|
||||
|
||||
# FieldRef example
|
||||
# - name: MY_POD_LABEL
|
||||
# valueFrom:
|
||||
# fieldRef:
|
||||
# fieldPath: metadata.labels['my_label']
|
||||
|
||||
# Additional command-line arguments that will be passed to the node-agent. Optional.
|
||||
# e.g.: extraArgs: ["--foo=bar"]
|
||||
extraArgs: []
|
||||
|
||||
# Configure the dnsPolicy of the node-agent daemonset
|
||||
# See: https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy
|
||||
dnsPolicy: ClusterFirst
|
||||
|
||||
# Configure hostAliases for node-agent daemonset. Optional
|
||||
# For more information, check: https://kubernetes.io/docs/tasks/network/customize-hosts-file-for-pods/
|
||||
hostAliases: []
|
||||
# - ip: "127.0.0.1"
|
||||
# hostnames:
|
||||
# - "foo.local"
|
||||
# - "bar.local"
|
||||
|
||||
# SecurityContext to use for the Velero deployment. Optional.
|
||||
# Set fsGroup for `AWS IAM Roles for Service Accounts`
|
||||
# see more informations at: https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html
|
||||
podSecurityContext:
|
||||
runAsUser: 0
|
||||
# fsGroup: 1337
|
||||
|
||||
# Container Level Security Context for the 'node-agent' container of the node-agent daemonset. Optional.
|
||||
# See: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-container
|
||||
containerSecurityContext: {}
|
||||
|
||||
# Container Lifecycle Hooks to use for the node-agent daemonset. Optional.
|
||||
lifecycle: {}
|
||||
|
||||
# Node selector to use for the node-agent daemonset. Optional.
|
||||
nodeSelector: {}
|
||||
|
||||
# Affinity to use with node-agent daemonset. Optional.
|
||||
affinity: {}
|
||||
|
||||
# DNS configuration to use for the node-agent daemonset. Optional.
|
||||
dnsConfig: {}
|
||||
|
||||
# Update strategy to use for the node-agent daemonset. Optional.
|
||||
updateStrategy: {}
|
||||
|
||||
# Backup schedules to create.
|
||||
# Eg:
|
||||
# schedules:
|
||||
# mybackup:
|
||||
# disabled: false
|
||||
# labels:
|
||||
# myenv: foo
|
||||
# annotations:
|
||||
# myenv: foo
|
||||
# schedule: "0 0 * * *"
|
||||
# useOwnerReferencesInBackup: false
|
||||
# paused: false
|
||||
# skipImmediately: false
|
||||
# template:
|
||||
# ttl: "240h"
|
||||
# storageLocation: default
|
||||
# includedNamespaces:
|
||||
# - foo
|
||||
# # See: https://velero.io/docs/v1.14/resource-filtering/#excludes
|
||||
# excludedNamespaceScopedResources:
|
||||
# - persistentVolumeClaims
|
||||
# excludedClusterScopedResources:
|
||||
# - persistentVolumes
|
||||
schedules: {}
|
||||
|
||||
# Velero ConfigMaps.
|
||||
# Eg:
|
||||
# configMaps:
|
||||
# See: https://velero.io/docs/v1.11/file-system-backup/
|
||||
# fs-restore-action-config:
|
||||
# labels:
|
||||
# velero.io/plugin-config: ""
|
||||
# velero.io/pod-volume-restore: RestoreItemAction
|
||||
# data:
|
||||
# image: velero/velero:v1.17.1
|
||||
# cpuRequest: 200m
|
||||
# memRequest: 128Mi
|
||||
# cpuLimit: 200m
|
||||
# memLimit: 128Mi
|
||||
# secCtx: |
|
||||
# capabilities:
|
||||
# drop:
|
||||
# - ALL
|
||||
# add: []
|
||||
# allowPrivilegeEscalation: false
|
||||
# readOnlyRootFilesystem: true
|
||||
# runAsUser: 1001
|
||||
# runAsGroup: 999
|
||||
configMaps: {}
|
||||
|
||||
##
|
||||
## End of additional Velero resource settings.
|
||||
##
|
||||
|
||||
Binary file not shown.
|
|
@ -1,4 +1,4 @@
|
|||
# This file is maintained automatically by "tofu init".
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.opentofu.org/ovh/ovh" {
|
||||
|
|
@ -21,3 +21,84 @@ provider "registry.opentofu.org/ovh/ovh" {
|
|||
"zh:f953ef37e4a1b3cac22f851c3269e7376704018ffa642b4768869cc227ffccc9",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/google" {
|
||||
version = "5.45.2"
|
||||
constraints = "~> 5.0"
|
||||
hashes = [
|
||||
"h1:y2hf6zus1eKA5vpAfoyYkNBKDBQTVqmx4OVh3iRBaRo=",
|
||||
"zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a",
|
||||
"zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804",
|
||||
"zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e",
|
||||
"zh:3e555cf22a570a4bd247964671f421ed7517970cd9765ceb46f335edc2c6f392",
|
||||
"zh:688bd5b05a75124da7ae6e885b2b92bd29f4261808b2b78bd5f51f525c1052ca",
|
||||
"zh:6db3ef37a05010d82900bfffb3261c59a0c247e0692049cb3eb8c2ef16c9d7bf",
|
||||
"zh:70316fde75f6a15d72749f66d994ccbdde5f5ed4311b6d06b99850f698c9bbf9",
|
||||
"zh:84b8e583771a4f2bd514e519d98ed7fd28dce5efe0634e973170e1cfb5556fb4",
|
||||
"zh:9d4b8ef0a9b6677935c604d94495042e68ff5489932cfd1ec41052e094a279d3",
|
||||
"zh:a2089dd9bd825c107b148dd12d6b286f71aa37dfd4ca9c35157f2dcba7bc19d8",
|
||||
"zh:f03d795c0fd9721e59839255ee7ba7414173017dc530b4ce566daf3802a0d6dd",
|
||||
"zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/local" {
|
||||
version = "2.9.0"
|
||||
constraints = "~> 2.0"
|
||||
hashes = [
|
||||
"h1:oF4vw1rikPMqQtMMBoUjc/pERp50DlWwO+C2I6/sNI8=",
|
||||
"zh:0baa4566cf77f1ff52f4293d1c8536202dd23edc197c3196413a28343c3ac3a0",
|
||||
"zh:16b5559c3c07088ddad11a9bb9e9c0799999363c2958e9a5be2bcbbf2cd9ca64",
|
||||
"zh:197c79015a10d1cce904a8ea722cbc750c42aeae2da53f44a6a0751d9fd1aa90",
|
||||
"zh:29d0b03e5343a80677ebfeb2e2c31cbe4b1f65e736e53417454a4277fec2544c",
|
||||
"zh:4896bfa6cf1d2fd562b47ef2e87f47862ae92a04f8ad5d764380f0c6653473b8",
|
||||
"zh:531f8529cbca49f681883e57761a05a8398afaef6d1ab0d205d26bf12f4428e8",
|
||||
"zh:6aaf5011d83161c86d2bfb80c0923ec934e578288758da2f37acb7aec129004b",
|
||||
"zh:7430275253d3d3c40aa6179e0ec0d63212874dbbc06c5a51b9d07ec590f9756c",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:be17dc611e95e26cdf6cad79dfccf1064f0e32032a2efeb939a9bbe7fb1cbfe9",
|
||||
"zh:f0e3b0aa644202e1d79d2000dca91f6019425da71e9800fa23f27e51c034f195",
|
||||
"zh:f62bae4519e4ead49182ddc8afe8cf61e2a4c3ba3973b0fbba967736a2696aa3",
|
||||
"zh:fcafa360a5b0b96244f26f4e3a6d642b716a376557142c2442ff2fb12d11da18",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/hashicorp/random" {
|
||||
version = "3.9.0"
|
||||
hashes = [
|
||||
"h1:o0s5Mk9NXMP60nlheO1r0LsDGGratFb3oL0t7bD2QnM=",
|
||||
"zh:161ad0bd9a75768c82f53fb6e7172a9d8be2d4889b012645a34795031aaf1bf1",
|
||||
"zh:19dc9a5b17729725ccfc4f45b0500af0ee5bc6b6b160c7adb8f2bf617d2c80ea",
|
||||
"zh:269eda8fe42daa7974d5a34d166c3ba9defe80cde86c01e4dadcfdf2e1f05e5f",
|
||||
"zh:373f7c65566f8f2cc7f45d698654feb9d988996957e1266a69ca00c52d6d16d0",
|
||||
"zh:5599d16804c41c83009ec621b6d6b6f74e102f5827678a4750f8809055546b61",
|
||||
"zh:583be0440469a22bff70dcfa56593b01566860b29607437264adb51060cf46fc",
|
||||
"zh:5f211d8ec3f2e1f414870d9584bfe26e6995560ef81c748f8447a48164767398",
|
||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
||||
"zh:7b547fd16216761ef86efc3ed516ac5ac0c5c42b7c7eb24a08cef2d93f69ed5e",
|
||||
"zh:7e7c0679daf2a382151d05068c8c3f0dae6b7b7dccf818827b73dd08638df2ef",
|
||||
"zh:8089dec888a8038b9b4fb23b3df7e1057293dbc5b60b42cc47ff690d69d4b61b",
|
||||
"zh:c51f15a031edfd6f23ce8ced3446ca7f8d8d647e2499890d7d5d10d5016d7257",
|
||||
"zh:c94784f005708890dc6895afd53636ec00ec1e430b15d41e5aebfb1d4b39bd04",
|
||||
]
|
||||
}
|
||||
|
||||
provider "registry.terraform.io/ovh/ovh" {
|
||||
version = "2.15.0"
|
||||
hashes = [
|
||||
"h1:Y6pQdKjn1OR4wNlxRpy+nI7Dvap/bKzFb68fynMxTmI=",
|
||||
"zh:22d361f99caa70b07532fce13208fce8b84072cf83eaf6ddfcd9096df88b57ea",
|
||||
"zh:497f88936649d3c352fdb639ba5ebdabfec8ec7a59d67bf619013e3afbbaefb4",
|
||||
"zh:62e49208e2950daf366b396694dc7de5bba31a8b7a10aac7c021f2c78fd64f70",
|
||||
"zh:97e06f7494706df30c136f4809314d92a25129738cf080e22c5c9e47ec70c4cf",
|
||||
"zh:be23cf1ecb2eb76c60dad72eb4f9d005c6184cae6e2cf9f13b0462030133644c",
|
||||
"zh:bf60c1af6db5ca51ff4372fee8e64a21462134d281ae9cb3968ca5b209338c4b",
|
||||
"zh:c9e5ad5c063249786595c5b6c3f59791bd21f5157ff0572a4ed2d52ca8141c50",
|
||||
"zh:d2bf2603741df6fc29a88384f7026fb8b0e982b8a270796e672037d287b41e83",
|
||||
"zh:d944de8032fc01d8488087755bf7fb813e7b8c259e23a7b68ed654905ad19b56",
|
||||
"zh:dcf2a7620abac38e9d8f0a88f2a79de0fddd917ba0facba1108f6581eb1b7f6f",
|
||||
"zh:e7c7905954f69f0d5b6cf9bd87572101924e128aa33311856dbbfedd711361d2",
|
||||
"zh:f6a9977d744ed879a0b3c28a5a800b6a1f49aabe8d71e87ca1d02fe6a77a31ba",
|
||||
"zh:fd0e48de826df04e18489d33b0ff35bd776cb6e7704188f67dad66cbb041e5af",
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
variable "gcp_project_id" {
|
||||
type = string
|
||||
description = "The GCP Project ID where the Service Account should be created. If empty, a new project named 'velero-backup-xxxx' will be created."
|
||||
default = ""
|
||||
}
|
||||
|
||||
resource "random_id" "project_suffix" {
|
||||
byte_length = 4
|
||||
}
|
||||
|
||||
resource "google_project" "backup_project" {
|
||||
count = var.gcp_project_id == "" ? 1 : 0
|
||||
name = "velero-backup"
|
||||
project_id = "velero-backup-${random_id.project_suffix.hex}"
|
||||
}
|
||||
|
||||
locals {
|
||||
project_id = var.gcp_project_id == "" ? google_project.backup_project[0].project_id : var.gcp_project_id
|
||||
}
|
||||
|
||||
resource "google_project_service" "drive_api" {
|
||||
project = local.project_id
|
||||
service = "drive.googleapis.com"
|
||||
disable_on_destroy = false
|
||||
}
|
||||
|
||||
resource "google_project_organization_policy" "allow_key_creation" {
|
||||
project = local.project_id
|
||||
constraint = "iam.disableServiceAccountKeyCreation"
|
||||
|
||||
boolean_policy {
|
||||
enforced = false
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_service_account" "velero_backup_sa" {
|
||||
project = local.project_id
|
||||
account_id = "velero-backup-sa"
|
||||
display_name = "Velero GDrive Backup Service Account"
|
||||
depends_on = [google_project_service.drive_api]
|
||||
}
|
||||
|
||||
resource "google_service_account_key" "velero_backup_sa_key" {
|
||||
service_account_id = google_service_account.velero_backup_sa.name
|
||||
depends_on = [google_project_organization_policy.allow_key_creation]
|
||||
}
|
||||
|
||||
resource "local_file" "gdrive_sa_key_file" {
|
||||
content = base64decode(google_service_account_key.velero_backup_sa_key.private_key)
|
||||
filename = "${path.module}/../k8s/velero/gdrive_sa.json.secret"
|
||||
}
|
||||
|
||||
output "velero_backup_sa_email" {
|
||||
value = google_service_account.velero_backup_sa.email
|
||||
description = "The email address of the Google Cloud Service Account. Share your Google Drive backup folder with this email address and grant it Editor access."
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -3,6 +3,14 @@ terraform {
|
|||
ovh = {
|
||||
source = "ovh/ovh"
|
||||
}
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "~> 5.0"
|
||||
}
|
||||
local = {
|
||||
source = "hashicorp/local"
|
||||
version = "~> 2.0"
|
||||
}
|
||||
}
|
||||
required_version = ">= 0.13"
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue