From 64af4b1d1abe2c3ad8f67a97a6253fedafba7248 Mon Sep 17 00:00:00 2001 From: Zeynel Koca Date: Mon, 23 Mar 2026 00:55:45 +0100 Subject: [PATCH] Add dashboard provisioning via k8s-sidecar k8s-sidecar watches ConfigMaps labeled "hyperdx.io/dashboard: true" across all namespaces and writes dashboard JSON to a shared volume. HyperDX reads and upserts them natively via file-based provisioner. Requires hyperdxio/hyperdx#1962 --- .changeset/dashboard-provisioning.md | 5 + .../hyperdx/dashboard-configmap.yaml | 18 ++ .../hyperdx/dashboard-provisioner-rbac.yaml | 39 ++++ .../templates/hyperdx/deployment.yaml | 49 ++++ .../tests/dashboard-provisioner_test.yaml | 219 ++++++++++++++++++ charts/clickstack/values.yaml | 27 +++ 6 files changed, 357 insertions(+) create mode 100644 .changeset/dashboard-provisioning.md create mode 100644 charts/clickstack/templates/hyperdx/dashboard-configmap.yaml create mode 100644 charts/clickstack/templates/hyperdx/dashboard-provisioner-rbac.yaml create mode 100644 charts/clickstack/tests/dashboard-provisioner_test.yaml diff --git a/.changeset/dashboard-provisioning.md b/.changeset/dashboard-provisioning.md new file mode 100644 index 0000000..6d1dbec --- /dev/null +++ b/.changeset/dashboard-provisioning.md @@ -0,0 +1,5 @@ +--- +"clickstack": minor +--- + +feat: add dashboard provisioning via k8s-sidecar that discovers labeled ConfigMaps across namespaces. Requires hyperdxio/hyperdx#1962 (file-based dashboard provisioner). diff --git a/charts/clickstack/templates/hyperdx/dashboard-configmap.yaml b/charts/clickstack/templates/hyperdx/dashboard-configmap.yaml new file mode 100644 index 0000000..2a7f5e9 --- /dev/null +++ b/charts/clickstack/templates/hyperdx/dashboard-configmap.yaml @@ -0,0 +1,18 @@ +{{- if and .Values.hyperdx.dashboards.enabled .Values.hyperdx.dashboards.configMaps }} +{{- /* + Inline dashboard ConfigMap; labeled for discovery by the dashboard provisioner + alongside any external dashboard ConfigMaps from application charts. +*/ -}} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "clickstack.fullname" . }}-dashboards + labels: + {{- include "clickstack.labels" . | nindent 4 }} + hyperdx.io/dashboard: "true" +data: + {{- range $key, $value := .Values.hyperdx.dashboards.configMaps }} + {{ $key }}: | + {{- $value | nindent 4 }} + {{- end }} +{{- end }} diff --git a/charts/clickstack/templates/hyperdx/dashboard-provisioner-rbac.yaml b/charts/clickstack/templates/hyperdx/dashboard-provisioner-rbac.yaml new file mode 100644 index 0000000..7996203 --- /dev/null +++ b/charts/clickstack/templates/hyperdx/dashboard-provisioner-rbac.yaml @@ -0,0 +1,39 @@ +{{- if .Values.hyperdx.dashboards.enabled }} +{{- /* + RBAC for the dashboard provisioner. + ClusterRole so it can discover labeled dashboard ConfigMaps across all namespaces, + allowing application teams to manage dashboards from their own namespaces. +*/ -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "clickstack.fullname" . }}-dashboard-provisioner + labels: + {{- include "clickstack.labels" . | nindent 4 }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ include "clickstack.fullname" . }}-dashboard-provisioner + labels: + {{- include "clickstack.labels" . | nindent 4 }} +rules: + - apiGroups: [""] + resources: ["configmaps"] + verbs: ["list", "get", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ include "clickstack.fullname" . }}-dashboard-provisioner + labels: + {{- include "clickstack.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ include "clickstack.fullname" . }}-dashboard-provisioner +subjects: + - kind: ServiceAccount + name: {{ include "clickstack.fullname" . }}-dashboard-provisioner + namespace: {{ .Release.Namespace }} +{{- end }} diff --git a/charts/clickstack/templates/hyperdx/deployment.yaml b/charts/clickstack/templates/hyperdx/deployment.yaml index 5d8f500..3cd689b 100644 --- a/charts/clickstack/templates/hyperdx/deployment.yaml +++ b/charts/clickstack/templates/hyperdx/deployment.yaml @@ -47,6 +47,9 @@ spec: imagePullSecrets: {{- toYaml .Values.global.imagePullSecrets | nindent 8 }} {{- end }} + {{- if .Values.hyperdx.dashboards.enabled }} + serviceAccountName: {{ include "clickstack.fullname" . }}-dashboard-provisioner + {{- end }} {{- if .Values.mongodb.enabled }} initContainers: - name: wait-for-mongodb @@ -118,6 +121,52 @@ spec: value: {{ tpl .Values.hyperdx.deployment.defaultSources . | quote }} {{- end }} {{- end }} + {{- if .Values.hyperdx.dashboards.enabled }} + - name: DASHBOARD_PROVISIONER_DIR + value: "/dashboards" + - name: DASHBOARD_PROVISIONER_INTERVAL + value: {{ mul .Values.hyperdx.dashboards.syncInterval 1000 | quote }} + - name: DASHBOARD_PROVISIONER_ALL_TEAMS + value: "true" + {{- end }} {{- with .Values.hyperdx.deployment.env }} {{- toYaml . | nindent 12 }} {{- end }} + {{- if .Values.hyperdx.dashboards.enabled }} + volumeMounts: + - name: dashboards + mountPath: /dashboards + readOnly: true + {{- end }} + {{- if .Values.hyperdx.dashboards.enabled }} + - name: dashboard-watcher + image: {{ .Values.hyperdx.dashboards.sidecarImage }} + resources: + limits: + cpu: 50m + memory: 64Mi + requests: + cpu: 10m + memory: 32Mi + env: + - name: LABEL + value: "hyperdx.io/dashboard" + - name: LABEL_VALUE + value: "true" + - name: FOLDER + value: "/dashboards" + - name: RESOURCE + value: "configmap" + - name: NAMESPACE + value: "ALL" + - name: UNIQUE_FILENAMES + value: "true" + volumeMounts: + - name: dashboards + mountPath: /dashboards + {{- end }} + {{- if .Values.hyperdx.dashboards.enabled }} + volumes: + - name: dashboards + emptyDir: {} + {{- end }} diff --git a/charts/clickstack/tests/dashboard-provisioner_test.yaml b/charts/clickstack/tests/dashboard-provisioner_test.yaml new file mode 100644 index 0000000..4f6a530 --- /dev/null +++ b/charts/clickstack/tests/dashboard-provisioner_test.yaml @@ -0,0 +1,219 @@ +suite: Test Dashboard Provisioner +templates: + - hyperdx/deployment.yaml + - hyperdx/dashboard-configmap.yaml + - hyperdx/dashboard-provisioner-rbac.yaml +tests: + - it: should not render RBAC or ConfigMap when dashboards are disabled + set: + hyperdx: + dashboards: + enabled: false + asserts: + - hasDocuments: + count: 0 + template: hyperdx/dashboard-configmap.yaml + - hasDocuments: + count: 0 + template: hyperdx/dashboard-provisioner-rbac.yaml + + - it: should not add sidecar when dashboards are disabled + set: + hyperdx: + dashboards: + enabled: false + asserts: + - lengthEqual: + path: spec.template.spec.containers + count: 1 + template: hyperdx/deployment.yaml + + - it: should add dashboard-watcher sidecar when enabled + set: + hyperdx: + dashboards: + enabled: true + asserts: + - lengthEqual: + path: spec.template.spec.containers + count: 2 + template: hyperdx/deployment.yaml + - equal: + path: spec.template.spec.containers[1].name + value: dashboard-watcher + template: hyperdx/deployment.yaml + + - it: should use the k8s-sidecar image for watcher + set: + hyperdx: + dashboards: + enabled: true + sidecarImage: "kiwigrid/k8s-sidecar:2.5.0" + asserts: + - equal: + path: spec.template.spec.containers[1].image + value: "kiwigrid/k8s-sidecar:2.5.0" + template: hyperdx/deployment.yaml + + - it: should configure watcher to discover labeled ConfigMaps across all namespaces + set: + hyperdx: + dashboards: + enabled: true + asserts: + - contains: + path: spec.template.spec.containers[1].env + content: + name: LABEL + value: "hyperdx.io/dashboard" + template: hyperdx/deployment.yaml + - contains: + path: spec.template.spec.containers[1].env + content: + name: NAMESPACE + value: "ALL" + template: hyperdx/deployment.yaml + - contains: + path: spec.template.spec.containers[1].env + content: + name: UNIQUE_FILENAMES + value: "true" + template: hyperdx/deployment.yaml + + - it: should set DASHBOARD_PROVISIONER_DIR on the app container + set: + hyperdx: + dashboards: + enabled: true + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: DASHBOARD_PROVISIONER_DIR + value: "/dashboards" + template: hyperdx/deployment.yaml + + - it: should set DASHBOARD_PROVISIONER_INTERVAL in milliseconds + set: + hyperdx: + dashboards: + enabled: true + syncInterval: 60 + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: DASHBOARD_PROVISIONER_INTERVAL + value: "60000" + template: hyperdx/deployment.yaml + + - it: should share a dashboards volume between app and watcher + set: + hyperdx: + dashboards: + enabled: true + asserts: + - contains: + path: spec.template.spec.volumes + content: + name: dashboards + emptyDir: {} + template: hyperdx/deployment.yaml + - contains: + path: spec.template.spec.containers[0].volumeMounts + content: + name: dashboards + mountPath: /dashboards + readOnly: true + template: hyperdx/deployment.yaml + - contains: + path: spec.template.spec.containers[1].volumeMounts + content: + name: dashboards + mountPath: /dashboards + template: hyperdx/deployment.yaml + + - it: should use the dashboard-provisioner service account + set: + hyperdx: + dashboards: + enabled: true + asserts: + - equal: + path: spec.template.spec.serviceAccountName + value: RELEASE-NAME-clickstack-dashboard-provisioner + template: hyperdx/deployment.yaml + + - it: should create ServiceAccount, ClusterRole, and ClusterRoleBinding + set: + hyperdx: + dashboards: + enabled: true + asserts: + - isKind: + of: ServiceAccount + documentIndex: 0 + template: hyperdx/dashboard-provisioner-rbac.yaml + - isKind: + of: ClusterRole + documentIndex: 1 + template: hyperdx/dashboard-provisioner-rbac.yaml + - isKind: + of: ClusterRoleBinding + documentIndex: 2 + template: hyperdx/dashboard-provisioner-rbac.yaml + + - it: should grant cluster-wide configmap list, get, and watch permissions + set: + hyperdx: + dashboards: + enabled: true + asserts: + - equal: + path: rules[0].verbs + value: ["list", "get", "watch"] + documentIndex: 1 + template: hyperdx/dashboard-provisioner-rbac.yaml + + - it: should not render inline ConfigMap when configMaps is empty + set: + hyperdx: + dashboards: + enabled: true + configMaps: {} + asserts: + - hasDocuments: + count: 0 + template: hyperdx/dashboard-configmap.yaml + + - it: should render inline ConfigMap with discovery label + set: + hyperdx: + dashboards: + enabled: true + configMaps: + test.json: | + { "name": "Test", "tiles": [] } + asserts: + - equal: + path: metadata.labels["hyperdx.io/dashboard"] + value: "true" + template: hyperdx/dashboard-configmap.yaml + + - it: should include multiple dashboard files in inline ConfigMap + set: + hyperdx: + dashboards: + enabled: true + configMaps: + k8s-overview.json: | + { "name": "Kubernetes Overview", "tiles": [] } + app-metrics.json: | + { "name": "App Metrics", "tiles": [] } + asserts: + - isNotNull: + path: data["k8s-overview.json"] + template: hyperdx/dashboard-configmap.yaml + - isNotNull: + path: data["app-metrics.json"] + template: hyperdx/dashboard-configmap.yaml diff --git a/charts/clickstack/values.yaml b/charts/clickstack/values.yaml index 957b8c9..e45b829 100644 --- a/charts/clickstack/values.yaml +++ b/charts/clickstack/values.yaml @@ -15,6 +15,33 @@ hyperdx: app: 3000 opamp: 4320 + # ── Dashboard provisioning ──────────────────────────────── + # Discovers and upserts dashboard JSON into MongoDB (matched by name, never deletes) + # + # Two ways to provide dashboards: + # 1. Inline: set configMaps below with dashboard JSON + # 2. External: any ConfigMap in the cluster with the label "hyperdx.io/dashboard: true" + # will be discovered automatically (ideal for application charts managing their own dashboards) + dashboards: + enabled: false + # Image for the k8s-sidecar that watches for labeled ConfigMaps + sidecarImage: "kiwigrid/k8s-sidecar:2.5.0" + # Seconds between MongoDB upsert cycles + syncInterval: 30 + # Inline dashboard definitions - key is filename, value is exported dashboard JSON + configMaps: {} + # Example: + # configMaps: + # k8s-overview.json: | + # { "name": "Kubernetes Overview", "tiles": [...] } + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 50m + memory: 64Mi + # ── K8s ConfigMap (clickstack-config) ──────────────────── # Shared non-sensitive environment variables. Used by HyperDX and OTEL collector via envFrom. # All values support Helm template expressions (rendered via tpl).