# logging/grafana-values.yaml # Grafana — dashboarding and log/metrics exploration UI. # Deployed in the logging namespace alongside Loki and Promtail. # # Secrets never set here: # adminPassword — injected via helmfile --set (GRAFANA_ADMIN_PASSWORD) # OAuth client secret — mounted from the grafana-oidc K8s Secret (envFromSecret below) replicas: 1 # RWO PVC (Longhorn) — old pod must fully terminate before the new one can # mount the volume. Recreate avoids the "two pods fighting over one PVC" failure. deploymentStrategy: type: Recreate podAnnotations: secret.reloader.stakater.com/reload: "grafana-oidc" adminUser: admin resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 256Mi # ── Persistence ─────────────────────────────────────────────────────────────── # Stores dashboards saved in the UI, datasource edits, and user sessions. # Longhorn provides the RWO block volume; 5Gi is ample for dashboard JSON. persistence: enabled: true storageClassName: longhorn accessModes: - ReadWriteOnce size: 5Gi # ── Grafana config ──────────────────────────────────────────────────────────── grafana.ini: server: root_url: https://grafana.riotpiao.homelab.com # No anonymous read access — every user must log in via Authentik SSO. auth.anonymous: enabled: false # Explore tab: required for ad-hoc LogQL/PromQL queries against Loki/Prometheus. explore: enabled: true # WAL for the embedded SQLite DB — prevents corruption on ungraceful shutdown. database: wal: true # ── Authentik OIDC (generic OAuth2) ─────────────────────────────────────── # Grafana v10+ supports OIDC auto-discovery; we wire it manually here because # Authentik's discovery endpoint is internal-only (no external DNS for iam.svc). # All URLs use the external hostname so CoreDNS rewrites them to # authentik-server.iam.svc — this keeps the Host header correct so Authentik # doesn't return localhost redirects in its token responses. # # role_attribute_path: JMESPath expression evaluated against the userinfo # response. Members of the 'grafana-admins' Authentik group get Admin role; # everyone else gets Viewer. The group name must match exactly what Authentik # sends in the 'groups' claim. auth.generic_oauth: enabled: true name: Authentik allow_sign_up: true client_id: grafana scopes: openid email profile auth_url: https://authentik.riotpiao.homelab.com/application/o/authorize/ token_url: https://authentik.riotpiao.homelab.com/application/o/token/ api_url: https://authentik.riotpiao.homelab.com/application/o/userinfo/ role_attribute_path: "contains(groups[*], 'grafana-admins') && 'Admin' || 'Viewer'" use_pkce: false use_refresh_token: false skip_org_role_sync: false tls_skip_verify_insecure: true # Authentik uses self-signed cert; verify in prod # GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET is injected from the grafana-oidc K8s # Secret (created by k8s/talos-iam/setup_talos_iam.sh). envFromSecret mounts # every key in that secret as an environment variable — keeps secrets out of # values files and out of git. envFromSecret: grafana-oidc # ── Datasources ─────────────────────────────────────────────────────────────── # Provisioned at install — no manual "Add datasource" step in the UI. # Loki is the default (log exploration); Prometheus is secondary (metrics). # Both use in-cluster service DNS — Grafana never leaves the cluster for data. datasources: datasources.yaml: apiVersion: 1 datasources: - name: Loki type: loki uid: loki access: proxy url: http://loki.logging.svc.cluster.local:3100 isDefault: true version: 1 editable: true jsonData: maxLines: 1000 timeout: 60 - name: Prometheus type: prometheus uid: prometheus access: proxy url: http://prometheus-kube-prometheus-prometheus.monitoring.svc.cluster.local:9090 isDefault: false version: 1 editable: true jsonData: timeInterval: 30s timeout: 60 # ── Dashboard providers ─────────────────────────────────────────────────────── # Tells Grafana to watch a directory for dashboard JSON files. # The `dashboards` block below populates that directory via an init container # that downloads from grafana.com at install time. dashboardProviders: dashboardproviders.yaml: apiVersion: 1 providers: - name: default orgId: 1 folder: "" type: file disableDeletion: false editable: true options: path: /var/lib/grafana/dashboards/default # ── Pre-loaded dashboards ────────────────────────────────────────────────────── # Fetched from grafana.com by an init container at helm-install time and baked # into ConfigMaps. gnetId + revision pin the exact dashboard version so an # upstream change on grafana.com doesn't silently alter what's deployed. dashboards: default: node-exporter-full: gnetId: 1860 revision: 37 datasource: Prometheus kubernetes-cluster: gnetId: 7249 revision: 1 datasource: Prometheus # Allow scheduling on the control-plane node (talos-cp-1 carries NoSchedule taint). tolerations: - key: node-role.kubernetes.io/control-plane operator: Exists effect: NoSchedule # ClusterIP only — access via ingress (grafana.riotpiao.homelab.com) or port-forward. service: type: ClusterIP port: 80 # Ingress managed by k8s/ingress/ingress.yaml — not the chart's built-in ingress. ingress: enabled: false serviceMonitor: enabled: false # ── Dashboard-as-code (sidecar discovery) ────────────────────────────────────── # Additive to the gnetId-download mechanism above. The sidecar watches for # ConfigMaps labeled grafana_dashboard=1 in ANY namespace and loads them live — # no Grafana restart needed when a new dashboard ConfigMap is applied. sidecar: dashboards: enabled: true label: grafana_dashboard labelValue: "1" folder: /var/lib/grafana/dashboards/custom folderAnnotation: grafana_folder provider: name: custom folder: "Homelab" disableDelete: false foldersFromFilesStructure: true searchNamespace: ALL