Skip to content

Commit d2aa9f7

Browse files
committed
fix(manifests): add NVML init container for NVIDIA GPU Operator support
Closes #2484 Kepler failed to load NVML on clusters using the NVIDIA GPU Operator because the operator exposes driver libraries at /run/nvidia/driver (driver-container root FS) but the chart mounted that hostPath directly into the Kepler container, pulling in glibc from the driver image and breaking dynamic linking. Replace the direct hostPath mount with an init container that copies only libnvidia-ml.so* into an emptyDir mounted at /usr/local/nvidia/lib64. The emptyDir holds just the NVML libraries -- no glibc conflict. Changes: manifests/helm/kepler/values.yaml - Chart-only knobs under daemonset.nvidia.* (not config.experimental.gpu.* so templates/configmap.yaml does not leak chart internals into the binary config). Enabling config.experimental.gpu.enabled also triggers the init-container plumbing. manifests/helm/kepler/templates/daemonset.yaml - Gated nvidia-libs init container, emptyDir mount, and LD_LIBRARY_PATH when gpu.enabled is true. manifests/k8s/daemonset.yaml - Same pattern, unconditional (raw manifest has no values system). .pre-commit-config.yaml - Local helm-lint-gpu hook for the gpu.enabled=true variant (keeps validation in pre-commit rather than a dedicated CI job). docs/user/installation.md - Document GPU Operator enablement, overrides, and flag coupling. Init container notes (deep rationale): - Copy globs the known driver lib dirs (usr/lib64, usr/lib/{x86_64,aarch64}-linux-gnu) instead of running find over the driver root FS. That root contains the driver container's /proc: find walked procfs for minutes and exited nonzero on unreadable fdinfo entries, so init crash-looped on real GPU Operator clusters. - set -e with no || true on the copy pass so ENOSPC/EACCES fail init. - cp -P (not -L): preserve the driver symlink chain so leftover driver versions do not blow past the 200Mi emptyDir sizeLimit. - runAsUser/runAsGroup 0 with capabilities dropped for root-owned driver libs without depending on the image USER directive. - NVIDIA_VISIBLE_DEVICES / NVIDIA_MIG_MONITOR_DEVICES stay unconditional for the runtimeClassName: nvidia workaround. Verified: helm lint passes both states; gpu-disabled render has no init/nvml volume path; gpu-enabled ConfigMap keeps only enabled/idlePower/dcgmEndpoint under experimental.gpu. Init validated end to end on a 4-node kubeadm cluster (staged driver root with live procfs: sub-second copy, symlink chain intact, no-driver fallback on 3 nodes, usr/lib64 layout) and against a Tesla T4 where NVML was initialized strictly from the copied directory. Signed-off-by: Himanshu Verma <himnshuverma10152006@gmail.com>
1 parent b0bf879 commit d2aa9f7

5 files changed

Lines changed: 184 additions & 5 deletions

File tree

.pre-commit-config.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,3 +95,16 @@ repos:
9595
- id: helmlint
9696
name: helm-lint
9797
files: ^manifests/helm/[^/]+/(Chart\.yaml|values\.yaml|templates/.*)$
98+
99+
# GPU-enabled helm-lint: covers variants the helmlint hook above cannot
100+
# run (it does not accept --set flags).
101+
- repo: local
102+
hooks:
103+
- id: helm-lint-gpu
104+
name: helm-lint (gpu enabled)
105+
language: system
106+
pass_filenames: false
107+
files: ^manifests/helm/[^/]+/(Chart\.yaml|values\.yaml|templates/.*)$
108+
entry: >
109+
bash -c 'helm lint manifests/helm/kepler
110+
--set config.experimental.gpu.enabled=true'

docs/user/installation.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,41 @@ helm install kepler oci://quay.io/sustainable_computing_io/charts/kepler \
100100
--values values.yaml
101101
```
102102

103+
#### Enabling GPU Power Monitoring (NVIDIA GPU Operator)
104+
105+
To export GPU power metrics on clusters with the [NVIDIA GPU Operator](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/overview.html), enable the experimental GPU flag. The chart then adds an `nvidia-libs` init container that copies `libnvidia-ml.so*` from the host driver path into an `emptyDir`, and points `LD_LIBRARY_PATH` at it:
106+
107+
```bash
108+
helm install kepler oci://quay.io/sustainable_computing_io/charts/kepler \
109+
--namespace kepler \
110+
--create-namespace \
111+
--set config.experimental.gpu.enabled=true
112+
```
113+
114+
What this changes:
115+
116+
- Adds an `nvidia-libs` init container that copies `libnvidia-ml.so*` into a 200Mi `emptyDir` using `cp -P` (preserves the driver symlink chain; `cp -L` can exceed the size limit when leftover driver versions are present).
117+
- Mounts that `emptyDir` read-only at `/usr/local/nvidia/lib64` and sets `LD_LIBRARY_PATH=/usr/local/nvidia/lib64`.
118+
- Mounts the host driver directory (default `/run/nvidia/driver`, overridable via `daemonset.nvidia.driverPath`) with `DirectoryOrCreate` so non-GPU nodes still schedule.
119+
120+
Defaults assume the standard GPU Operator layout. Override via `values.yaml` if needed:
121+
122+
```yaml
123+
config:
124+
experimental:
125+
gpu:
126+
enabled: true
127+
128+
daemonset:
129+
nvidia:
130+
driverPath: /run/nvidia/driver
131+
nvmlInitImage:
132+
repository: busybox
133+
tag: 1.36.1
134+
```
135+
136+
Note: `config.experimental.gpu.enabled` is intentionally coupled with the chart's `daemonset.nvidia.*` plumbing. Enabling the binary flag without the init path (or the reverse) is not a supported split. Leave `enabled: false` if you do not want GPU power monitoring.
137+
103138
#### Helm Management Commands
104139

105140
```bash

