Skip to content

refactor: migrate to dra amdgpu - #3449

Draft
Tanguille wants to merge 7 commits into
mainfrom
refactor/migrate-to-dra-amdgpu
Draft

refactor: migrate to dra amdgpu#3449
Tanguille wants to merge 7 commits into
mainfrom
refactor/migrate-to-dra-amdgpu

Conversation

@Tanguille

@Tanguille Tanguille commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Migrates AMD GPU access from squat/generic-device-plugin (extended resource squat.ai/dri) to Kubernetes Dynamic Resource Allocation (DRA) via k8s-gpu-dra-driver (ROCm upstream, buroa/helm chart v0.1.0).

  • Drops generic-device-plugin; adds k8s-gpu-dra-driver (wait: true) in kube-system
  • Replaces squat.ai/dri: 1 requests with DRA resourceClaims / claims across llama-server, llama-embeddings, fileflows, jellyfin, gpu-builder, drm-exporter
  • Adds components/dra-dgpu (+ igpu template) ResourceClaimTemplates
  • Relocates drm-exporter to kube-system with DRA-native mode + resource.kubernetes.io/admin-access namespace label
  • media shares the dGPU between jellyfin + fileflows via a namespace-level shared-dgpu ResourceClaim

⚠️ Blocker — cross-namespace dGPU sharing is not supported by this driver

Back to draft because the core scenario does not work as written.

The ROCm driver (docker.io/rocm/k8s-gpu-dra-driver, v1.0.0 image) implements neither:

  • adminAccess — no code path (lets a second claim bind an already-allocated device), and
  • Consumable capacity / KEP-5075 — not implemented driver-side (only static CPX/DPX hardware partitions, which RDNA4 / R9700 does not expose).

ResourceClaim is namespaced, so a pod can only reference a claim in its own namespace. control-1's single dGPU is wanted by three namespaces simultaneously:

Namespace Workload Claim
ai llama-server / vllm (inference) dgpu template → exclusive
media jellyfin + fileflows (transcode) shared-dgpu claim → shared within media
kube-system drm-exporter (monitoring) chart-generated

With no adminAccess and no consumable capacity, DRA allocates the device to exactly one claim — the other namespaces' pods go Pending. jellyfin + fileflows share fine with each other (same namespace, one claim); they cannot share with inference or monitoring across namespaces.

(gpu-builder is exempt — it is scale-to-zero and mutually exclusive with inference by design.)

When this works properly

  • Same-namespace sharing: works today (media side is correct).
  • Cross-namespace shared dGPU: unlocks when the ROCm driver ships either adminAccess or consumable capacity (KEP-5075). The K8s scheduler side is already in place (cluster on ~1.36, KEP-5075 beta); the driver is the only blocker.

Canary test before any full flip

drm-exporter (kube-system, admin-access) + llama-server (ai, exclusive) are already a cross-namespace pair in this PR. Deploy the driver and observe whether both land Running on control-1:

  • Both RunningadminAccess works (it is control-plane-enforced; the driver may not need explicit code). Path forward: give jellyfin/fileflows adminAccess claims and label media + ai namespaces.
  • drm-exporter Pending / evicts inference → adminAccess is unavailable here. Fall back to a hybrid: hostPath /dev/dri + /dev/kfd for secondary consumers, or keep generic-device-plugin for the dGPU and use DRA only where cross-namespace sharing is not required.

Upstream tracking

No dated timeline exists yet for either blocker — tracking for follow-up:

  • Kubernetes corekubernetes/enhancements#5075 "DRA: Consumable Capacity". Alpha since v1.34, beta target v1.36, stable target v1.38 (minor-version milestones only, no calendar dates).
  • llmkubedefilantech/LLMKube#1378 "Position LLMKube for GPU Dynamic Resource Allocation (DRA) without losing cross-pod GPU sharing" (open, undated).
  • ROCm k8s-gpu-dra-driver — no tracking issue exists upstream for adminAccess/consumable-capacity support (checked all open issues as of 2026-08-09).

Recent changes on this branch

  • refactor(media): decouple shared-dgpu claim from fileflows — moved the shared-dgpu ResourceClaim to the media namespace level so jellyfin no longer depends on fileflows for claim lifecycle; clarified the kube-system namespace-patch comment.

Decision needed

Run the canary first. Do not flip media/inference off generic-device-plugin until co-scheduling on control-1 is confirmed.

@tanguille-cluster

tanguille-cluster Bot commented Jun 22, 2026

Copy link
Copy Markdown
@@ metadata.labels.app.kubernetes.io/controller @@
# apps/v1/DaemonSet/kube-system/generic-device-plugin
! ± value change
- generic-device-plugin
+ generic-device-plugin-igpu

@@ spec.selector.matchLabels.app.kubernetes.io/controller @@
# apps/v1/DaemonSet/kube-system/generic-device-plugin
! ± value change
- generic-device-plugin
+ generic-device-plugin-igpu

@@ spec.template.metadata.labels.app.kubernetes.io/controller @@
# apps/v1/DaemonSet/kube-system/generic-device-plugin
! ± value change
- generic-device-plugin
+ generic-device-plugin-igpu

@@ spec.template.spec.containers.app.args @@
# apps/v1/DaemonSet/kube-system/generic-device-plugin
! - one list entry removed:
- - |
-   name: dri
-   groups:
-     - count: 4
-       paths:
-         - path: /dev/dri
-         - path: /dev/kfd
! + one list entry added:
+ - |
+   name: dri
+   groups:
+     - count: 2
+       paths:
+         - path: /dev/dri

@@ spec.template.spec.nodeSelector @@
# apps/v1/DaemonSet/kube-system/generic-device-plugin
! - one map entry removed:
- amd.com/gpu: "true"
! + one map entry added:
+ amd.com/igpu: "true"

@@ spec.template.spec @@
# apps/v1/Deployment/media/fileflows
! + one map entry added:
+ resourceClaims:
+ - name: gpu
+   resourceClaimName: shared-dgpu

@@ spec.template.spec.containers.app.resources @@
# apps/v1/Deployment/media/fileflows
! + one map entry added:
+ claims:
+ - name: gpu

@@ spec.template.spec.containers.app.resources.limits @@
# apps/v1/Deployment/media/fileflows
! - one map entry removed:
- squat.ai/dri: 1

@@ spec.template.spec.containers.app.resources.requests @@
# apps/v1/Deployment/media/fileflows
! - one map entry removed:
- squat.ai/dri: 1

@@ spec.template.spec @@
# apps/v1/Deployment/media/jellyfin
! + one map entry added:
+ resourceClaims:
+ - name: gpu
+   resourceClaimName: shared-dgpu

@@ spec.template.spec.containers.app.resources @@
# apps/v1/Deployment/media/jellyfin
! + one map entry added:
+ claims:
+ - name: gpu

@@ spec.template.spec.containers.app.resources.limits @@
# apps/v1/Deployment/media/jellyfin
! - one map entry removed:
- squat.ai/dri: 1

@@ spec.template.spec.containers.app.resources.requests @@
# apps/v1/Deployment/media/jellyfin
! - one map entry removed:
- squat.ai/dri: 1

@@ (root level) @@
# apps/v1/DaemonSet/kube-system/generic-device-plugin-generic-device-plugin-igpu
! - one document removed:
- apiVersion: apps/v1
- kind: DaemonSet
- metadata:
-   name: generic-device-plugin-generic-device-plugin-igpu
-   namespace: kube-system
-   labels:
-     app.kubernetes.io/controller: generic-device-plugin-igpu
-     app.kubernetes.io/instance: generic-device-plugin
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/name: generic-device-plugin
-     helm.toolkit.fluxcd.io/name: generic-device-plugin
-     helm.toolkit.fluxcd.io/namespace: kube-system
- spec:
-   revisionHistoryLimit: 3
-   selector:
-     matchLabels:
-       app.kubernetes.io/controller: generic-device-plugin-igpu
-       app.kubernetes.io/instance: generic-device-plugin
-       app.kubernetes.io/name: generic-device-plugin
-   template:
-     metadata:
-       annotations: null
-       labels:
-         app.kubernetes.io/controller: generic-device-plugin-igpu
-         app.kubernetes.io/instance: generic-device-plugin
-         app.kubernetes.io/name: generic-device-plugin
-     spec:
-       automountServiceAccountToken: false
-       containers:
-       - name: app
-         image: "ghcr.io/squat/generic-device-plugin:latest@sha256:dc192e164c69b03f156765793a1be62ca437709ae477b27ca7d8f3dcf5021576"
-         args:
-         - "--domain=squat.ai"
-         - "--device"
-         - |
-           name: dri
-           groups:
-             - count: 2
-               paths:
-                 - path: /dev/dri
-           
-         securityContext:
-           privileged: true
-         resources:
-           limits:
-             memory: 64Mi
-           requests:
-             cpu: 10m
-             memory: 16Mi
-         volumeMounts:
-         - name: dev
-           mountPath: /dev
-         - name: device-plugin
-           mountPath: /var/lib/kubelet/device-plugins
-       dnsPolicy: ClusterFirst
-       enableServiceLinks: false
-       hostIPC: false
-       hostNetwork: false
-       hostPID: false
-       nodeSelector:
-         amd.com/igpu: "true"
-       priorityClassName: system-node-critical
-       serviceAccountName: generic-device-plugin
-       volumes:
-       - name: dev
-         hostPath:
-           path: /dev
-       - name: device-plugin
-         hostPath:
-           path: /var/lib/kubelet/device-plugins

@@ (root level) @@
# v1/ServiceAccount/observability/drm-exporter
! - one document removed:
- apiVersion: v1
- kind: ServiceAccount
- metadata:
-   name: drm-exporter
-   namespace: observability
-   labels:
-     app.kubernetes.io/component: exporter
-     app.kubernetes.io/instance: drm-exporter
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/namespace: observability
- automountServiceAccountToken: false

