NVIDIA device plugin for Kubernetes
lastmod 2026-08-12created 2026-07-205 edits
NVIDIA device plugin for Kubernetes
About
The procedure for making a Kubernetes node advertise its GPU as a schedulable resource (nvidia.com/gpu).
Why is it needed?
RuntimeClass nvidia alone is enough to push a GPU into a pod. But then the cluster does not know about GPU occupancy. If you create two serving endpoints on a node with only one GPU, both grab the same 8 GB and OOM each other.
When the device plugin advertises nvidia.com/gpu, the second endpoint waits in Pending instead of dying at runtime. Even if the result is the same "can't use it", a failure the scheduler knows about is better — because the cause is written in a single line of kubectl describe pod rather than in the logs.
Prerequisites
All three must be satisfied before installation. If any one is missing, the plugin starts but reports 0 GPUs.
1. NVIDIA driver
It is fine if the GPU name and memory are shown.
2. Install nvidia-container-toolkit
If it is not there:
## 배포판 저장소 등록
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
## 설치
sudo apt-get install -y nvidia-container-toolkit
3. RuntimeClass nvidia
On startup, k3s auto-detects nvidia-container-runtime, adds it to the containerd configuration, and creates the nvidia RuntimeClass. If it is missing, restart k3s after installing the toolkit:
Installation
sudo is not needed. A kubeconfig is all you need.
Manifest
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
namespace: kube-system
spec:
selector:
matchLabels:
name: nvidia-device-plugin-ds
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
name: nvidia-device-plugin-ds
spec:
runtimeClassName: nvidia # <-- k3s 용 로컬 수정 (아래 참고)
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
priorityClassName: "system-node-critical"
containers:
- image: nvcr.io/nvidia/k8s-device-plugin:v0.17.1
name: nvidia-device-plugin-ctr
env:
- name: FAIL_ON_INIT_ERROR
value: "false"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
Differences from the original manifest
Only one line, runtimeClassName: nvidia, was added to the original (NVIDIA/k8s-device-plugin v0.17.1).
| NOTE |
| The default runtime of k3s is not nvidia. Without this line, the plugin container cannot open NVML and reports 0 GPUs — the pod is |
Verification
# 1) 파드가 떴는가
kubectl -n kube-system get pods -l name=nvidia-device-plugin-ds
# 2) 노드가 GPU 를 광고하는가 (핵심)
kubectl get node -o jsonpath='{.items[0].status.allocatable.nvidia\.com/gpu}'
Success if the second command prints 1 (or the number of GPUs). If it is empty or 0, go to #Troubleshooting below.
Everything at a glance:
kubectl get nodes -o json | python3 -c "
import json,sys
for n in json.load(sys.stdin)['items']:
st = n['status']
print(n['metadata']['name'],
'capacity=', st.get('capacity',{}).get('nvidia.com/gpu'),
'allocatable=', st.get('allocatable',{}).get('nvidia.com/gpu'))
"
Troubleshooting
| Symptom | Cause | Fix |
| Pod is | | Add that line to the manifest and reapply |
| Pod is in | Toolkit not installed or containerd configuration missing | Install the toolkit, then |
| | Driver cannot see the GPU | Get |
| Serving pod is | Working as intended. The GPU is already exclusively allocated to another pod | See #Adjusting the GPU count below |
Who is holding the GPU:
kubectl get pods -A -o json | python3 -c "
import json,sys
for p in json.load(sys.stdin)['items']:
for c in p['spec'].get('containers',[]):
r = c.get('resources',{})
g = (r.get('limits') or {}).get('nvidia.com/gpu') or (r.get('requests') or {}).get('nvidia.com/gpu')
if g:
print(f\"{p['metadata']['namespace']}/{p['metadata']['name']} \"
f\"phase={p['status'].get('phase')} gpu={g}\")
"
Adjusting the GPU count
The default is 1 GPU = 1 pod (exclusive allocation). To let multiple pods share one GPU, enable time-slicing.
ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: nvidia-device-plugin-config
namespace: kube-system
data:
config.yaml: |
version: v1
sharing:
timeSlicing:
resources:
- name: nvidia.com/gpu
replicas: 4 # 1장을 4개처럼 광고
Connecting to the DaemonSet
env:
- name: FAIL_ON_INIT_ERROR
value: "false"
- name: CONFIG_FILE
value: /config/config.yaml
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
- name: plugin-config
mountPath: /config
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
- name: plugin-config
configMap:
name: nvidia-device-plugin-config
After applying, allocatable changes to 4.
Know this before enabling
| WARNING |
| Time-slicing does not partition memory. Even if it is advertised as 4, the 8 GB is still a single pool that the pods share. If the total exceeds it, you get a CUDA OOM at runtime. |
This choice partially undoes the reason for installing this plugin — it trades "waiting the scheduler knows about" for "possible OOM at runtime". Enable it only when you know how much memory each pod actually uses.
Comparison of alternatives
| Method | Memory isolation | In this environment |
| Exclusive allocation (default) | Full | Possible — only 1 pod at a time |
| time-slicing | None (shared) | Possible — within 8 GB total |
| MPS | None (shared) | Possible, but heavier to configure |
| MIG | Hard partitioning | Not possible — data center cards (A100/H100 class) only |
Per-tenant limits
The geo-mlops configuration has no GPU quota (only serving_gpu_runtime_class and serving_gpu_resource). To prevent a tenant from monopolizing the GPU, put a Kubernetes ResourceQuota on the namespace:
apiVersion: v1
kind: ResourceQuota
metadata:
name: gpu-quota
namespace: tenant-kia
spec:
hard:
requests.nvidia.com/gpu: "1"
Full time-slicing code
# NVIDIA device plugin — 노드가 GPU 를 **스케줄 가능한 자원**으로 광고하게 한다.
# (plan 17 S9)
#
# 왜 필요한가: `RuntimeClass nvidia` 만으로도 파드에 GPU 를 밀어 넣을 수는 있지만,
# 그러면 클러스터가 GPU 점유를 모른다. GPU 가 하나뿐인 노드에서 서빙 엔드포인트를 둘
# 만들면 둘 다 같은 8 GB 를 잡고 서로 OOM 낸다. 이 플러그인이 `nvidia.com/gpu` 를
# 광고하면 두 번째 엔드포인트는 런타임에 죽는 대신 Pending 으로 대기한다 — 같은 실패라도
# 스케줄러가 아는 실패가 낫다. 그 상한을 올리는 것이 아래 시분할 설정이다.
#
# 원본: https://github.com/NVIDIA/k8s-device-plugin (v0.19.3, Apache-2.0)
# 로컬 수정: `runtimeClassName: nvidia` 한 줄. k3s 의 기본 런타임은 nvidia 가 아니라서
# 이게 없으면 플러그인 컨테이너가 NVML 을 못 열고 GPU 를 0개로 보고한다.
#
# 버전 하한: v0.18.2 ("Ignore errors getting device memory using NVML"). 통합 메모리
# GPU 는 `nvmlDeviceGetMemoryInfo` 에 `Not Supported` 를 돌려주는데, 그 이전 버전은
# 이것을 치명적 오류로 보고 디바이스 열거 자체를 포기한다 — GPU 를 정상 인식하고도
# `error building device map ... error getting device memory: Not Supported` 로 죽는다.
#
# 시분할(time-slicing): DGX Spark 는 GPU 가 **하나**다. 기본 설정이면 노드는
# `nvidia.com/gpu: 1` 만 광고하므로 두 번째 엔드포인트부터 영원히 Pending 이다
# (`0/1 nodes are available: 1 Insufficient nvidia.com/gpu`). 아래 ConfigMap 이
# 그 하나를 `replicas` 개의 논리 GPU 로 광고해 여러 인스턴스가 같은 GPU 를 나눠 쓰게
# 한다. GB10 은 MIG 를 지원하지 않으므로 하드웨어 분할은 선택지가 아니다.
#
# 시분할이 나눠 주는 것은 **실행 시간뿐이다.** 메모리는 격리되지 않는다 — 128 GB 통합
# 메모리를 N 개 파드가 그냥 같이 쓴다. 즉 `replicas` 는 "동시에 띄울 인스턴스 수"이지
# "안전하게 띄울 수 있는 수"가 아니다. 인스턴스 하나가 메모리를 다 먹으면 나머지는
# 여전히 OOM 이다. 다만 실패 지점이 스케줄러(Pending)에서 런타임으로 옮겨갈 뿐이다.
#
# `replicas` 조정: 동시에 돌릴 인스턴스 수보다 넉넉하게 잡는다. 광고만 하는 숫자라
# 남아도는 슬롯에 비용은 없다. 바꾼 뒤에는 **반드시 롤아웃**해야 반영된다:
# kubectl -n kube-system rollout restart ds/nvidia-device-plugin-daemonset
#
# `renameByDefault: false` 를 유지하는 이유: true 면 자원 이름이
# `nvidia.com/gpu.shared` 로 바뀌어 앱의 `serving_gpu_resource`(config.py) 와
# 학습 러너의 요청까지 전부 따라 바꿔야 한다.
#
# `failRequestsGreaterThanOne: true` 를 켜는 이유: 시분할 슬롯 2개는 GPU 2장이
# 아니다. 이게 없으면 `gpu: 2` 요청이 스케줄에 성공한 뒤 실제로는 GPU 하나를 자기끼리
# 시분할하는, 조용히 틀린 상태가 된다. 켜 두면 admission 단계에서 즉시 거부된다.
#
# 적용 (sudo 불필요 — kubeconfig 만 있으면 된다):
# kubectl apply -f scripts/serving/nvidia-device-plugin.yml
#
# 확인 (노드가 여럿이면 `items[0]` 은 GPU 노드가 아닐 수 있다 — 전부 본다):
# kubectl get nodes -o custom-columns='NAME:.metadata.name,ALLOC:.status.allocatable.nvidia\.com/gpu'
apiVersion: v1
kind: ConfigMap
metadata:
name: nvidia-device-plugin-config
namespace: kube-system
data:
# 파일 하나만 두고 DaemonSet 이 `CONFIG_FILE` 로 직접 가리킨다. 노드마다 다른 설정을
# 쓰려면(`nvidia.com/device-plugin.config` 라벨) config-manager 사이드카가 필요한데,
# 단일 노드에는 과하다.
config.yaml: |-
version: v1
flags:
migStrategy: none
sharing:
timeSlicing:
renameByDefault: false
failRequestsGreaterThanOne: true
resources:
- name: nvidia.com/gpu
replicas: 8
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: nvidia-device-plugin-daemonset
namespace: kube-system
spec:
selector:
matchLabels:
name: nvidia-device-plugin-ds
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
name: nvidia-device-plugin-ds
spec:
runtimeClassName: nvidia
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
# Mark this pod as a critical add-on; when enabled, the critical add-on
# scheduler reserves resources for critical add-on pods so that they can
# be rescheduled after a failure.
# See https://kubernetes.io/docs/tasks/administer-cluster/guaranteed-scheduling-critical-addon-pods/
priorityClassName: "system-node-critical"
containers:
- image: nvcr.io/nvidia/k8s-device-plugin:v0.19.3
name: nvidia-device-plugin-ctr
env:
- name: FAIL_ON_INIT_ERROR
value: "false"
# 시분할 설정을 읽는다. 이 줄이 없으면 ConfigMap 은 마운트만 되고 무시된다.
- name: CONFIG_FILE
value: /etc/nvidia-device-plugin/config.yaml
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
volumeMounts:
- name: device-plugin
mountPath: /var/lib/kubelet/device-plugins
- name: plugin-config
mountPath: /etc/nvidia-device-plugin
readOnly: true
volumes:
- name: device-plugin
hostPath:
path: /var/lib/kubelet/device-plugins
- name: plugin-config
configMap:
name: nvidia-device-plugin-config
I added the time-slicing configuration to scripts/serving/nvidia-device-plugin.yml. YAML parsing and the wiring (ConfigMap ↔ CONFIG_FILE ↔ volumeMount) have been verified.
Why Unschedulable appears
DGX Spark physically has only 1 GPU, so the plugin advertises only nvidia.com/gpu: 1.
Once the first instance occupies that one, every instance from the second onward stays in Pending forever with 0/1 nodes are available: 1 Insufficient nvidia.com/gpu
GB10 does not support MIG, so hardware partitioning is not an option, and advertising N logical GPUs via time-slicing is the only practical approach.
Changes
- Added
ConfigMap nvidia-device-plugin-config—timeSlicing.resources[nvidia.com/gpu].replicas: 8 -
CONFIG_FILE=/etc/nvidia-device-plugin/config.yamlenv + ConfigMap mount on the DaemonSet
kubectl apply -f scripts/serving/nvidia-device-plugin.yml
kubectl -n kube-system rollout restart ds/nvidia-device-plugin-daemonset
kubectl get nodes -o custom-columns='NAME:.metadata.name,ALLOC:.status.allocatable.nvidia\.com/gpu'
The final output looks like this:
Things to know
Time-slicing only divides time. Memory is not isolated. The 8 pods simply share the 128 GB of unified memory. In other words, replicas: 8 is "the number that can run at the same time", not "the number that can run safely" — the failure point merely moves from the scheduler (Pending) to runtime (OOM), so you still have to enforce the per-instance memory budget yourself.
Two flags were set this way intentionally:
-
renameByDefault: false— if true, the resource name changes tonvidia.com/gpu.shared, andserving_gpu_resourceinconfig.py:514and the training runner's requests would all have to be changed to match. -
failRequestsGreaterThanOne: true— 2 slots are not 2 GPUs. Without this, agpu: 2request from the training side would schedule successfully and then actually split one GPU among itself, a silently wrong state. With it enabled, the request is rejected immediately at admission, making the problem visible. As a consequence, however,gpu: 2training jobs fail on Spark withUnexpectedAdmissionError, so if there are such jobs, they must be lowered togpu: 1.
The replicas number is set to 8 — since it is a number that is only advertised, spare slots cost nothing, so it just needs to be comfortably larger than the number of concurrent instances. Changing it requires a rollout restart (editing the ConfigMap alone does not take effect).
NVIDIA container runtime support in k3s
See Lightweight Kubernetes#NVIDIA container runtime support.