manifests/helm/kepler/templates/daemonset.yaml

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,51 @@ spec:
4141
affinity:
4242
{{- toYaml . | nindent 8 }}
4343
{{- end }}
44+
{{- if .Values.config.experimental.gpu.enabled }}
45+
initContainers:
46+
- name: nvidia-libs
47+
image: "{{ .Values.daemonset.nvidia.nvmlInitImage.repository }}:{{ .Values.daemonset.nvidia.nvmlInitImage.tag }}"
48+
imagePullPolicy: IfNotPresent
49+
securityContext:
50+
# Root required to read root-owned driver libs; capabilities dropped.
51+
runAsNonRoot: false
52+
runAsUser: 0
53+
runAsGroup: 0
54+
readOnlyRootFilesystem: true
55+
allowPrivilegeEscalation: false
56+
capabilities:
57+
drop: [ALL]
58+
command:
59+
- sh
60+
- -c
61+
- |
62+
set -e
63+
# Glob known lib dirs only; never walk the driver rootfs — it
64+
# contains the driver container's /proc (slow, unreadable).
65+
# cp -P keeps driver symlink chains (avoids 200Mi emptyDir blowup).
66+
copied=0
67+
for d in /run-nvidia/usr/lib64 \
68+
/run-nvidia/usr/lib/x86_64-linux-gnu \
69+
/run-nvidia/usr/lib/aarch64-linux-gnu; do
70+
[ -d "$d" ] || continue
71+
for f in "$d"/libnvidia-ml.so*; do
72+
[ -e "$f" ] || continue
73+
cp -P "$f" /nvml-libs/
74+
copied=$((copied+1))
75+
done
76+
done
77+
if [ "$copied" -gt 0 ]; then
78+
echo "Copied $copied NVML library file(s) to /nvml-libs"
79+
else
80+
echo "No NVML libraries found at /run-nvidia — GPU metrics will be unavailable on this node"
81+
fi
82+
volumeMounts:
83+
- name: lib-nvidia
84+
mountPath: /run-nvidia
85+
readOnly: true
86+
- name: nvml-libs
87+
mountPath: /nvml-libs
88+
{{- end }}
4489
containers:
4590
- name: kepler
4691
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
@@ -66,6 +111,11 @@ spec:
66111
readOnly: true
67112
- name: cfm
68113
mountPath: /etc/kepler
114+
{{- if .Values.config.experimental.gpu.enabled }}
115+
- name: nvml-libs
116+
mountPath: /usr/local/nvidia/lib64
117+
readOnly: true
118+
{{- end }}
69119
{{- with .Values.daemonset.startupProbe }}
70120
startupProbe:
71121
{{- toYaml . | nindent 12 }}
@@ -84,11 +134,16 @@ spec:
84134
fieldRef:
85135
apiVersion: v1
86136
fieldPath: spec.nodeName
87-
# Required for NVML to see all MIG devices when running in a container
137+
# Always set (independent of gpu.enabled) for runtimeClassName: nvidia.
88138
- name: NVIDIA_VISIBLE_DEVICES
89139
value: "all"
90140
- name: NVIDIA_MIG_MONITOR_DEVICES
91141
value: "all"
142+
{{- if .Values.config.experimental.gpu.enabled }}
143+
# Path for libnvidia-ml.so from the nvidia-libs init container.
144+
- name: LD_LIBRARY_PATH
145+
value: /usr/local/nvidia/lib64
146+
{{- end }}
92147
{{- with .Values.daemonset.resources }}
93148
resources:
94149
{{- toYaml . | nindent 12 }}
@@ -103,3 +158,13 @@ spec:
103158
- name: cfm
104159
configMap:
105160
name: {{ include "kepler.fullname" . }}
161+
{{- if .Values.config.experimental.gpu.enabled }}
162+
- name: lib-nvidia
163+
hostPath:
164+
path: {{ .Values.daemonset.nvidia.driverPath | quote }}
165+
# DirectoryOrCreate so non-GPU nodes still schedule.
166+
type: DirectoryOrCreate
167+
- name: nvml-libs
168+
emptyDir:
169+
sizeLimit: 200Mi
170+
{{- end }}

manifests/helm/kepler/values.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ daemonset:
7272
initialDelaySeconds: 5
7373
periodSeconds: 10
7474