@@ (root level) @@
# v1/ConfigMap/observability/drm-exporter-dashboard
! - one document removed:
- apiVersion: v1
- kind: ConfigMap
- metadata:
-   name: drm-exporter-dashboard
-   namespace: observability
-   labels:
-     app.kubernetes.io/component: exporter
-     app.kubernetes.io/instance: drm-exporter
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/namespace: observability
- data:
-   drm-exporter.json: |
-     
-     {
-       "uid": "drm-exporter",
-       "title": "drm-exporter \u2014 GPU metrics",
-       "tags": [
-         "drm-exporter",
-         "gpu",
-         "intel",
-         "amd"
-       ],
-       "editable": true,
-       "schemaVersion": 39,
-       "timezone": "",
-       "refresh": "30s",
-       "time": {
-         "from": "now-6h",
-         "to": "now"
-       },
-       "timepicker": {},
-       "annotations": {
-         "list": [
-           {
-             "builtIn": 1,
-             "datasource": {
-               "type": "grafana",
-               "uid": "-- Grafana --"
-             },
-             "enable": true,
-             "hide": true,
-             "name": "Annotations & Alerts",
-             "type": "dashboard"
-           }
-         ]
-       },
-       "templating": {
-         "list": [
-           {
-             "name": "datasource",
-             "type": "datasource",
-             "query": "prometheus",
-             "label": "Data source",
-             "current": {},
-             "hide": 0,
-             "refresh": 1,
-             "regex": ""
-           },
-           {
-             "name": "node",
-             "type": "query",
-             "label": "Node",
-             "datasource": {
-               "type": "prometheus",
-               "uid": "${datasource}"
-             },
-             "query": {
-               "qryType": 1,
-               "query": "label_values(drm_info, node)",
-               "refId": "PrometheusVariableQueryEditor-VariableQuery"
-             },
-             "current": {},
-             "refresh": 2,
-             "includeAll": true,
-             "multi": true,
-             "allValue": ".*",
-             "sort": 1
-           },
-           {
-             "name": "device",
-             "type": "query",
-             "label": "Device",
-             "datasource": {
-               "type": "prometheus",
-               "uid": "${datasource}"
-             },
-             "query": {
-               "qryType": 1,
-               "query": "label_values(drm_info{node=~\"$node\"}, device)",
-               "refId": "PrometheusVariableQueryEditor-VariableQuery"
-             },
-             "current": {},
-             "refresh": 2,
-             "includeAll": true,
-             "multi": true,
-             "allValue": ".*",
-             "sort": 1
-           }
-         ]
-       },
-       "panels": [
-         {
-           "id": 1,
-           "type": "stat",
-           "title": "GPUs",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 4,
-             "w": 4,
-             "x": 0,
-             "y": 0
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "none",
-               "color": {
-                 "mode": "thresholds"
-               },
-               "thresholds": {
-                 "mode": "absolute",
-                 "steps": [
-                   {
-                     "color": "green",
-                     "value": null
-                   }
-                 ]
-               }
-             },
-             "overrides": []
-           },
-           "options": {
-             "reduceOptions": {
-               "calcs": [
-                 "lastNotNull"
-               ],
-               "fields": "",
-               "values": false
-             },
-             "colorMode": "value",
-             "graphMode": "area",
-             "textMode": "auto",
-             "justifyMode": "auto"
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "count(group by (node, device) (drm_info{node=~\"$node\", device=~\"$device\"}))",
-               "legendFormat": "",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 2,
-           "type": "stat",
-           "title": "Peak engine busy",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 4,
-             "w": 4,
-             "x": 4,
-             "y": 0
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "percentunit",
-               "color": {
-                 "mode": "thresholds"
-               },
-               "thresholds": {
-                 "mode": "absolute",
-                 "steps": [
-                   {
-                     "color": "green",
-                     "value": null
-                   }
-                 ]
-               },
-               "min": 0,
-               "max": 1
-             },
-             "overrides": []
-           },
-           "options": {
-             "reduceOptions": {
-               "calcs": [
-                 "max"
-               ],
-               "fields": "",
-               "values": false
-             },
-             "colorMode": "value",
-             "graphMode": "area",
-             "textMode": "auto",
-             "justifyMode": "auto"
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "max(drm_engine_utilization_ratio{node=~\"$node\", device=~\"$device\"})",
-               "legendFormat": "",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 3,
-           "type": "table",
-           "title": "GPU inventory",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 4,
-             "w": 16,
-             "x": 8,
-             "y": 0
-           },
-           "fieldConfig": {
-             "defaults": {},
-             "overrides": []
-           },
-           "options": {
-             "showHeader": true
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_info{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "",
-               "range": false,
-               "instant": true,
-               "format": "table",
-               "refId": "A"
-             }
-           ],
-           "transformations": [
-             {
-               "id": "filterFieldsByName",
-               "options": {
-                 "include": {
-                   "names": [
-                     "node",
-                     "device",
-                     "vendor",
-                     "model",
-                     "driver",
-                     "type",
-                     "revision",
-                     "pci_id",
-                     "dev_node"
-                   ]
-                 }
-               }
-             }
-           ]
-         },
-         {
-           "id": 4,
-           "type": "timeseries",
-           "title": "Engine utilization",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 8,
-             "w": 24,
-             "x": 0,
-             "y": 4
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "percentunit",
-               "custom": {
-                 "drawStyle": "line",
-                 "lineInterpolation": "linear",
-                 "lineWidth": 1,
-                 "fillOpacity": 10,
-                 "showPoints": "never",
-                 "spanNulls": false,
-                 "axisPlacement": "auto"
-               },
-               "color": {
-                 "mode": "palette-classic"
-               },
-               "min": 0,
-               "max": 1
-             },
-             "overrides": []
-           },
-           "options": {
-             "legend": {
-               "displayMode": "table",
-               "placement": "bottom",
-               "calcs": [
-                 "lastNotNull",
-                 "max"
-               ]
-             },
-             "tooltip": {
-               "mode": "multi",
-               "sort": "desc"
-             }
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_engine_utilization_ratio{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{engine}}",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 5,
-           "type": "timeseries",
-           "title": "Memory used",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 8,
-             "w": 12,
-             "x": 0,
-             "y": 12
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "bytes",
-               "custom": {
-                 "drawStyle": "line",
-                 "lineInterpolation": "linear",
-                 "lineWidth": 1,
-                 "fillOpacity": 10,
-                 "showPoints": "never",
-                 "spanNulls": false,
-                 "axisPlacement": "auto"
-               },
-               "color": {
-                 "mode": "palette-classic"
-               }
-             },
-             "overrides": []
-           },
-           "options": {
-             "legend": {
-               "displayMode": "table",
-               "placement": "bottom",
-               "calcs": [
-                 "lastNotNull",
-                 "max"
-               ]
-             },
-             "tooltip": {
-               "mode": "multi",
-               "sort": "desc"
-             }
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_memory_used_bytes{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{pool}}",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 6,
-           "type": "timeseries",
-           "title": "Memory utilization",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 8,
-             "w": 12,
-             "x": 12,
-             "y": 12
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "percentunit",
-               "custom": {
-                 "drawStyle": "line",
-                 "lineInterpolation": "linear",
-                 "lineWidth": 1,
-                 "fillOpacity": 10,
-                 "showPoints": "never",
-                 "spanNulls": false,
-                 "axisPlacement": "auto"
-               },
-               "color": {
-                 "mode": "palette-classic"
-               },
-               "min": 0,
-               "max": 1
-             },
-             "overrides": []
-           },
-           "options": {
-             "legend": {
-               "displayMode": "table",
-               "placement": "bottom",
-               "calcs": [
-                 "lastNotNull",
-                 "max"
-               ]
-             },
-             "tooltip": {
-               "mode": "multi",
-               "sort": "desc"
-             }
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_memory_used_bytes{node=~\"$node\", device=~\"$device\"} / drm_memory_total_bytes{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{pool}}",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 7,
-           "type": "timeseries",
-           "title": "Clock frequency",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 8,
-             "w": 12,
-             "x": 0,
-             "y": 20
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "hertz",
-               "custom": {
-                 "drawStyle": "line",
-                 "lineInterpolation": "linear",
-                 "lineWidth": 1,
-                 "fillOpacity": 10,
-                 "showPoints": "never",
-                 "spanNulls": false,
-                 "axisPlacement": "auto"
-               },
-               "color": {
-                 "mode": "palette-classic"
-               }
-             },
-             "overrides": []
-           },
-           "options": {
-             "legend": {
-               "displayMode": "table",
-               "placement": "bottom",
-               "calcs": [
-                 "lastNotNull",
-                 "max"
-               ]
-             },
-             "tooltip": {
-               "mode": "multi",
-               "sort": "desc"
-             }
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_frequency_hertz{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{domain}} \u00b7 {{kind}}",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 8,
-           "type": "timeseries",
-           "title": "Power draw",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 8,
-             "w": 12,
-             "x": 12,
-             "y": 20
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "watt",
-               "custom": {
-                 "drawStyle": "line",
-                 "lineInterpolation": "linear",
-                 "lineWidth": 1,
-                 "fillOpacity": 10,
-                 "showPoints": "never",
-                 "spanNulls": false,
-                 "axisPlacement": "auto"
-               },
-               "color": {
-                 "mode": "palette-classic"
-               }
-             },
-             "overrides": []
-           },
-           "options": {
-             "legend": {
-               "displayMode": "table",
-               "placement": "bottom",
-               "calcs": [
-                 "lastNotNull",
-                 "max"
-               ]
-             },
-             "tooltip": {
-               "mode": "multi",
-               "sort": "desc"
-             }
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_power_watts{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{domain}}",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 9,
-           "type": "timeseries",
-           "title": "Temperature",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 8,
-             "w": 12,
-             "x": 0,
-             "y": 28
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "celsius",
-               "custom": {
-                 "drawStyle": "line",
-                 "lineInterpolation": "linear",
-                 "lineWidth": 1,
-                 "fillOpacity": 10,
-                 "showPoints": "never",
-                 "spanNulls": false,
-                 "axisPlacement": "auto",
-                 "thresholdsStyle": {
-                   "mode": "dashed"
-                 }
-               },
-               "color": {
-                 "mode": "palette-classic"
-               },
-               "thresholds": {
-                 "mode": "absolute",
-                 "steps": [
-                   {
-                     "color": "green",
-                     "value": null
-                   },
-                   {
-                     "color": "orange",
-                     "value": 80
-                   },
-                   {
-                     "color": "red",
-                     "value": 95
-                   }
-                 ]
-               }
-             },
-             "overrides": []
-           },
-           "options": {
-             "legend": {
-               "displayMode": "table",
-               "placement": "bottom",
-               "calcs": [
-                 "lastNotNull",
-                 "max"
-               ]
-             },
-             "tooltip": {
-               "mode": "multi",
-               "sort": "desc"
-             }
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_temperature_celsius{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{sensor}}",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         },
-         {
-           "id": 10,
-           "type": "timeseries",
-           "title": "Fan speed",
-           "datasource": {
-             "type": "prometheus",
-             "uid": "${datasource}"
-           },
-           "gridPos": {
-             "h": 8,
-             "w": 12,
-             "x": 12,
-             "y": 28
-           },
-           "fieldConfig": {
-             "defaults": {
-               "unit": "rotrpm",
-               "custom": {
-                 "drawStyle": "line",
-                 "lineInterpolation": "linear",
-                 "lineWidth": 1,
-                 "fillOpacity": 10,
-                 "showPoints": "never",
-                 "spanNulls": false,
-                 "axisPlacement": "auto"
-               },
-               "color": {
-                 "mode": "palette-classic"
-               }
-             },
-             "overrides": []
-           },
-           "options": {
-             "legend": {
-               "displayMode": "table",
-               "placement": "bottom",
-               "calcs": [
-                 "lastNotNull",
-                 "max"
-               ]
-             },
-             "tooltip": {
-               "mode": "multi",
-               "sort": "desc"
-             }
-           },
-           "targets": [
-             {
-               "datasource": {
-                 "type": "prometheus",
-                 "uid": "${datasource}"
-               },
-               "editorMode": "code",
-               "expr": "drm_fan_speed_rpm{node=~\"$node\", device=~\"$device\"}",
-               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{fan}}",
-               "range": true,
-               "instant": false,
-               "format": "time_series",
-               "refId": "A"
-             }
-           ]
-         }
-       ]
-     }

