Skip to content

Commit e4c6b21

Browse files
committed
[LEP-2916] feat(helm/gpud): support label reader, fix mounts (optional)
Signed-off-by: Gyuho Lee <gyuhol@nvidia.com>
1 parent aaad4ab commit e4c6b21

6 files changed

Lines changed: 438 additions & 25 deletions

File tree

deployments/helm/gpud/Chart.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ apiVersion: v2
22
name: gpud
33
description: GPUd Helm chart for Kubernetes
44
type: application
5-
version: 0.8.0
6-
appVersion: "v0.8.0"
5+
version: 0.9.0
6+
appVersion: "v0.9.0"
77
icon: https://assets.nvidiagrid.net/ngc/logos/Infrastructure.png

deployments/helm/gpud/templates/daemonset.yaml

Lines changed: 276 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ spec:
2727
spec:
2828
# --- Pod Identity & Security ---
2929
serviceAccountName: {{ include "gpud.serviceAccountName" . }}
30+
# K8s auto-mounts SA tokens unless explicitly disabled; see https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#opt-out-of-automounting-service-account-tokens
31+
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
3032
{{- with .Values.podSecurityContext }}
3133
securityContext:
3234
{{- toYaml . | nindent 8 }}
@@ -37,9 +39,11 @@ spec:
3739
{{- end }}
3840

3941
# --- Node-level Settings & Lifecycle ---
40-
# Required for the driver to interact with the host's network stack.
42+
# Required for the driver to interact with the host's network stack (e.g. for correct IP reporting).
43+
# GPUd is intentionally exposed via host networking, so there is no Service object for this chart.
44+
# Consumers should reach GPUd via each node's IP/port (e.g. https://<node-ip>:15132).
4145
hostNetwork: true
42-
# Required for the driver to inspect processes on the host.
46+
# Required for the driver to inspect processes on the host (e.g. via /proc).
4347
hostPID: true
4448
restartPolicy: Always
4549
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
@@ -62,27 +66,83 @@ spec:
6266

