Skip to content

feat(local): --nv-sentinel Tilt consumer for GPU health monitoring and fault remediation - #716

Draft
giuliocalzo wants to merge 35 commits into
NVIDIA:mainfrom
giuliocalzo:feat/nv-sentinel-tilt-consumer
Draft

feat(local): --nv-sentinel Tilt consumer for GPU health monitoring and fault remediation#716
giuliocalzo wants to merge 35 commits into
NVIDIA:mainfrom
giuliocalzo:feat/nv-sentinel-tilt-consumer

Conversation

@giuliocalzo

@giuliocalzo giuliocalzo commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Summary

Ports the standalone docs/demo/nv-sentinel/ demo into the Tilt local-dev environment as a --nv-sentinel consumer, so the whole NVSentinel remediation loop runs on a Kind cluster with no GPUs:

tilt up -- --nv-sentinel

NVSentinel runs unmodified against the mock GPUs through real NVIDIA DCGM. Two manual triggers assert the loop end to end rather than leaving it to be eyeballed: quarantine-node heats a GPU past its hardware slowdown limit and asserts the node is cordoned and the GPU workload actually evicted and replaced elsewhere (~10-21s); recover-node cools it and asserts the node comes back with GpuThermalMarginWatch healthy and no DCGM restart (~10-16s).

The standalone demo is kept as-is for use outside Tilt — this consumer skips its dedicated cluster and per-worker nvidia-container-toolkit install, since the shared Kind node image bakes both in.

The diff is confined to local/nv-sentinel/ plus the root Tiltfile wiring and docs; no existing consumer's files are touched.

One repo-wide change, called out for review

The default --gpu-profile moves from a100 to gb300 (Tiltfile, one line, plus the docs that quoted the old default). Ada-and-later profiles are the ones that report the slowdown T.Limit thermal margin the thermal-margin consumers key on, so the default no longer excludes them and a plain tilt up can drive the same watch. --gpu-profile a100 restores the previous shape, and the heterogeneous --multi-gpu-profile fleet is untouched (still a100 + t4). Flagging it because it is the only change here that affects sessions that never pass --nv-sentinel; happy to drop it and keep the default local to the flag if you would rather.

Notable decisions

  • --gpu-profile defaults to gb300 and pre-Ada profiles are rejected. GpuThermalMarginWatch arms only from the GPU's slowdown T.Limit offset, which real hardware reports on Ada and later only — and the mock gates it identically. On a100/t4 every Tilt resource goes green while the watch stays inert, so a heated GPU is detected by nothing on a stack that looks perfectly healthy. The allow-list (h100, l40s, b200, gb200, gb300) fails closed.
  • The NVSentinel release deliberately omits --wait, which would deadlock: Helm runs post-install hooks only after --wait is satisfied, and the pods --wait is waiting on cannot become Ready until the hook creates their MongoDB collections. nvsentinel-ready is the wait instead, and it asserts the thermal watch is armed rather than merely scheduled — a green gpu-operator resource does not mean DCGM is reachable.
  • MongoDB is external and runs the official image. The chart's mongodb-store subchart uses Bitnami images, which are amd64-only and run Bitnami-specific startup scripts — unusable on Apple Silicon.
  • Each of the six load-bearing nvsentinel.values.yaml overrides carries its reason inline, including two that must stay at their defaults on real clusters (circuitBreaker, node-drainer mode).

A pre-existing --observability bug found along the way, deliberately not fixed here

--nv-sentinel composes with --observability, and verifying that combination surfaced a bug that predates this branch and affects plain --observability too: the overlay lowers the dcgm-exporter ServiceMonitor's interval to 5s but leaves the chart's default scrapeTimeout at 10s, and prometheus-operator rejects any endpoint whose timeout exceeds its interval. It logs skipping object ... scrapeTimeout "10s" greater than scrapeInterval "5s" with an InvalidConfiguration event, but the ServiceMonitor still exists and every Tilt resource still goes green — so the only symptom is empty GPU panels. Setting scrapeTimeout: 5s fixes it: both exporter endpoints then scrape healthy and DCGM_FI_DEV_GPU_TEMP reports all 16 mock GPUs.

I had that fix in this branch and have taken it out, to keep this PR scoped to local/nv-sentinel/. It belongs in its own PR where the observability owner will see it. Happy to open that, or leave it to whoever owns that consumer — say which you'd prefer. Until it lands, --nv-sentinel --observability brings the stack up correctly but the Grafana GPU panels stay empty.

Test plan

