Tutorial: Writing a New Health Monitor
Tutorial: Writing a New Health Monitor
This tutorial walks you through building a brand-new health monitor from an empty directory to a deployed, fault-reporting component — without needing any GPU hardware.
By the end you will have:
- A working Go health monitor that detects a fault and reports it to NVSentinel.
- A container image for it, deployed to a live cluster as a DaemonSet.
- Verification that NVSentinel sees the fault (it surfaces as a Kubernetes node condition).
Who is this for? Teams already running NVSentinel who want to extend it with a new fault detector.
Just want the AI to do it? Jump to Appendix: One-shot AI prompt.
Prerequisites
- Tools:
dockerandkubectl. - A cluster with NVSentinel running. Verification needs NVSentinel already running — at minimum
platform-connectors(it sets the node condition this tutorial verifies). SeeDEVELOPMENT.mdif you need to bring a cluster up locally, and confirm the core pods are green withkubectl get pods -n nvsentinelbefore verifying — otherwise a failed verification just means an unhealthy cluster, not a broken monitor.
1. What is a health monitor
NVSentinel detects faults on cluster nodes and remediates them (cordon → drain → break-fix). The thing that detects a fault is a health monitor.
A health monitor is just a standalone process that:
- Observes something (GPU via DCGM, kernel logs, a cloud API, a file in
/proc, …). - Decides whether a node/component is healthy or not.
- Sends a
HealthEventto the platform-connector over a local gRPC Unix domain socket.
The platform-connector turns fatal events into Kubernetes node conditions — and that’s where this tutorial ends. Your monitor only has to detect and report.
Key consequences of this design:
- Your monitor never talks to the Kubernetes API to cordon/drain. It only emits events.
- The transport is a Unix socket on the node, shared via a
hostPathvolume. - The contract is a single gRPC method and a single message type. That’s all you implement.
2. The contract (the only API you must speak)
The contract lives in data-models/protobufs/health_event.proto.
Go types are generated into the package github.com/nvidia/nvsentinel/data-models/pkg/protos.
There is exactly one RPC:
You send a batch (HealthEvents) containing one or more HealthEvents.
The HealthEvent message
The two enums you care about:
RecommendedAction values (from
data-models/protobufs/health_event.proto):
The golden rules
agentis your identity. Downstream consumers match on it, so pick a stable, unique name.- Prefer emitting on state transitions, not every poll. Send an unhealthy event when a fault appears and a healthy event when it clears. This is a best-effort optimization to cut noise, not a correctness requirement — repeated events are deduped, so re-sending the same state is safe.
- A healthy event clears the node condition. Send
isHealthy=true,recommendedAction=NONEwhen things recover. isFatal=trueis what triggers action. Fatal events surface as a Kubernetes node condition; non-fatal events become Kubernetes Events instead.
3. Choose your workload model
A monitor can observe almost anything — node-local state (/proc, /sys, GPUs, logs), the
Kubernetes API, or an external service. That choice only affects the Kubernetes workload
type:
- DaemonSet (one pod per node) — for node-local state; each pod reports its own node.
- Deployment (single replica) — for a single cluster-wide source (the K8s API or an
external API); it sets
nodeNamefrom the observed entity instead of fromNODE_NAME.
The publishing path is identical either way. This tutorial builds a DaemonSet, the most common shape.
Our worked example is a demo-health-monitor that periodically opens a TCP connection to a
configurable endpoint (CHECK_TARGET) the node depends on. It reports the node unhealthy when
the endpoint is unreachable and healthy again once it recovers.
4. Scaffold the module
Your monitor is an ordinary, standalone Go module. It depends on two NVSentinel Go modules:
github.com/nvidia/nvsentinel/data-models— the event contract (theHealthEventtypes).github.com/nvidia/nvsentinel/commons— the shared publisher (commons/pkg/healthpub), which adds socket-presence gating, retries, and Prometheus metrics over the raw gRPC client.
Create the module (any module path works):
Add the two dependencies. They live in subdirectories of the NVSentinel repo and aren’t
published as tagged releases, so fetch them by commit (@main, or pin a specific commit for
reproducibility). Because commons refers to data-models by an internal placeholder version,
add one replace so your build resolves the real commit:
5. Write the monitor
Create main.go. The structure is: parse config from env → dial the socket → poll →
build an event on each state change → publish it through the shared healthpub publisher.
Tidy and build:
That’s a complete, contract-correct health monitor. Everything else is packaging, deployment, and hardening.
6. Containerize
Package the monitor as a container image with a small multi-stage Dockerfile in the monitor
directory. Because dependencies are fetched from the module proxy (not local paths), the build
context is just the monitor directory itself:
Build the image and push it to a registry your cluster can pull from. We use Docker Hub
here — replace <dockerhub-user> with your Docker Hub username. Run from the monitor directory:
If you use a private repository, make sure the cluster has the required image pull credentials (e.g. an
imagePullSecretin thenvsentinelnamespace, referenced from the DaemonSet’sspec.template.spec.imagePullSecrets). A public repository needs none.
7. Run and verify
Deploy the image as a DaemonSet, then trigger a fault and watch it flow through the pipeline. (Needs the Prerequisites.)
Apply the DaemonSet. We start CHECK_TARGET at a guaranteed-unreachable, reserved
TEST-NET address so the monitor reports a fault right
away — then we’ll point it at a reachable endpoint to clear it. It mounts the platform-connector
socket (hostPath /var/run/nvsentinel → /var/run) and sets NODE_NAME via the downward API:
Confirm the pods are up:
Trigger and verify
Assumes the Prerequisites are met, these steps are cluster-agnostic.
Because CHECK_TARGET is unreachable, within one poll interval the monitor emits an
unhealthy, fatal HealthEvent. Platform-connector turns fatal events into a Kubernetes
node condition automatically, using your checkName (NetworkReachability) as the condition
Type. Pick any node a monitor pod runs on and watch the condition appear:
You should see the condition reported as a fault:
Note the inverted semantics: NVSentinel sets
Status=Trueto mean a fault is present (andStatus=Falsefor healthy). The conditionTypeis exactly yourcheckName, andReasonis<checkName>IsNotHealthy/<checkName>IsHealthy. The same condition is visible under Conditions inkubectl describe node "$NODE".
Clear the fault — point CHECK_TARGET at a reachable endpoint (kubernetes.default.svc:443 is
reachable from any in-cluster pod). On the next poll the monitor emits a healthy event and the
condition flips back (Status=False, Reason=NetworkReachabilityIsHealthy,
Message="No Health Failures"):
The condition now reports healthy:
That’s the end of the path this tutorial covers: your monitor detects a fault and NVSentinel surfaces it as a node condition.
If nothing arrives at the platform-connector, the usual cause is the socket mount. See
docs/runbooks/health-monitor-uds-failures.md.
Appendix: One-shot AI prompt
Paste this to an AI coding agent. It is self-contained: the monitor is a standalone module that can be created in any repository (it does not need to be inside the NVSentinel repo). Replace the bracketed parts.