NRI (Node Resource Interface) is a plugin API for CRI-compatible container runtimes — containerd and CRI-O — that enables out-of-process plugins to intercept container lifecycle events and customize resource allocation without modifying the runtime itself. Platform teams use NRI plugins to implement CPU pinning, memory tiering, device allocation, and topology-aware scheduling at the node level. When NRI plugins fail, the runtime falls back to default resource allocation silently: CPUs aren't pinned, NUMA-aware memory placement is skipped, and specialized hardware isn't assigned to the containers that need it. Vigilmon gives you external monitoring that watches the NRI plugin layer and validates that container lifecycle hooks are completing on schedule.
What You'll Build
- Vigilmon HTTP monitors for NRI plugin registration and health endpoints
- A heartbeat monitor validating that NRI hooks execute during container lifecycle events
- A resource allocation integrity heartbeat to catch silent plugin bypass
- Alert channels routed to your platform engineering team
Prerequisites
- NRI enabled in containerd (
/etc/containerd/config.toml) or CRI-O - At least one NRI plugin deployed (e.g., Kubernetes NRI devices plugin, topology manager plugin)
- A free account at vigilmon.online
Why Monitoring NRI Matters
NRI is invisible to most Kubernetes monitoring because it operates below the pod API layer. Its failure modes are soft — containers still run, just without the customizations the platform team deployed NRI to provide:
Plugin registration failures mean no lifecycle hooks fire. NRI plugins connect to the runtime via a Unix socket and register for the events they want to handle. If a plugin process crashes and fails to reconnect, all containers scheduled after the crash run without that plugin's customizations. The runtime does not surface plugin absence as an error — it simply proceeds without calling hooks that aren't registered.
Hook timeout failures are the most operationally dangerous NRI problem. If a plugin's hook handler takes too long to respond, the runtime can time out the hook and proceed. This means a CPU topology plugin that runs slow (perhaps because the NUMA library is querying a degraded hardware interface) fails open — the container starts on an arbitrary CPU set. Performance SLOs for latency-sensitive workloads like trading systems or real-time media pipelines are silently broken.
Socket file permission drift blocks plugin reconnection after a runtime restart. The NRI socket (/var/run/nri/nri.sock by default) is created by the runtime. If a runtime upgrade changes the socket permissions or path, existing plugins that reconnect after a restart fail to register. The failure is quiet — plugin logs show a connection refused error, but no runtime-level alert fires.
Version skew between runtime and plugin API occurs when NRI plugin binaries lag behind containerd upgrades. NRI has a versioned gRPC API; newer containerd versions may deprecate older plugin API versions. Plugins using a deprecated API silently receive no events for newly created containers while appearing healthy in their own metrics.
Step 1: Monitor NRI Plugin Registration
Verify that NRI is enabled and plugins are registered with the runtime:
# Check NRI socket exists and containerd NRI is enabled
ls -la /var/run/nri/nri.sock
# Check containerd NRI configuration
grep -A5 '\[plugins."io.containerd.nri.v1"\]' /etc/containerd/config.toml
# Expected: disable = false
# List registered NRI plugins (requires containerd debug endpoint)
ctr plugin ls | grep nri
Deploy a health endpoint that reports plugin registration status:
# /usr/local/bin/nri-health-server.sh
#!/bin/bash
while true; do
NRI_SOCKET="/var/run/nri/nri.sock"
NRI_ENABLED=$(grep -c 'disable = false' /etc/containerd/config.toml 2>/dev/null || echo 0)
if [ -S "$NRI_SOCKET" ] && [ "$NRI_ENABLED" -gt 0 ]; then
# Check if any plugin processes are connected to the socket
PLUGIN_COUNT=$(ss -xp 2>/dev/null | grep -c nri.sock || echo 0)
if [ "$PLUGIN_COUNT" -gt 0 ]; then
STATUS="200 OK"
BODY="{\"status\":\"ok\",\"plugins\":$PLUGIN_COUNT}"
else
STATUS="503 Service Unavailable"
BODY='{"status":"degraded","plugins":0}'
fi
else
STATUS="503 Service Unavailable"
BODY='{"status":"error","reason":"nri disabled or socket missing"}'
fi
printf "HTTP/1.1 %s\r\nContent-Type: application/json\r\n\r\n%s" \
"$STATUS" "$BODY" | nc -l -p 9103 -q 1
done
# /etc/systemd/system/nri-health.service
[Unit]
Description=NRI health endpoint
After=containerd.service
[Service]
ExecStart=/usr/local/bin/nri-health-server.sh
Restart=always
[Install]
WantedBy=multi-user.target
chmod +x /usr/local/bin/nri-health-server.sh
systemctl enable --now nri-health
In the Vigilmon dashboard:
- Go to Add Monitor → HTTP
- Configure:
| Field | Value |
|---|---|
| Name | NRI plugin registration |
| URL | http://your-node-ip:9103 |
| Method | GET |
| Expected status | 200 |
| Response body contains | "status":"ok" |
| Check interval | 1 minute |
| Timeout | 10 seconds |
| Alert after | 2 consecutive failures |
Step 2: Monitor NRI Hook Execution via Container Lifecycle Probe
Validate that NRI hooks fire and complete during container creation. Deploy a test plugin that marks container creation events and exposes that signal as an HTTP endpoint:
# /usr/local/bin/nri-hook-probe.sh
#!/bin/bash
set -euo pipefail
HEARTBEAT_URL="${NRI_HOOK_HEARTBEAT_URL}"
PROBE_NAMESPACE="nri-probe"
MARKER_FILE="/tmp/nri-hook-last-fired"
# Start a test container and observe that NRI hooks fired
# (your actual NRI plugin should write a marker file or emit a metric)
podman run --rm \
--annotation "nri.test/probe=lifecycle-check" \
alpine:latest /bin/true 2>/dev/null
# Check if your NRI plugin touched the marker file during the container's lifecycle
LAST_FIRED=$(stat -c %Y "$MARKER_FILE" 2>/dev/null || echo 0)
NOW=$(date +%s)
AGE=$(( NOW - LAST_FIRED ))
if [ "$AGE" -lt 120 ]; then
echo "NRI hook fired ${AGE}s ago — OK"
curl -fsS --max-time 10 "$HEARTBEAT_URL" -o /dev/null
echo "NRI hook heartbeat sent"
else
echo "ERROR: NRI hook last fired ${AGE}s ago (threshold: 120s)" >&2
exit 1
fi
# /etc/systemd/system/nri-hook-probe.service
[Unit]
Description=NRI hook execution probe
After=containerd.service
[Service]
Type=oneshot
EnvironmentFile=/etc/nri-probe.env
ExecStart=/usr/local/bin/nri-hook-probe.sh
# /etc/systemd/system/nri-hook-probe.timer
[Unit]
Description=NRI hook probe every 5 minutes
[Timer]
OnBootSec=2min
OnUnitActiveSec=5min
Unit=nri-hook-probe.service
[Install]
WantedBy=timers.target
# /etc/nri-probe.env
NRI_HOOK_HEARTBEAT_URL=https://vigilmon.online/heartbeat/YOUR_HOOK_TOKEN
chmod +x /usr/local/bin/nri-hook-probe.sh
systemctl daemon-reload
systemctl enable --now nri-hook-probe.timer
Create the heartbeat monitor in Vigilmon:
- Add Monitor → Heartbeat
- Name:
NRI hook execution - Expected interval: 5 minutes
- Grace period: 3 minutes
Step 3: Heartbeat Monitor for Resource Allocation Integrity
For CPU topology or NUMA-aware NRI plugins, validate that resource allocation is actually being applied. This probe checks that a container started with CPU affinity annotations has the expected cpuset:
# /usr/local/bin/nri-resource-probe.sh
#!/bin/bash
set -euo pipefail
HEARTBEAT_URL="${NRI_RESOURCE_HEARTBEAT_URL}"
# Start container with CPU affinity annotation
CONTAINER_ID=$(podman run -d --rm \
--cpuset-cpus 0 \
--annotation "nri.resource/cpu-pinning=true" \
alpine:latest sleep 10 2>/dev/null)
if [ -z "$CONTAINER_ID" ]; then
echo "ERROR: Container failed to start" >&2
exit 1
fi
# Wait for NRI plugin to apply cpuset
sleep 2
# Verify cpuset was applied (via cgroup or container inspect)
CPUSET=$(podman exec "$CONTAINER_ID" \
cat /sys/fs/cgroup/cpuset.cpus.effective 2>/dev/null || echo "unknown")
podman stop "$CONTAINER_ID" >/dev/null 2>&1 || true
if [ "$CPUSET" != "unknown" ]; then
echo "NRI resource allocation verified: cpuset=$CPUSET"
curl -fsS --max-time 10 "$HEARTBEAT_URL" -o /dev/null
echo "NRI resource heartbeat sent"
else
echo "ERROR: Could not verify NRI resource allocation" >&2
exit 1
fi
Create the heartbeat monitor in Vigilmon:
- Add Monitor → Heartbeat
- Name:
NRI resource allocation - Expected interval: 15 minutes
- Grace period: 5 minutes
Step 4: Configure Alert Channels
Route NRI alerts to your platform infrastructure team:
- In Vigilmon: Alert Channels → Add Channel → Slack Webhook
- Add your
#infrastructureor#platform-engineeringwebhook URL - Assign to all NRI monitors
For resource allocation failures (silent resource mis-assignment is a performance and fairness issue):
- Alert Channels → Add Channel → Email
- Add your platform team's on-call address
- Assign specifically to the
NRI resource allocationheartbeat
What You're Now Monitoring
| Component | Monitor Type | What It Detects | |---|---|---| | NRI plugin registration | HTTP GET | Plugins disconnected, NRI socket missing, NRI disabled | | NRI hook execution | Heartbeat (5 min) | Hooks not firing, plugin timeout, runtime bypass | | NRI resource allocation | Heartbeat (15 min) | Resource assignments not applied, cpuset/NUMA drift |
Conclusion
NRI's value to a platform team is its ability to customize container resource allocation without patching runtimes — but that same out-of-process design means NRI failures are invisible to the runtime itself. A containerd that has lost all its NRI plugins behaves identically to one with healthy plugins, from the Kubernetes control plane's perspective. External monitoring with Vigilmon closes this gap by watching the plugin registration layer from outside the runtime and running periodic lifecycle probes that confirm the full NRI hook chain is executing.
Get started free at vigilmon.online. The plugin health endpoint and hook execution heartbeat together give you the two most critical NRI signals in under ten minutes: are plugins registered and connected, and are hooks actually firing on container lifecycle events?