@@ (root level) @@
# v1/Service/observability/drm-exporter
! - one document removed:
- apiVersion: v1
- kind: Service
- metadata:
-   name: drm-exporter
-   namespace: observability
-   labels:
-     app.kubernetes.io/component: exporter
-     app.kubernetes.io/instance: drm-exporter
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/namespace: observability
- spec:
-   type: ClusterIP
-   selector:
-     app.kubernetes.io/instance: drm-exporter
-     app.kubernetes.io/name: drm-exporter
-   ports:
-   - name: metrics
-     port: 8081
-     protocol: TCP
-     targetPort: metrics

@@ (root level) @@
# apps/v1/DaemonSet/observability/drm-exporter
! - one document removed:
- apiVersion: apps/v1
- kind: DaemonSet
- metadata:
-   name: drm-exporter
-   namespace: observability
-   labels:
-     app.kubernetes.io/component: exporter
-     app.kubernetes.io/instance: drm-exporter
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/namespace: observability
- spec:
-   selector:
-     matchLabels:
-       app.kubernetes.io/instance: drm-exporter
-       app.kubernetes.io/name: drm-exporter
-   template:
-     metadata:
-       labels:
-         app.kubernetes.io/instance: drm-exporter
-         app.kubernetes.io/name: drm-exporter
-     spec:
-       automountServiceAccountToken: false
-       containers:
-       - name: drm-exporter
-         image: "ghcr.io/home-operations/drm-exporter@sha256:7229811068328ff3d9f9ac70b17319bb036e5e13dff6d743d5c5e3705d961488"
-         imagePullPolicy: IfNotPresent
-         env:
-         - name: DRM_EXPORTER_ADDRESS
-           value: "0.0.0.0"
-         - name: DRM_EXPORTER_PORT
-           value: "8081"
-         - name: DRM_EXPORTER_INTERVAL_SECONDS
-           value: "5"
-         - name: RUST_LOG
-           value: info
-         livenessProbe:
-           httpGet:
-             path: /healthz
-             port: metrics
-           initialDelaySeconds: 5
-           periodSeconds: 20
-         ports:
-         - name: metrics
-           containerPort: 8081
-           protocol: TCP
-         readinessProbe:
-           httpGet:
-             path: /readyz
-             port: metrics
-           initialDelaySeconds: 2
-           periodSeconds: 10
-         resources:
-           limits:
-             memory: 128Mi
-             squat.ai/dri: 1
-           requests:
-             cpu: 10m
-             memory: 32Mi
-             squat.ai/dri: 1
-         volumeMounts:
-         - name: sys
-           mountPath: /sys
-           readOnly: true
-         securityContext:
-           allowPrivilegeEscalation: false
-           capabilities:
-             add:
-             - PERFMON
-             - SYS_RAWIO
-             drop:
-             - ALL
-           privileged: false
-           readOnlyRootFilesystem: true
-       nodeSelector:
-         amd.com/gpu: "true"
-       securityContext:
-         runAsGroup: 0
-         runAsNonRoot: false
-         runAsUser: 0
-         seccompProfile:
-           type: Unconfined
-         supplementalGroups:
-         - 44
-         - 226
-       serviceAccountName: drm-exporter
-       terminationGracePeriodSeconds: 30
-       tolerations:
-       - key: amd.com/gpu
-         effect: NoSchedule
-         operator: Exists
-       volumes:
-       - name: sys
-         hostPath:
-           type: Directory
-           path: /sys
-   updateStrategy:
-     type: RollingUpdate

@@ (root level) @@
# grafana.integreatly.org/v1beta1/GrafanaDashboard/observability/drm-exporter-dashboard
! - one document removed:
- apiVersion: grafana.integreatly.org/v1beta1
- kind: GrafanaDashboard
- metadata:
-   name: drm-exporter-dashboard
-   namespace: observability
-   labels:
-     app.kubernetes.io/component: exporter
-     app.kubernetes.io/instance: drm-exporter
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/namespace: observability
- spec:
-   allowCrossNamespaceImport: true
-   configMapRef:
-     name: drm-exporter-dashboard
-     key: drm-exporter.json
-   folder: hardware
-   instanceSelector:
-     matchLabels:
-       grafana.internal/instance: grafana
-   resyncPeriod: 10m

@@ (root level) @@
# monitoring.coreos.com/v1/ServiceMonitor/observability/drm-exporter
! - one document removed:
- apiVersion: monitoring.coreos.com/v1
- kind: ServiceMonitor
- metadata:
-   name: drm-exporter
-   namespace: observability
-   labels:
-     app.kubernetes.io/component: exporter
-     app.kubernetes.io/instance: drm-exporter
-     app.kubernetes.io/managed-by: Helm
-     app.kubernetes.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/name: drm-exporter
-     helm.toolkit.fluxcd.io/namespace: observability
- spec:
-   endpoints:
-   - interval: 30s
-     path: /metrics
-     port: metrics
-     relabelings:
-     - sourceLabels:
-       - __meta_kubernetes_pod_node_name
-       targetLabel: node
-     - action: replace
-       regex: "(.*)"
-       replacement: $1
-       sourceLabels:
-       - __meta_kubernetes_pod_node_name
-       targetLabel: kubernetes_node
-     - action: replace
-       regex: "(.*)"
-       replacement: $1
-       sourceLabels:
-       - __meta_kubernetes_pod_node_name
-       targetLabel: nodename
-     scrapeTimeout: 10s
-   selector:
-     matchLabels:
-       app.kubernetes.io/instance: drm-exporter
-       app.kubernetes.io/name: drm-exporter

@@ (root level) @@
# v1/ServiceAccount/kube-system/drm-exporter
! + one document added:
+ apiVersion: v1
+ kind: ServiceAccount
+ metadata:
+   name: drm-exporter
+   namespace: kube-system
+   labels:
+     app.kubernetes.io/component: exporter
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/managed-by: Helm
+     app.kubernetes.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/namespace: kube-system
+ automountServiceAccountToken: false

