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 }}
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