Verified live on a 3-node Kind cluster (1 control-plane, 2 workers):

  • tilt ci -- --nv-sentinel converges; cold ~8-15 min (dominated by a ~14 min gpu-health-monitor image pull), warm under a minute
  • tilt ci -- --nv-sentinel --observability converges (Prometheus and Grafana healthy; GPU panels empty pending the ServiceMonitor fix above)
  • GPU health flows DCGM field 153 -> health monitor -> platform-connector -> MongoDB -> fault-quarantine, confirmed with stored GpuThermalMarginWatch verdicts
  • quarantine-node then recover-node pass, repeatedly and back-to-back
  • Both triggers fail closed: recover-node refuses a healthy cluster, a hand-cordoned node, and a quarantined node with no hot pin; quarantine-node fails rather than reporting a reschedule that did not happen, and refuses to inject unless exactly one workload pod is Running (a second replica would satisfy the eviction assertion on its own)
  • Guardrails: --nv-sentinel implies --gpu-operator; rejects --fgo, --compute-domain, --topograph, --multi-gpu-profile and pre-Ada profiles, each with a message naming the reason
  • make test and make helm-tests pass; golangci-lint clean (make lint fails only on pre-existing govulncheck CVEs in vendored deps, unrelated to this change)
  • make docs (mkdocs --strict) passes with the branch's docs applied on top of current main

Known trade-off, documented in the README: deleting the mongodb-ext pod leaves an uninitialised replica set behind a green Tilt UI (emptyDir data + one-shot init Job). Recovery is tilt ci -- --nv-sentinel. The obvious fix — a readiness probe asserting PRIMARY — was tried and reverted because it deadlocks against the Job that performs the initialisation.

@giuliocalzo
giuliocalzo marked this pull request as draft August 24, 2026 10:22
NVSentinel's GPU Health Monitor polls nv-hostengine through the GPU Operator's
standalone DCGM Service, which the shared operator values disable because
nothing in the plain --gpu-operator scenario polls one. Layer it back on
through the extra_values hook, and turn on the mock's dynamic metrics so a
heated GPU reads differently from its idle siblings.

The flag implies --gpu-operator and refuses --fgo and --compute-domain, which
either remove the DCGM operand or bring their own cluster shape.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
… true

tilt up -- --nv-sentinel --fgo died with "--fgo is mutually exclusive with
--gpu-operator", naming a flag the user never passed: --nv-sentinel implies
--gpu-operator, so the pre-existing --fgo check fired first and the
nv-sentinel-specific message below it was unreachable. Check the nv-sentinel
pair before the --fgo pair.

Two comments also asserted things the code around them contradicts. The DCGM
overlay said the baseline disables the standalone DaemonSet because nothing
polls one; the baseline in fact says the mock NVML cannot support DCGM at all,
so say that and say why this overlay overrides it anyway. And disjoint values
keys were offered as proof the nv-sentinel and observability overlays compose,
which does not follow: dcgm.enabled=true reroutes dcgm-exporter onto the
shared nvidia-dcgm:5555 hostengine, so record the coupling instead.

The evaluated Tiltfile result is byte-identical either way; nothing deployed
changes.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The ordering is load-bearing but was silent about it: --nv-sentinel implies
--gpu-operator, so moving these checks below the --fgo pair silently restores
the bug where the failure names a flag the user never passed.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
NVSentinel needs change streams, so its datastore must be a replica set, and it
talks TLS to it. The chart's bundled Bitnami MongoDB is amd64-only and cannot
start on Apple Silicon, so run the official multi-arch image as an external
datastore with a cert-manager-issued certificate, as the standalone demo does.

The Namespace and the MONGODB_URI Secret move into the manifest: run.sh created
both with imperative kubectl calls, which Tilt has no equivalent for. The URI
carries no credentials.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Bound the mongodb-ext-rs-init Job with activeDeadlineSeconds: its two wait
loops are unbounded, so a mongod that answers but never reaches PRIMARY hung
the Job forever and `tilt ci` waited on it instead of failing.

Split _CERT_MANAGER_NAMESPACE out of _CERT_MANAGER_RELEASE, order the
cert-manager resource behind nvml-mock like the other consumers do (which
retires an unused parameter), correct the crds.enabled comment, and drop
install()'s unused return value along with the header sentence promising it.

Also record why the MongoDB readiness probe stays a liveness check: asserting
a writable PRIMARY there deadlocks, because Tilt withholds the Job that runs
rs.initiate() until the pod it initialises is already Ready.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Ports the demo's values unchanged, comments included: they are the only record
of why five non-default options are load-bearing here, from the thermal watch
that only fires with store-only off to the circuit breaker that would otherwise
halt recovery on a two-worker cluster.