@@ (root level) @@
# v1/ConfigMap/kube-system/drm-exporter-dashboard
! + one document added:
+ apiVersion: v1
+ kind: ConfigMap
+ metadata:
+   name: drm-exporter-dashboard
+   namespace: kube-system
+   labels:
+     app.kubernetes.io/component: exporter
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/managed-by: Helm
+     app.kubernetes.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/namespace: kube-system
+ data:
+   drm-exporter.json: |
+     
+     {
+       "uid": "drm-exporter",
+       "title": "drm-exporter \u2014 GPU metrics",
+       "tags": [
+         "drm-exporter",
+         "gpu",
+         "intel",
+         "amd"
+       ],
+       "editable": true,
+       "schemaVersion": 39,
+       "timezone": "",
+       "refresh": "30s",
+       "time": {
+         "from": "now-6h",
+         "to": "now"
+       },
+       "timepicker": {},
+       "annotations": {
+         "list": [
+           {
+             "builtIn": 1,
+             "datasource": {
+               "type": "grafana",
+               "uid": "-- Grafana --"
+             },
+             "enable": true,
+             "hide": true,
+             "name": "Annotations & Alerts",
+             "type": "dashboard"
+           }
+         ]
+       },
+       "templating": {
+         "list": [
+           {
+             "name": "datasource",
+             "type": "datasource",
+             "query": "prometheus",
+             "label": "Data source",
+             "current": {},
+             "hide": 0,
+             "refresh": 1,
+             "regex": ""
+           },
+           {
+             "name": "node",
+             "type": "query",
+             "label": "Node",
+             "datasource": {
+               "type": "prometheus",
+               "uid": "${datasource}"
+             },
+             "query": {
+               "qryType": 1,
+               "query": "label_values(drm_info, node)",
+               "refId": "PrometheusVariableQueryEditor-VariableQuery"
+             },
+             "current": {},
+             "refresh": 2,
+             "includeAll": true,
+             "multi": true,
+             "allValue": ".*",
+             "sort": 1
+           },
+           {
+             "name": "device",
+             "type": "query",
+             "label": "Device",
+             "datasource": {
+               "type": "prometheus",
+               "uid": "${datasource}"
+             },
+             "query": {
+               "qryType": 1,
+               "query": "label_values(drm_info{node=~\"$node\"}, device)",
+               "refId": "PrometheusVariableQueryEditor-VariableQuery"
+             },
+             "current": {},
+             "refresh": 2,
+             "includeAll": true,
+             "multi": true,
+             "allValue": ".*",
+             "sort": 1
+           }
+         ]
+       },
+       "panels": [
+         {
+           "id": 1,
+           "type": "stat",
+           "title": "GPUs",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 4,
+             "w": 4,
+             "x": 0,
+             "y": 0
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "none",
+               "color": {
+                 "mode": "thresholds"
+               },
+               "thresholds": {
+                 "mode": "absolute",
+                 "steps": [
+                   {
+                     "color": "green",
+                     "value": null
+                   }
+                 ]
+               }
+             },
+             "overrides": []
+           },
+           "options": {
+             "reduceOptions": {
+               "calcs": [
+                 "lastNotNull"
+               ],
+               "fields": "",
+               "values": false
+             },
+             "colorMode": "value",
+             "graphMode": "area",
+             "textMode": "auto",
+             "justifyMode": "auto"
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "count(group by (node, device) (drm_info{node=~\"$node\", device=~\"$device\"}))",
+               "legendFormat": "",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 2,
+           "type": "stat",
+           "title": "Peak engine busy",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 4,
+             "w": 4,
+             "x": 4,
+             "y": 0
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "percentunit",
+               "color": {
+                 "mode": "thresholds"
+               },
+               "thresholds": {
+                 "mode": "absolute",
+                 "steps": [
+                   {
+                     "color": "green",
+                     "value": null
+                   }
+                 ]
+               },
+               "min": 0,
+               "max": 1
+             },
+             "overrides": []
+           },
+           "options": {
+             "reduceOptions": {
+               "calcs": [
+                 "max"
+               ],
+               "fields": "",
+               "values": false
+             },
+             "colorMode": "value",
+             "graphMode": "area",
+             "textMode": "auto",
+             "justifyMode": "auto"
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "max(drm_engine_utilization_ratio{node=~\"$node\", device=~\"$device\"})",
+               "legendFormat": "",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 3,
+           "type": "table",
+           "title": "GPU inventory",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 4,
+             "w": 16,
+             "x": 8,
+             "y": 0
+           },
+           "fieldConfig": {
+             "defaults": {},
+             "overrides": []
+           },
+           "options": {
+             "showHeader": true
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_info{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "",
+               "range": false,
+               "instant": true,
+               "format": "table",
+               "refId": "A"
+             }
+           ],
+           "transformations": [
+             {
+               "id": "filterFieldsByName",
+               "options": {
+                 "include": {
+                   "names": [
+                     "node",
+                     "device",
+                     "vendor",
+                     "model",
+                     "driver",
+                     "type",
+                     "revision",
+                     "pci_id",
+                     "dev_node"
+                   ]
+                 }
+               }
+             }
+           ]
+         },
+         {
+           "id": 4,
+           "type": "timeseries",
+           "title": "Engine utilization",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 8,
+             "w": 24,
+             "x": 0,
+             "y": 4
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "percentunit",
+               "custom": {
+                 "drawStyle": "line",
+                 "lineInterpolation": "linear",
+                 "lineWidth": 1,
+                 "fillOpacity": 10,
+                 "showPoints": "never",
+                 "spanNulls": false,
+                 "axisPlacement": "auto"
+               },
+               "color": {
+                 "mode": "palette-classic"
+               },
+               "min": 0,
+               "max": 1
+             },
+             "overrides": []
+           },
+           "options": {
+             "legend": {
+               "displayMode": "table",
+               "placement": "bottom",
+               "calcs": [
+                 "lastNotNull",
+                 "max"
+               ]
+             },
+             "tooltip": {
+               "mode": "multi",
+               "sort": "desc"
+             }
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_engine_utilization_ratio{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{engine}}",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 5,
+           "type": "timeseries",
+           "title": "Memory used",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 8,
+             "w": 12,
+             "x": 0,
+             "y": 12
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "bytes",
+               "custom": {
+                 "drawStyle": "line",
+                 "lineInterpolation": "linear",
+                 "lineWidth": 1,
+                 "fillOpacity": 10,
+                 "showPoints": "never",
+                 "spanNulls": false,
+                 "axisPlacement": "auto"
+               },
+               "color": {
+                 "mode": "palette-classic"
+               }
+             },
+             "overrides": []
+           },
+           "options": {
+             "legend": {
+               "displayMode": "table",
+               "placement": "bottom",
+               "calcs": [
+                 "lastNotNull",
+                 "max"
+               ]
+             },
+             "tooltip": {
+               "mode": "multi",
+               "sort": "desc"
+             }
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_memory_used_bytes{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{pool}}",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 6,
+           "type": "timeseries",
+           "title": "Memory utilization",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 8,
+             "w": 12,
+             "x": 12,
+             "y": 12
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "percentunit",
+               "custom": {
+                 "drawStyle": "line",
+                 "lineInterpolation": "linear",
+                 "lineWidth": 1,
+                 "fillOpacity": 10,
+                 "showPoints": "never",
+                 "spanNulls": false,
+                 "axisPlacement": "auto"
+               },
+               "color": {
+                 "mode": "palette-classic"
+               },
+               "min": 0,
+               "max": 1
+             },
+             "overrides": []
+           },
+           "options": {
+             "legend": {
+               "displayMode": "table",
+               "placement": "bottom",
+               "calcs": [
+                 "lastNotNull",
+                 "max"
+               ]
+             },
+             "tooltip": {
+               "mode": "multi",
+               "sort": "desc"
+             }
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_memory_used_bytes{node=~\"$node\", device=~\"$device\"} / drm_memory_total_bytes{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{pool}}",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 7,
+           "type": "timeseries",
+           "title": "Clock frequency",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 8,
+             "w": 12,
+             "x": 0,
+             "y": 20
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "hertz",
+               "custom": {
+                 "drawStyle": "line",
+                 "lineInterpolation": "linear",
+                 "lineWidth": 1,
+                 "fillOpacity": 10,
+                 "showPoints": "never",
+                 "spanNulls": false,
+                 "axisPlacement": "auto"
+               },
+               "color": {
+                 "mode": "palette-classic"
+               }
+             },
+             "overrides": []
+           },
+           "options": {
+             "legend": {
+               "displayMode": "table",
+               "placement": "bottom",
+               "calcs": [
+                 "lastNotNull",
+                 "max"
+               ]
+             },
+             "tooltip": {
+               "mode": "multi",
+               "sort": "desc"
+             }
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_frequency_hertz{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{domain}} \u00b7 {{kind}}",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 8,
+           "type": "timeseries",
+           "title": "Power draw",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 8,
+             "w": 12,
+             "x": 12,
+             "y": 20
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "watt",
+               "custom": {
+                 "drawStyle": "line",
+                 "lineInterpolation": "linear",
+                 "lineWidth": 1,
+                 "fillOpacity": 10,
+                 "showPoints": "never",
+                 "spanNulls": false,
+                 "axisPlacement": "auto"
+               },
+               "color": {
+                 "mode": "palette-classic"
+               }
+             },
+             "overrides": []
+           },
+           "options": {
+             "legend": {
+               "displayMode": "table",
+               "placement": "bottom",
+               "calcs": [
+                 "lastNotNull",
+                 "max"
+               ]
+             },
+             "tooltip": {
+               "mode": "multi",
+               "sort": "desc"
+             }
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_power_watts{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{domain}}",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 9,
+           "type": "timeseries",
+           "title": "Temperature",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 8,
+             "w": 12,
+             "x": 0,
+             "y": 28
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "celsius",
+               "custom": {
+                 "drawStyle": "line",
+                 "lineInterpolation": "linear",
+                 "lineWidth": 1,
+                 "fillOpacity": 10,
+                 "showPoints": "never",
+                 "spanNulls": false,
+                 "axisPlacement": "auto",
+                 "thresholdsStyle": {
+                   "mode": "dashed"
+                 }
+               },
+               "color": {
+                 "mode": "palette-classic"
+               },
+               "thresholds": {
+                 "mode": "absolute",
+                 "steps": [
+                   {
+                     "color": "green",
+                     "value": null
+                   },
+                   {
+                     "color": "orange",
+                     "value": 80
+                   },
+                   {
+                     "color": "red",
+                     "value": 95
+                   }
+                 ]
+               }
+             },
+             "overrides": []
+           },
+           "options": {
+             "legend": {
+               "displayMode": "table",
+               "placement": "bottom",
+               "calcs": [
+                 "lastNotNull",
+                 "max"
+               ]
+             },
+             "tooltip": {
+               "mode": "multi",
+               "sort": "desc"
+             }
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_temperature_celsius{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{sensor}}",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         },
+         {
+           "id": 10,
+           "type": "timeseries",
+           "title": "Fan speed",
+           "datasource": {
+             "type": "prometheus",
+             "uid": "${datasource}"
+           },
+           "gridPos": {
+             "h": 8,
+             "w": 12,
+             "x": 12,
+             "y": 28
+           },
+           "fieldConfig": {
+             "defaults": {
+               "unit": "rotrpm",
+               "custom": {
+                 "drawStyle": "line",
+                 "lineInterpolation": "linear",
+                 "lineWidth": 1,
+                 "fillOpacity": 10,
+                 "showPoints": "never",
+                 "spanNulls": false,
+                 "axisPlacement": "auto"
+               },
+               "color": {
+                 "mode": "palette-classic"
+               }
+             },
+             "overrides": []
+           },
+           "options": {
+             "legend": {
+               "displayMode": "table",
+               "placement": "bottom",
+               "calcs": [
+                 "lastNotNull",
+                 "max"
+               ]
+             },
+             "tooltip": {
+               "mode": "multi",
+               "sort": "desc"
+             }
+           },
+           "targets": [
+             {
+               "datasource": {
+                 "type": "prometheus",
+                 "uid": "${datasource}"
+               },
+               "editorMode": "code",
+               "expr": "drm_fan_speed_rpm{node=~\"$node\", device=~\"$device\"}",
+               "legendFormat": "{{node}} \u00b7 {{device}} \u00b7 {{fan}}",
+               "range": true,
+               "instant": false,
+               "format": "time_series",
+               "refId": "A"
+             }
+           ]
+         }
+       ]
+     }