6367
# --- Storage ---
6468
volumes:
69+
# System logs directory for GPUd to write logs and monitor system log files
70+
# Required by: pkg/gpud-manager/systemd/systemd.go (writes to /var/log/gpud.log)
6571
- name: host-log
6672
hostPath:
6773
path: /var/log
74+
type: Directory
75+
76+
# Persistent storage for kernel crash dumps and system error logs
77+
# Required for: analyzing system crashes and kernel panics
6878
- name: host-pstore
6979
hostPath:
7080
path: /var/lib/systemd/pstore
7181
type: DirectoryOrCreate
82+
83+
# Device files for hardware access and kernel message buffer
84+
# Required by: pkg/kmsg/watcher.go:46 (reads /dev/kmsg for kernel messages)
85+
# Also provides access to GPU devices (/dev/nvidia*), block devices, etc.
7286
- name: host-dev
7387
hostPath:
7488
path: /dev
89+
type: Directory
90+
91+
{{- if .Values.gpud.mountHostSys }}
92+
# Sysfs virtual filesystem for hardware and device information
93+
# Required by:
94+
# - components/accelerator/nvidia/infiniband/class/class.go:34 (/sys/class/infiniband - InfiniBand monitoring)
95+
# - pkg/host/machine_id.go:152 (/sys/class/dmi - DMI/hardware info for machine ID)
96+
# - pkg/fuse/fuse.go:18 (/sys/fs/fuse/connections - FUSE filesystem monitoring)
97+
# Can be disabled for security-sensitive environments via gpud.mountHostSys=false
98+
- name: host-sys
99+
hostPath:
100+
path: /sys
101+
type: Directory
102+
{{- end }}
103+
104+
{{- if .Values.gpud.mountHostProc }}
105+
# Procfs virtual filesystem for process and kernel information
106+
# Required by:
107+
# - pkg/file/descriptors_linux.go:27,41 (/proc/sys/fs/file-max, file-nr - file descriptor limits)
108+
# - pkg/host/boot_id.go:9 (/proc/sys/kernel/random/boot_id - unique boot identifier)
109+
# - pkg/disk/mount.go:14,105 (/proc/self/mountinfo - mount point information)
110+
# Can be disabled for security-sensitive environments via gpud.mountHostProc=false
111+
- name: host-proc
112+
hostPath:
113+
path: /proc
114+
type: Directory
115+
{{- end }}
116+
117+
# GPUd's persistent data directory for state, cache, and configuration
118+
# Used for storing GPUd runtime data, labels from nodeLabelExporter, etc.
75119
- name: lib-gpud
76120
hostPath:
77121
path: /var/lib/gpud
122+
type: DirectoryOrCreate
123+
124+
# Machine ID for unique node identification
125+
# Required by: pkg/host/machine_id.go (reads machine-id for node identification)
78126
- name: host-machine-id
79127
hostPath:
80128
path: /etc/machine-id
81129
type: FileOrCreate
130+
131+
{{- if .Values.gpud.mountHostDevMem }}
132+
# Required for direct memory access to inspect hardware state (e.g. PCIe config space).
133+
# Addressed https://github.com/leptonai/gpud/issues/1144:
134+
# On some environments (e.g. GKE), /dev/mem may not be exposed or not be a CharDevice.
135+
# This conditional allows disabling the mount to prevent pod startup failures.
82136
- name: host-dev-mem
83137
hostPath:
84138
path: /dev/mem
139+
# We explicitly specify CharDevice to ensure we are mounting the correct device.
140+
# If this fails, set gpud.mountHostDevMem to false.
85141
type: CharDevice
142+
{{- end }}
143+
144+
# Kubelet directory for container and pod information
145+
# Required for: monitoring containerized workloads and Kubernetes integration
86146
- name: host-kubelet-vol
87147
hostPath:
88148
path: /var/lib/kubelet
@@ -96,30 +156,151 @@ spec:
96156
{{- end }}
97157

98158
# --- Containers ---
99-
{{- with .Values.initContainers }}
159+
# --- OPTIONAL: Init Containers ---
160+
# This section is entirely OPTIONAL and only renders when:
161+
# 1. nodeLabelExporter.enabled=true (for Kubernetes node label reading), OR
162+
# 2. Custom initContainers are specified in values.yaml
163+
# By default, nodeLabelExporter.enabled=false, so no init containers are created.
164+
{{- if or .Values.nodeLabelExporter.enabled .Values.initContainers }}
100165
initContainers:
166+
{{- if .Values.nodeLabelExporter.enabled }}
167+
# OPTIONAL: Node Label Reader Init Container
168+
# This init container fetches Kubernetes node labels ONCE before gpud starts.
169+
# It writes labels to a file that gpud reads for endpoint/token/machine-id configuration.
170+
# Enable with: nodeLabelExporter.enabled=true
171+
# Requires RBAC: nodeLabelExporter.rbac.create=true (creates ClusterRole for node read access)
172+
- name: node-label-init
173+
# Needs a shell-capable image; distroless kubectl images lack /bin/sh so the script would fail.
174+
image: "{{ .Values.nodeLabelExporter.image.repository }}:{{ .Values.nodeLabelExporter.image.tag }}"
175+
imagePullPolicy: {{ .Values.nodeLabelExporter.image.pullPolicy }}
176+
command:
177+
- /bin/sh
178+
- -ec
179+
- |
180+
set -o errexit
181+
set -o nounset
182+
set -o pipefail 2>/dev/null || true # pipefail not supported in dash (Ubuntu's /bin/sh)
183+
184+
tmp_file="${OUTPUT_FILE}.tmp"
185+
mkdir -p "$(dirname "${OUTPUT_FILE}")"
186+
187+
# Fetch node labels once before main container starts
188+
# Safe implementation: uses atomic move to prevent partial reads by consumers.
189+
if kubectl get node "${NODE_NAME}" \
190+
-o jsonpath='{range $k,$v := .metadata.labels}{printf "%s=%s\n" $k $v}{end}' \
191+
> "${tmp_file}"; then
192+
mv "${tmp_file}" "${OUTPUT_FILE}"
193+
echo "Successfully wrote node labels to ${OUTPUT_FILE}"
194+
else
195+
echo "Warning: Failed to fetch node labels, file will be created by sidecar"
196+
rm -f "${tmp_file}"
197+
fi
198+
env:
199+
- name: NODE_NAME
200+
valueFrom:
201+
fieldRef:
202+
fieldPath: spec.nodeName
203+
- name: OUTPUT_FILE
204+
value: {{ .Values.nodeLabelExporter.outputFile | quote }}
205+
volumeMounts:
206+
- name: lib-gpud
207+
mountPath: /var/lib/gpud
208+
resources:
209+
{{- toYaml .Values.nodeLabelExporter.resources | nindent 12 }}
210+
{{- end }}
211+
{{- with .Values.initContainers }}
101212
{{- toYaml . | nindent 8 }}
213+
{{- end }}
102214
{{- end }}
103215
containers:
104216
- name: {{ .Chart.Name }}
105217
# Uses the image tag from values.yaml, but defaults to the chart's appVersion if not set.
106218
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
107219
imagePullPolicy: {{ .Values.image.pullPolicy }}
220+
# Privileged access is required for hardware inspection and low-level system access.
108221
securityContext:
109222
{{- toYaml .Values.securityContext | nindent 12 }}
110223