75+
# Chart-only knobs for the NVML init-container pattern. These do NOT live
76+
# under .Values.config because that tree is dumped verbatim into
77+
# /etc/kepler/config.yaml (templates/configmap.yaml), and the Kepler binary
78+
# doesn't (and shouldn't) know about driver paths or init-image names.
79+
nvidia:
80+
# Path on the host where the NVIDIA GPU Operator exposes the driver
81+
# container root FS. Override if your GPU Operator uses a non-default path.
82+
driverPath: /run/nvidia/driver
83+
# Init container used to copy NVML libraries into an emptyDir.
84+
# Override repository/tag for airgapped or private-registry environments.
85+
nvmlInitImage:
86+
repository: busybox
87+
tag: 1.36.1
88+
7589
config:
7690
log:
7791
level: debug
@@ -129,6 +143,9 @@ config:
129143
zones: [] # List of zones to enable (default enable all)
130144
chipRules: [] # User-defined chip pairing rules (override/add to hardcoded defaults)
131145
gpu:
146+
# Enabling this also triggers the chart's NVML init container and
147+
# LD_LIBRARY_PATH injection (daemonset.nvidia.*). Leave false if you
148+
# do not want GPU power monitoring.
132149
enabled: false # Enable experimental GPU power monitoring
133150
idlePower: 0 # GPU idle power in Watts (0 = auto-detect)
134151
dcgmEndpoint: "" # dcgm-exporter metrics URL for MIG (auto-discovered if empty)

manifests/k8s/daemonset.yaml

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,49 @@ spec:
2626
- key: node-role.kubernetes.io/master
2727
operator: Exists
2828
effect: NoSchedule
29+
initContainers:
30+
- name: nvidia-libs
31+
image: busybox:1.36.1
32+
imagePullPolicy: IfNotPresent
33+
securityContext:
34+
# Root required to read root-owned driver libs; capabilities dropped.
35+
runAsNonRoot: false
36+
runAsUser: 0
37+
runAsGroup: 0
38+
readOnlyRootFilesystem: true
39+
allowPrivilegeEscalation: false
40+
capabilities:
41+
drop: [ALL]
42+
command:
43+
- sh
44+
- -c
45+
- |
46+
set -e
47+
# Glob known lib dirs only; never walk the driver rootfs — it
48+
# contains the driver container's /proc (slow, unreadable).
49+
# cp -P keeps driver symlink chains (avoids 200Mi emptyDir blowup).
50+
copied=0
51+
for d in /run-nvidia/usr/lib64 \
52+
/run-nvidia/usr/lib/x86_64-linux-gnu \
53+
/run-nvidia/usr/lib/aarch64-linux-gnu; do
54+
[ -d "$d" ] || continue
55+
for f in "$d"/libnvidia-ml.so*; do
56+
[ -e "$f" ] || continue
57+
cp -P "$f" /nvml-libs/
58+
copied=$((copied+1))
59+
done
60+
done
61+
if [ "$copied" -gt 0 ]; then
62+
echo "Copied $copied NVML library file(s) to /nvml-libs"
63+
else
64+
echo "No NVML libraries found at /run-nvidia — GPU metrics will be unavailable on this node"
65+
fi
66+
volumeMounts:
67+
- name: lib-nvidia
68+
mountPath: /run-nvidia
69+
readOnly: true
70+
- name: nvml-libs
71+
mountPath: /nvml-libs
2972
containers:
3073
- name: kepler
3174
image: <KEPLER_IMAGE>
@@ -54,8 +97,9 @@ spec:
5497
mountPath: /etc/kepler/config.yaml
5598
subPath: config.yaml
5699
readOnly: true
57-
- name: nvidia-driver
58-
mountPath: /run/nvidia/driver
100+
# NVML libs only (copied by init); avoids mounting driver glibc.
101+
- name: nvml-libs
102+
mountPath: /usr/local/nvidia/lib64
59103
readOnly: true
60104
livenessProbe:
61105
httpGet:
@@ -75,8 +119,9 @@ spec:
75119
fieldRef:
76120
apiVersion: v1
77121
fieldPath: spec.nodeName
122+
# Static path; K8s cannot expand the image's existing LD_LIBRARY_PATH.
78123
- name: LD_LIBRARY_PATH
79-
value: /run/nvidia/driver/usr/lib64
124+
value: /usr/local/nvidia/lib64
80125
# Required for NVML to see all MIG devices when running in a container
81126
- name: NVIDIA_VISIBLE_DEVICES
82127
value: all
@@ -92,7 +137,11 @@ spec:
92137
- name: cfm
93138
configMap:
94139
name: kepler
95-
- name: nvidia-driver
140+
# lib-nvidia: host driver path; nvml-libs: init-populated emptyDir.
141+
- name: lib-nvidia
96142
hostPath:
97143
path: /run/nvidia/driver
98144
type: DirectoryOrCreate
145+
- name: nvml-libs
146+
emptyDir:
147+
sizeLimit: 200Mi

0 commit comments

Comments
 (0)