@@ (root level) @@
# v1/Service/kube-system/drm-exporter
! + one document added:
+ apiVersion: v1
+ kind: Service
+ metadata:
+   name: drm-exporter
+   namespace: kube-system
+   labels:
+     app.kubernetes.io/component: exporter
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/managed-by: Helm
+     app.kubernetes.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   type: ClusterIP
+   selector:
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/name: drm-exporter
+   ports:
+   - name: metrics
+     port: 8081
+     protocol: TCP
+     targetPort: metrics

@@ (root level) @@
# apps/v1/DaemonSet/kube-system/drm-exporter
! + one document added:
+ apiVersion: apps/v1
+ kind: DaemonSet
+ metadata:
+   name: drm-exporter
+   namespace: kube-system
+   labels:
+     app.kubernetes.io/component: exporter
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/managed-by: Helm
+     app.kubernetes.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   selector:
+     matchLabels:
+       app.kubernetes.io/instance: drm-exporter
+       app.kubernetes.io/name: drm-exporter
+   template:
+     metadata:
+       labels:
+         app.kubernetes.io/instance: drm-exporter
+         app.kubernetes.io/name: drm-exporter
+     spec:
+       affinity:
+         nodeAffinity:
+           requiredDuringSchedulingIgnoredDuringExecution:
+             nodeSelectorTerms:
+             - matchExpressions:
+               - key: amd.com/gpu
+                 operator: In
+                 values:
+                 - "true"
+             - matchExpressions:
+               - key: amd.com/igpu
+                 operator: In
+                 values:
+                 - "true"
+       automountServiceAccountToken: false
+       containers:
+       - name: drm-exporter
+         image: "ghcr.io/home-operations/drm-exporter@sha256:7229811068328ff3d9f9ac70b17319bb036e5e13dff6d743d5c5e3705d961488"
+         imagePullPolicy: IfNotPresent
+         env:
+         - name: DRM_EXPORTER_ADDRESS
+           value: "0.0.0.0"
+         - name: DRM_EXPORTER_PORT
+           value: "8081"
+         - name: DRM_EXPORTER_INTERVAL_SECONDS
+           value: "5"
+         - name: RUST_LOG
+           value: info
+         livenessProbe:
+           httpGet:
+             path: /healthz
+             port: metrics
+           initialDelaySeconds: 5
+           periodSeconds: 20
+         ports:
+         - name: metrics
+           containerPort: 8081
+           protocol: TCP
+         readinessProbe:
+           httpGet:
+             path: /readyz
+             port: metrics
+           initialDelaySeconds: 2
+           periodSeconds: 10
+         resources:
+           claims:
+           - name: gpu
+           limits:
+             memory: 128Mi
+           requests:
+             cpu: 10m
+         volumeMounts:
+         - name: sys
+           mountPath: /sys
+           readOnly: true
+         securityContext:
+           allowPrivilegeEscalation: false
+           capabilities:
+             add:
+             - PERFMON
+             drop:
+             - ALL
+           privileged: false
+           readOnlyRootFilesystem: true
+       resourceClaims:
+       - name: gpu
+         resourceClaimTemplateName: drm-exporter-gpu
+       securityContext:
+         runAsGroup: 0
+         runAsNonRoot: false
+         runAsUser: 0
+         seccompProfile:
+           type: RuntimeDefault
+       serviceAccountName: drm-exporter
+       terminationGracePeriodSeconds: 30
+       volumes:
+       - name: sys
+         hostPath:
+           type: Directory
+           path: /sys
+   updateStrategy:
+     type: RollingUpdate

@@ (root level) @@
# grafana.integreatly.org/v1beta1/GrafanaDashboard/kube-system/drm-exporter-dashboard
! + one document added:
+ apiVersion: grafana.integreatly.org/v1beta1
+ kind: GrafanaDashboard
+ metadata:
+   name: drm-exporter-dashboard
+   namespace: kube-system
+   labels:
+     app.kubernetes.io/component: exporter
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/managed-by: Helm
+     app.kubernetes.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   allowCrossNamespaceImport: true
+   configMapRef:
+     name: drm-exporter-dashboard
+     key: drm-exporter.json
+   folder: hardware
+   instanceSelector:
+     matchLabels:
+       grafana.internal/instance: grafana
+   resyncPeriod: 10m

@@ (root level) @@
# resource.k8s.io/v1/ResourceClaimTemplate/kube-system/drm-exporter-gpu
! + one document added:
+ apiVersion: resource.k8s.io/v1
+ kind: ResourceClaimTemplate
+ metadata:
+   name: drm-exporter-gpu
+   namespace: kube-system
+   labels:
+     app.kubernetes.io/component: exporter
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/managed-by: Helm
+     app.kubernetes.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   spec:
+     devices:
+       requests:
+       - name: drm-exporter
+         exactly:
+           adminAccess: true
+           allocationMode: All
+           deviceClassName: gpu.amd.com

@@ (root level) @@
# monitoring.coreos.com/v1/ServiceMonitor/kube-system/drm-exporter
! + one document added:
+ apiVersion: monitoring.coreos.com/v1
+ kind: ServiceMonitor
+ metadata:
+   name: drm-exporter
+   namespace: kube-system
+   labels:
+     app.kubernetes.io/component: exporter
+     app.kubernetes.io/instance: drm-exporter
+     app.kubernetes.io/managed-by: Helm
+     app.kubernetes.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/name: drm-exporter
+     helm.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   endpoints:
+   - interval: 30s
+     path: /metrics
+     port: metrics
+     relabelings:
+     - sourceLabels:
+       - __meta_kubernetes_pod_node_name
+       targetLabel: node
+     - action: replace
+       regex: "(.*)"
+       replacement: $1
+       sourceLabels:
+       - __meta_kubernetes_pod_node_name
+       targetLabel: kubernetes_node
+     - action: replace
+       regex: "(.*)"
+       

$$


This message was truncated. Download full message

@tanguille-cluster

tanguille-cluster Bot commented Jun 22, 2026

Copy link
Copy Markdown
@@ metadata.labels @@
# v1/Namespace/kube-system
! + one map entry added:
+ resource.kubernetes.io/admin-access: "true"

@@ spec @@
# kustomize.toolkit.fluxcd.io/v1/Kustomization/media/fileflows
! + one map entry added:
+ dependsOn:
+ - name: k8s-gpu-dra-driver
+   namespace: kube-system

@@ spec @@
# kustomize.toolkit.fluxcd.io/v1/Kustomization/media/jellyfin
! + one map entry added:
+ dependsOn:
+ - name: k8s-gpu-dra-driver
+   namespace: kube-system

@@ spec.values.controllers @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/kube-system/generic-device-plugin
! - one map entry removed:
- generic-device-plugin:
-   type: daemonset
-   pod:
-     nodeSelector:
-       amd.com/gpu: "true"
-   containers:
-     app:
-       resources:
-         limits:
-           memory: 64Mi
-         requests:
-           cpu: 10m
-           memory: 16Mi
-       args:
-       - "--domain=squat.ai"
-       - "--device"
-       - |
-         name: dri
-         groups:
-           - count: 4
-             paths:
-               - path: /dev/dri
-               - path: /dev/kfd
-         
-       image:
-         repository: ghcr.io/squat/generic-device-plugin
-         tag: "latest@sha256:dc192e164c69b03f156765793a1be62ca437709ae477b27ca7d8f3dcf5021576"
-       securityContext:
-         privileged: true

@@ spec.values.controllers.fileflows.containers.app.resources @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/fileflows
! + one map entry added:
+ claims:
+ - name: gpu

@@ spec.values.controllers.fileflows.containers.app.resources.limits @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/fileflows
! - one map entry removed:
- squat.ai/dri: 1

@@ spec.values.controllers.fileflows.containers.app.resources.requests @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/fileflows
! - one map entry removed:
- squat.ai/dri: 1