111224
command:
112-
{{- toYaml .Values.gpud.command | nindent 12 }}
113-
args:
114-
{{- toYaml .Values.gpud.args | nindent 12 }}
225+
- /bin/sh
226+
- -ec
227+
- |
228+
# Shell safety options:
229+
# - errexit: Exit immediately if any command fails
230+
# - nounset: Treat unset variables as errors
231+
# - pipefail: Pipeline fails if any command fails (not just the last)
232+
set -o errexit
233+
set -o nounset
234+
set -o pipefail 2>/dev/null || true # pipefail not supported in dash (Ubuntu's /bin/sh)
235+
236+
# --- OPTIONAL: Read configuration from Kubernetes node labels ---
237+
# If nodeLabelExporter sidecar is enabled, it writes node labels to this file.
238+
# This allows dynamic configuration of endpoint/token/machine-id from K8s labels.
239+
LABEL_FILE="{{ .Values.nodeLabelExporter.outputFile }}"
240+
241+
# Label keys can be overridden via environment variables (useful for testing)
242+
EP_LABEL_KEY="${GPUD_ENDPOINT_LABEL_KEY:-{{ .Values.nodeLabelExporter.labelKeys.endpoint }}}"
243+
TOKEN_LABEL_KEY="${GPUD_TOKEN_LABEL_KEY:-{{ .Values.nodeLabelExporter.labelKeys.token }}}"
244+
MID_LABEL_KEY="${GPUD_MACHINE_ID_LABEL_KEY:-{{ .Values.nodeLabelExporter.labelKeys.machineId }}}"
245+
246+
# Initialize empty - will be populated from label file if it exists
247+
EP=""
248+
TOKEN=""
249+
MID=""
250+
251+
if [ -f "$LABEL_FILE" ]; then
252+
# Parse label file using awk with index() for EXACT string matching:
253+
# - index($0, key"=") == 1: Line must START with "key=" (prevents partial matches)
254+
# - substr($0, length(key)+2): Extract everything AFTER the "=" sign
255+
# - This handles dots/slashes in keys and preserves "=" in values (e.g., base64)
256+
# Example: "gpud.dgxc.lepton.ai/endpoint=https://api.example.com" -> "https://api.example.com"
257+
EP="$(awk -v key="$EP_LABEL_KEY" 'index($0, key"=") == 1 {print substr($0, length(key)+2); exit}' "$LABEL_FILE")"
258+
TOKEN="$(awk -v key="$TOKEN_LABEL_KEY" 'index($0, key"=") == 1 {print substr($0, length(key)+2); exit}' "$LABEL_FILE")"
259+
MID="$(awk -v key="$MID_LABEL_KEY" 'index($0, key"=") == 1 {print substr($0, length(key)+2); exit}' "$LABEL_FILE")"
260+
fi
261+
262+
# --- Build gpud command arguments ---
263+
# Using "set --" to safely build argument list without eval or quoting issues.
264+
# Each "set -- "$@" ..." appends to the positional parameters ($1, $2, etc.)
265+
set -- run
266+
set -- "$@" --listen-address="{{ .Values.gpud.listenAddress }}"
267+
set -- "$@" --log-level="{{ .Values.gpud.logLevel }}"
268+
269+
# Endpoint priority: Node label > values.yaml > (none)
270+
ENDPOINT="{{ .Values.gpud.endpoint }}"
271+
if [ -n "$EP" ]; then
272+
ENDPOINT="$EP" # Label value overrides values.yaml
273+
fi
274+
if [ -n "$ENDPOINT" ]; then
275+
set -- "$@" --endpoint="$ENDPOINT"
276+
fi
277+
278+
# Token and machine-id are only added if present (from node labels)
279+
if [ -n "$TOKEN" ]; then
280+
set -- "$@" --token="$TOKEN"
281+
fi
282+
if [ -n "$MID" ]; then
283+
set -- "$@" --machine-id="$MID"
284+
fi
285+
286+
set -- "$@" --enable-auto-update={{ .Values.gpud.enableAutoUpdate }}
287+
set -- "$@" --auto-update-exit-code={{ .Values.gpud.autoUpdateExitCode }}
288+
289+
# Log the command for debugging, then exec to replace shell with gpud.
290+
# Using exec ensures gpud receives signals directly (important for graceful shutdown).
291+
echo "Starting: /usr/local/bin/gpud $*"
292+
exec /usr/local/bin/gpud "$@"
115293
116294
env:
117295
- name: GPUD_NO_USAGE_STATS
118296
value: {{ not .Values.gpud.telemetry.enabled | quote }}
119297

