# Kubernetes GPU Scheduling: DRA, Device Plugins und CDI verständlich erklärt

Kubernetes GPU Scheduling bezeichnet die deklarative Zuweisung von Grafikprozessoren und anderen Beschleunigern an Pods. Dabei entscheidet der Scheduler nicht nur, welcher Node technisch passt, sondern auch, wie GPUs per Bin-Packing ausgelastet, über ResourceClaims angefordert und unabhängig von herstellerspezifischen Container-Details bereitgestellt werden.

## Was ist Kubernetes GPU Scheduling?

Ein [Kubernetes-Cluster](https://www.ionos.at/digitalguide/server/knowhow/kubernetes-cluster/) besteht aus **mehreren Servern**, die als [Nodes](https://www.ionos.at/digitalguide/server/konfiguration/kubernetes-node/) bezeichnet werden. Auf diesen Nodes führt [Kubernetes](https://www.ionos.at/digitalguide/server/knowhow/was-ist-kubernetes/) Anwendungen in sogenannten [Pods](https://www.ionos.at/digitalguide/server/konfiguration/kubernetes-pod/) aus. Benötigt eine Anwendung eine GPU, muss Kubernetes einen Node auswählen, auf dem eine passende und noch verfügbare GPU vorhanden ist. Diese Entscheidung übernimmt der Kubernetes Scheduler. Er prüft zunächst, welche Nodes die Anforderungen des Pods erfüllen. Anschließend bewertet er die geeigneten Nodes und wählt den am besten passenden aus.

Beim klassischen GPU Scheduling sieht Kubernetes eine GPU im Wesentlichen als zählbare Ressource. Ein Pod fordert beispielsweise eine Einheit der Ressource `nvidia.com/gpu` an. Kubernetes weiß dann zwar, dass der Pod eine NVIDIA-GPU benötigt, kann über diesen einfachen Mechanismus aber nur begrenzt ausdrücken, welches Modell oder welche Konfiguration benötigt wird.

Dynamic Resource Allocation, kurz DRA, erweitert dieses Modell. Statt lediglich „eine GPU“ anzufordern, kann eine Anwendung beispielsweise eine GPU der [Blackwell](https://www.ionos.at/digitalguide/server/knowhow/nvidia-blackwell/)-Generation, eine bestimmte Speicherkapazität oder eine bestimmte [MIG](https://www.ionos.at/digitalguide/server/knowhow/was-ist-nvidia-mig/)-Konfiguration verlangen.

### Device Plugin, MIG und DRA im Vergleich

Für die Zuweisung von GPUs stehen **mehrere Verfahren** zur Verfügung. Sie schließen sich nicht grundsätzlich gegenseitig aus: MIG beschreibt beispielsweise eine Aufteilung der Hardware, während Device Plugins und DRA bestimmen, wie Kubernetes diese Hardware verwaltet und Pods zuweist.

<table>
  <thead>
    <tr>
      <th><strong>Methode</strong></th>
      <th><strong>Mechanismus</strong></th>
      <th><strong>Vorteil</strong></th>
      <th><strong>Best Use Case</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Device Plugin</td>
      <td>GPUs werden als ganzzahlige Extended Resource am Node registriert. Ein Pod fordert eine oder mehrere Einheiten an.</td>
      <td>Bewährtes, einfaches Verfahren mit breiter Unterstützung</td>
      <td>Bestehende oder homogene Cluster, in denen überwiegend vollständige GPUs benötigt werden</td>
    </tr>
    <tr>
      <td>MIG: GPU Slicing</td>
      <td>Eine unterstützte physische GPU wird in mehrere voneinander getrennte GPU-Instanzen aufgeteilt.</td>
      <td>Bessere Auslastung großer GPUs und stärkere Isolation als einfaches Time-Slicing</td>
      <td>Inferenz, kleinere Trainingsjobs und Multi-Tenant-Cluster</td>
    </tr>
    <tr>
      <td>DRA: neuer Standard</td>
      <td>Geräte werden über ResourceSlices beschrieben, über DeviceClasses kategorisiert und mit ResourceClaims angefordert. CEL-Ausdrücke filtern nach Modell, Speicher oder Architektur.</td>
      <td>Präzise Hardwareauswahl, dynamische Konfiguration, Sharing und bessere Hardwareabstraktion</td>
      <td>Heterogene GPU-Cluster, NVIDIA-Blackwell-Systeme und anspruchsvolle MLOps-Plattformen</td>
    </tr>
  </tbody>
</table>

DRA arbeitet mit drei zentralen Kubernetes-Objekten. Sie beschreiben, welche Geräte im Cluster vorhanden sind und welche Hardware ein Pod benötigt:

<table>
  <thead>
    <tr>
      <th><strong>DRA-Objekt</strong></th>
      <th><strong>Aufgabe</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>ResourceSlice</td>
      <td>Zeigt Kubernetes, welche GPUs und anderen Geräte auf den Nodes verfügbar sind</td>
    </tr>
    <tr>
      <td>DeviceClass</td>
      <td>Fasst passende Geräte anhand von Eigenschaften wie Modell oder Architektur zusammen</td>
    </tr>
    <tr>
      <td>ResourceClaim</td>
      <td>Fordert für einen Pod ein Gerät aus einer DeviceClass an</td>
    </tr>
  </tbody>
</table>

Der NVIDIA-DRA-Treiber erstellt die ResourceSlices automatisch und installiert bei aktiviertem GPU-Support auch die Standardklassen `gpu.nvidia.com` und `mig.nvidia.com`. Zusätzliche, genauer zugeschnittene DeviceClasses, beispielsweise für eine B200, kann die Cluster-Administration selbst definieren. Anwendungen fordern die gewünschte GPU anschließend über ResourceClaims an.

## So trifft Kubernetes eine GPU-Scheduling-Entscheidung

Eine Scheduling-Entscheidung besteht vereinfacht aus **zwei Phasen**: Filtern und Bewerten.

Beim Filtern entfernt Kubernetes alle Nodes, die den Pod nicht ausführen können. Gründe können fehlender Arbeitsspeicher, eine nicht verfügbare GPU, ein unpassendes GPU-Modell, Node Taints oder nicht erfüllte Affinity-Regeln sein. Danach bewertet der Scheduler die verbleibenden Nodes. Der Node mit der höchsten Bewertung wird ausgewählt. Diese Bewertung kann beispielsweise darauf ausgerichtet werden, Anwendungen möglichst gleichmäßig zu verteilen oder vorhandene Nodes möglichst stark zu füllen.

Bei DRA kommt zusätzlich die Geräteauswahl hinzu:

1. Der NVIDIA-DRA-Treiber erkennt die GPUs auf jedem Node.
2. Er veröffentlicht die verfügbaren Geräte als ResourceSlices.
3. Eine DeviceClass beschreibt, welche Geräte zu einer bestimmten Kategorie gehören.
4. Ein ResourceClaim beschreibt, welches Gerät eine Anwendung benötigt.
5. Der Scheduler sucht nach einer passenden GPU und einem Node, auf dem diese GPU erreichbar ist.
6. Der DRA-Treiber bereitet das ausgewählte Gerät für den Pod vor.
7. Die Container-Runtime stellt die GPU über CDI im Container bereit.

## Was bedeutet Bin-Packing bei GPU-Clustern?

Beim Bin-Packing versucht Kubernetes, GPU-Workloads **möglichst kompakt** auf vorhandenen Nodes unterzubringen. Dadurch können einzelne Server besser ausgelastet und andere Nodes für größere Jobs freigehalten oder abgeschaltet werden. Der Scheduler bewertet dabei jedoch die angeforderten Ressourcen und nicht die tatsächliche GPU-Auslastung. Für deren Überwachung ist zusätzliches Monitoring erforderlich. Bei Multi-GPU-Workloads sollten außerdem NVLink-, PCIe- und NUMA-Topologien berücksichtigt werden, da eine möglichst dichte Verteilung nicht automatisch die beste Leistung bietet.

## Welche Rolle spielt CDI bei NVIDIA Blackwell?

CDI steht für Container Device Interface und standardisiert, wie eine ausgewählte GPU in einen Container eingebunden wird. DRA entscheidet dabei, welche GPU ein Pod erhält. CDI sorgt anschließend dafür, dass das Gerät samt benötigter Zugänge, Bibliotheken und Einstellungen im Container verfügbar ist.

Beim NVIDIA GPU Operator ist CDI **standardmäßig aktiviert** und arbeitet mit Container-Runtimes wie containerd oder CRI-O zusammen. Für Blackwell-Systeme ist das besonders hilfreich, weil unterschiedliche GPU-Modelle und MIG-Konfigurationen einheitlich bereitgestellt werden können.

## Tutorial: NVIDIA-GPUs mit DRA in Kubernetes zuweisen

Das folgende Tutorial zeigt eine vollständige DRA-Zuweisung für eine NVIDIA B200. Sie installieren zunächst den GPU Operator ohne klassisches Device Plugin, richten anschließend den DRA-Treiber ein und erstellen dann eine eigene DeviceClass sowie einen ResourceClaim.

### Voraussetzungen

Für den beschriebenen Aufbau empfiehlt sich Kubernetes 1.34 oder neuer.

Zusätzlich benötigen Sie:

- mindestens einen Kubernetes-Node mit unterstützter NVIDIA-GPU,
- Administratorrechte im Cluster,
- ein konfiguriertes `kubectl`,
- Helm 3,
- containerd oder CRI-O als Container-Runtime und
- Zugriff auf die NVIDIA-Helm-Charts.

### Schritt 1: GPU Operator und DRA-Treiber mit Helm installieren

Zunächst müssen Sie festlegen, auf welchen Nodes der DRA-Kubelet-Plugin-Pod ausgeführt werden soll. Lassen Sie sich die Namen Ihrer Nodes anzeigen:

```bash
kubectl get nodes
```

Kennzeichnen Sie anschließend jeden GPU-Node. Ersetzen Sie `<GPU-NODE>` durch den tatsächlichen Namen:

```bash
kubectl label node <GPU-NODE> nvidia.com/dra-kubelet-plugin=true
```

Die Kennzeichnung ist erforderlich, damit der NVIDIA Driver Manager den DRA-Kubelet-Plugin-Pod bei Treiberaktualisierungen korrekt behandeln kann.

Fügen Sie danach das NVIDIA-Helm-Repository hinzu:

```bash
helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
helm repo update
```

Installieren Sie nun den GPU Operator. Der entscheidende Parameter ist `devicePlugin.enabled=false`. Damit deaktivieren Sie das klassische NVIDIA Kubernetes Device Plugin und vermeiden, dass Device Plugin und DRA-Treiber dieselben GPUs gleichzeitig verwalten.

```bash
helm upgrade --install gpu-operator nvidia/gpu-operator \
    --version=v26.3.3 \
    --create-namespace \
    --namespace gpu-operator \
    --set devicePlugin.enabled=false \
    --set 'driver.manager.env[0].name=NODE_LABEL_FOR_GPU_POD_EVICTION' \
    --set 'driver.manager.env[0].value=nvidia.com/dra-kubelet-plugin'
```

Das Deaktivieren des Device Plugins allein aktiviert DRA noch nicht. Der DRA-Treiber wird weiterhin über ein eigenes Helm-Chart installiert.

Erstellen Sie dafür die Datei `dra-values.yaml` mit folgendem Inhalt:

```none
image:
    pullPolicy: IfNotPresent
kubeletPlugin:
    nodeSelector:
        nvidia.com/dra-kubelet-plugin: "true"
```

Installieren Sie anschließend den NVIDIA-DRA-Treiber:

```bash
helm upgrade --install dra-driver-nvidia-gpu \
    nvidia/dra-driver-nvidia-gpu \
    --version=0.4.1 \
    --namespace nvidia-dra-driver-gpu \
    --create-namespace \
    --set nvidiaDriverRoot=/run/nvidia/driver \
    --set gpuResourcesEnabledOverride=true \
    -f dra-values.yaml
```

Prüfen Sie danach, ob die Pods des Treibers laufen:

```bash
kubectl get pods -n nvidia-dra-driver-gpu
```

Abhängig von der aktivierten Konfiguration sollte die Ausgabe ungefähr so aussehen:

```none
NAME                                             READY   STATUS
dra-driver-nvidia-gpu-controller-...             1/1     Running
dra-driver-nvidia-gpu-kubelet-plugin-...         2/2     Running
```

Kontrollieren Sie außerdem die automatisch angelegten DeviceClasses:

```bash
kubectl get deviceclass
```

Für die GPU-Zuweisung sollten mindestens die folgenden Klassen vorhanden sein:

```bash
gpu.nvidia.com
mig.nvidia.com
```

Bei aktiviertem ComputeDomain-Support erscheinen zusätzliche Klassen für Multi-Node NVLink. Prüfen Sie abschließend, ob der Treiber die GPUs als ResourceSlices veröffentlicht:

```bash
kubectl get resourceslice -o wide
```

Wenn keine ResourceSlices für `gpu.nvidia.com` erscheinen, kann Kubernetes noch keine GPU über DRA zuweisen.

### Schritt 2: DeviceClass für NVIDIA B200 definieren

Der NVIDIA-DRA-Treiber legt bereits die allgemeine DeviceClass `gpu.nvidia.com` an. Diese umfasst vollständige NVIDIA-GPUs unterschiedlicher Modelle. Für einen heterogenen Cluster können Sie zusätzlich eine eigene Klasse erstellen, die ausschließlich B200-GPUs auswählt.

Sehen Sie sich zunächst die vom Treiber gemeldeten Produktnamen an:

```bash
kubectl get resourceslice -o yaml
```

Suchen Sie in der Ausgabe nach `productName`. Die genaue Bezeichnung hängt von Hardware, Treiber und Systemvariante ab. Der folgende reguläre Ausdruck sucht deshalb nicht nach einer vollständig identischen Zeichenfolge, sondern nach dem Bestandteil `B200`.

Erstellen Sie die Datei `b200-deviceclass.yaml`:

```none
apiVersion: resource.k8s.io/v1
kind: DeviceClass
metadata:
    name: b200.gpu.nvidia.com
spec:
    selectors:
    - cel:
            expression: |
                device.driver == 'gpu.nvidia.com' &&
                device.attributes['gpu.nvidia.com'].type == 'gpu' &&
                device.attributes['gpu.nvidia.com'].productName
                    .lowerAscii()
                    .matches('^.*b200.*$')
```

Der Ausdruck enthält drei Bedingungen:

- Das Gerät muss vom NVIDIA-GPU-DRA-Treiber stammen.
- Es muss sich um eine vollständige GPU und nicht um eine MIG-Instanz handeln.
- Der gemeldete Produktname muss `B200` enthalten.

Wenden Sie die Datei an:

```bash
kubectl apply -f b200-deviceclass.yaml
```

Kontrollieren Sie die DeviceClass:

```bash
kubectl describe deviceclass b200.gpu.nvidia.com
```

Eine DeviceClass reserviert noch keine GPU. Sie definiert lediglich den Gerätepool, aus dem spätere Claims auswählen dürfen.

### Schritt 3: ResourceClaim und Pod erstellen

Erstellen Sie zunächst einen eigenen Namespace für den Test:

```bash
kubectl create namespace gpu-example
```

Danach erstellen Sie einen ResourceClaim. Speichern Sie den folgenden Inhalt als `b200-resourceclaim.yaml`:

```none
apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
    name: b200-claim
    namespace: gpu-example
spec:
    devices:
        requests:
        - name: b200
            exactly:
                deviceClassName: b200.gpu.nvidia.com
```

Ohne weitere Mengenangabe fordert dieser Claim genau ein passendes Gerät an.

Erstellen Sie den ResourceClaim:

```bash
kubectl apply -f b200-resourceclaim.yaml
```

Der Claim wird normalerweise erst vollständig zugewiesen, wenn ein Pod ihn verwendet. Kubernetes muss schließlich nicht nur eine freie GPU, sondern auch einen dazu passenden Node auswählen.

Erstellen Sie nun die Datei `b200-pod.yaml`:

```none
apiVersion: v1
kind: Pod
metadata:
    name: b200-test
    namespace: gpu-example
spec:
    restartPolicy: Never
    containers:
    - name: gpu-test
        image: ubuntu:22.04
        command:
        - bash
        - -c
        - |
            nvidia-smi -L
            sleep 3600
        resources:
            claims:
            - name: gpu
    resourceClaims:
    - name: gpu
        resourceClaimName: b200-claim
    tolerations:
    - key: nvidia.com/gpu
        operator: Exists
        effect: NoSchedule
```

Im Pod taucht der Claim an zwei Stellen auf:

1. Unter `spec.resourceClaims` wird der vorhandene ResourceClaim für den Pod verfügbar gemacht:

```none
resourceClaims:
- name: gpu
    resourceClaimName: b200-claim
```

2. Unter `containers[].resources.claims` wird festgelegt, welcher Container das Gerät verwenden darf:

```none
resources:
    claims:
    - name: gpu
```

Diese Trennung ermöglicht es, einen Claim gezielt **einem oder mehreren Containern** im Pod bereitzustellen. Mehrere Container können denselben Claim referenzieren und dadurch dieselbe GPU sehen.

Erstellen Sie den Pod:

```bash
kubectl apply -f b200-pod.yaml
```

### Schritt 4: GPU-Zuweisung mit kubectl validieren

Prüfen Sie zunächst den allgemeinen Pod-Status:

```bash
kubectl get pod b200-test -n gpu-example -o wide
```

Der Pod sollte nach erfolgreicher Zuweisung den Status `Running` erhalten. In der Spalte `NODE` sehen Sie, auf welchem GPU-Node er ausgeführt wird.

Rufen Sie anschließend die ausführliche Beschreibung ab:

```bash
kubectl describe pod b200-test -n gpu-example
```

Achten Sie insbesondere auf folgende Bereiche:

- **Node:** Zeigt den ausgewählten GPU-Node.
- **Resource Claims:** Zeigt den vom Pod verwendeten Claim.
- **Containers:** Zeigt, welcher Container auf den Claim zugreift.
- **Events:** Enthält Fehlermeldungen, falls kein passendes Gerät gefunden wurde.

Bleibt der Pod im Status `Pending`, finden Sie unter `Events` in aller Regel einen Hinweis auf die Ursache, etwa einen nicht zuweisbare ResourceClaim oder ein fehlendes passendes Gerät.

Prüfen Sie anschließend den ResourceClaim:

```bash
kubectl get resourceclaim b200-claim \
    -n gpu-example \
    -o yaml
```

Unter `status.allocation.devices.results` sollte die zugewiesene GPU erscheinen. Kontrollieren Sie abschließend, ob die GPU im Container verfügbar ist:

```bash
kubectl exec -n gpu-example b200-test -- nvidia-smi -L
```

Eine erfolgreiche Ausgabe kann beispielsweise so aussehen:

```none
GPU 0: NVIDIA B200 (UUID: GPU-...)
```

Damit ist bestätigt, dass Kubernetes eine passende B200 ausgewählt und über CDI im Container bereitgestellt hat.

## Häufige Fehler beim Kubernetes GPU Scheduling mit DRA

### Der Pod bleibt im Status Pending

Prüfen Sie zunächst die Pod-Events und den ResourceClaim:

```bash
kubectl describe pod b200-test -n gpu-example
kubectl describe resourceclaim b200-claim -n gpu-example
```

Fehlt eine passende GPU, kontrollieren Sie die veröffentlichten Geräte und deren Produktnamen:

```bash
kubectl get resourceslice -o yaml
```

### Es werden keine ResourceSlices angezeigt

Prüfen Sie, ob die Pods des DRA-Treibers laufen:

```bash
kubectl get pods -n nvidia-dra-driver-gpu
```

Bei Fehlern helfen die Logs des Kubelet-Plugins:

```bash
kubectl logs \
    -n nvidia-dra-driver-gpu \
    <NAME-DES-KUBELET-PLUGIN-PODS> \
    -c gpus
```

Häufige Ursachen sind ein falscher `nvidiaDriverRoot`, fehlende Node Labels oder nicht erkannte GPUs.

### Der B200-Selektor findet kein Gerät

Vergleichen Sie den verwendeten `productName` mit dem Eintrag in der ResourceSlice. Zum Test können Sie den Selektor vorübergehend auf alle vollständigen NVIDIA-GPUs erweitern:

```bash
expression: |
    device.driver == 'gpu.nvidia.com' &&
    device.attributes['gpu.nvidia.com'].type == 'gpu'
```

Funktioniert die Zuweisung damit, liegt der Fehler wahrscheinlich im Modellselektor.

Hinweis Das klassische Device Plugin sollte im DRA-Modus deaktiviert sein. Außerdem können höher priorisierte Pods bereits belegte DRA-GPUs derzeit nicht automatisch übernehmen.

## Best Practices für GPU-Cluster

Verwenden Sie DeviceClasses als **Schnittstelle zwischen Anwendungen und Hardware**. So müssen Workloads keinen bestimmten Node kennen, sondern können beispielsweise eine B200-GPU oder eine GPU mit besonders viel Speicher anfordern. Filtern Sie außerdem nur dann nach einem konkreten Modell, wenn es **technisch notwendig** ist. Eine Anforderung wie „mindestens 80 GiB GPU-Speicher“ ist oft flexibler als die feste Auswahl eines bestimmten GPU-Modells.

Nutzen Sie ResourceClaimTemplates für Deployments und Jobs mit mehreren Pods. Direkte ResourceClaims eignen sich dagegen für **einzelne Pods oder Ressourcen**, die bewusst weiterverwendet werden sollen. Beachten Sie, dass Kubernetes reservierte Ressourcen bewertet, nicht die tatsächliche GPU-Auslastung. Für deren Überwachung benötigen Sie zusätzliches Monitoring, beispielsweise mit dem NVIDIA DCGM Exporter.

## Fazit

Kubernetes GPU Scheduling entwickelt sich von einfachen, ganzzahligen GPU-Anforderungen zu einer **deklarativen Geräteverwaltung**. Das klassische NVIDIA Device Plugin bleibt für bestehende und homogene Cluster eine einfache Lösung. MIG verbessert die Auslastung großer GPUs, indem eine physische GPU in kleinere Instanzen aufgeteilt wird.

Für neue, heterogene GPU-Plattformen bietet DRA jedoch das **flexiblere Modell**. DeviceClasses abstrahieren die Hardware, ResourceClaims beschreiben den konkreten Bedarf und ResourceSlices liefern dem Scheduler die notwendigen Geräteinformationen. CDI sorgt anschließend dafür, dass die ausgewählte GPU zuverlässig im Container erscheint.

Besonders für NVIDIA-Blackwell-Systeme ist diese Trennung sinnvoll: Eine Anwendung kann gezielt eine B200 anfordern, ohne einen bestimmten Node festzulegen.


This is a markdown version of: [https://www.ionos.at/digitalguide/server/konfiguration/kubernetes-gpu-scheduling/](https://www.ionos.at/digitalguide/server/konfiguration/kubernetes-gpu-scheduling/) for AI/LLM consumption.