@@ spec.values.defaultPodOptions @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/fileflows
! + one map entry added:
+ resourceClaims:
+ - name: gpu
+   resourceClaimName: shared-dgpu

@@ spec.values.controllers.jellyfin.containers.app.resources @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/jellyfin
! + one map entry added:
+ claims:
+ - name: gpu

@@ spec.values.controllers.jellyfin.containers.app.resources.limits @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/jellyfin
! - one map entry removed:
- squat.ai/dri: 1

@@ spec.values.controllers.jellyfin.containers.app.resources.requests @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/jellyfin
! - one map entry removed:
- squat.ai/dri: 1

@@ spec.values.defaultPodOptions @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/media/jellyfin
! + one map entry added:
+ resourceClaims:
+ - name: gpu
+   resourceClaimName: shared-dgpu

@@ (root level) @@
# kustomize.toolkit.fluxcd.io/v1/Kustomization/observability/drm-exporter
! - one document removed:
- apiVersion: kustomize.toolkit.fluxcd.io/v1
- kind: Kustomization
- metadata:
-   name: drm-exporter
-   namespace: observability
-   labels:
-     kustomize.toolkit.fluxcd.io/name: cluster-apps
-     kustomize.toolkit.fluxcd.io/namespace: flux-system
- spec:
-   decryption:
-     provider: sops
-     secretRef:
-       name: sops-age
-   deletionPolicy: WaitForTermination
-   dependsOn:
-   - name: generic-device-plugin
-     namespace: kube-system
-   - name: prometheus-operator-crds
-     namespace: observability
-   interval: 1h
-   patches:
-   - patch: |
-       apiVersion: helm.toolkit.fluxcd.io/v2
-       kind: HelmRelease
-       metadata:
-         name: _
-       spec:
-         install:
-           crds: CreateReplace
-         rollback:
-           cleanupOnFail: true
-         upgrade:
-           cleanupOnFail: true
-           crds: CreateReplace
-           strategy:
-             name: RemediateOnFailure
-           remediation:
-             remediateLastFailure: true
-             retries: 2
-     target:
-       kind: HelmRelease
-       group: helm.toolkit.fluxcd.io
-   path: ./kubernetes/apps/observability/exporters/drm-exporter/app
-   postBuild:
-     substituteFrom:
-     - name: cluster-settings
-       kind: ConfigMap
-       optional: false
-     - name: cluster-secrets
-       kind: Secret
-       optional: false
-   prune: true
-   retryInterval: 2m
-   sourceRef:
-     name: flux-system
-     kind: GitRepository
-     namespace: flux-system
-   targetNamespace: observability
-   timeout: 5m
-   wait: false

@@ (root level) @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/observability/drm-exporter
! - one document removed:
- apiVersion: helm.toolkit.fluxcd.io/v2
- kind: HelmRelease
- metadata:
-   name: drm-exporter
-   namespace: observability
-   labels:
-     kustomize.toolkit.fluxcd.io/name: drm-exporter
-     kustomize.toolkit.fluxcd.io/namespace: observability
- spec:
-   chartRef:
-     name: drm-exporter
-     kind: OCIRepository
-   install:
-     crds: CreateReplace
-   interval: 1h
-   rollback:
-     cleanupOnFail: true
-   upgrade:
-     cleanupOnFail: true
-     crds: CreateReplace
-     remediation:
-       remediateLastFailure: true
-       retries: 2
-     strategy:
-       name: RemediateOnFailure
-   values:
-     resources:
-       limits:
-         memory: 128Mi
-         squat.ai/dri: 1
-       requests:
-         cpu: 10m
-         memory: 32Mi
-         squat.ai/dri: 1
-     fullnameOverride: drm-exporter
-     nodeSelector:
-       amd.com/gpu: "true"
-     podSecurityContext:
-       seccompProfile:
-         type: Unconfined
-       supplementalGroups:
-       - 44
-       - 226
-     tolerations:
-     - key: amd.com/gpu
-       effect: NoSchedule
-       operator: Exists
-     monitoring:
-       dashboards:
-         enabled: true
-         grafanaOperator:
-           enabled: true
-           folder: hardware
-           matchLabels:
-             grafana.internal/instance: grafana
-       serviceMonitor:
-         enabled: true
-         relabelings:
-         - action: replace
-           regex: "(.*)"
-           replacement: $1
-           sourceLabels:
-           - __meta_kubernetes_pod_node_name
-           targetLabel: kubernetes_node
-         - action: replace
-           regex: "(.*)"
-           replacement: $1
-           sourceLabels:
-           - __meta_kubernetes_pod_node_name
-           targetLabel: nodename

@@ (root level) @@
# source.toolkit.fluxcd.io/v1/OCIRepository/observability/drm-exporter
! - one document removed:
- apiVersion: source.toolkit.fluxcd.io/v1
- kind: OCIRepository
- metadata:
-   name: drm-exporter
-   namespace: observability
-   labels:
-     kustomize.toolkit.fluxcd.io/name: drm-exporter
-     kustomize.toolkit.fluxcd.io/namespace: observability
- spec:
-   url: "oci://ghcr.io/home-operations/charts/drm-exporter"
-   interval: 1h
-   layerSelector:
-     mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
-     operation: copy
-   ref:
-     tag: "0.3.3"

@@ (root level) @@
# monitoring.coreos.com/v1/PrometheusRule/observability/drm-exporter-rules
! - one document removed:
- apiVersion: monitoring.coreos.com/v1
- kind: PrometheusRule
- metadata:
-   name: drm-exporter-rules
-   namespace: observability
-   labels:
-     kustomize.toolkit.fluxcd.io/name: drm-exporter
-     kustomize.toolkit.fluxcd.io/namespace: observability
- spec:
-   groups:
-   - name: drm-exporter.rules
-     rules:
-     - alert: GpuFanStalled
-       annotations:
-         summary: "{{ $labels.kubernetes_node }} GPU fan has stalled under load — card has no thermal margin without it, power off the GPU workload"
-       expr: |
-         max by (kubernetes_node) (drm_fan_speed_rpm) < 1000
-         and
-         max by (kubernetes_node) (drm_power_watts) > 100
-       for: 5m
-       labels:
-         severity: critical

@@ (root level) @@
# kustomize.toolkit.fluxcd.io/v1/Kustomization/kube-system/drm-exporter
! + one document added:
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
+ kind: Kustomization
+ metadata:
+   name: drm-exporter
+   namespace: kube-system
+   labels:
+     kustomize.toolkit.fluxcd.io/name: cluster-apps
+     kustomize.toolkit.fluxcd.io/namespace: flux-system
+ spec:
+   decryption:
+     provider: sops
+     secretRef:
+       name: sops-age
+   deletionPolicy: WaitForTermination
+   dependsOn:
+   - name: k8s-gpu-dra-driver
+     namespace: kube-system
+   - name: prometheus-operator-crds
+     namespace: observability
+   interval: 1h
+   patches:
+   - patch: |
+       apiVersion: helm.toolkit.fluxcd.io/v2
+       kind: HelmRelease
+       metadata:
+         name: _
+       spec:
+         install:
+           crds: CreateReplace
+         rollback:
+           cleanupOnFail: true
+         upgrade:
+           cleanupOnFail: true
+           crds: CreateReplace
+           strategy:
+             name: RemediateOnFailure
+           remediation:
+             remediateLastFailure: true
+             retries: 2
+     target:
+       kind: HelmRelease
+       group: helm.toolkit.fluxcd.io
+   path: ./kubernetes/apps/kube-system/drm-exporter/app
+   postBuild:
+     substituteFrom:
+     - name: cluster-settings
+       kind: ConfigMap
+       optional: false
+     - name: cluster-secrets
+       kind: Secret
+       optional: false
+   prune: true
+   retryInterval: 2m
+   sourceRef:
+     name: flux-system
+     kind: GitRepository
+     namespace: flux-system
+   targetNamespace: kube-system
+   timeout: 5m
+   wait: false

@@ (root level) @@
# kustomize.toolkit.fluxcd.io/v1/Kustomization/kube-system/k8s-gpu-dra-driver
! + one document added:
+ apiVersion: kustomize.toolkit.fluxcd.io/v1
+ kind: Kustomization
+ metadata:
+   name: k8s-gpu-dra-driver
+   namespace: kube-system
+   labels:
+     kustomize.toolkit.fluxcd.io/name: cluster-apps
+     kustomize.toolkit.fluxcd.io/namespace: flux-system
+ spec:
+   decryption:
+     provider: sops
+     secretRef:
+       name: sops-age
+   deletionPolicy: WaitForTermination
+   interval: 1h
+   patches:
+   - patch: |
+       apiVersion: helm.toolkit.fluxcd.io/v2
+       kind: HelmRelease
+       metadata:
+         name: _
+       spec:
+         install:
+           crds: CreateReplace
+         rollback:
+           cleanupOnFail: true
+         upgrade:
+           cleanupOnFail: true
+           crds: CreateReplace
+           strategy:
+             name: RemediateOnFailure
+           remediation:
+             remediateLastFailure: true
+             retries: 2
+     target:
+       kind: HelmRelease
+       group: helm.toolkit.fluxcd.io
+   path: ./kubernetes/apps/kube-system/k8s-gpu-dra-driver/app
+   postBuild:
+     substituteFrom:
+     - name: cluster-settings
+       kind: ConfigMap
+       optional: false
+     - name: cluster-secrets
+       kind: Secret
+       optional: false
+   prune: true
+   retryInterval: 2m
+   sourceRef:
+     name: flux-system
+     kind: GitRepository
+     namespace: flux-system
+   targetNamespace: kube-system
+   timeout: 5m
+   wait: true