120298
{{- if .Values.service.enabled }}
121299
ports:
122-
- name: http
300+
- name: https
301+
# Must match the port in gpud.listenAddress (default 15132)
302+
# Uses service.port for reliability - livenessProbe.httpGet.port may not exist
303+
# if user customizes livenessProbe to use exec or tcpSocket instead of httpGet.
123304
containerPort: {{ .Values.service.port }}
124305
protocol: TCP
125306
{{- end }}
@@ -131,23 +312,111 @@ spec:
131312
{{- toYaml .Values.resources | nindent 12 }}
132313

133314
volumeMounts:
315+
# System logs - GPUd writes logs here and monitors system logs
134316
- name: host-log
135317
mountPath: /var/log
318+
319+
# Kernel crash dumps and persistent storage for error analysis
136320
- name: host-pstore
137321
mountPath: /var/lib/systemd/pstore
322+
323+
# Device files - Critical for:
324+
# - /dev/kmsg: kernel message buffer (pkg/kmsg/watcher.go)
325+
# - /dev/nvidia*: GPU devices for NVIDIA monitoring
326+
# - /dev/dri/*: GPU DRM devices
138327
- name: host-dev
139328
mountPath: /dev
329+
330+
{{- if .Values.gpud.mountHostSys }}
331+
# Sysfs - Hardware/device info (read-only for safety)
332+
# - /sys/class/infiniband: InfiniBand network monitoring
333+
# - /sys/class/dmi: Hardware/DMI information
334+
# - /sys/fs/fuse/connections: FUSE filesystem stats
335+
- name: host-sys
336+
mountPath: /sys
337+
readOnly: true
338+
{{- end }}
339+
340+
{{- if .Values.gpud.mountHostProc }}
341+
# Procfs - Kernel/process info (read-only for safety)
342+
# - /proc/sys/fs/file-*: File descriptor limits and usage
343+
# - /proc/sys/kernel/random/boot_id: Unique boot identifier
344+
# - /proc/self/mountinfo: Current mount points
345+
- name: host-proc
346+
mountPath: /proc
347+
readOnly: true
348+
{{- end }}
349+
350+
# GPUd data directory for runtime state and configuration
140351
- name: lib-gpud
141352
mountPath: /var/lib/gpud
353+
354+
# Machine ID for unique node identification (read-only)
142355
- name: host-machine-id
143356
mountPath: /etc/machine-id
144357
readOnly: true
358+
359+
{{- if .Values.gpud.mountHostDevMem }}
360+
# Physical memory access for hardware diagnostics (read-only)
361+
# Conditional mount - can be disabled for GKE/restricted environments
362+
# via gpud.mountHostDevMem=false (addresses issue #1144)
145363
- name: host-dev-mem
146364
mountPath: /dev/mem
147365
readOnly: true
366+
{{- end }}
367+
368+
# Kubelet data for container/pod monitoring (read-only)
148369
- name: host-kubelet-vol
149370
mountPath: /var/lib/kubelet
150371
readOnly: true
151372
{{- if .Values.extraVolumeMounts }}
152373
{{- toYaml .Values.extraVolumeMounts | nindent 12 }}
153374
{{- end }}
375+
{{- if .Values.nodeLabelExporter.enabled }}
376+
# OPTIONAL: Node Label Exporter Sidecar
377+
# This sidecar periodically syncs Kubernetes node labels to a file for gpud consumption.
378+
# Runs alongside the main gpud container and updates labels at the configured interval.
379+
# Enable with: nodeLabelExporter.enabled=true
380+
- name: node-label-exporter
381+
# Needs a shell-capable image; distroless kubectl images lack /bin/sh so the looped script would fail.
382+
image: "{{ .Values.nodeLabelExporter.image.repository }}:{{ .Values.nodeLabelExporter.image.tag }}"
383+
imagePullPolicy: {{ .Values.nodeLabelExporter.image.pullPolicy }}
384+
command:
385+
- /bin/sh
386+
- -ec
387+
- |
388+
set -o errexit
389+
set -o nounset
390+
set -o pipefail 2>/dev/null || true # pipefail not supported in dash (Ubuntu's /bin/sh)
391+
392+
tmp_file="${OUTPUT_FILE}.tmp"
393+
mkdir -p "$(dirname "${OUTPUT_FILE}")"
394+
395+
while true; do
396+
if kubectl get node "${NODE_NAME}" \
397+
-o jsonpath='{range $k,$v := .metadata.labels}{printf "%s=%s\n" $k $v}{end}' \
398+
> "${tmp_file}"; then
399+
mv "${tmp_file}" "${OUTPUT_FILE}"
400+
else
401+
rm -f "${tmp_file}"
402+
fi
403+
sleep "${UPDATE_INTERVAL}"
404+
done
405+
env:
406+
- name: NODE_NAME
407+
valueFrom:
408+
fieldRef:
409+
fieldPath: spec.nodeName
410+
- name: OUTPUT_FILE
411+
value: {{ .Values.nodeLabelExporter.outputFile | quote }}
412+
- name: UPDATE_INTERVAL
413+
value: {{ int .Values.nodeLabelExporter.updateIntervalSeconds | quote }}
414+
{{- with .Values.nodeLabelExporter.extraEnv }}
415+
{{- toYaml . | nindent 12 }}
416+
{{- end }}
417+
volumeMounts:
418+
- name: lib-gpud
419+
mountPath: /var/lib/gpud
420+
resources:
421+
{{- toYaml .Values.nodeLabelExporter.resources | nindent 12 }}
422+
{{- end }}

0 commit comments

Comments
 (0)