The release deliberately does not pass --wait. Its collection-setup Job is a
post-install hook and the DB-consuming pods cannot go Ready before it runs,
while Helm runs post-install hooks only after --wait is satisfied, so --wait
deadlocks the release against its own hook. A separate nvsentinel-ready step
waits for the Job and for the DCGM Service to have ready endpoints, then
restarts the pods that raced them, instead of leaving them to time out an
exponential backoff.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
NVSentinel's GpuThermalMarginWatch arms only once it has the GPU's slowdown
T.Limit offset (NVML field 194), which real hardware reports on Ada and later
only and the mock gates on architecture the same way. On the default a100
profile the whole stack came up green with the watch inert, logging "missing
slowdown TLIMIT threshold metadata" for every GPU and writing no thermal
verdict at all — the silent no-op this consumer exists to rule out.

So --nv-sentinel now defaults to h100, the profile the standalone demo pins,
and rejects the two pre-Ada profiles instead of simulating a fault nothing can
see. --multi-gpu-profile is rejected for the same reason: its fleet is exactly
a100 plus t4.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Heats one mock GPU past its hardware slowdown limit and fails unless NVSentinel
cordons the node and the GPU workload lands on the healthy worker, so the
detect-and-remediate path is asserted rather than eyeballed.

The temperature is derived from the profile the mock actually loaded instead of
hardcoded. Slowdown thresholds range from 87C to 93C across profiles and
shutdown sits only 3-5C above, where the mock clamps, so a fixed value would
silently fail to trip anything on an l40s worker.

The run also cannot pass on residue: it clears every override on every mock node
and waits for the whole GPU fleet to be schedulable before injecting. Clearing
only the target is not enough — the target follows the workload, so it
alternates between runs, and the node the previous run left hot and cordoned is
exactly the one this run needs as a drain destination.

The workload drops the demo's nvml-mock-gpu nodeSelector. Requesting
nvidia.com/gpu already keeps it on the workers, since the device plugin does not
tolerate the control-plane taint.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The resource `tilt ci` blocks on could report success over a stack that
detects nothing. Four reachable paths, all closed here:

- The DCGM gate broke on any ready endpoint, so on a two-worker cluster it
  could restart both health monitors while one node's DCGM was still
  pulling, handing that monitor a dead hostengine it then reports Ready
  over. It now requires as many ready endpoints as the DCGM DaemonSet has
  scheduled pods, and a desired count of 0 no longer satisfies it.
- An empty pod list read as "all Ready", which is also what a get landing
  in the window the pod delete opens returns. It now keeps polling.
- The DCGM probe swallowed every error class through `2>/dev/null` and
  `|| true` on kubectl and jq alike, so an RBAC denial cost the full 20
  minutes and then blamed the image pull. jq is gone (jsonpath instead, on
  a path that had no other jq dependency), kubectl's stderr is kept, and a
  run of consecutive API errors fails with the error text — while a
  NotFound, the one class waiting does fix, still waits.
- Nothing asserted that GpuThermalMarginWatch actually armed. Every check
  above passed on the a100 profile while the watch was inert. The script
  now requires the field-153 watch line from every monitor pod and the
  absence of "missing slowdown TLIMIT threshold metadata" over a full
  fresh poll cycle, so green means armed rather than scheduled.

Also fail immediately on a Failed hook Job instead of waiting out the
timeout, and clamp the progress stride so POLL_INTERVAL_S above 60 prints
progress instead of a division-by-zero error.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The guardrail rejected a100 and t4 by name, so any profile added later
was allowed by default — and a new pre-Ada profile would have brought
back the green-but-blind stack the guardrail exists to prevent. Invert it
into an allow-list of the Ada-or-later profiles: an unknown or misspelled
profile now fails with the same message instead of installing.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
install() returned the release name and nothing consumed it. The values
header claimed "five options here are non-default", which a reader can
disprove in ten seconds — the file overrides many more keys than five;
the five are the ones whose behaviour differs from the chart defaults.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The drain half of this scenario could pass without a drain. The workload's
node was read before a pre-injection wait that may block 300s, and phase 2
only asked whether the workload was Running on some node other than the
target — so a pod that was never on the target satisfied it on the first
poll, with no eviction, and the script printed that the workload had moved.