@@ (root level) @@
# resource.k8s.io/v1/ResourceClaim/media/shared-dgpu
! + one document added:
+ apiVersion: resource.k8s.io/v1
+ kind: ResourceClaim
+ metadata:
+   name: shared-dgpu
+   namespace: media
+   labels:
+     kustomize.toolkit.fluxcd.io/name: cluster-apps
+     kustomize.toolkit.fluxcd.io/namespace: flux-system
+ spec:
+   devices:
+     requests:
+     - name: gpu
+       exactly:
+         allocationMode: ExactCount
+         count: 1
+         deviceClassName: gpu.amd.com
+         selectors:
+         - cel:
+             expression: "device.attributes[\"gpu.amd.com\"].type == \"amdgpu\""

@@ (root level) @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/kube-system/drm-exporter
! + one document added:
+ apiVersion: helm.toolkit.fluxcd.io/v2
+ kind: HelmRelease
+ metadata:
+   name: drm-exporter
+   namespace: kube-system
+   labels:
+     kustomize.toolkit.fluxcd.io/name: drm-exporter
+     kustomize.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   chartRef:
+     name: drm-exporter
+     kind: OCIRepository
+   install:
+     crds: CreateReplace
+   interval: 1h
+   rollback:
+     cleanupOnFail: true
+   upgrade:
+     cleanupOnFail: true
+     crds: CreateReplace
+     remediation:
+       remediateLastFailure: true
+       retries: 2
+     strategy:
+       name: RemediateOnFailure
+   values:
+     resources:
+       limits:
+         memory: 128Mi
+       requests:
+         cpu: 10m
+     dra:
+       deviceClassName: gpu.amd.com
+       enabled: true
+     securityContext:
+       capabilities:
+         add:
+         - PERFMON
+     monitoring:
+       dashboards:
+         enabled: true
+         grafanaOperator:
+           enabled: true
+           folder: hardware
+           matchLabels:
+             grafana.internal/instance: grafana
+       serviceMonitor:
+         enabled: true
+         relabelings:
+         - action: replace
+           regex: "(.*)"
+           replacement: $1
+           sourceLabels:
+           - __meta_kubernetes_pod_node_name
+           targetLabel: kubernetes_node
+         - action: replace
+           regex: "(.*)"
+           replacement: $1
+           sourceLabels:
+           - __meta_kubernetes_pod_node_name
+           targetLabel: nodename
+     affinity:
+       nodeAffinity:
+         requiredDuringSchedulingIgnoredDuringExecution:
+           nodeSelectorTerms:
+           - matchExpressions:
+             - key: amd.com/gpu
+               operator: In
+               values:
+               - "true"
+           - matchExpressions:
+             - key: amd.com/igpu
+               operator: In
+               values:
+               - "true"

@@ (root level) @@
# source.toolkit.fluxcd.io/v1/OCIRepository/kube-system/drm-exporter
! + one document added:
+ apiVersion: source.toolkit.fluxcd.io/v1
+ kind: OCIRepository
+ metadata:
+   name: drm-exporter
+   namespace: kube-system
+   labels:
+     kustomize.toolkit.fluxcd.io/name: drm-exporter
+     kustomize.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   url: "oci://ghcr.io/home-operations/charts/drm-exporter"
+   interval: 1h
+   layerSelector:
+     mediaType: application/vnd.cncf.helm.chart.content.v1.tar+gzip
+     operation: copy
+   ref:
+     tag: "0.3.3"

@@ (root level) @@
# monitoring.coreos.com/v1/PrometheusRule/kube-system/drm-exporter-rules
! + one document added:
+ apiVersion: monitoring.coreos.com/v1
+ kind: PrometheusRule
+ metadata:
+   name: drm-exporter-rules
+   namespace: kube-system
+   labels:
+     kustomize.toolkit.fluxcd.io/name: drm-exporter
+     kustomize.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   groups:
+   - name: drm-exporter.rules
+     rules:
+     - alert: GpuFanStalled
+       annotations:
+         summary: "{{ $labels.kubernetes_node }} GPU fan has stalled under load — card has no thermal margin without it, power off the GPU workload"
+       expr: |
+         max by (kubernetes_node) (drm_fan_speed_rpm) < 1000
+         and
+         max by (kubernetes_node) (drm_power_watts) > 100
+       for: 5m
+       labels:
+         severity: critical

@@ (root level) @@
# helm.toolkit.fluxcd.io/v2/HelmRelease/kube-system/k8s-gpu-dra-driver
! + one document added:
+ apiVersion: helm.toolkit.fluxcd.io/v2
+ kind: HelmRelease
+ metadata:
+   name: k8s-gpu-dra-driver
+   namespace: kube-system
+   labels:
+     kustomize.toolkit.fluxcd.io/name: k8s-gpu-dra-driver
+     kustomize.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   chart:
+     spec:
+       version: v1.0.1
+       chart: k8s-gpu-dra-driver
+       sourceRef:
+         name: k8s-gpu-dra-driver
+         kind: HelmRepository
+   install:
+     crds: CreateReplace
+   interval: 1h
+   rollback:
+     cleanupOnFail: true
+   upgrade:
+     cleanupOnFail: true
+     crds: CreateReplace
+     remediation:
+       remediateLastFailure: true
+       retries: 2
+     strategy:
+       name: RemediateOnFailure
+   values:
+     kubeletPlugin:
+       affinity:
+         nodeAffinity:
+           requiredDuringSchedulingIgnoredDuringExecution:
+             nodeSelectorTerms:
+             - matchExpressions:
+               - key: amd.com/gpu
+                 operator: In
+                 values:
+                 - "true"

@@ (root level) @@
# source.toolkit.fluxcd.io/v1/HelmRepository/kube-system/k8s-gpu-dra-driver
! + one document added:
+ apiVersion: source.toolkit.fluxcd.io/v1
+ kind: HelmRepository
+ metadata:
+   name: k8s-gpu-dra-driver
+   namespace: kube-system
+   labels:
+     kustomize.toolkit.fluxcd.io/name: k8s-gpu-dra-driver
+     kustomize.toolkit.fluxcd.io/namespace: kube-system
+ spec:
+   url: "https://rocm.github.io/k8s-gpu-dra-driver"
+   interval: 1h

@Tanguille
Tanguille marked this pull request as draft June 23, 2026 17:47
@Tanguille

Copy link
Copy Markdown
Owner Author

Parking as draft — blocked on GPU sharing, not the config itself.

This migrates the dGPU (R9700, control-1) off generic-device-plugin to DRA, but the AMD DRA driver allocates a GPU exclusively per ResourceClaim and can only share a GPU via pods referencing the same claim — which is namespace-scoped. Both the LLM (ai) and media (jellyfin/fileflows, media) need the single dGPU, so they cannot share it under DRA, and RDNA4 has no timeslicing/MPS/partitioning to fall back on. generic-device-plugin (count: 4) is the only thing that does cooperative cross-namespace sharing today.

Revisit when: DRA Consumable Capacity (KEP-5075) reaches beta and the AMD GPU DRA driver advertises allowMultipleAllocations for the R9700 (~2027+).

@Tanguille
Tanguille marked this pull request as ready for review June 27, 2026 13:07
@Tanguille
Tanguille force-pushed the refactor/migrate-to-dra-amdgpu branch 2 times, most recently from 3c33d46 to f430e1a Compare June 27, 2026 13:26
@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown

AI Automated Review

Analysis engine: qwen-3.6-fast@http://litellm.ai.svc.cluster.local/v1 (openai)

Recommendation

Approve with verification pending. This PR correctly migrates the cluster from the squat/generic-device-plugin to the AMD ROCm k8s-gpu-dra-driver (v0.1.0) for Dynamic Resource Allocation (DRA). The changes consistently update resource requests from extended resources (squat.ai/dri) to ResourceClaimTemplates and explicit ResourceClaims for shared devices.

The implementation aligns with the repository's GitOps structure, introducing appropriate dependsOn hooks in Kustomizations to ensure the DRA driver is deployed before consumer workloads. The removal of the generic device plugin and the addition of the resource.kubernetes.io/admin-access label to the kube-system namespace are critical and correctly implemented prerequisites for DRA in the current Kubernetes version.

Change-by-Change Findings

1. DRA Driver Deployment (kube-system)

  • kubernetes/apps/kube-system/k8s-gpu-dra-driver/: New HelmRelease and OCIRepository for k8s-gpu-dra-driver:v0.1.0. Affinity correctly targets nodes with amd.com/gpu or amd.com/igpu.
  • kubernetes/apps/kube-system/kustomization.yaml: Adds the required resource.kubernetes.io/admin-access: "true" label to the kube-system namespace. This is mandatory for DRA; the patch target logic (not-used -> kube-system via transformer) is a standard Kustomize pattern for this repo and is correct.
  • kubernetes/apps/kube-system/drm-exporter/: Moved from observability to kube-system and updated to use DRA (dra.enabled: true). This reduces dependency fragmentation.
  • kubernetes/apps/kube-system/generic-device-plugin/: Removed. This cleans up the legacy device plugin approach.

2. Consumer Updates (ai, actions-runner-system, media)

  • kubernetes/apps/ai/sglang/: Updated to use ResourceClaimTemplate: dgpu. Removed squat.ai/dri from resources. Added dependsOn: k8s-gpu-dra-driver.
  • kubernetes/apps/ai/llmkube/: Updated to use ResourceClaimTemplate: igpu. Correctly pins to iGPU nodes. Added dependsOn: k8s-gpu-dra-driver.
  • kubernetes/apps/actions-runner-system/.../gpu-builder/: Updated to use ResourceClaimTemplate: dgpu. Added dependency on the new DRA driver.
  • kubernetes/apps/media/:
    • Shared Claim: Introduces shared-dgpu-claim.yaml (a namespace-scoped ResourceClaim) for fileflows and jellyfin. This is an excellent pattern for sharing a single GPU device between multiple pods without requiring individual claim templates for each.
    • Consumers: fileflows and jellyfin updated to reference shared-dgpu via resourceClaimName instead of resourceClaimTemplateName. This prevents accidental over-provisioning of the exclusive GPU resource.

