mirror of
https://gitlab.kube.huskypup.net/Scooby/Homelabv4.git
synced 2026-08-20 23:16:49 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,34 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: grafana-dashboards-provider
|
||||
namespace: grafana
|
||||
data:
|
||||
dashboards.yaml: |
|
||||
apiVersion: 1
|
||||
providers:
|
||||
- name: 'default'
|
||||
orgId: 1
|
||||
folder: ''
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards/default
|
||||
- name: 'kubernetes'
|
||||
orgId: 1
|
||||
folder: 'Kubernetes'
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards/kubernetes
|
||||
- name: 'infrastructure'
|
||||
orgId: 1
|
||||
folder: 'Infrastructure'
|
||||
type: file
|
||||
disableDeletion: false
|
||||
editable: true
|
||||
options:
|
||||
path: /var/lib/grafana/dashboards/infrastructure
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,20 @@
|
||||
apiVersion: gateway.networking.k8s.io/v1
|
||||
kind: HTTPRoute
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: grafana
|
||||
spec:
|
||||
parentRefs:
|
||||
- name: edge
|
||||
namespace: gateway
|
||||
sectionName: https
|
||||
hostnames:
|
||||
- grafana.kube.huskypup.net
|
||||
rules:
|
||||
- matches:
|
||||
- path:
|
||||
type: PathPrefix
|
||||
value: /
|
||||
backendRefs:
|
||||
- name: grafana
|
||||
port: 80
|
||||
@@ -0,0 +1,12 @@
|
||||
# infrastructure/grafana/grafana-oauth-secret.yaml
|
||||
# Grafana OAuth secret - hardcoded from blueprint
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: grafana-authentik-oauth
|
||||
namespace: grafana
|
||||
type: Opaque
|
||||
stringData:
|
||||
GF_AUTH_GENERIC_OAUTH_CLIENT_ID: "bd03e9139dd2063c6c44c4d2f65f51d69de3ba0b6d6b1b9b41c255d2376d2dcc"
|
||||
GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET: "6b6f2deecfe8fd56cae9c512cc71eedd463d67ff08f24c816b15b15e78ce36bc4f06a2276c5ffe3f67799d935994a32ed32cdc02ec674f279c82d7cfe3ca05d5"
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: networking.istio.io/v1beta1
|
||||
kind: VirtualService
|
||||
metadata:
|
||||
name: grafana
|
||||
namespace: grafana
|
||||
spec:
|
||||
hosts:
|
||||
- grafana.kube.huskypup.net
|
||||
gateways:
|
||||
- istio-system/edge
|
||||
http:
|
||||
- route:
|
||||
- destination:
|
||||
host: grafana.grafana.svc.cluster.local
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,11 @@
|
||||
---
|
||||
# Secret containing TeslaMate database password for Grafana datasource
|
||||
# This is created by the Grafana presync hook from the CNPG secret
|
||||
#
|
||||
# The secret is referenced in values.yaml:
|
||||
# envFromSecrets:
|
||||
# - name: grafana-teslamate-datasource
|
||||
#
|
||||
# And used in the datasource configuration:
|
||||
# secureJsonData:
|
||||
# password: $__env{TESLAMATE_DB_PASSWORD}
|
||||
@@ -0,0 +1,195 @@
|
||||
---
|
||||
# CronJob to sync TeslaMate database password from CNPG secret to Grafana datasource secret
|
||||
# This ensures Grafana always has the current password even when CNPG rotates it
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: sync-teslamate-password
|
||||
namespace: grafana
|
||||
spec:
|
||||
# Run every 30 minutes to catch password rotations (rotations happen at most daily)
|
||||
schedule: "*/30 * * * *"
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
concurrencyPolicy: Forbid
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: teslamate-password-sync
|
||||
spec:
|
||||
serviceAccountName: teslamate-password-sync
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: sync
|
||||
image: docker.io/alpine/k8s:1.32.13
|
||||
securityContext:
|
||||
runAsUser: 10000
|
||||
runAsGroup: 10000
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
volumeMounts:
|
||||
- name: tmp
|
||||
mountPath: /tmp
|
||||
env:
|
||||
- name: GRAFANA_ADMIN_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: grafana-admin-secret
|
||||
key: admin-password
|
||||
optional: true
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
echo "Fetching current password from CNPG secret in teslamate namespace..."
|
||||
CURRENT_PASSWORD=$(kubectl get secret -n teslamate pg-teslamate-app -o jsonpath='{.data.password}' | base64 -d)
|
||||
|
||||
echo "Fetching current password from Grafana datasource secret..."
|
||||
GRAFANA_PASSWORD=$(kubectl get secret -n grafana grafana-teslamate-datasource -o jsonpath='{.data.TESLAMATE_DB_PASSWORD}' | base64 -d)
|
||||
|
||||
if [ "$CURRENT_PASSWORD" != "$GRAFANA_PASSWORD" ]; then
|
||||
echo "Passwords differ - updating Grafana secret..."
|
||||
kubectl create secret generic grafana-teslamate-datasource \
|
||||
--from-literal=TESLAMATE_DB_PASSWORD="$CURRENT_PASSWORD" \
|
||||
-n grafana \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
echo "Password synced to secret"
|
||||
else
|
||||
echo "Passwords match in secrets"
|
||||
fi
|
||||
|
||||
echo "Finding running Grafana pod..."
|
||||
GRAFANA_POD=$(kubectl get pod -n grafana -l app.kubernetes.io/name=grafana --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}')
|
||||
|
||||
if [ -z "$GRAFANA_POD" ]; then
|
||||
echo "No running Grafana pod found, skipping API update"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Using Grafana pod: $GRAFANA_POD"
|
||||
echo "Updating Grafana datasource password via API (ensures password works after Grafana restart)..."
|
||||
|
||||
# Get admin password from Grafana secret (fallback to 'admin' if not found)
|
||||
ADMIN_PASS=$(kubectl get secret -n grafana grafana -o jsonpath='{.data.admin-password}' 2>/dev/null | base64 -d || echo "admin")
|
||||
|
||||
# Update datasource via API with current password
|
||||
RESULT=$(kubectl exec -n grafana "$GRAFANA_POD" -c grafana -- curl -s -X PUT \
|
||||
-H "Content-Type: application/json" \
|
||||
-u "admin:$ADMIN_PASS" \
|
||||
http://localhost:3000/api/datasources/uid/TeslaMate \
|
||||
-d "{
|
||||
\"name\": \"TeslaMate\",
|
||||
\"type\": \"grafana-postgresql-datasource\",
|
||||
\"uid\": \"TeslaMate\",
|
||||
\"url\": \"pg-teslamate-rw.teslamate.svc.cluster.local:5432\",
|
||||
\"database\": \"teslamate\",
|
||||
\"user\": \"teslamate\",
|
||||
\"access\": \"proxy\",
|
||||
\"isDefault\": false,
|
||||
\"secureJsonData\": {
|
||||
\"password\": \"$CURRENT_PASSWORD\"
|
||||
},
|
||||
\"jsonData\": {
|
||||
\"sslmode\": \"disable\",
|
||||
\"postgresVersion\": 1600,
|
||||
\"timescaledb\": false,
|
||||
\"database\": \"teslamate\"
|
||||
}
|
||||
}")
|
||||
|
||||
echo "API Response: $RESULT"
|
||||
|
||||
# Test datasource connection
|
||||
echo "Testing datasource connection..."
|
||||
TEST_RESULT=$(kubectl exec -n grafana "$GRAFANA_POD" -c grafana -- curl -s -X POST \
|
||||
-u "admin:$ADMIN_PASS" \
|
||||
http://localhost:3000/api/datasources/uid/TeslaMate/health)
|
||||
|
||||
echo "Health Check: $TEST_RESULT"
|
||||
|
||||
if echo "$TEST_RESULT" | grep -q '"status":"OK"'; then
|
||||
echo "✅ Datasource password updated and verified successfully!"
|
||||
else
|
||||
echo "⚠️ Datasource updated but connection test failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Done!"
|
||||
volumes:
|
||||
- name: tmp
|
||||
emptyDir: {}
|
||||
---
|
||||
# ServiceAccount for the CronJob
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
---
|
||||
# Role with permissions to read CNPG secret and update Grafana secret
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "create", "patch", "update"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "pods/exec"]
|
||||
verbs: ["get", "list", "watch", "create"]
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
verbs: ["get", "patch"]
|
||||
---
|
||||
# Role to read secret from teslamate namespace
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: teslamate
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
resourceNames: ["pg-teslamate-app"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
# RoleBinding in grafana namespace
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: teslamate-password-sync
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
# RoleBinding in teslamate namespace
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: teslamate-password-sync-grafana
|
||||
namespace: teslamate
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: teslamate-password-sync
|
||||
namespace: grafana
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: teslamate-password-sync
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -0,0 +1,141 @@
|
||||
# values/grafana.values.yaml
|
||||
|
||||
# --- Admin credentials via Vault/ESO ---
|
||||
# Admin username & password are stored in Vault at:
|
||||
# secret/grafana-admin
|
||||
# and pulled into a K8s Secret grafana-admin-secret by ExternalSecret.
|
||||
# admin:
|
||||
# existingSecret: grafana-admin-secret
|
||||
# userKey: admin-user
|
||||
# passwordKey: admin-password
|
||||
|
||||
# These plain values are ignored when existingSecret is set, but leave
|
||||
# them harmless defaults so you can still helm template without ESO.
|
||||
adminUser: admin
|
||||
adminPassword: "admin"
|
||||
|
||||
# --- Deployment annotations for Reloader ---
|
||||
# Automatically restart Grafana when secrets change
|
||||
# Note: Unpoller has its own Reloader annotation in unpoller namespace
|
||||
deploymentAnnotations:
|
||||
secret.reloader.stakater.com/reload: "grafana-authentik-oauth,grafana-teslamate-datasource"
|
||||
|
||||
# --- Persistence for dashboards and config ---
|
||||
persistence:
|
||||
enabled: true
|
||||
type: pvc
|
||||
storageClassName: rook-ceph-block
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
size: 10Gi
|
||||
|
||||
# --- Service type ---
|
||||
service:
|
||||
type: ClusterIP
|
||||
# Security context for Talos compatibility
|
||||
securityContext:
|
||||
runAsUser: 472
|
||||
runAsGroup: 472
|
||||
runAsNonRoot: true
|
||||
fsGroup: 472
|
||||
|
||||
# --- Ingress disabled - Istio VirtualService handles routing ---
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# --- Authentik OIDC Integration ---
|
||||
# ============================================================================
|
||||
# AUTOMATIC ADMIN ACCESS - BOOTSTRAP READY
|
||||
# ============================================================================
|
||||
# Role mapping assigns Grafana roles based on Authentik group membership:
|
||||
#
|
||||
# Authentik Group → Grafana Role
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# authentik Admins → Admin (full access) - AUTOMATIC!
|
||||
# Grafana Admins → Admin (full access)
|
||||
# Grafana Editors → Editor (can edit dashboards)
|
||||
# Grafana Viewers → Viewer (read-only)
|
||||
# (any other user) → Viewer (read-only)
|
||||
#
|
||||
# BOOTSTRAP BEHAVIOR:
|
||||
# ✓ authentik Admins get automatic Grafana admin access (no manual config!)
|
||||
# ✓ Groups auto-created by Authentik blueprint during bootstrap
|
||||
# ✓ Add users to groups in Authentik UI for access control
|
||||
#
|
||||
# TO GRANT ADMIN ACCESS TO OTHER USERS:
|
||||
# 1. Log into Authentik at https://auth.kube.huskypup.net
|
||||
# 2. Go to Directory → Groups → "Grafana Admins"
|
||||
# 3. Add users to the group
|
||||
# 4. Users log out/in to Grafana to receive admin role
|
||||
# ============================================================================
|
||||
grafana.ini:
|
||||
server:
|
||||
root_url: https://grafana.kube.huskypup.net
|
||||
auth.generic_oauth:
|
||||
enabled: true
|
||||
name: Authentik
|
||||
scopes: openid profile email
|
||||
auth_url: https://auth.kube.huskypup.net/application/o/authorize/
|
||||
token_url: https://auth.kube.huskypup.net/application/o/token/
|
||||
api_url: https://auth.kube.huskypup.net/application/o/userinfo/
|
||||
# Role mapping: authentik Admins OR Grafana Admins → Admin, Grafana Editors → Editor, else → Viewer
|
||||
role_attribute_path: contains(groups[*], 'authentik Admins') && 'Admin' || contains(groups[*], 'Grafana Admins') && 'Admin' || contains(groups[*], 'Grafana Editors') && 'Editor' || 'Viewer'
|
||||
allow_sign_up: true
|
||||
client_id: $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_ID}
|
||||
client_secret: $__env{GF_AUTH_GENERIC_OAUTH_CLIENT_SECRET}
|
||||
|
||||
|
||||
# Load OAuth credentials and datasource passwords from secrets as environment variables
|
||||
envFromSecrets:
|
||||
- name: grafana-authentik-oauth
|
||||
- name: grafana-teslamate-datasource
|
||||
|
||||
|
||||
|
||||
# --- Datasources ---
|
||||
datasources:
|
||||
datasources.yaml:
|
||||
apiVersion: 1
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
uid: Prometheus
|
||||
url: http://kube-prometheus-stack-prometheus.prometheus.svc.cluster.local:9090
|
||||
access: proxy
|
||||
isDefault: true
|
||||
- name: TeslaMate
|
||||
type: grafana-postgresql-datasource
|
||||
uid: TeslaMate
|
||||
url: pg-teslamate-rw.teslamate.svc.cluster.local:5432
|
||||
database: teslamate
|
||||
user: teslamate
|
||||
access: proxy
|
||||
isDefault: false
|
||||
editable: true
|
||||
secureJsonData:
|
||||
password: $__env{TESLAMATE_DB_PASSWORD}
|
||||
jsonData:
|
||||
sslmode: disable
|
||||
postgresVersion: 1600
|
||||
timescaledb: false
|
||||
database: teslamate
|
||||
|
||||
# --- Sidecar to auto-discover dashboards from ConfigMaps ---
|
||||
# Dashboards are deployed as ConfigMaps with the label grafana_dashboard: "1"
|
||||
# The sidecar automatically discovers them and loads them into Grafana
|
||||
containerSecurityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
|
||||
sidecar:
|
||||
dashboards:
|
||||
enabled: true
|
||||
label: grafana_dashboard
|
||||
labelValue: "1"
|
||||
folder: /var/lib/grafana/dashboards
|
||||
searchNamespace: ALL
|
||||
defaultFolderName: "General"
|
||||
provider:
|
||||
foldersFromFilesStructure: true
|
||||
Reference in New Issue
Block a user