Files
homelab/k8s/infra/logging/grafana-values.yaml
T
rock f61e8f1f68 fix(grafana): use preferred_username for OAuth login lookup
Reverts to standard OpenID 'preferred_username' claim which is guaranteed to be present in all userinfo responses. The 'email' claim requires explicit scope mapping in Authentik that may not be consistently returned.

Authentik user 'rock' has preferred_username='rock' which matches the existing Grafana user login.
2026-09-15 00:17:50 +09:00

212 lines
8.1 KiB
YAML

# logging/grafana-values.yaml
# Grafana — dashboarding and log/metrics exploration UI.
# Deployed in the logging namespace alongside Loki and Promtail.
#
# Secrets provided by ArgoCD SOPS CMP plugin:
# adminPassword — mounted from the grafana-admin K8s Secret (admin.existingSecret)
# 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,grafana-admin"
adminUser: admin
admin:
existingSecret: grafana-admin
existingSecretKey: admin-password
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.com
# Allow embedding dashboards in iframes (NextJS integration)
security:
allow_embedding: true
# 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. akadmin gets GrafanaAdmin (server admin, can impersonate);
# homelab-admins members get Admin (org admin); everyone else Viewer.
auth.generic_oauth:
enabled: true
name: Authentik
allow_sign_up: true # Allow sync to create/update users from OAuth claims
client_id: grafana
scopes: openid email profile groups
auth_url: https://authentik.riotpiao.com/application/o/authorize/
token_url: https://authentik.riotpiao.com/application/o/token/
api_url: https://authentik.riotpiao.com/application/o/userinfo/
# Read identity straight from the userinfo/id_token claims. Without these,
# Grafana falls back to the GitHub-style "<api_url>/emails" call, which
# Authentik doesn't serve — request 404s with "Error getting email address"
# and the whole OAuth login fails.
email_attribute_path: email
login_attribute_path: preferred_username # Use preferred_username — OpenID standard claim, always present
name_attribute_path: name
role_attribute_path: "preferred_username == 'akadmin' && 'GrafanaAdmin' || contains(groups[*], 'homelab-admins') && 'Admin' || 'Viewer'"
allow_assign_grafana_admin: true
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
# Pin to az-a (talos-cp-1) — the only node running Longhorn. Without this the
# scheduler may place grafana on cp-2/cp-3, where its Longhorn PVC cannot attach
# ("CSINode does not contain driver driver.longhorn.io").
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: [az-a]
# ClusterIP only — access via ingress (grafana.riotpiao.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