The target is now resolved after that wait, from the node the workload is
actually on, and phase 2 requires the pod NAME to change as well: what is
asserted is that the pod which was Running on the target when the fault went
in is gone and its replacement runs elsewhere. Resolving the target after the
wait rather than failing on a stale reading keeps an interrupted previous run
recoverable, which is what the fleet-wide reset exists for; the premise is
still asserted, and a workload that never becomes Running now fails before
anything is injected instead of leaving a GPU pinned hot.

The threshold reads were also unreachable diagnostics: `threshold()` is a
pipeline under `pipefail`, so a missing key or a failed exec aborted the
script on the assignment before the guard that would explain it, and
`2>/dev/null` hid kubectl's own error. Both now reach the guard with the
error text intact.

Also: every post-injection failure names the GPU left pinned and the command
that clears it, the informational `kubectl get nodes` can no longer fail the
run after the assertions passed, `diagnose` reports the node each condition
belongs to, and the exec form lives in one `mock_ctl_on` helper.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The scenarios hardcode `default` as the workload namespace, while the
manifest left it implicit and so followed whatever namespace the applying
context selects. Making it explicit keeps the assertions looking where the
workload actually is.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
An evicted pod keeps status.phase=Running until it is actually gone, so a
drain still in flight offered this scenario a pod under deletion as its
target. Its replacement is by then already Running on the other node, which
would satisfy the phase 2 eviction check without this run having evicted
anything. Excluding pods with a deletionTimestamp closes that.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Clears the temperature override on every cordoned GPU node and fails unless
NVSentinel uncordons it, with no DCGM restart anywhere. That self-clearing is
the reason this scenario is driven through the thermal margin rather than a
latched XID or ECC fault, which would stay latched until nv-hostengine was
bounced.

Refuses to run when nothing is cordoned instead of asserting "the node is
schedulable" against a node that was never quarantined, which would pass
without exercising the recovery path at all.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
local/README.md carries the flag and the two triggers; the consumer README
carries the reference detail a reader needs once the stack is up: what lands in
the cluster, what each file is for, the six NVSentinel options that make the
loop work, and the behaviours that look like bugs and are not — chiefly the
pods that crash-loop until the post-install hook creates the MongoDB
collections, and why the release must not pass --wait.

The demo index and the demo's own README now point at the Tilt flag, and it
points back at them, so neither way of running this is a dead end.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The baseline GPU Operator values justified dcgm.enabled=false with "DCGM is not
supported by the mock NVML (requires full driver stack)". The --nv-sentinel
consumer disproved that: pointed at the mock driver root, nv-hostengine comes up
and serves the nvidia-dcgm Service on :5555. The real reason it stays off by
default is that nothing else polls a standalone hostengine — dcgm-exporter
embeds its own — so say that instead. Comment only; the rendered values are
unchanged.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
recover-node's pin guard matched any temperature_gpu_c override at any
value, so a node carrying only a cold pin from a by-hand session passed a
guard meant to prove a hot one — and the uncordon it went on to assert
was then somebody else's. Compare the pinned value against the profile's
own slowdown_threshold_c instead, read through a second copy of
quarantine-node's reader rather than a shared library, since scenario
scripts here are self-contained.

Proven on the live cluster: with a node genuinely quarantined and its pin
replaced by a cold 40C one, the previous script exits 0 claiming the
recovery, this one exits 1 in a second naming the 40C pin and the 87C
threshold.

Also: state the recovery time this path measured rather than Task 4's,
hedge the fleet-check message for a node re-quarantined mid-poll, drop
the unchecked "unmoved by the recovery" claim from a header that only
reports placement, give both copies of cordoned_gpu_nodes the same
separator, and say why recover-node shares the workload dependency.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
…iceMonitor

The overlay lowers the ServiceMonitor interval to 5s but left the chart's
default scrapeTimeout of 10s, and prometheus-operator rejects any
endpoint whose scrapeTimeout exceeds its interval. Measured under
`tilt ci -- --nv-sentinel --observability`: the ServiceMonitor is
created, every Tilt resource goes green, and the operator logs
`skipping object ... scrapeTimeout "10s" greater than scrapeInterval
"5s"` with an InvalidConfiguration event — so Prometheus had no
dcgm-exporter target at all and every GPU panel was empty. With
scrapeTimeout at 5s both exporter endpoints scrape healthy and
DCGM_FI_DEV_GPU_TEMP reports all 16 mock GPUs.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
…armed