3. Component Libraries

  • kubernetes/components/dra-dgpu/ and kubernetes/components/dra-igpu/: New Kustomize components providing ResourceClaimTemplate definitions. This centralizes the device class and CEL selector logic (device.attributes["gpu.amd.com"].type == "amdgpu"), ensuring consistency across all consumers.

Required Checks Verification

  1. Validate manifest against target cluster version:
    • Verified: DRA (resource.k8s.io/v1) requires Kubernetes v1.31+ (Alpha/Beta) or v1.32+ (GA). The cluster uses Talos Linux with AMD GPU support, which implies a recent Kubernetes version (likely v1.32+ given the DRA usage). The ResourceClaim and ResourceClaimTemplate APIs are present in the diff. The resource.kubernetes.io/admin-access label is explicitly added, which is a requirement for DRA in supported versions. The migration suggests the cluster API supports these CRDs.
  2. Check for resource quota / limit changes:
    • Verified: CPU/Memory limits for consumers (sglang, fileflows, jellyfin) remain unchanged. The GPU allocation mechanism changes from extended resources to DRA, but the logical count (1 GPU per workload, shared for media apps) is preserved. The shared-dgpu-claim explicitly requests count: 1.
  3. Verify file path sanitization:
    • N/A: No file serving paths are exposed via public routes in this PR. The media apps (fileflows, jellyfin) serve internal media libraries via NFS mounts. Path handling is managed by the application containers, not the manifest.
  4. Check for directory traversal vulnerabilities:
    • N/A: DRA device mapping (/dev/dri, /dev/kfd) is handled by the driver CDI (Container Device Interface) or equivalent DRA mounting logic. The manifests define which devices are requested, not the mount paths. The security context (runAsUser: 0, supplementalGroups) is consistent with the previous privileged device access. No user input paths are exposed in the manifest configuration.
  5. Review for path traversal vulnerabilities:
    • N/A: Same as above. The risk of path traversal in DRA is associated with the driver implementation, not the Kubernetes manifests requesting the resources. The manifests correctly request specific device classes.
  6. Test with edge-case paths (null bytes, symlinks):
    • N/A: This is a manifest update, not application code. The cluster operator must verify that the k8s-gpu-dra-driver correctly resolves device nodes on the host without symlink confusion. This is outside the scope of the PR review but should be tested manually after merge.

Standards Compliance

  • GitOps Structure: Changes follow the kubernetes/apps/<app>/<type>/ structure.
  • Kustomize: Correct use of components for reusable DRA claims.
  • Security: runAsUser: 0 and privileged: true (for gpu-builder) are retained where necessary for ROCm/device access, consistent with previous behavior.
  • Dependencies: dependsOn is correctly used in ks.yaml files to enforce reconciliation order (Driver -> Consumers).

Linked Issue Fit

No linked issues were found. The PR title refactor: migrate to dra amdgpu accurately describes the work.

Unknowns / Needs Verification

  • Cluster Version: Confirm the target Kubernetes version is >= v1.32 (or v1.31 if using Alpha/Beta features with appropriate CRDs installed). If the cluster is older, DRA APIs may not be available.
  • Driver Compatibility: Verify that k8s-gpu-dra-driver:v0.1.0 is compatible with the specific ROCm/kernel version installed on the Talos nodes. The OCI image is from ghcr.io/buroa/helm/k8s-gpu-dra-driver.
  • Rollback Plan: If the migration fails, the old generic-device-plugin manifests are deleted. A rollback would require restoring the previous commit. Ensure Flux reconciliation doesn't immediately re-apply the new state if the driver fails to start (the dependsOn helps mitigate this by pausing consumer reconciliation if the driver isn't ready).

@Tanguille
Tanguille force-pushed the refactor/migrate-to-dra-amdgpu branch from f430e1a to 583d60f Compare June 28, 2026 21:29
@Tanguille
Tanguille marked this pull request as draft June 29, 2026 20:29

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inline findings from the automated review (summary in the sticky comment).

Comment thread kubernetes/apps/kube-system/kustomization.yaml
Comment thread kubernetes/apps/media/shared-dgpu-claim.yaml
Comment thread kubernetes/apps/kube-system/k8s-gpu-dra-driver/app/ocirepository.yaml Outdated
Tanguille and others added 4 commits July 4, 2026 00:28
Move the shared-dgpu ResourceClaim to the media namespace level
(kubernetes/apps/media/shared-dgpu-claim.yaml) so that jellyfin no
longer depends on fileflows for claim lifecycle. The claim is now
reconciled by the root cluster-apps Kustomization alongside the
namespace bootstrap, removing cross-app coupling between two
independent workloads that happen to share the dGPU.

Also clarify the kube-system/kustomization.yaml patch comment to
explain why the target name is "not-used" (the common component
placeholder, renamed to "kube-system" by the namespace transformer).
…nventions

Main moved on since this PR was opened: gpu-builder was deleted entirely
(chore(ci): remove gpu builder), and the iGPU embedding fleet grew a
3-way squat.ai/dri sharing scenario (qwen3-embedding + vmcp-embedding +
qwen35-2b) that the AMD DRA driver can't serve (no timeslicing/consumable
capacity). Scope this PR's DRA migration to the exclusive dGPU only:

- Drop the dra-igpu component and revert qwen3-embedding.yaml to
  squat.ai/dri; keep generic-device-plugin's iGPU daemonset alive
  (trimmed the now-DRA-managed dGPU daemonset out of it).
- k8s-gpu-dra-driver's kubeletPlugin now targets only amd.com/gpu nodes.
- Fix llmkube-models' dependsOn back to generic-device-plugin (nothing
  there uses DRA) and drop the dead k8s-gpu-dra-driver dependsOn from the
  actions-runner-controller-runners Kustomization (gpu-builder is gone).
- Adopt main's now-standard OCIRepository interval: 1h (was 15m/10m).
Conflict resolutions needing judgment:

- ai/sglang/app/helmrelease.yaml: deletion accepted. main retired the sglang
  HelmRelease entirely (7f6e08f/7149d0483, cutover to the llmkube
  qwen36-27b InferenceService). This branch's only change to the file was the
  squat.ai/dri -> DRA claim swap, which dies with the app.
- observability/exporters/drm-exporter -> kube-system/drm-exporter: relocation
  stands. main's only substantive change to the old path (dropping
  runAsUser/runAsGroup 0) is already satisfied by the DRA-mode HelmRelease,
  which has no podSecurityContext at all. Ported main's serviceMonitor
  relabelings forward: main's new GpuFanStalled PrometheusRule groups by
  kubernetes_node, a label only those relabelings produce.
- media/jellyfin/app/helmrelease.yaml: union. Took main's cpu 100m / memory 1Gi
  request and the new container securityContext; kept this branch's DRA claim
  in place of squat.ai/dri.
- ai/litellm/instance/kustomization.yaml and
  kube-system/node-problem-detector/ks.yaml: false rename pairings against the
  deleted sglang kustomization.yaml/ks.yaml. Took main verbatim.
- kube-system/drm-exporter/app/prometheusrule.yaml: main's new alert, carried
  onto the relocated path by git's directory-rename detection. Kept.

Known gap for follow-up: llmkube's qwen36-27b-sglang/-vllm Models still request
squat.ai/dri on control-1, whose generic-device-plugin daemonset this branch
removed in favour of DRA. They are the successor to the sglang dGPU tenant and
need a DRA path before this branch is deployable.
@Tanguille
Tanguille force-pushed the refactor/migrate-to-dra-amdgpu branch from 3bf4745 to adcca03 Compare August 8, 2026 13:48
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fa355ff2-71af-4b07-8388-b27c3a2ee038

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@deepsource-io

deepsource-io Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

DeepSource Code Review

We reviewed changes in 795e4ff...d7557e8 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.

See full review on DeepSource ↗

PR Report Card

Overall Grade   Security  

Reliability  

Complexity  

Hygiene  

Code Review Summary

Analyzer Status Updated (UTC) Details
JavaScript Aug 8, 2026 3:33p.m. Review ↗
Shell Aug 8, 2026 3:33p.m. Review ↗

Important

AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.

…leted

ghcr.io/buroa/helm/k8s-gpu-dra-driver is gone — the repo behind it (buroa/helm)
was deleted upstream, returning 403 even anonymously. ROCm ships its own chart
in-tree now (helm-charts-k8s/) instead of publishing an OCI package, so this
tracks it via GitRepository + chart.spec instead of chartRef/OCIRepository.

Pinned to v1.0.1 (the current driver release; Chart.yaml's own appVersion is
stale at v0.1.0, hence the explicit image.tag override). Verified with
'helm template' against the real chart + our values — renders clean, node
affinity applies as expected.
# Conflicts:
#	kubernetes/apps/kube-system/kustomization.yaml
ROCm publishes the chart via GitHub Pages (rocm.github.io/k8s-gpu-dra-driver,
.github/workflows/release.yaml's helm-gh-pages job) rather than an OCI
package. HelmRepository + chart.spec is the native fit for that — closer to
how every other app in this repo resolves a chart than pinning a raw git
tag/path, and the published chart's version already matches its appVersion
(v1.0.1), so the explicit image.tag override from the previous commit is
gone too. Verified with 'helm template' against the actual published
.tgz artifact.
@Tanguille
Tanguille force-pushed the refactor/migrate-to-dra-amdgpu branch from d7557e8 to 9f29bcb Compare August 21, 2026 21:03
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Warning

Your free Security trial is over. An organization admin can activate billing to continue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant