Skip to content

NRI Plugin

The component that puts mock GPUs inside a container the pod author never changed.

The node daemon stages a GPU tree on the host, but a container sees only what its runtime gives it. The usual way in is a pod spec change — a resource request, a hostPath mount, some MOCK_* environment. The NRI plugin removes that step: it registers with containerd's Node Resource Interface and edits containers as they are created, so an unmodified workload comes up believing it has GPUs.

What NRI is

The Node Resource Interface is a framework for plugging extensions into OCI-compatible container runtimes. A plugin registers with the runtime, is notified as containers are created, and may make limited adjustments to a container's OCI spec — mounts, devices, environment — before it starts.

It sits below the Container Runtime Interface, so it is a runtime feature rather than a Kubernetes API: there is no NRI object in the Kubernetes API and nothing on kubernetes.io describing it. containerd 1.7 or later is required. See the NRI project and containerd's NRI documentation.

For where it sits in the wider system, see the architecture overview.

Two layers of injection

The plugin does two separable things, and conflating them is the usual source of confusion.

Layer Applies to Delivers
Overlay every container, unless skipped The mock driver tree and environment — enough for nvidia-smi to run and report the node's profile
Devices only containers that opt in Actual /dev/nvidia* nodes, or a CDI reference the runtime resolves

The overlay is ambient: a plain pod that requests nothing gets it. Devices are opt-in, because handing every container real device nodes would be both surprising and wrong.

What happens to a container

Adjustment runs as a fixed sequence, and no step can fail:

flowchart TB
    create[containerd: CreateContainer] --> skip{Skip?}
    skip -->|opt-out annotation<br/>excluded namespace<br/>overlay already mounted| asis[Leave exactly as authored]
    skip -->|no| overlay[Mount overlay]
    overlay --> env[Set environment]
    env --> gpus{Device annotation?}
    gpus -->|no| imex
    gpus -->|yes| served{Already has<br/>GPU devices?}
    served -->|yes — device plugin served it| imex[Attach IMEX channels]
    served -->|no| attach[Attach devices<br/>raw nodes or CDI ref] --> imex

When a container is left alone

Three conditions, any of which skips adjustment entirely:

  • the container carries nvml-mock.nvidia.com/inject: "false";
  • its namespace is in the excluded list;
  • it already mounts the overlay at the destination path.

That last check is what makes re-adjustment safe. A container that already has the overlay has been through here before, so re-running would double-apply.

Composing with the NVIDIA device plugin

Both this plugin and the real k8s-device-plugin can put GPU devices into a container. Left alone they would both do it, and a pod asking for one GPU would see every mock GPU on the node.

The rule, from MEP-0002: whatever the device plugin already served wins. Before injecting devices, the plugin checks whether the container arrived carrying GPUs that something else put there, recognising both delivery mechanisms the device plugin supports:

Evidence Produced by
A device path under /dev/nvidia device plugin with --pass-device-specs
A CDI device named nvidia.com/… device plugin with --device-list-strategy=cdi-*

If either is present, device injection is suppressed and the container keeps exactly the GPUs it was allocated. The overlay still applies, so nvidia-smi works — it just reports the allocated subset rather than the whole node.

IMEX sits outside this rule

IMEX channel injection is deliberately not suppressed. The device plugin never delivers IMEX channels, so there is nothing to defer to.

Device injection modes

When the plugin does deliver devices, deviceInjectionMode picks the mechanism. It changes how, never whether — suppression is decided before this is consulted.

Mode Delivers Use when
raw (default) The mock /dev/nvidiaN nodes, staged directly into the adjustment Always works. Required by MEP-0002 to stay reachable, and the only mode that works where CDI is off or absent
cdi A CDI device reference the runtime resolves from the spec the cdi simulator wrote containerd 2.x, which enables CDI by default — no container toolkit needed on the node

An unknown value is rejected rather than coerced. A typo that silently fell back to raw would look identical to a working CDI deployment, and the difference is only visible in the OCI spec of an already-running pod.

Annotations

Annotation Effect
nvml-mock.nvidia.com/inject: "false" Opt out of adjustment entirely
nvml-mock.nvidia.com/devices: "true" Opt in to GPU device injection
nvml-mock.nvidia.com/imex-channels Request IMEX channels

Failing open

Every step degrades rather than blocks. Nothing orders this plugin's DaemonSet after the node daemon's, so on a fresh node the plugin may be asked to adjust a container before the GPU tree exists. When a surface is missing, injection is reduced — overlay-only instead of overlay-plus-devices — and container creation proceeds.

The alternative would be worse: a plugin that errors on a missing surface blocks every container on the node, including the daemon that would have created the surface.

That choice has a consequence worth knowing about. A plugin containerd has unregistered stays alive and silently stops injecting — nothing crashes, pods just quietly come up without GPUs. The plugin therefore reports whether injection is actually happening, rather than merely whether the process is running.

It also watches for wedged requests. If an in-flight adjustment exceeds the timeout containerd told the plugin it is applying, the runtime has already abandoned that request and the container was created without injection. The threshold is a multiple of the runtime's own timeout, so a single slow request cannot trigger a restart, but a genuinely stuck plugin gets replaced.

How the code is split

Package Role
internal/nri The runtime-coupled half: registers with containerd, translates its container types, reports health
internal/nri/inject The decision: what to add to a container. No containerd types cross this boundary, so every rule above is exercisable as a plain table test
To read about See
How the whole system fits together Architecture
What stages the tree this plugin mounts Node Daemon
Enabling NRI and its chart values Installation
A runnable walkthrough Node-Wide Injection