A pod keeps status.phase=Running while it terminates, so the pod restart
earlier in the script leaves doomed pods matching the field selector for
a few seconds. One was selected, went away during the 20s log
observation, and failed an otherwise healthy bring-up on
`pods "gpu-health-monitor-dcgm-4.x-85b7v" not found` rather than on
anything about the watch. Excluding them makes the DaemonSet's
replacement the pod that is checked, and the desired-count guard already
turns "not Running yet" into a wait.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Every command the README prints was run at this HEAD. Four claims did not
survive that:

- detection is 10-21s, not ~15s, and the eviction is in the same second
  or the next 5s poll, not always the same second;
- `tilt trigger` needs a live `tilt up` session, and on the `tilt ci`
  path it fails on a refused connection to localhost:10350;
- re-running the rs-init Job is not the whole MongoDB recovery: the
  HealthEventsDatabase collections go with the emptyDir, and until the
  chart's setup hook re-runs, fault-quarantine and node-drainer
  crash-loop on `no collection with name HealthEvents`;
- adding --observability to a stack whose nvidia-dcgm is already serving
  restarts no dcgm-exporter pod, so the documented crash-loop belongs to
  the cold DCGM pull rather than to the flag pair.

Adds the drain/monitoring interaction that composed run exposed —
node-drainer's userNamespaces "*" evicts Prometheus if it sits on the
quarantined worker, and its storage is not persistent — plus the
composed bring-up cost and recover-node's narrowed pin guard.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
This reverts commit 355ec5d.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
…ter ServiceMonitor"

This reverts commit 10abc97.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
nvsentinel-ready asserted the thermal watch was armed by the ABSENCE of the
per-GPU "missing slowdown TLIMIT threshold metadata" message. A monitor that
enumerated zero GPUs produces that same absence: its per-GPU evaluation loop
iterates an empty list, so nothing is evaluated and nothing is logged, while
the armed line is still printed and the DCGM endpoint gate is still satisfied
by a healthy hostengine with no devices behind it. Every check passed over a
fleet monitored by nothing.

Pair the absence with a positive count read from the monitor's own
"dcgm gpu_id are [...]" init line, cross-checked against the GPUs the node
advertises. Also make the file executable, like every other script under
local/.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
mkdocs builds only docs/, so a relative link out to local/ resolves against
nothing and `mkdocs build --strict` fails the docs job. Point at the file on
GitHub, matching how the demo page already links local/observability and
local/README.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
--topograph implies --compute-domain, whose 4-worker cluster NVSentinel is
untested on, so the pair would otherwise land on the --compute-domain guardrail
and name a flag the user never passed.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Ada-and-later profiles are the ones that report the slowdown T.Limit thermal
margin the thermal-margin consumers key on, so making gb300 the default stops
the out-of-the-box shape from excluding them: --nv-sentinel no longer has to
override the default, and a plain `tilt up` can drive the same watch. Pass
--gpu-profile a100 for the previous shape; the --multi-gpu-profile fleet is
untouched and stays a100 + t4.

Documented thresholds follow the new default: gb300 slows down at 90C and shuts
down at 95C, so quarantine-node injects 93C rather than h100's 90C.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
quarantine-node took the first of however many pods matched the workload
selector, and phase 2 then passes as soon as a pod with a different name is
Running elsewhere. A second replica satisfies that before node-drainer evicts
anything, so the drain assertion silently weakens to "the workload is somewhere
else" — green without a drain. The workload ships replicas: 1, so this asserts
an invariant the selection already depended on; it is polled rather than failed
outright because a rollout can briefly surge to two Running pods.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The control-plane section gave a false reason for a true conclusion: the node
DOES carry nvidia.com/gpu.present=true and driver.installed=true, and
NVSentinel's metadata-collector schedules there on exactly those labels. What
keeps nvidia.com/gpu off it is narrower — the device plugin tolerates only
nvidia.com/gpu:NoSchedule, not the control-plane taint.

Neither README covered what happens when --observability's inject-thermal runs
under --nv-sentinel. Its fixed 90C pin is chosen against each profile's shutdown
threshold, but GpuThermalMarginWatch keys on the lower slowdown threshold and
fails once the pin is strictly above it: on h100 (87C) the observability trigger
therefore cordons and drains the worker and leaves it that way, since it keeps
the pin deliberately. Documented on both sides, per profile, with the recovery.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Applying a Certificate only asks cert-manager to issue one, so shipping the
Certificates in the same Tilt resource as the StatefulSet that mounts the
resulting Secret starts the pod against a Secret that is not there yet. A live
bring-up shows the cost: `FailedMount ... secret "mongodb-server-tls" not
found`, after which kubelet retries on a backoff that outlasts the issuance —
recovered only because a 2m33s image pull happened to cover the gap.

The Certificates move into their own mongodb-certs resource, a
mongodb-certs-ready wait blocks on both of them reaching Ready, and mongodb-ext
orders behind that. Both Certificates, not just the server one, because
mongodb-ca-issuer signs with the CA Secret and NVSentinel mounts it as its trust
root.

Note for anyone with a running session: this moves objects between Tilt
resources, including the Namespace, so the first reload tears the nvsentinel
namespace down and rebuilds it. One time only, and Tilt does not cascade the
rebuild on its own — trigger mongodb-certs, mongodb-ext, mongodb-ext-rs-init and
nvsentinel in order, or restart the session.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The comment credited fault-quarantine and "the analyzer", but this deployment
sets healthEventsAnalyzer.enabled=false. Measured against the running stack,
the two open change streams on HealthEventsDatabase.HealthEvents belong to
fault-quarantine and node-drainer.

Also states why --replSet cannot simply be dropped, which the comment left as an
assertion: change streams are derived from the oplog, so a standalone mongod
answers a watch with Location40573 and the pipeline would store health events
while reacting to none of them.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The comment claimed tls.enabled had to be true because the chart renders it true
regardless via a `| default true`. That default exists, but only on the tls block
inside the rendered datastore.yaml, and no pod mounts that file. The components
read the setting from the nvsentinel-datastore-config ConfigMap through envFrom,
where tls.enabled: false does render MONGODB_TLS_ENABLED="false" — the chart
documents exactly that for external datastores without TLS.

So TLS here is a deliberate choice to match a real deployment, not a constraint.
Saying otherwise would send the next reader looking for a chart bug to work
around instead of a decision to weigh.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
The one-shot rs-init Job could not be combined with a readiness probe that
asserts a writable primary: the Job depended on mongodb-ext being Ready, so a
probe waiting for rs.initiate() withheld the Job that performs it. That left the
pod Ready on a ping alone, and a recreated pod — emptyDir, so no replica set
config survives — came up uninitialised behind an all-green UI.

Running rs.initiate() in a postStart hook inverts the dependency: the hook runs
inside the container that needs initiating, so the probe is free to require
db.hello().isWritablePrimary, and the replica set now re-initiates with the pod
instead of needing a Job re-triggered by hand.

The hook must resolve the pod's own service DNS name, which is not published
while postStart blocks the container from reaching Running, so rs.initiate()
failed NodeNotFound and cost a restart plus ~3.5 minutes. A hostAliases entry
mapping mongodb-ext.nvsentinel.svc.cluster.local to 127.0.0.1 lets mongod
recognise itself immediately: measured Ready in 8 seconds with zero restarts.

TLS stays on, since it mirrors the standalone demo and a real deployment.
Plaintext is possible but takes two settings, not one: tls.enabled: false only
renders MONGODB_TLS_ENABLED, while each component separately defaults
clientCertMountPath to a real path and its binary reads ca.crt from there before
connecting — with no CA mounted it logs "Failed to read CA certificate" for 360s
and exits 1 while reporting Ready throughout. Both files record that trap.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Main now keeps nvml-mock on labelled workers only (NVIDIA#724), so three comments here
justified their design with a control-plane mock pod that no longer exists. The
designs are unchanged and still correct — the scenarios and the workload key on
the advertised nvidia.com/gpu resource, which the device plugin never publishes
on a control-plane it cannot tolerate — only the reasoning needed correcting.

The DCGM enumeration example also implied eight GPUs, where the default gb300 is
now a 4-GPU NVL72 compute tray (NVIDIA#727) and enumerates [0 1 2 3].

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
@giuliocalzo
giuliocalzo force-pushed the feat/nv-sentinel-tilt-consumer branch from 405636b to 6e0f871 Compare August 27, 2026 08:24
The mock pod gained a node-agent sidecar, so every `kubectl exec` here printed
`Defaulted container "nvml-mock" out of: nvml-mock, node-agent` and depended on
container ordering to reach nvml-mock-ctl and the rendered profile, neither of
which exists in the sidecar. Naming the container makes the dependency explicit
and drops the warning from otherwise clean scenario output. The two hints that
tell a reader how to clear a pin by hand name it too, since a copy-pasted command
should not need the same reasoning.

Also corrects the DCGM enumeration example to the format the monitor actually
logs, `[0, 1, 2, 3]`, verified against a live gb300 run.

Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant